Commits

Mike Bayer committed 99efced

updates

Comments (0)

Files changed (1)

source/builder/changelog.py

 import textwrap
 import itertools
 import collections
+import md5
 
 def _comma_list(text):
     return re.split(r"\s*,\s*", text.strip())
     d["text"] = content[idx:]
     return d
 
-def _ticketurl(ticket):
-    return "http://www.sqlalchemy.org/trac/ticket/%s" % ticket
 
 class EnvDirective(object):
     @property
 
     type_ = "change"
 
-    sections = _comma_list("general, orm, orm declarative, orm querying, \
-                orm configuration, engine, sql, \
-                schema, \
-                postgresql, mysql, sqlite, mssql, oracle, firebird, misc")
-
-    subsections = ["feature", "bug", "moved", "changed", "removed", ""]
-
+    default_section = 'misc'
 
     def _organize_by_section(self, changes):
         compound_sections = [(s, s.split(" ")) for s in
                                 self.sections if " " in s]
 
         bysection = collections.defaultdict(list)
+        all_sections = set()
         for rec in changes:
-            subsection = rec['tags'].intersection(self.subsections)
-            if subsection:
-                subsection = subsection.pop()
+            inner_tag = rec['tags'].intersection(self.inner_tag_sort)
+            if inner_tag:
+                inner_tag = inner_tag.pop()
             else:
-                subsection = ""
+                inner_tag = ""
 
             for compound, comp_words in compound_sections:
                 if rec['tags'].issuperset(comp_words):
-                    bysection[(compound, subsection)].append(rec)
+                    bysection[(compound, inner_tag)].append(rec)
+                    all_sections.add(compound)
                     break
-
-            intersect = rec['tags'].intersection(self.sections)
-            if intersect:
-                bysection[(intersect.pop(), subsection)].append(rec)
-                continue
-
-            bysection[('misc', subsection)].append(rec)
-        return bysection
+            else:
+                intersect = rec['tags'].intersection(self.sections)
+                if intersect:
+                    for sec in rec['sorted_tags']:
+                        if sec in intersect:
+                            bysection[(sec, inner_tag)].append(rec)
+                            all_sections.add(sec)
+                            break
+                else:
+                    bysection[(self.default_section, inner_tag)].append(rec)
+        return bysection, all_sections
 
     @classmethod
     def changes(cls, env):
         return env.temp_data['ChangeLogDirective_%s_changes' % cls.type_]
 
     def _setup_run(self):
+        self.sections = self.env.config.changelog_sections
+        self.inner_tag_sort = self.env.config.changelog_inner_tag_sort + [""]
         self.env.temp_data['ChangeLogDirective_%s_changes' % self.type_] = []
         self._parsed_content = _parse_content(self.content)
 
         changes = self.changes(self.env)
         output = []
 
-        version = self._parsed_content.get('version', '')
+        self.version = version = self._parsed_content.get('version', '')
         id_prefix = "%s-%s" % (self.type_, version)
         topsection = self._run_top(id_prefix)
         output.append(topsection)
 
-        bysection = self._organize_by_section(changes)
+        bysection, all_sections = self._organize_by_section(changes)
 
         counter = itertools.count()
 
-        for section in self.sections:
-            sec, append_sec = self._section(section, id_prefix)
+        sections_to_render = [s for s in self.sections if s in all_sections]
+        if not sections_to_render:
+            for cat in self.inner_tag_sort:
+                append_sec = self._append_node()
 
-            for cat in self.subsections:
-                for rec in bysection[(section, cat)]:
+                for rec in bysection[(self.default_section, cat)]:
                     rec["id"] = "%s-%s" % (id_prefix, next(counter))
 
-                    self._render_rec(rec, section, cat, append_sec)
+                    self._render_rec(rec, None, cat, append_sec)
 
