123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #! /usr/bin/python
- # -*- coding: utf-8 -*-
- #
- # bin/news-to-specfile-changelog
- # Part of ComixCursors, a desktop cursor theme.
- #
- # Copyright © 2010–2013 Ben Finney <ben+opendesktop@benfinney.id.au>
- #
- # This work is free software: you can redistribute it and/or modify it
- # under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This work is distributed in the hope that it will be useful, but
- # WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this work. If not, see <http://www.gnu.org/licenses/>.
- """ Render the NEWS file to a specfile changelog field value.
- The project NEWS file is a reStructuredText document, with each
- section describing a version of the project. The document is
- intended to be readable as-is by end users.
- This program transforms the document to a compact list of changes,
- suitable for the “changelog” field of an RPM specfile.
- Requires:
- * Docutils <http://docutils.sourceforge.net/>
- """
- import datetime
- import textwrap
- from docutils.core import publish_cmdline, default_description
- import docutils.nodes
- import docutils.writers
- class SpecChangelogWriter(docutils.writers.Writer):
- """ Docutils writer to produce a changelog field for RPM spec files.
- """
- supported = ('spec_changelog')
- """ Formats this writer supports. """
- def __init__(self):
- docutils.writers.Writer.__init__(self)
- self.translator_class = SpecChangelogTranslator
- def translate(self):
- visitor = self.translator_class(self.document)
- self.document.walkabout(visitor)
- self.output = visitor.astext()
- class NewsEntry(object):
- """ An individual entry from the NEWS document. """
- def __init__(
- self, released=None, version=None, maintainer=None, body=None):
- self.released = released
- self.version = version
- self.maintainer = maintainer
- self.body = body
- def as_specfile_changelog_entry(self):
- """ Format the news entry as an RPM specfile changelog entry. """
- # Reference: <URL:http://fedoraproject.org/wiki/PackagingGuidelines>
- released_timestamp_text = self.released.strftime("%a %b %d %Y")
- header = " ".join([
- "*", released_timestamp_text, self.maintainer, "-", self.version])
- text = "\n".join([header, self.body])
- return text
- def get_name_for_field_body(node):
- """ Return the text of the field name of a field body node. """
- field_node = node.parent
- field_name_node_index = field_node.first_child_matching_class(
- docutils.nodes.field_name)
- field_name_node = field_node.children[field_name_node_index]
- field_name = unicode(field_name_node.children[0])
- return field_name
- class InvalidFormatError(ValueError):
- """ Raised when the document is not a valid NEWS document. """
- def news_timestamp_to_datetime(text):
- """ Return a datetime value from the news entry timestamp. """
- if text == "FUTURE":
- timestamp = datetime.datetime.max
- else:
- timestamp = datetime.datetime.strptime(text, "%Y-%m-%d")
- return timestamp
- class SpecChangelogTranslator(docutils.nodes.SparseNodeVisitor):
- """ Translator from document nodes to a changelog for RPM spec files. """
- wrap_width = 78
- field_convert_funcs = {
- 'released': news_timestamp_to_datetime,
- 'maintainer': unicode,
- }
- def __init__(self, document):
- docutils.nodes.NodeVisitor.__init__(self, document)
- self.settings = document.settings
- self.current_field_name = None
- self.body = u""
- self.indent_width = 0
- self.initial_indent = u""
- self.subsequent_indent = u""
- self.current_entry = None
- def astext(self):
- """ Return the translated document as text. """
- return self.body
- def append_to_current_entry(self, text):
- if self.current_entry is not None:
- if self.current_entry.body is not None:
- self.current_entry.body += text
- def visit_Text(self, node):
- raw_text = node.astext()
- text = textwrap.fill(
- raw_text,
- width=self.wrap_width,
- initial_indent=self.initial_indent,
- subsequent_indent=self.subsequent_indent)
- self.append_to_current_entry(text)
- def depart_Text(self, node):
- pass
- def visit_comment(self, node):
- raise docutils.nodes.SkipNode
- def depart_comment(self, node):
- pass
- def visit_field_body(self, node):
- convert_func = self.field_convert_funcs[self.current_field_name]
- attr_name = self.current_field_name
- attr_value = convert_func(node.astext())
- setattr(self.current_entry, attr_name, attr_value)
- raise docutils.nodes.SkipNode
- def depart_field_body(self, node):
- pass
- def visit_field_list(self, node):
- section_node = node.parent
- if not isinstance(section_node, docutils.nodes.section):
- raise InvalidFormatError(
- "Unexpected field list within " + repr(section_node))
- def depart_field_list(self, node):
- self.current_field_name = None
- if self.current_entry is not None:
- self.current_entry.body = u""
- def visit_field_name(self, node):
- field_name = node.astext()
- if field_name.lower() not in ("released", "maintainer"):
- raise InvalidFormatError(
- "Unexpected field name %(field_name)s" % vars())
- self.current_field_name = field_name.lower()
- raise docutils.nodes.SkipNode
- def depart_field_name(self, node):
- pass
- def adjust_indent_width(self, delta):
- self.indent_width += delta
- self.subsequent_indent = u" " * self.indent_width
- self.initial_indent = self.subsequent_indent
- def visit_list_item(self, node):
- self.adjust_indent_width(+2)
- self.initial_indent = self.subsequent_indent[:-2]
- self.append_to_current_entry(self.initial_indent + "- ")
- def depart_list_item(self, node):
- self.adjust_indent_width(-2)
- self.append_to_current_entry("\n")
- def visit_section(self, node):
- self.current_entry = NewsEntry()
- def depart_section(self, node):
- self.body += self.current_entry.as_specfile_changelog_entry() + "\n"
- self.current_entry = None
- def visit_title(self, node):
- title_text = node.astext()
- words = title_text.split(" ")
- self.current_entry.version = words[-1]
- def depart_title(self, node):
- pass
- description = (
- u"Render the NEWS file to a specfile changelog field value."
- + u" " + default_description)
- publish_cmdline(writer=SpecChangelogWriter(), description=description)
|