#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # # new-to-feed - Convert NEWS.adoc to feed.xml # # This script converts from "NEWS.adoc" in the main Freedoom repository to # "feed.xml" on the Freedoom website, which is an RSS feed. # # It's possible that there is no need for this script given that GitHub # generates an Atom feed based on the releases done on GitHub. It can be # found at: # https://github.com/freedoom/freedoom/releases.atom # Perhaps the website should link to the above in place of the current # "atom.xml", and "feed.xml" should be abandon. import datetime import os import re import sys from xml.dom.minidom import Document # Globals channel = None leading_whitespace = " " date = None desc_lines = [] doc = None pubdate = None ver = None # Regular expressions # A regular expression that matches version / date lines. ver_date_patt = re.compile(r'\s*==\s+((?:\d+\.)+\d+)\s+\((\d{4}-\d{2}-\d{2})\)') # Initial document processing and header prior to the first version. def add_header(): global channel global doc doc = Document() rss = doc.createElement('rss') rss.setAttribute('version', '2.0') doc.appendChild(rss) channel = doc.createElement('channel') rss.appendChild(channel) title = doc.createElement('title') title.appendChild(doc.createTextNode('Freedoom Feed')) channel.appendChild(title) link = doc.createElement('link') link.appendChild(doc.createTextNode('https://freedoom.github.io')) channel.appendChild(link) description = doc.createElement('description') description.appendChild(doc.createTextNode('The latest news from Freedoom')) channel.appendChild(description) language = doc.createElement('language') language.appendChild(doc.createTextNode('en-us')) channel.appendChild(language) image = doc.createElement('image') channel.appendChild(image) url = doc.createElement('url') url.appendChild(doc.createTextNode('https://freedoom.github.io/favicon.ico')) image.appendChild(url) # Called each time we have a complete version section def add_version(): item = doc.createElement('item') channel.appendChild(item) title_elem = doc.createElement('title') title_elem.appendChild(doc.createTextNode(date + ": Freedoom " + ver + " released")) item.appendChild(title_elem) pubdate_elem = doc.createElement('pubDate') pubdate_elem.appendChild(doc.createTextNode(pubdate)) item.appendChild(pubdate_elem) link_elem = doc.createElement('link') link_elem.appendChild(doc.createTextNode("https://freedoom.github.io/#freedoom-" + ver)) item.appendChild(link_elem) # Avoid join() in order to treat newlines specially. nl_ws = "\n" + leading_whitespace desc_text = "\n" for desc_line in desc_lines: if desc_line: desc_text += leading_whitespace + desc_line + "\n" + leading_whitespace + "
\n" else: desc_text += "\n" desc_elem = doc.createElement('description') desc_elem.appendChild(doc.createTextNode(desc_text)) item.appendChild(desc_elem) # Iterates through news_adoc in order to process each version found. def add_versions(): global date global desc_lines global pubdate global ver first_section = True desc_line = "" with open(news_adoc) as news_hand: line_num = 0 for line in news_hand: line_num += 1 line = line.rstrip() ver_date_match = ver_date_patt.match(line) if ver_date_match: ver_date_match_groups = ver_date_match.groups() if len(ver_date_match_groups) != 2: # Should not happen. print("Line number", line_num, "has", len(ver_date_match_groups), "groups.", file=sys.stderr) sys.exit(1) # With the exception of the "HEAD" each new version line indicates # that the previous one is complete, so output it before setting # "pubdate" to the new value. if pubdate: add_version() desc_lines = [] ver, date = ver_date_match.groups() pubdate = datetime.datetime.strptime(date, '%Y-%m-%d').strftime('%d %b %Y') first_section = True else: # Possible description text, but only after the first version date line. if not pubdate: continue line = line.strip() if line.startswith("=== "): # Add the existing line, if any. if desc_line: desc_lines.append(desc_line) desc_line = "" if first_section: first_section = False else: desc_lines.append("") desc_lines.append("> " + line[4:]) first_section = False elif line.startswith("*"): if desc_line: desc_lines.append(desc_line) desc_line = "" try: space_index = line.index(" ") except ValueError: # Should not happen. print("Line number", line_num, "begins with \"*\", but has no space.") sys.exit(1) desc_line = ("- " * (space_index - 1) if (space_index > 1) \ else "") + line[space_index + 1:] elif line: # Assume it's a continuation of the previous "*" line. desc_line += (" " + line) else: # Blank line marking the end of a bullet. if desc_line: desc_lines.append(desc_line) desc_line = "" if not pubdate: print("No pubdate at the end.", file=sys.stderr) sys.exit(1) if desc_line: desc_lines.append(desc_line) desc_line = "" add_version() # Main enty point. def main(): parse_args() add_header() add_versions() output_feed() # Write the feed to feed_xml. def output_feed(): with open(feed_xml, "w") as feed_hand: print(doc.toprettyxml(indent=" "), file=feed_hand, end="") # Parse the command line arguments and store the result in 'args'. def parse_args(): global news_adoc global feed_xml if len(sys.argv) != 3: print("Usage:", os.path.basename(sys.argv[0]), \ "news-file feed-xml", file=sys.stderr) sys.exit(1) news_adoc = sys.argv[1] feed_xml = sys.argv[2] # So that this script may be accessed as a module. if __name__ == "__main__": main()