././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1766243969.7499623 changelog-0.6.2/0000755000175100017510000000000015121537202013112 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/LICENSE0000644000175100017510000000216215121537172014126 0ustar00runnerrunnerThis 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/MANIFEST.in0000644000175100017510000000010315121537172014650 0ustar00runnerrunnerrecursive-include changelog *.py *.css include README* LICENSE ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1766243969.7499623 changelog-0.6.2/PKG-INFO0000644000175100017510000001217515121537202014215 0ustar00runnerrunnerMetadata-Version: 2.4 Name: changelog Version: 0.6.2 Summary: Provides simple Sphinx markup to render changelog displays. Home-page: https://github.com/sqlalchemyorg/changelog Author: Mike Bayer Author-email: mike@zzzcomputing.com License: MIT Keywords: Sphinx 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 License-File: LICENSE Requires-Dist: Sphinx>=4.0.0 Requires-Dist: docutils Requires-Dist: looseversion Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: requires-dist Dynamic: summary ========== Changelog ========== |PyPI| |Python| |Downloads| .. |PyPI| image:: https://img.shields.io/pypi/v/changelog :target: https://pypi.org/project/changelog :alt: PyPI .. |Python| image:: https://img.shields.io/pypi/pyversions/changelog :target: https://pypi.org/project/changelog :alt: PyPI - Python Version .. |Downloads| image:: https://img.shields.io/pypi/dm/changelog :target: https://pypi.org/project/changelog :alt: PyPI - Downloads 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"] # section css classes - optional changelog_caption_class = "caption" # tags to sort on inside of sections - also optional changelog_inner_tag_sort = ["feature", "bug"] # whether sections should be hidden from tags list changelog_hide_sections_from_tags = False # whether tags should be hidden from entries changelog_hide_tags_in_entry = False # 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` Other things not documented yet =============================== * the ``:version:`` directive, which indicates a changelog entry should be listed in other versions as well * the ``.. changelog_imports::`` directive - reads other changelog.rst files looking for ``:version:`` directives which apply to this changelog file, adding those entries to the changelog entries in this file * the ``:include_notes_from:`` symbol - imports all the .rst files in a directory into the current one so that changes can be one-per-file, makes git merges possible * the ``changelog release-notes`` command that at release time gathers up the above-mentioned change-per-file .rst files and renders them into the main changelog.rst file, running "git rm" on the individual files * the changelog.rst -> markdown converter, used for web guis that want changelog sections written in markdown * the changelog.rst -> stream per changelog markdown API function, which can for example stream the changelogs per release to the github releases API ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/README.rst0000644000175100017510000001030715121537172014610 0ustar00runnerrunner========== Changelog ========== |PyPI| |Python| |Downloads| .. |PyPI| image:: https://img.shields.io/pypi/v/changelog :target: https://pypi.org/project/changelog :alt: PyPI .. |Python| image:: https://img.shields.io/pypi/pyversions/changelog :target: https://pypi.org/project/changelog :alt: PyPI - Python Version .. |Downloads| image:: https://img.shields.io/pypi/dm/changelog :target: https://pypi.org/project/changelog :alt: PyPI - Downloads 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"] # section css classes - optional changelog_caption_class = "caption" # tags to sort on inside of sections - also optional changelog_inner_tag_sort = ["feature", "bug"] # whether sections should be hidden from tags list changelog_hide_sections_from_tags = False # whether tags should be hidden from entries changelog_hide_tags_in_entry = False # 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` Other things not documented yet =============================== * the ``:version:`` directive, which indicates a changelog entry should be listed in other versions as well * the ``.. changelog_imports::`` directive - reads other changelog.rst files looking for ``:version:`` directives which apply to this changelog file, adding those entries to the changelog entries in this file * the ``:include_notes_from:`` symbol - imports all the .rst files in a directory into the current one so that changes can be one-per-file, makes git merges possible * the ``changelog release-notes`` command that at release time gathers up the above-mentioned change-per-file .rst files and renders them into the main changelog.rst file, running "git rm" on the individual files * the changelog.rst -> markdown converter, used for web guis that want changelog sections written in markdown * the changelog.rst -> stream per changelog markdown API function, which can for example stream the changelogs per release to the github releases API ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1766243969.7479625 changelog-0.6.2/changelog/0000755000175100017510000000000015121537202015041 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/__init__.py0000644000175100017510000000007415121537172017161 0ustar00runnerrunner__version__ = "0.6.2" from .sphinxext import setup # noqa ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/changelog.css0000644000175100017510000000015315121537172017507 0ustar00runnerrunnera.changelog-reference { visibility: hidden; } li:hover a.changelog-reference { visibility: visible; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/cmd.py0000644000175100017510000000607315121537172016172 0ustar00runnerrunnerimport argparse import os import re import shutil import sys import tempfile from . import mdwriter def release_notes_into_changelog_file(target_filename, version, release_date): """Read changelog fragment files and render them into a single .rst file. remove the fragment files afterwards using git rm. The fragment files are located by looking for ':include_notes_from:' directives in the given changelog file. """ output = tempfile.NamedTemporaryFile( mode="w", delete=False, encoding="utf-8" ) with open(target_filename) as handle: for line in handle: m = re.match(r".*:version: %s" % version, line) if m: output.write(line) output.write(" :released: %s\n" % release_date) continue m = re.match(r".*:include_notes_from: (.+)", line) if m: notes_dir = os.path.join( os.path.dirname(target_filename), m.group(1) ) for fname in os.listdir(notes_dir): if not fname.endswith(".rst"): continue fname_path = os.path.join(notes_dir, fname) output.write("\n") with open(fname_path) as inner: for inner_line in inner: output.write((" " + inner_line).rstrip() + "\n") os.system("git rm %s" % fname_path) else: output.write(line) output.close() shutil.move(output.name, target_filename) def main(argv=None): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() subparser = subparsers.add_parser( "release-notes", help="Merge notes files into changelog and git rm" ) subparser.add_argument("filename", help="target changelog filename") subparser.add_argument( "version", help="version string as it appears in changelog" ) subparser.add_argument("date", help="full text of datestamp to insert") subparser.set_defaults( cmd=( release_notes_into_changelog_file, ["filename", "version", "date"], ) ) subparser = subparsers.add_parser( "generate-md", help="Generate file into markdown" ) subparser.add_argument("filename", help="target changelog filename") subparser.add_argument("-c", "--config", help="path to conf.py") subparser.add_argument( "-v", "--version", type=str, help="render changelog only for version given", ) subparser.add_argument( "-s", "--sections-only", action="store_true", help="render changelogs as top level sections", ) subparser.set_defaults( cmd=( mdwriter.render_changelog_as_md, ["filename", "config", "version", "sections_only"], ) ) options = parser.parse_args(argv) fn, argnames = options.cmd fn(*[getattr(options, name) for name in argnames]) if __name__ == "__main__": main(sys.argv) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/docutils.py0000644000175100017510000003362115121537172017254 0ustar00runnerrunnerimport collections import hashlib as md5 import os import re import sys import warnings from docutils import nodes from docutils.parsers.rst import Directive from docutils.parsers.rst import directives from docutils.parsers.rst import roles from looseversion import LooseVersion from . import generate_rst from .environment import Environment py3k = sys.version_info >= (3, 0) 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 Environment.from_document_settings(self.state.document.settings) @classmethod def get_changes_list(cls, env, hash_on_version): key = ("ChangeLogDirective_changes", hash_on_version) if key not in env.temp_data: env.temp_data[key] = collections.OrderedDict() return env.temp_data[key] class ChangeLogDirective(EnvDirective, Directive): """Implement the ``.. changelog::`` directive.""" has_content = True default_section = "misc" def run(self): self._parse() if not ChangeLogImportDirective.in_include_directive(self.env): return generate_rst.render_changelog(self) else: return [] def _parse(self): # 1. pull in global configuration from conf.py self.sections = self.env.changelog_sections self.caption_classes = self.env.changelog_caption_class.split(" ") self.inner_tag_sort = self.env.changelog_inner_tag_sort + [""] self.hide_sections_from_tags = bool( self.env.changelog_hide_sections_from_tags ) self.hide_tags_in_entry = bool(self.env.changelog_hide_tags_in_entry) # 2. examine top level directives inside the .. changelog:: # directive. version, release date self._parsed_content = parsed = _parse_content(self.content) self.version = version = parsed.get("version", "") self.release_date = parsed.get("released", None) self.is_released = bool(self.release_date) self.env.temp_data["ChangeLogDirective"] = self content = self.content # 3. read extra per-file included notes if "include_notes_from" in parsed: if content.items and content.items[0]: source = content.items[0][0] # seems we are now getting strings like: # changelog/changelog_11.rst source = source.split(" ")[0] path = os.path.join( os.path.dirname(source), parsed["include_notes_from"] ) else: path = parsed["include_notes_from"] if not os.path.exists(path): raise Exception("included nodes path %s does not exist" % path) files = [ fname for fname in os.listdir(path) if fname.endswith(".rst") ] for fname in self.env.status_iterator( files, "reading changelog note files (version %s)..." % version ): fpath = os.path.join(path, fname) self.env.sphinx_env.note_dependency(fpath) with open(fpath) as handle: content.append("", path, 0) for num, line in enumerate(handle): if not py3k: line = line.decode("utf-8") if "\t" in line: warnings.warn( "file %s has a tab in it! please " "convert to spaces." % fname ) line = line.replace("\t", " ") line = line.rstrip() content.append(line, path, num) # 4. parse the content of the .. changelog:: directive. This # is where we parse individual .. change:: directives and construct # a list of items, stored in the env via self.get_changes_list(env) p = nodes.paragraph("", "") self.state.nested_parse(content[1:], 0, p) class ChangeLogImportDirective(EnvDirective, Directive): """Implement the ``.. changelog_imports::`` directive. Here, we typically load in other changelog.rst files which may feature elements that also apply to our current changelog.rst file, when they specify the ``:version:`` modifier. """ has_content = True @classmethod def in_include_directive(cls, env): return "ChangeLogDirective_includes" in env.temp_data def run(self): # tell ChangeLogDirective we're here, also prevent # nested .. include calls if not self.in_include_directive(self.env): 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 SeeAlsoDirective(EnvDirective, Directive): """implement a quick version of Sphinx "seealso" when running outside of sphinx.""" has_content = True def run(self): text = "\n".join(self.content) ad = nodes.admonition(rawsource=text) st = nodes.strong() st.append(nodes.Text("See also:")) ad.append(st) ad.append(nodes.Text("\n")) self.state.nested_parse(self.content, 0, ad) return [ad] class ChangeDirective(EnvDirective, Directive): """Implement the ``.. change::`` directive.""" has_content = True def run(self): # don't do anything if we're not inside of a version if "ChangeLogDirective" not in self.env.temp_data: return [] content = _parse_content(self.content) sorted_tags = _comma_list(content.get("tags", "")) changelog_directive = self.env.temp_data["ChangeLogDirective"] declared_version = changelog_directive.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 ChangeLogImportDirective.in_include_directive(self.env): return [] body_paragraph = nodes.paragraph( "", "", classes=changelog_directive.caption_classes ) self.state.nested_parse(content["text"], 0, body_paragraph) raw_text = _text_rawsource_from_node(body_paragraph) tickets = set(_comma_list(content.get("tickets", ""))).difference([""]) pullreq = set(_comma_list(content.get("pullreq", ""))).difference([""]) tags = set(sorted_tags).difference([""]) for hash_on_version in versions: issue_hash = _get_robust_version_hash( raw_text, hash_on_version, tickets, tags ) rec = ChangeLogDirective.get_changes_list( changelog_directive.env, hash_on_version ).setdefault(issue_hash, {}) if not rec: sorted(versions, key=_str_version_as_tuple) rec.update( { "hash": issue_hash, "render_for_version": hash_on_version, "tags": tags, "tickets": tickets, "pullreq": pullreq, "changeset": set( _comma_list(content.get("changeset", "")) ).difference([""]), "node": body_paragraph, "raw_text": raw_text, "type": "change", "title": content.get("title", None), "sorted_tags": sorted_tags, "versions": versions, "version_to_hash": { version: _get_legacy_version_hash( raw_text, version ) for version in versions }, "source_versions": [declared_version], "sorted_versions": list( reversed( sorted(versions, key=_str_version_as_tuple) ) ), } ) else: # This seems to occur repeated times for each included # changelog, not clear if sphinx has changed the scope # of self.env to lead to this occurring more often self.env.log_debug( "Merging changelog record '%s' from version(s) %s " "with that of version %s", _quick_rec_str(rec), ", ".join(rec["source_versions"]), declared_version, ) rec["source_versions"].append(declared_version) assert rec["raw_text"] == raw_text assert rec["tags"] == tags assert rec["render_for_version"] == hash_on_version rec["tickets"].update(tickets) rec["pullreq"].update(pullreq) rec["changeset"].update( set(_comma_list(content.get("changeset", ""))).difference( [""] ) ) rec["versions"].update(versions) rec["version_to_hash"].update( { version: _get_legacy_version_hash(raw_text, version) for version in versions } ) rec["sorted_versions"] = list( reversed( sorted(rec["versions"], key=_str_version_as_tuple) ) ) return [] def _quick_rec_str(rec): """try to print an identifiable description of a record""" if rec["tickets"]: return "[tickets: %s]" % ", ".join(rec["tickets"]) else: return "%s..." % rec["raw_text"][0:25] def _get_legacy_version_hash(raw_text, version): # this needs to stay like this for link compatibility # with thousands of already-published changelogs to_hash = "%s %s" % (version, raw_text[0:100]) return md5.md5(to_hash.encode("ascii", "ignore")).hexdigest() def _get_robust_version_hash(raw_text, version, tickets, tags): # this needs to stay like this for link compatibility # with thousands of already-published changelogs to_hash = "%s %s %s %s" % ( version, ", ".join(tickets), ", ".join(tags), raw_text, ) return md5.md5(to_hash.encode("ascii", "ignore")).hexdigest() def _text_rawsource_from_node(node): src = [] stack = [node] while stack: n = stack.pop(0) if isinstance(n, nodes.Text): src.append(str(n)) stack.extend(n.children) return "".join(src) _VERSION_IDS = {} def _str_version_as_tuple(ver): if ver in _VERSION_IDS: return _VERSION_IDS[ver] result = LooseVersion(ver) _VERSION_IDS[ver] = result return result def make_ticket_link( name, rawtext, text, lineno, inliner, options={}, content=[] ): env = Environment.from_document_settings(inliner.document.settings) render_ticket = env.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 make_generic_attrref( name, rawtext, text, lineno, inliner, options={}, content=[] ): text = text.lstrip(".") lt = nodes.literal(rawtext=rawtext) lt.append(nodes.Text(text)) return [lt], [] def make_generic_funcref( name, rawtext, text, lineno, inliner, options={}, content=[] ): if not text.endswith("()"): text += "()" return make_generic_attrref( name, rawtext, text, lineno, inliner, options, content ) def make_generic_docref( name, rawtext, text, lineno, inliner, options={}, content=[] ): lt = nodes.literal(rawtext=rawtext) lt.append(nodes.Text(text)) return [lt], [] def setup_docutils(): """register docutils directives and roles assuming Sphinx is not in use.""" directives.register_directive("changelog", ChangeLogDirective) directives.register_directive("change", ChangeDirective) directives.register_directive( "changelog_imports", ChangeLogImportDirective ) directives.register_directive("seealso", SeeAlsoDirective) roles.register_canonical_role("ticket", make_ticket_link) # sphinx autodoc stuff we don't have in this context roles.register_canonical_role("func", make_generic_funcref) roles.register_canonical_role("class", make_generic_attrref) roles.register_canonical_role("paramref", make_generic_attrref) roles.register_canonical_role("attr", make_generic_attrref) roles.register_canonical_role("mod", make_generic_attrref) roles.register_canonical_role("meth", make_generic_funcref) roles.register_canonical_role("obj", make_generic_attrref) roles.register_canonical_role("exc", make_generic_attrref) roles.register_canonical_role("doc", make_generic_docref) roles.register_canonical_role("ref", make_generic_docref) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/environment.py0000644000175100017510000000572315121537172017774 0ustar00runnerrunnerimport logging import sys LOG = logging.getLogger(__name__) class Environment(object): __slots__ = () env_classes = () @classmethod def register(cls, env_class): cls.env_classes = (env_class,) + cls.env_classes @classmethod def from_document_settings(cls, settings): for cls in cls.env_classes: e = cls.from_document_settings(settings) if e is not None: return e raise NotImplementedError("TODO") @property def temp_data(self): raise NotImplementedError() @property def changelog_sections(self): raise NotImplementedError() @property def changelog_caption_class(self): raise NotImplementedError() @property def changelog_inner_tag_sort(self): raise NotImplementedError() @property def changelog_hide_sections_from_tags(self): raise NotImplementedError() @property def changelog_render_ticket(self): raise NotImplementedError() @property def changelog_render_pullreq(self): raise NotImplementedError() @property def changelog_render_changeset(self): raise NotImplementedError() def status_iterator(self, elements, message): raise NotImplementedError() class DefaultEnvironment(Environment): @classmethod def from_document_settings(cls, settings): return settings.changelog_env def __init__(self, config_filename=None): self._temp_data = {} self.config = {} if config_filename is not None: exec(open(config_filename).read(), self.config) def log_debug(self, msg, *args): LOG.debug(msg, *args) @property def temp_data(self): return self._temp_data @property def changelog_sections(self): return self.config.get("changelog_sections", []) @property def changelog_caption_class(self): return self.config.get("changelog_caption_class", "caption") @property def changelog_inner_tag_sort(self): return self.config.get("changelog_inner_tag_sort", []) @property def changelog_hide_sections_from_tags(self): return self.config.get("changelog_hide_sections_from_tags", []) @property def changelog_hide_tags_in_entry(self): return self.config.get("changelog_hide_tags_in_entry", []) @property def changelog_render_ticket(self): return self.config.get("changelog_render_ticket", "ticket:%s") @property def changelog_render_pullreq(self): return self.config.get("changelog_render_pullreq", "pullreq:%s") @property def changelog_render_changeset(self): return self.config.get("changelog_render_changeset", "changeset:%s") def status_iterator(self, elements, message): for i, element in enumerate(elements, 1): percent = (i / len(elements)) * 100 sys.stderr.write(message + "...[%d%%] %s\n" % (percent, element)) yield element ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/generate_rst.py0000644000175100017510000001722415121537172020111 0ustar00runnerrunner#! coding: utf-8 import collections import itertools from docutils import nodes def render_changelog(changelog_directive): changes = changelog_directive.get_changes_list( changelog_directive.env, changelog_directive.version ).values() output = [] id_prefix = "change-%s" % (changelog_directive.version,) topsection = _run_top(changelog_directive, id_prefix) output.append(topsection) bysection, all_sections = _organize_by_section( changelog_directive, changes ) counter = itertools.count() sections_to_render = [ s for s in changelog_directive.sections if s in all_sections ] if not sections_to_render: for cat in changelog_directive.inner_tag_sort: append_sec = _append_node(changelog_directive) for rec in bysection[(changelog_directive.default_section, cat)]: rec["id"] = "%s-%s" % (id_prefix, next(counter)) _render_rec(changelog_directive, rec, None, cat, append_sec) if append_sec.children: topsection.append(append_sec) else: for section in sections_to_render + [ changelog_directive.default_section ]: sec = nodes.section( "", nodes.title(section, section), ids=["%s-%s" % (id_prefix, section.replace(" ", "-"))], ) append_sec = _append_node(changelog_directive) sec.append(append_sec) for cat in changelog_directive.inner_tag_sort: for rec in bysection[(section, cat)]: rec["id"] = "%s-%s" % (id_prefix, next(counter)) _render_rec( changelog_directive, rec, section, cat, append_sec ) if append_sec.children: topsection.append(sec) return output def _organize_by_section(changelog_directive, changes): compound_sections = [ (s, s.split(" ")) for s in changelog_directive.sections if " " in s ] bysection = collections.defaultdict(list) all_sections = set() for rec in changes: assert changelog_directive.version == rec["render_for_version"] inner_tag = rec["tags"].intersection( changelog_directive.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(changelog_directive.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[ (changelog_directive.default_section, inner_tag) ].append(rec) return bysection, all_sections def _append_node(changelog_directive): return nodes.bullet_list() def _run_top(changelog_directive, id_prefix): version = changelog_directive._parsed_content.get("version", "") topsection = nodes.section( "", nodes.title(version, version, classes=["release-version"]), ids=[id_prefix], version_string=version, ) if changelog_directive._parsed_content.get("released"): topsection.append( nodes.Text( "Released: %s" % changelog_directive._parsed_content["released"] ) ) else: topsection.append(nodes.Text("no release date")) intro_para = nodes.paragraph("", "") len_ = -1 for len_, text in enumerate(changelog_directive._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: changelog_directive.state.nested_parse( changelog_directive._parsed_content["text"][0:len_], 0, intro_para ) topsection.append(intro_para) return topsection def _render_rec(changelog_directive, rec, section, cat, append_sec): para = rec["node"].deepcopy() targetid = "change-%s" % ( rec["version_to_hash"][changelog_directive.version], ) targetnode = nodes.target("", "", ids=[targetid]) sections = section.split(" ") if section else [] section_tags = [tag for tag in sections if tag in rec["tags"]] category_tags = [cat] if cat in rec["tags"] else [] other_tags = list( sorted(rec["tags"].difference(section_tags + category_tags)) ) all_items = [] if not changelog_directive.hide_sections_from_tags: all_items.extend(section_tags) all_items.extend(category_tags) all_items.extend(other_tags) all_items = all_items or ["no_tags"] permalink = nodes.reference( "", "", nodes.Text(u"¶", u"¶"), refid=targetid, classes=["changelog-reference", "headerlink"], ) if not changelog_directive.hide_tags_in_entry: tag_node = nodes.strong("", " ".join("[%s]" % t for t in all_items)) targetnode.insert(0, nodes.Text(" ", " ")) targetnode.insert(0, tag_node) targetnode.append(permalink) para.insert(0, targetnode) if len(rec["versions"]) > 1: backported_changes = rec["sorted_versions"][ rec["sorted_versions"].index(changelog_directive.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) if changelog_directive.hide_tags_in_entry: para.append(permalink) insert_ticket = nodes.paragraph("") para.append(insert_ticket) i = 0 for collection, render, prefix in ( ( rec["tickets"], changelog_directive.env.changelog_render_ticket, "#%s", ), ( rec["pullreq"], changelog_directive.env.changelog_render_pullreq, "pull request %s", ), ( rec["changeset"], changelog_directive.env.changelog_render_changeset, "r%s", ), ): for refname in sorted(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) append_sec.append( nodes.list_item("", nodes.target("", "", ids=[rec["id"]]), para) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/mdwriter.py0000644000175100017510000002202615121537172017260 0ustar00runnerrunnerimport io from docutils import nodes from docutils import writers from docutils.core import publish_file from docutils.core import publish_string from .docutils import setup_docutils from .environment import DefaultEnvironment from .environment import Environment class Writer(writers.Writer): supported = ("markdown",) def __init__(self, limit_version=None, receive_sections=None): super(Writer, self).__init__() self.limit_version = limit_version self.receive_sections = receive_sections def translate(self): translator = MarkdownTranslator( self.document, self.limit_version, self.receive_sections ) self.document.walkabout(translator) self.output = translator.output_buf.getvalue() class MarkdownTranslator(nodes.NodeVisitor): def __init__(self, document, limit_version, receive_sections): super(MarkdownTranslator, self).__init__(document) self.buf = self.output_buf = io.StringIO() self.limit_version = limit_version self.receive_sections = receive_sections self.section_level = 1 self.stack = [] self._standalone_section_display = ( self.limit_version or self.receive_sections ) if self._standalone_section_display: self.disable_writing() def enable_writing(self): self.buf = self.output_buf def disable_writing(self): self.buf = io.StringIO() def _detect_section_was_squashed_into_subtitle(self, document_node): # docutils converts a single section we generated into a "subtitle" # and squashes the section object # http://docutils.sourceforge.net/FAQ.html\ # #how-can-i-indicate-the-document-title-subtitle for subnode in document_node: if "version_string" in subnode.attributes: if isinstance(subnode, nodes.subtitle): return subnode elif isinstance(subnode, nodes.section): return False else: raise NotImplementedError( "detected version_string in unexpected node type: %s" % subnode ) def visit_document(self, node): self.document = node self.env = Environment.from_document_settings(self.document.settings) subtitle_node = self._detect_section_was_squashed_into_subtitle(node) if subtitle_node: version = subtitle_node.attributes["version_string"] rebuild_our_lost_section = nodes.section( "", nodes.title(version, version, classes=["release-version"]), **subtitle_node.attributes ) # note we are taking the nodes from the document and moving them # into the new section. nodes have a "parent" which means that # parent changes, which means we are mutating the internal # state of the document node. so use deepcopy() to avoid this # side effect; deepcopy() for these nodes seems to not be a # performance problem. rebuild_our_lost_section.extend([n.deepcopy() for n in node[2:]]) rebuild_our_lost_section.walkabout(self) raise nodes.SkipNode() def visit_standalone_version_node(self, node, version_string): """visit a section or document that has a changelog version string at the top""" if self.limit_version and self.limit_version != version_string: return self.section_level = 1 self.enable_writing() if self.receive_sections: self.buf = io.StringIO() def depart_standalone_version_node(self, node, version_string): """depart a section or document that has a changelog version string at the top""" if self.limit_version and self.limit_version != version_string: return if self.receive_sections: self.receive_sections(version_string, self.buf.getvalue()) self.disable_writing() def visit_section(self, node): if ( "version_string" in node.attributes and self._standalone_section_display ): self.visit_standalone_version_node( node, node.attributes["version_string"] ) else: self.section_level += 1 def depart_section(self, node): if ( "version_string" in node.attributes and self._standalone_section_display ): self.depart_standalone_version_node( node, node.attributes["version_string"] ) else: self.section_level -= 1 def visit_strong(self, node): self.buf.write("**") def depart_strong(self, node): self.buf.write("**") def visit_emphasis(self, node): self.buf.write("*") def depart_emphasis(self, node): self.buf.write("*") def visit_literal(self, node): self.buf.write("`") def visit_Text(self, node): self.buf.write(node.astext()) def depart_Text(self, node): pass def depart_paragraph(self, node): self.buf.write("\n\n") def depart_literal(self, node): self.buf.write("`") def visit_title(self, node): self.buf.write( "\n%s %s\n\n" % ("#" * self.section_level, node.astext()) ) raise nodes.SkipNode() def visit_changeset_link(self, node): # it would be nice to have an absolutely link to the HTML # hosted changelog but this requires being able to generate # the absolute link from the document filename and all that. # it can perhaps be sent on the commandline raise nodes.SkipNode() def depart_changeset_link(self, node): pass def visit_reference(self, node): if "changelog-reference" in node.attributes["classes"]: self.visit_changeset_link(node) else: self.buf.write("[") def depart_reference(self, node): if "changelog-reference" in node.attributes["classes"]: self.depart_changeset_link(node) else: self.buf.write("](%s)" % node.attributes["refuri"]) def visit_admonition(self, node): # "seealsos" typically have internal sphinx references so at the # moment we're not prepared to look those up, future version can # perhaps use sphinx object lookup raise nodes.SkipNode() def visit_list_item(self, node): self.stack.append(self.buf) self.buf = io.StringIO() def depart_list_item(self, node): popped = self.buf self.buf = self.stack.pop(-1) indent_level = len(self.stack) indent_string = " " * 4 * indent_level value = popped.getvalue().strip() lines = value.split("\n") self.buf.write("\n" + indent_string + "- ") line = lines.pop(0) self.buf.write(line + "\n") for line in lines: self.buf.write(indent_string + " " + line + "\n") def _visit_generic_node(self, node): pass def __getattr__(self, name): if not name.startswith("_"): return self._visit_generic_node else: raise AttributeError(name) def stream_changelog_sections( target_filename, config_filename, receive_sections, version=None ): """Send individual changelog sections to a callable, one per version. The callable accepts two arguments, the string version number of the changelog section, and the markdown-formatted content of the changelog section. Used for APIs that receive changelog sections per version. """ Environment.register(DefaultEnvironment) setup_docutils() with open(target_filename, encoding="utf-8") as handle: publish_string( handle.read(), source_path=target_filename, writer=Writer( limit_version=version, receive_sections=receive_sections ), settings_overrides={ "changelog_env": DefaultEnvironment(config_filename), "report_level": 3, }, ) def render_changelog_as_md( target_filename, config_filename, version, sections_only ): Environment.register(DefaultEnvironment) setup_docutils() if sections_only: def receive_sections(version_string, text): print(text) else: receive_sections = None writer = Writer(limit_version=version, receive_sections=receive_sections) settings_overrides = { "changelog_env": DefaultEnvironment(config_filename), "report_level": 3, } with open(target_filename, encoding="utf-8") as handle: if receive_sections: publish_string( handle.read(), source_path=target_filename, writer=writer, settings_overrides=settings_overrides, ) else: publish_file( handle, writer=writer, settings_overrides=settings_overrides ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/changelog/sphinxext.py0000644000175100017510000001000215121537172017444 0ustar00runnerrunnerimport os from sphinx.util import logging from sphinx.util.console import bold from sphinx.util.osutil import copyfile from .docutils import ChangeDirective from .docutils import ChangeLogDirective from .docutils import ChangeLogImportDirective from .docutils import make_ticket_link from .environment import Environment try: from sphinx.util.display import status_iterator except ImportError: from sphinx.util import status_iterator LOG = logging.getLogger(__name__) def _is_html(app): return app.builder.name in ("html", "readthedocs") class SphinxEnvironment(Environment): __slots__ = ("sphinx_env",) @classmethod def from_document_settings(cls, settings): return SphinxEnvironment(settings.env) def __init__(self, sphinx_env): self.sphinx_env = sphinx_env def log_debug(self, msg, *args): LOG.debug(msg, *args) @property def temp_data(self): return self.sphinx_env.temp_data @property def changelog_sections(self): return self.sphinx_env.config.changelog_sections @property def changelog_caption_class(self): return self.sphinx_env.config.changelog_caption_class @property def changelog_inner_tag_sort(self): return self.sphinx_env.config.changelog_inner_tag_sort @property def changelog_hide_sections_from_tags(self): return self.sphinx_env.config.changelog_hide_sections_from_tags @property def changelog_hide_tags_in_entry(self): return self.sphinx_env.config.changelog_hide_tags_in_entry @property def changelog_render_ticket(self): return self.sphinx_env.config.changelog_render_ticket @property def changelog_render_pullreq(self): return self.sphinx_env.config.changelog_render_pullreq @property def changelog_render_changeset(self): return self.sphinx_env.config.changelog_render_changeset def status_iterator(self, elements, message): return status_iterator( elements, message, "purple", length=len(elements), verbosity=self.sphinx_env.app.verbosity, ) def add_stylesheet(app): # changed in 1.8 from add_stylesheet() # https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_css_file app.add_css_file("changelog.css") def copy_stylesheet(app, exception): LOG.info( bold("The name of the builder is: %s" % app.builder.name), nonl=True ) if not _is_html(app) or exception: return LOG.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) LOG.info("done") def setup(app): Environment.register(SphinxEnvironment) 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_caption_class", "caption", "env") app.add_config_value("changelog_inner_tag_sort", [], "env") app.add_config_value("changelog_hide_sections_from_tags", False, "env") app.add_config_value("changelog_hide_tags_in_entry", False, "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) return {"parallel_read_safe": True, "parallel_write_safe": True} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1766243969.7499623 changelog-0.6.2/changelog.egg-info/0000755000175100017510000000000015121537202016533 5ustar00runnerrunner././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/PKG-INFO0000644000175100017510000001217515121537201017635 0ustar00runnerrunnerMetadata-Version: 2.4 Name: changelog Version: 0.6.2 Summary: Provides simple Sphinx markup to render changelog displays. Home-page: https://github.com/sqlalchemyorg/changelog Author: Mike Bayer Author-email: mike@zzzcomputing.com License: MIT Keywords: Sphinx 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 License-File: LICENSE Requires-Dist: Sphinx>=4.0.0 Requires-Dist: docutils Requires-Dist: looseversion Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: requires-dist Dynamic: summary ========== Changelog ========== |PyPI| |Python| |Downloads| .. |PyPI| image:: https://img.shields.io/pypi/v/changelog :target: https://pypi.org/project/changelog :alt: PyPI .. |Python| image:: https://img.shields.io/pypi/pyversions/changelog :target: https://pypi.org/project/changelog :alt: PyPI - Python Version .. |Downloads| image:: https://img.shields.io/pypi/dm/changelog :target: https://pypi.org/project/changelog :alt: PyPI - Downloads 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"] # section css classes - optional changelog_caption_class = "caption" # tags to sort on inside of sections - also optional changelog_inner_tag_sort = ["feature", "bug"] # whether sections should be hidden from tags list changelog_hide_sections_from_tags = False # whether tags should be hidden from entries changelog_hide_tags_in_entry = False # 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` Other things not documented yet =============================== * the ``:version:`` directive, which indicates a changelog entry should be listed in other versions as well * the ``.. changelog_imports::`` directive - reads other changelog.rst files looking for ``:version:`` directives which apply to this changelog file, adding those entries to the changelog entries in this file * the ``:include_notes_from:`` symbol - imports all the .rst files in a directory into the current one so that changes can be one-per-file, makes git merges possible * the ``changelog release-notes`` command that at release time gathers up the above-mentioned change-per-file .rst files and renders them into the main changelog.rst file, running "git rm" on the individual files * the changelog.rst -> markdown converter, used for web guis that want changelog sections written in markdown * the changelog.rst -> stream per changelog markdown API function, which can for example stream the changelogs per release to the github releases API ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/SOURCES.txt0000644000175100017510000000071615121537201020422 0ustar00runnerrunnerLICENSE MANIFEST.in README.rst setup.cfg setup.py changelog/__init__.py changelog/changelog.css changelog/cmd.py changelog/docutils.py changelog/environment.py changelog/generate_rst.py changelog/mdwriter.py changelog/sphinxext.py changelog.egg-info/PKG-INFO changelog.egg-info/SOURCES.txt changelog.egg-info/dependency_links.txt changelog.egg-info/entry_points.txt changelog.egg-info/not-zip-safe changelog.egg-info/requires.txt changelog.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/dependency_links.txt0000644000175100017510000000000115121537201022600 0ustar00runnerrunner ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/entry_points.txt0000644000175100017510000000006115121537201022025 0ustar00runnerrunner[console_scripts] changelog = changelog.cmd:main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/not-zip-safe0000644000175100017510000000000115121537201020760 0ustar00runnerrunner ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/requires.txt0000644000175100017510000000004415121537201021130 0ustar00runnerrunnerSphinx>=4.0.0 docutils looseversion ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243969.0 changelog-0.6.2/changelog.egg-info/top_level.txt0000644000175100017510000000001215121537201021255 0ustar00runnerrunnerchangelog ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1766243969.7509623 changelog-0.6.2/setup.cfg0000644000175100017510000000050215121537202014730 0ustar00runnerrunner[flake8] show-source = true enable-extensions = G ignore = A003, D, E203,E305,E711,E712,E721,E722,E741, F821 N801,N802,N806, RST304,RST303,RST299,RST399, W503,W504 exclude = .venv,.git,.tox,dist,doc,*egg,build import-order-style = google application-import-names = changelog [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1766243962.0 changelog-0.6.2/setup.py0000644000175100017510000000243315121537172014634 0ustar00runnerrunnerimport os import re from setuptools import setup 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="https://github.com/sqlalchemyorg/changelog", license="MIT", packages=["changelog"], install_requires=[ "Sphinx>=4.0.0", "docutils", "looseversion", ], include_package_data=True, zip_safe=False, entry_points={"console_scripts": ["changelog = changelog.cmd:main"]}, )