-            if append_sec.children:
-                topsection.append(sec)
+                if append_sec.children:
+                    topsection.append(append_sec)
+        else:
+            for section in sections_to_render + [self.default_section]:
+                sec = nodes.section('',
+                        nodes.title(section, section),
+                        ids=["%s-%s" % (id_prefix, section.replace(" ", "-"))]
+                )
+
+                append_sec = self._append_node()
+                sec.append(append_sec)
+
+                for cat in self.inner_tag_sort:
+                    for rec in bysection[(section, cat)]:
+                        rec["id"] = "%s-%s" % (id_prefix, next(counter))
+                        self._render_rec(rec, section, cat, append_sec)
+
+                if append_sec.children:
+                    topsection.append(sec)
 
         return output
 
-    def _section(self, section, id_prefix):
-        bullets = nodes.bullet_list()
-        sec = nodes.section('',
-                nodes.title(section, section),
-                bullets,
-                ids=["%s-%s" % (id_prefix, section.replace(" ", "-"))]
-        )
-        return sec, bullets
+    def _append_node(self):
+        return nodes.bullet_list()
 
     def _run_top(self, id_prefix):
         version = self._parsed_content.get('version', '')
                 ids=[id_prefix]
             )
 
-        if "released" in self._parsed_content:
-            topsection.append(nodes.Text("Released: %s" % self._parsed_content['released']))
+        if self._parsed_content.get("released"):
+            topsection.append(nodes.Text("Released: %s" %
+                        self._parsed_content['released']))
         else:
             topsection.append(nodes.Text("no release date"))
         return topsection
 
+
     def _render_rec(self, rec, section, cat, append_sec):
         para = rec['node'].deepcopy()
+
+        text = _text_rawsource_from_node(para)
+
+        to_hash = "%s %s" % (self.version, text[0:100])
+        targetid = "%s-%s" % (self.type_,
+                        md5.md5(to_hash.encode('ascii', 'ignore')
+                            ).hexdigest())
+        targetnode = nodes.target('', '', ids=[targetid])
+        para.insert(0, targetnode)
+        permalink = nodes.reference('', '',
+                        nodes.Text("(link)", "(link)"),
+                        refid=targetid,
+                        classes=['changeset-link']
+                    )
+        para.append(permalink)
+
         insert_ticket = nodes.paragraph('')
         para.append(insert_ticket)
 
-        for i, ticket in enumerate(rec['tickets']):
-            if i > 0:
-                insert_ticket.append(nodes.Text(", ", ", "))
-            else:
-                insert_ticket.append(nodes.Text(" ", " "))
-            insert_ticket.append(
-                nodes.reference('', '',
-                    nodes.Text("#%s" % ticket, "#%s" % ticket),
-                    refuri=_ticketurl(ticket)
-                )
-            )
+        i = 0
+        for collection, render, prefix in (
+                (rec['tickets'], self.env.config.changelog_render_ticket, "#%s"),
+                (rec['pullreq'], self.env.config.changelog_render_pullreq,
+                                            "pull request %s"),
+                (rec['changeset'], self.env.config.changelog_render_changeset, "r%s"),
+            ):
+            for refname in collection:
+                if i > 0:
+                    insert_ticket.append(nodes.Text(", ", ", "))
+                else:
+                    insert_ticket.append(nodes.Text(" ", " "))
+                i += 1
+                if render is not None:
+                    refuri = render % refname
+                    node = nodes.reference('', '',
+                            nodes.Text(prefix % refname, prefix % refname),
+                            refuri=refuri
+                        )
+                else:
+                    node = nodes.Text(prefix % refname, prefix % refname)
+                insert_ticket.append(node)
 
-        if cat or rec['tags']:
-            #tag_node = nodes.strong('',
-            #            "[" + cat + "] "
-            #        )
+        if rec['tags']:
             tag_node = nodes.strong('',
                         " ".join("[%s]" % t for t
                             in
-                                [cat] +
-                                list(rec['tags'].difference([cat]))
-                            if t
+                                [t1 for t1 in [section, cat]
+                                    if t1 in rec['tags']] +
+
+                                list(rec['tags'].difference([section, cat]))
                         ) + " "
                     )
             para.children[0].insert(0, tag_node)
         )
 
 
