# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Minimal Atom feed writer.
"""
import datetime
import hashlib
import xml.etree.ElementTree as ET
from . import util
__all__ = ['FeedEntry', 'write_atom']
ATOM_NS = "{http://www.w3.org/2005/Atom}"
XML_NS = "{http://www.w3.org/XML/1998/namespace}"
[docs]
class FeedEntry:
"""
Atom feed entry.
Parameters
----------
title : str
Title of the entry
updated : datetime
Update date
link : str, optional
Link (alternate) for the entry
content : str, optional
Body HTML text for the entry.
id_context : list of str, optional
Material to generate unique IDs from. Feed readers show each id
as a separate entry, so if an entry is updated, it appears as
a new entry only if the id_context changes.
Default: [title, link, content]
id_date : datetime
Date to include in the id.
Default: same as *updated*
"""
def __init__(self, title, updated, link=None, content=None, id_context=None, id_date=None):
[docs]
self.title = title
[docs]
self.updated = updated
[docs]
self.content = content
[docs]
self.id_context = id_context
[docs]
self.id_date = id_date
[docs]
def get_atom(self, id_prefix, language):
item = ET.Element(f'{ATOM_NS}entry')
id_context = ["entry"]
if self.id_context is None:
id_context += [self.title, self.link, self.content]
else:
id_context += list(self.id_context)
if self.id_date is None:
id_date = self.updated
else:
id_date = self.id_date
el = ET.Element(f'{ATOM_NS}id')
el.text = _get_id(id_prefix, id_date, id_context)
item.append(el)
el = ET.Element(f'{ATOM_NS}title')
el.attrib[f'{XML_NS}lang'] = language
el.text = self.title
item.append(el)
el = ET.Element(f'{ATOM_NS}updated')
el.text = self.updated.strftime('%Y-%m-%dT%H:%M:%SZ')
item.append(el)
if self.link:
el = ET.Element(f'{ATOM_NS}link')
el.attrib[f'{ATOM_NS}href'] = self.link
item.append(el)
el = ET.Element(f'{ATOM_NS}content')
el.attrib[f'{XML_NS}lang'] = language
if self.content:
el.text = self.content
el.attrib[f'{ATOM_NS}type'] = 'html'
else:
el.text = ' '
item.append(el)
return item
[docs]
def write_atom(dest, entries, author, title, address, updated=None, link=None, language="en"):
"""
Write an atom feed to a file.
Parameters
----------
dest : str
Destination file path, or a file-like object
entries : list of FeedEntry
Feed entries.
author : str
Author of the feed.
title : str
Title for the feed.
address : str
Address (domain name or email) to be used in building unique IDs.
updated : datetime, optional
Time stamp for the feed. If not given, take from the newest entry.
link : str, optional
Link for the feed.
language : str, optional
Language of the feed. Default is 'en'.
"""
if updated is None:
if entries:
updated = max(entry.updated for entry in entries)
else:
updated = datetime.datetime.now(datetime.timezone.utc)
root = ET.Element(f'{ATOM_NS}feed')
# id (obligatory)
el = ET.Element(f'{ATOM_NS}id')
el.text = _get_id(address, None, ["feed", author, title])
root.append(el)
# author (obligatory)
el = ET.Element(f'{ATOM_NS}author')
el2 = ET.Element(f'{ATOM_NS}name')
el2.text = author
el.append(el2)
root.append(el)
# title (obligatory)
el = ET.Element(f'{ATOM_NS}title')
el.attrib[f'{XML_NS}lang'] = language
el.text = title
root.append(el)
# updated (obligatory)
el = ET.Element(f'{ATOM_NS}updated')
el.text = updated.strftime('%Y-%m-%dT%H:%M:%SZ')
root.append(el)
# link
if link is not None:
el = ET.Element(f'{ATOM_NS}link')
el.attrib[f'{ATOM_NS}href'] = link
root.append(el)
# entries
for entry in entries:
root.append(entry.get_atom(address, language))
tree = ET.ElementTree(root)
def write(f):
tree.write(f, xml_declaration=True, default_namespace=ATOM_NS[1:-1], encoding="utf-8")
if hasattr(dest, 'write'):
write(dest)
else:
with util.long_path_open(dest, 'wb') as f:
write(f)
def _get_id(owner, date, content):
"""
Generate an unique Atom id for the given content
"""
h = hashlib.sha256()
# Hash still contains the original project url, keep as is
h.update(b"github.com/spacetelescope/asv")
for x in content:
if x is None:
h.update(b",")
else:
h.update(x.encode('utf-8'))
h.update(b",")
if date is None:
date = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
return f"tag:{owner},{date.strftime('%Y-%m-%d')}:/{h.hexdigest()}"