changelog-0.3.5/0000775000175000017500000000000013011374674014542 5ustar classicclassic00000000000000changelog-0.3.5/setup.py0000664000175000017500000000212313011372263016242 0ustar classicclassic00000000000000from setuptools import setup import os import re v = open(os.path.join(os.path.dirname(__file__), 'changelog', '__init__.py')) VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(v.read()).group(1) v.close() readme = os.path.join(os.path.dirname(__file__), 'README.rst') setup(name='changelog', version=VERSION, description="Provides simple Sphinx markup to render changelog displays.", long_description=open(readme).read(), classifiers=[ 'Development Status :: 3 - Alpha', 'Environment :: Console', 'Intended Audience :: Developers', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Documentation', ], keywords='Sphinx', author='Mike Bayer', author_email='mike@zzzcomputing.com', url='http://bitbucket.org/zzzeek/changelog', license='MIT', packages=['changelog'], include_package_data=True, zip_safe=False, ) changelog-0.3.5/changelog/0000775000175000017500000000000013011374674016471 5ustar classicclassic00000000000000changelog-0.3.5/changelog/changelog.css0000664000175000017500000000014113011372263021116 0ustar classicclassic00000000000000a.changeset-link { visibility: hidden; } li:hover a.changeset-link { visibility: visible; } changelog-0.3.5/changelog/changelog.py0000664000175000017500000003273513011372300020764 0ustar classicclassic00000000000000#! coding: utf-8 import re from sphinx.util.compat import Directive from docutils.statemachine import StringList from docutils import nodes from sphinx.util.console import bold import os from sphinx.util.osutil import copyfile import textwrap import itertools import collections import sys py2k = sys.version_info < (3, 0) if py2k: import md5 else: import hashlib as md5 def _is_html(app): return app.builder.name in ('html', 'readthedocs') # 'readthedocs', classy def _comma_list(text): return re.split(r"\s*,\s*", text.strip()) def _parse_content(content): d = {} d['text'] = [] idx = 0 for line in content: idx += 1 m = re.match(r' *\:(.+?)\:(?: +(.+))?', line) if m: attrname, value = m.group(1, 2) d[attrname] = value or '' elif idx == 1 and line: # accomodate a unique value on the edge of .. change:: continue else: break d["text"] = content[idx:] return d class EnvDirective(object): @property def env(self): return self.state.document.settings.env @classmethod def changes(cls, env): return env.temp_data['ChangeLogDirective_changes'] class ChangeLogDirective(EnvDirective, Directive): has_content = True 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: if self.version not in rec['versions']: continue inner_tag = rec['tags'].intersection(self.inner_tag_sort) if inner_tag: inner_tag = inner_tag.pop() else: inner_tag = "" for compound, comp_words in compound_sections: if rec['tags'].issuperset(comp_words): bysection[(compound, inner_tag)].append(rec) all_sections.add(compound) break 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 def _setup_run(self): self.sections = self.env.config.changelog_sections self.inner_tag_sort = self.env.config.changelog_inner_tag_sort + [""] if 'ChangeLogDirective_changes' not in self.env.temp_data: self.env.temp_data['ChangeLogDirective_changes'] = [] self._parsed_content = _parse_content(self.content) self.version = version = self._parsed_content.get('version', '') self.env.temp_data['ChangeLogDirective_version'] = version p = nodes.paragraph('', '',) self.state.nested_parse(self.content[1:], 0, p) def run(self): self._setup_run() if 'ChangeLogDirective_includes' in self.env.temp_data: return [] changes = self.changes(self.env) output = [] id_prefix = "change-%s" % (self.version, ) topsection = self._run_top(id_prefix) output.append(topsection) bysection, all_sections = self._organize_by_section(changes) counter = itertools.count() 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 rec in bysection[(self.default_section, cat)]: rec["id"] = "%s-%s" % (id_prefix, next(counter)) self._render_rec(rec, None, cat, 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 _append_node(self): return nodes.bullet_list() def _run_top(self, id_prefix): version = self._parsed_content.get('version', '') topsection = nodes.section('', nodes.title(version, version), ids=[id_prefix] ) if self._parsed_content.get("released"): topsection.append(nodes.Text("Released: %s" % self._parsed_content['released'])) else: topsection.append(nodes.Text("no release date")) intro_para = nodes.paragraph('', '') len_ = -1 for len_, text in enumerate(self._parsed_content['text']): if ".. change::" in text: break # if encountered any text elements that didn't start with # ".. change::", those become the intro if len_ > 0: self.state.nested_parse(self._parsed_content['text'][0:len_], 0, intro_para) topsection.append(intro_para) 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 = "change-%s" % ( md5.md5(to_hash.encode('ascii', 'ignore') ).hexdigest()) targetnode = nodes.target('', '', ids=[targetid]) para.insert(0, targetnode) permalink = nodes.reference('', '', nodes.Text(u"¶", u"¶"), refid=targetid, classes=['changeset-link', 'headerlink'], ) para.append(permalink) if len(rec['versions']) > 1: backported_changes = rec['sorted_versions'][rec['sorted_versions'].index(self.version) + 1:] if backported_changes: backported = nodes.paragraph('') backported.append(nodes.Text("This change is also ", "")) backported.append(nodes.strong("", "backported")) backported.append(nodes.Text(" to: %s" % ", ".join(backported_changes), "")) para.append(backported) insert_ticket = nodes.paragraph('') para.append(insert_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("References: """)) i += 1 if render is not None: if isinstance(render, dict): if ":" in refname: typ, refval = refname.split(":") else: typ = "default" refval = refname refuri = render[typ] % refval else: 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 rec['tags']: tag_node = nodes.strong('', " ".join("[%s]" % t for t in [t1 for t1 in [section, cat] if t1 in rec['tags']] + list(rec['tags'].difference([section, cat])) ) + " " ) para.children[0].insert(0, tag_node) append_sec.append( nodes.list_item('', nodes.target('', '', ids=[rec['id']]), para ) ) class ChangeLogImportDirective(EnvDirective, Directive): has_content = True def _setup_run(self): if 'ChangeLogDirective_changes' not in self.env.temp_data: self.env.temp_data['ChangeLogDirective_changes'] = [] def run(self): self._setup_run() # tell ChangeLogDirective we're here, also prevent # nested .. include calls if 'ChangeLogDirective_includes' not in self.env.temp_data: self.env.temp_data['ChangeLogDirective_includes'] = True p = nodes.paragraph('', '',) self.state.nested_parse(self.content, 0, p) del self.env.temp_data['ChangeLogDirective_includes'] return [] 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', '')) declared_version = self.env.temp_data['ChangeLogDirective_version'] versions = set(_comma_list(content.get("versions", ""))).difference(['']).\ union([declared_version]) # if we don't refer to any other versions and we're in an include, # skip if len(versions) == 1 and 'ChangeLogDirective_includes' in self.env.temp_data: return [] def int_ver(ver): out = [] for dig in ver.split("."): try: out.append(int(dig)) except ValueError: out.append(0) return tuple(out) rec = { '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': "change", "title": content.get("title", None), 'sorted_tags': sorted_tags, "versions": versions, "sorted_versions": list(reversed(sorted(versions, key=int_ver))) } self.state.nested_parse(content['text'], 0, p) ChangeLogDirective.changes(self.env).append(rec) return [] 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( [line.strip() for line in textwrap.dedent(text).split("\n")] ) def make_ticket_link(name, rawtext, text, lineno, inliner, options={}, content=[]): env = inliner.document.settings.env render_ticket = env.config.changelog_render_ticket or "%s" prefix = "#%s" if render_ticket: ref = render_ticket % text node = nodes.reference(rawtext, prefix % text, refuri=ref, **options) else: node = nodes.Text(prefix % text, prefix % text) return [node], [] def add_stylesheet(app): app.add_stylesheet('changelog.css') def copy_stylesheet(app, exception): app.info(bold('The name of the builder is: %s' % app.builder.name), nonl=True) if not _is_html(app) or exception: return app.info(bold('Copying sphinx_paramlinks stylesheet... '), nonl=True) source = os.path.abspath(os.path.dirname(__file__)) # the '_static' directory name is hardcoded in # sphinx.builders.html.StandaloneHTMLBuilder.copy_static_files. # would be nice if Sphinx could improve the API here so that we just # give it the path to a .css file and it does the right thing. dest = os.path.join(app.builder.outdir, '_static', 'changelog.css') copyfile(os.path.join(source, "changelog.css"), dest) app.info('done') def setup(app): app.add_directive('changelog', ChangeLogDirective) app.add_directive('change', ChangeDirective) app.add_directive('changelog_imports', ChangeLogImportDirective) 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') app.connect('builder-inited', add_stylesheet) app.connect('build-finished', copy_stylesheet) app.add_role('ticket', make_ticket_link) changelog-0.3.5/changelog/__init__.py0000664000175000017500000000006313011372304020565 0ustar classicclassic00000000000000__version__ = '0.3.5' from .changelog import setupchangelog-0.3.5/README.rst0000664000175000017500000000475213011372263016231 0ustar classicclassic00000000000000========== Changelog ========== A `Sphinx `_ extension to generate changelog files. This is an experimental, possibly-not-useful extension that's used by the `SQLAlchemy `_ project and related projects. Configuration ============= A sample configuration in ``conf.py`` looks like this:: extensions = [ # changelog extension 'changelog', # your other sphinx extensions # ... ] # section names - optional changelog_sections = ["general", "rendering", "tests"] # tags to sort on inside of sections - also optional changelog_inner_tag_sort = ["feature", "bug"] # how to render changelog links - these are plain # python string templates, ticket/pullreq/changeset number goes # in "%s" changelog_render_ticket = "http://bitbucket.org/myusername/myproject/issue/%s" changelog_render_pullreq = "http://bitbucket.org/myusername/myproject/pullrequest/%s" changelog_render_changeset = "http://bitbucket.org/myusername/myproject/changeset/%s" Usage ===== Changelog introduces the ``changelog`` and ``change`` directives:: ==================== Changelog for 1.5.6 ==================== .. changelog:: :version: 1.5.6 :released: Sun Oct 12 2008 .. change:: :tags: general :tickets: 27 Improved the frobnozzle. .. change:: :tags: rendering, tests :pullreq: 8 :changeset: a9d7cc0b56c2 Rendering tests now correctly render. With the above markup, the changes above will be rendered into document sections per changelog, then each change within organized into paragraphs, including special markup for tags, tickets mentioned, pull requests, changesets. The entries will be grouped and sorted by tag according to the configuration of the ``changelog_sections`` and ``changelog_inner_tag_sort`` configurations. A "compound tag" can also be used, if the configuration has a section like this:: changelog_sections = ["orm declarative", "orm"] Then change entries which contain both the ``orm`` and ``declarative`` tags will be grouped under a section called ``orm declarative``, followed by the ``orm`` section where change entries that only have ``orm`` will be placed. Other Markup ============ The ``:ticket:`` directive will make use of the ``changelog_render_ticket`` markup to render a ticket link:: :ticket:`456` changelog-0.3.5/MANIFEST.in0000664000175000017500000000010313011372263016262 0ustar classicclassic00000000000000recursive-include changelog *.py *.css include README* LICENSE changelog-0.3.5/PKG-INFO0000664000175000017500000000751013011374674015642 0ustar classicclassic00000000000000Metadata-Version: 1.1 Name: changelog Version: 0.3.5 Summary: Provides simple Sphinx markup to render changelog displays. Home-page: http://bitbucket.org/zzzeek/changelog Author: Mike Bayer Author-email: mike@zzzcomputing.com License: MIT Description: ========== Changelog ========== A `Sphinx `_ extension to generate changelog files. This is an experimental, possibly-not-useful extension that's used by the `SQLAlchemy `_ project and related projects. Configuration ============= A sample configuration in ``conf.py`` looks like this:: extensions = [ # changelog extension 'changelog', # your other sphinx extensions # ... ] # section names - optional changelog_sections = ["general", "rendering", "tests"] # tags to sort on inside of sections - also optional changelog_inner_tag_sort = ["feature", "bug"] # how to render changelog links - these are plain # python string templates, ticket/pullreq/changeset number goes # in "%s" changelog_render_ticket = "http://bitbucket.org/myusername/myproject/issue/%s" changelog_render_pullreq = "http://bitbucket.org/myusername/myproject/pullrequest/%s" changelog_render_changeset = "http://bitbucket.org/myusername/myproject/changeset/%s" Usage ===== Changelog introduces the ``changelog`` and ``change`` directives:: ==================== Changelog for 1.5.6 ==================== .. changelog:: :version: 1.5.6 :released: Sun Oct 12 2008 .. change:: :tags: general :tickets: 27 Improved the frobnozzle. .. change:: :tags: rendering, tests :pullreq: 8 :changeset: a9d7cc0b56c2 Rendering tests now correctly render. With the above markup, the changes above will be rendered into document sections per changelog, then each change within organized into paragraphs, including special markup for tags, tickets mentioned, pull requests, changesets. The entries will be grouped and sorted by tag according to the configuration of the ``changelog_sections`` and ``changelog_inner_tag_sort`` configurations. A "compound tag" can also be used, if the configuration has a section like this:: changelog_sections = ["orm declarative", "orm"] Then change entries which contain both the ``orm`` and ``declarative`` tags will be grouped under a section called ``orm declarative``, followed by the ``orm`` section where change entries that only have ``orm`` will be placed. Other Markup ============ The ``:ticket:`` directive will make use of the ``changelog_render_ticket`` markup to render a ticket link:: :ticket:`456` Keywords: Sphinx Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Documentation changelog-0.3.5/LICENSE0000664000175000017500000000216213011372263015540 0ustar classicclassic00000000000000This is the MIT license: http://www.opensource.org/licenses/mit-license.php Copyright (C) 2012 by Michael Bayer. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. changelog-0.3.5/changelog.egg-info/0000775000175000017500000000000013011374674020163 5ustar classicclassic00000000000000changelog-0.3.5/changelog.egg-info/SOURCES.txt0000664000175000017500000000042013011374674022043 0ustar classicclassic00000000000000LICENSE MANIFEST.in README.rst setup.py changelog/__init__.py changelog/changelog.css changelog/changelog.py changelog.egg-info/PKG-INFO changelog.egg-info/SOURCES.txt changelog.egg-info/dependency_links.txt changelog.egg-info/not-zip-safe changelog.egg-info/top_level.txtchangelog-0.3.5/changelog.egg-info/PKG-INFO0000664000175000017500000000751013011374674021263 0ustar classicclassic00000000000000Metadata-Version: 1.1 Name: changelog Version: 0.3.5 Summary: Provides simple Sphinx markup to render changelog displays. Home-page: http://bitbucket.org/zzzeek/changelog Author: Mike Bayer Author-email: mike@zzzcomputing.com License: MIT Description: ========== Changelog ========== A `Sphinx `_ extension to generate changelog files. This is an experimental, possibly-not-useful extension that's used by the `SQLAlchemy `_ project and related projects. Configuration ============= A sample configuration in ``conf.py`` looks like this:: extensions = [ # changelog extension 'changelog', # your other sphinx extensions # ... ] # section names - optional changelog_sections = ["general", "rendering", "tests"] # tags to sort on inside of sections - also optional changelog_inner_tag_sort = ["feature", "bug"] # how to render changelog links - these are plain # python string templates, ticket/pullreq/changeset number goes # in "%s" changelog_render_ticket = "http://bitbucket.org/myusername/myproject/issue/%s" changelog_render_pullreq = "http://bitbucket.org/myusername/myproject/pullrequest/%s" changelog_render_changeset = "http://bitbucket.org/myusername/myproject/changeset/%s" Usage ===== Changelog introduces the ``changelog`` and ``change`` directives:: ==================== Changelog for 1.5.6 ==================== .. changelog:: :version: 1.5.6 :released: Sun Oct 12 2008 .. change:: :tags: general :tickets: 27 Improved the frobnozzle. .. change:: :tags: rendering, tests :pullreq: 8 :changeset: a9d7cc0b56c2 Rendering tests now correctly render. With the above markup, the changes above will be rendered into document sections per changelog, then each change within organized into paragraphs, including special markup for tags, tickets mentioned, pull requests, changesets. The entries will be grouped and sorted by tag according to the configuration of the ``changelog_sections`` and ``changelog_inner_tag_sort`` configurations. A "compound tag" can also be used, if the configuration has a section like this:: changelog_sections = ["orm declarative", "orm"] Then change entries which contain both the ``orm`` and ``declarative`` tags will be grouped under a section called ``orm declarative``, followed by the ``orm`` section where change entries that only have ``orm`` will be placed. Other Markup ============ The ``:ticket:`` directive will make use of the ``changelog_render_ticket`` markup to render a ticket link:: :ticket:`456` Keywords: Sphinx Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Documentation changelog-0.3.5/changelog.egg-info/dependency_links.txt0000664000175000017500000000000113011374674024231 0ustar classicclassic00000000000000 changelog-0.3.5/changelog.egg-info/top_level.txt0000664000175000017500000000001213011374674022706 0ustar classicclassic00000000000000changelog changelog-0.3.5/changelog.egg-info/not-zip-safe0000664000175000017500000000000113011374666022412 0ustar classicclassic00000000000000 changelog-0.3.5/setup.cfg0000664000175000017500000000007313011374674016363 0ustar classicclassic00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0