-class MigrationLogDirective(ChangeLogDirective):
-    type_ = "migration"
-
-    sections = _comma_list("New Features, Behavioral Changes, Removed")
-
-    subsections = _comma_list("general, orm, orm declarative, orm querying, \
-                orm configuration, engine, sql, \
-                postgresql, mysql, sqlite")
-
-    def _run_top(self, id_prefix):
-        version = self._parsed_content.get('version', '')
-        title = "What's new in %s?" % version
-        topsection = nodes.section('',
-                nodes.title(title, title),
-                ids=[id_prefix]
-            )
-        if "released" in self._parsed_content:
-            topsection.append(nodes.Text("Released: %s" % self._parsed_content['released']))
-        return topsection
-
-    def _section(self, section, id_prefix):
-        sec = nodes.section('',
-                nodes.title(section, section),
-                ids=["%s-%s" % (id_prefix, section.replace(" ", "-"))]
-        )
-        return sec, sec
-
-    def _render_rec(self, rec, section, cat, append_sec):
-        para = rec['node'].deepcopy()
-
-        insert_ticket = nodes.paragraph('')
-        para.append(insert_ticket)
-
-        for i, ticket in enumerate(rec['tickets']):
-            if i > 0:
-                insert_ticket.append(nodes.Text(", ", ", "))
-            else:
-                insert_ticket.append(nodes.Text(" ", " "))
-            insert_ticket.append(
-                nodes.reference('', '',
-                    nodes.Text("#%s" % ticket, "#%s" % ticket),
-                    refuri=_ticketurl(ticket)
-                )
-            )
-
-        append_sec.append(
-            nodes.section('',
-                nodes.title(rec['title'], rec['title']),
-                para,
-                ids=[rec['id']]
-            )
-        )
-
-
-
 class ChangeDirective(EnvDirective, Directive):
     has_content = True
 
     def run(self):
         content = _parse_content(self.content)
         p = nodes.paragraph('', '',)
+        sorted_tags = _comma_list(content.get('tags', ''))
         rec = {
-            'tags': set(_comma_list(content.get('tags', ''))).difference(['']),
+            'tags': set(sorted_tags).difference(['']),
             'tickets': set(_comma_list(content.get('tickets', ''))).difference(['']),
+            'pullreq': set(_comma_list(content.get('pullreq', ''))).difference(['']),
+            'changeset': set(_comma_list(content.get('changeset', ''))).difference(['']),
             'node': p,
             'type': self.type_,
-            "title": content.get("title", None)
+            "title": content.get("title", None),
+            'sorted_tags': sorted_tags
         }
 
         if "declarative" in rec['tags']:
 
         return []
 
-class MigrationDirective(ChangeDirective):
-    type_ = "migration"
-    parent_cls = MigrationLogDirective
-
+def _text_rawsource_from_node(node):
+    src = []
+    stack = [node]
+    while stack:
+        n = stack.pop(0)
+        if isinstance(n, nodes.Text):
+            src.append(n.rawsource)
+        stack.extend(n.children)
+    return "".join(src)
 
 def _rst2sphinx(text):
     return StringList(
 
 def setup(app):
     app.add_directive('changelog', ChangeLogDirective)
-    app.add_directive('migrationlog', MigrationLogDirective)
-    app.add_directive('migration', MigrationDirective)
     app.add_directive('change', ChangeDirective)
-
+    app.add_config_value("changelog_sections", [], 'env')
+    app.add_config_value("changelog_inner_tag_sort", [], 'env')
+    app.add_config_value("changelog_render_ticket",
+            None,
+            'env'
+        )
+    app.add_config_value("changelog_render_pullreq",
+            None,
+            'env'
+        )
+    app.add_config_value("changelog_render_changeset",
+            None,
+            'env'
+        )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.