releases-1.0.0/ 0000755 0000765 0000024 00000000000 12616745107 014252 5 ustar jforcier staff 0000000 0000000 releases-1.0.0/dev-requirements.txt 0000644 0000765 0000024 00000000365 12616745031 020312 0 ustar jforcier staff 0000000 0000000 # Task runner
invoke>=0.6.0
invocations>=0.4.1
# Tests (N.B. integration suite also uses Invoke as above)
spec>=0.11.3
mock==1.0.1
# Just for tests...heh
six>=1.4.1
# Docs
-e .
sphinx>=1.1
sphinx_rtd_theme>=0.1.5
# Builds
wheel==0.24
twine==1.5
releases-1.0.0/docs/ 0000755 0000765 0000024 00000000000 12616745107 015202 5 ustar jforcier staff 0000000 0000000 releases-1.0.0/docs/.changelog.rst.swp 0000644 0000765 0000024 00000050000 12616744660 020547 0 ustar jforcier staff 0000000 0000000 b0VIM 7.3 ;Vs
] jforcier bahro.local ~jforcier/Code/oss/releases/docs/changelog.rst utf-8
3210 #"! U tp > I ad [ y M { [
B
9 + s X
M
-
f g [ ; ~ ^ a 8 b < h % W ^ x a ` @ * :release:`0.2.4 <2013.10.04>` deactivating it. regular bugs) is being retained as there's not a lot to gain from * Said behavior (non-role-prefixed bullet list items turning into behavior. for, the previous "vanilla bullet list items are treated as bugs" to issues in your tracker. It's an improvement over, and replacement * This feature lets you categorize changes that aren't directly related & discussion. displayed. Thanks to Markus Zapke-Gründemann and Hynek Schlawack for patches dummy issue 'number', which will result in no issue number/link being * :feature:`1` (also :issue:`3`, :issue:`10`) Allow using ``-`` or ``0`` as a 'unreleased'. Thanks to Donald Stufft for the report. behavior, including making sure ALL unreleased issues show up as * :bug:`9 major` Clean up additional 'unreleased' display/organization * :support:`0` Created a basic test suite to protect against regressions. * :support:`0` Move to actual Sphinx docs so we can use ourselves. looks OK under their old theme too! `_. Still new Read The Docs theme * :feature:`11` Fix up styling so changelogs don't look suboptimal under `the * :release:`0.3.0 <2013-11-21>` adequately explained in :doc:`/usage`. * :support:`15` Add :doc:`/concepts` to flesh out some assumptions not pseudo-release entries. display. Includes splitting unreleased display info into two 'Next release' * :bug:`16` Fix some edge cases regarding release ordering & unreleased issue * :release:`0.3.1 <2013-12-18>` an otherwise dormant line.) Useful for overriding default assumptions (e.g. a special bugfix release from * :feature:`17` Allow releases to explicitly define which issues they include. * :release:`0.4.0 <2013-12-24>` feature.) apply to all active lines (e.g. because they pertain to a recently added * :feature:`20` Allow specifying minimum release line in bugfixes that don't * :release:`0.5.0 <2014-02-11>` * :bug:`-` Fix silly bug in :issue:`20` that cropped up on Python 3.x. * :release:`0.5.1 <2014-02-11>` displayed prior. more than one paragraph - subsequent paragraphs/blocks were not being * :bug:`23` Rework implementation to deal with issue descriptions that span * :release:`0.5.2 <2014-03-13>` * :bug:`24` Broke inline issue parsing; fixed now. * :bug:`25` Empty/no-issue line items broke at some point; fixed. * :release:`0.5.3 <2014-03-15>` explicit release/issue URL strings. * :feature:`26` Allow specifying Github path shorthand config option instead of for the feature request. hardcoded as ``changelog`` (``.rst``) - configurable. Thanks to James Mills * :feature:`22` Make the document name used as the changelog - previously * :release:`0.6.0 <2014-04-03>` *filename*. accidentally referred to the Sphinx document *title* instead of the document * :bug:`-` Fix a silly issue with the new feature from :issue:`22` where it * :release:`0.6.1 <2014-04-06>` technically an implementation detail. Thanks to Dorian Puła for the patch. * :feature:`21` Allow duplicate issue numbers; not allowing them was & original patch (:issue:`33`). a ``MANIFEST.in`` so sdists pick it up. Thanks to Zygmunt Kry* :release:`0.7.0 <2014-09-04>` paragraphs or other non-bullet-list ele* :release:`0.7.0 <2014-09-04>` Thanks to** :release:`0.7.0 <2014-09-04>` Thanks to Rodrigue Cloutier for the original request/patch. paragraphs or other non-bullet-list elements above or below the changelog. * :feature:`41` Clean up changelog discovery so one can have comments, displayed in feature->bug->support order. * :feature:`42` For readability, issues within each release so they are * :release:`1.0.0 <2015-11-05>` ========= Changelog ========= ad ` 4 X A
O
/
` _ * :feature:`0` Basic functionality. * :release:`0.2.0 <2013.09.15>` * :bug:`0` Fixed a stupid bug causing invalid issue hyperlinks. * :release:`0.2.1 <2013.09.15>` * :bug:`0` Ensured Python 3 compatibility. * :release:`0.2.2 <2013.09.15>` * :bug:`0` Fix a handful of bugs in release assignment logic. * :release:`0.2.3 <2013.09.16>` '[Bug]' signifier (since they are otherwise treated as bugfix items.) * :bug:`0` Updated non-role-prefixed line items so they get prefixed with a items are preserved. * :support:`0 backported` Explicitly documented how non-role-prefixed line Releases works/operates. * :support:`0 backported` Edited the README/docs to be clearer about how changelog entries. Thanks again to Markus. * :bug:`0` Fix duplicate display of "bare" (not prefixed with an issue role) .gitignore file. Thanks to Markus Zapke-Gründemann. * :support:`0 backported` Handful of typos, doc tweaks & addition of a ad > a ? @
y
+
z 8 Q >
j
T 6 z ` h g W a x 0 [ * :release:`0.2.4 <2013.10.04>` * :release:`0.2.4 <2013.10.04>` deactivating it. regular bugs) is * :release:`0.2.4 <2013.1* :release:`0.2.4 * :release:`0.2.4 <2013.10.04>` deactivating it. regular bugs) i* :release:`0.2.4 <2013.10.04>` deactivating it* :release:`0.2.4 <2013.10.04>` * :release:`0.2.4 <2013.10.04>` deactivating it. regul* :r* :release:`0.2.4 <2013.10.04>` deactivating it. regular bugs) is being retained as there's not a lot to gain from * Said behavior (non-role-prefixed bullet list items turning into behavior. for, the previous "vanilla bullet list items are treated as bugs" to issues in your tracker. It's an improvement over, and replacement * This feature lets you categorize changes that aren't directly related & discussion. displayed. Thanks to Markus Zapke-Gründemann and Hynek Schlawack for patches dummy issue 'number', which will result in no issue number/link being * :feature:`1` (also :issue:`3`, :issue:`10`) Allow using ``-`` or ``0`` as a 'unreleased'. Thanks to Donald Stufft for the report. behavior, including making sure ALL unreleased issues show up as * :bug:`9 major` Clean up additional 'unreleased' display/organization * :support:`0` Created a basic test suite to protect against regressions. * :support:`0` Move to actual Sphinx docs so we can use ourselves. looks OK under their old theme too! `_. Still new Read The Docs theme * :feature:`11` Fix up styling so changelogs don't look suboptimal under `the * :release:`0.3.0 <2013-11-21>` adequately explained in :doc:`/usage`. * :support:`15` Add :doc:`/concepts` to flesh out some assumptions not pseudo-release entries. display. Includes splitting unreleased display info into two 'Next release' * :bug:`16` Fix some edge cases regarding release ordering & unreleased issue * :release:`0.3.1 <2013-12-18>` an otherwise dormant line.) Useful for overriding default assumptions (e.g. a special bugfix release from * :feature:`17` Allow releases to explicitly define which issues they include. * :release:`0.4.0 <2013-12-24>` feature.) apply to all active lines (e.g. because they pertain to a recently added * :feature:`20` Allow specifying minimum release line in bugfixes that don't * :release:`0.5.0 <2014-02-11>` * :bug:`-` Fix silly bug in :issue:`20` that cropped up on Python 3.x. * :release:`0.5.1 <2014-02-11>` displayed prior. more than one paragraph - subsequent paragraphs/blocks were not being * :bug:`23` Rework implementation to deal with issue descriptions that span * :release:`0.5.2 <2014-03-13>` * :bug:`24` Broke inline issue parsing; fixed now. * :bug:`25` Empty/no-issue line items broke at some point; fixed. * :release:`0.5.3 <2014-03-15>` explicit release/issue URL strings. * :feature:`26` Allow specifying Github path shorthand config option instead of for the feature request. hardcoded as ``changelog`` (``.rst``) - configurable. Thanks to James Mills * :feature:`22` Make the document name used as the changelog - previously * :release:`0.6.0 <2014-04-03>` *filename*. accidentally referred to the Sphinx document *title* instead of the document * :bug:`-` Fix a silly issue with the new feature from :issue:`22` where it * :release:`0.6.1 <2014-04-06>` technically an implementation detail. Thanks to Dorian Puła for the patch. * :feature:`21` Allow duplicate issue numbers; not allowing them was & original patch (:issue:`33`). a ``MANIFEST.in`` so sdists pick it up. Thanks to Zygmunt Krynicki for catch * :bug:`30 major` Add LICENSE (plus a handful of other administrative files) to releases-1.0.0/docs/changelog.rst 0000644 0000765 0000024 00000011115 12616744650 017664 0 ustar jforcier staff 0000000 0000000 =========
Changelog
=========
* :release:`1.0.0 <2015-11-05>`
* :feature:`42` For readability, issues within each release so they are
displayed in feature->bug->support order.
* :feature:`41` Clean up changelog discovery so one can have comments,
paragraphs or other non-bullet-list elements above or below the changelog.
Thanks to Rodrigue Cloutier for the original request/patch.
* :release:`0.7.0 <2014-09-04>`
* :bug:`30 major` Add LICENSE (plus a handful of other administrative files) to
a ``MANIFEST.in`` so sdists pick it up. Thanks to Zygmunt Krynicki for catch
& original patch (:issue:`33`).
* :feature:`21` Allow duplicate issue numbers; not allowing them was
technically an implementation detail. Thanks to Dorian Puła for the patch.
* :release:`0.6.1 <2014-04-06>`
* :bug:`-` Fix a silly issue with the new feature from :issue:`22` where it
accidentally referred to the Sphinx document *title* instead of the document
*filename*.
* :release:`0.6.0 <2014-04-03>`
* :feature:`22` Make the document name used as the changelog - previously
hardcoded as ``changelog`` (``.rst``) - configurable. Thanks to James Mills
for the feature request.
* :feature:`26` Allow specifying Github path shorthand config option instead of
explicit release/issue URL strings.
* :release:`0.5.3 <2014-03-15>`
* :bug:`25` Empty/no-issue line items broke at some point; fixed.
* :bug:`24` Broke inline issue parsing; fixed now.
* :release:`0.5.2 <2014-03-13>`
* :bug:`23` Rework implementation to deal with issue descriptions that span
more than one paragraph - subsequent paragraphs/blocks were not being
displayed prior.
* :release:`0.5.1 <2014-02-11>`
* :bug:`-` Fix silly bug in :issue:`20` that cropped up on Python 3.x.
* :release:`0.5.0 <2014-02-11>`
* :feature:`20` Allow specifying minimum release line in bugfixes that don't
apply to all active lines (e.g. because they pertain to a recently added
feature.)
* :release:`0.4.0 <2013-12-24>`
* :feature:`17` Allow releases to explicitly define which issues they include.
Useful for overriding default assumptions (e.g. a special bugfix release from
an otherwise dormant line.)
* :release:`0.3.1 <2013-12-18>`
* :bug:`16` Fix some edge cases regarding release ordering & unreleased issue
display. Includes splitting unreleased display info into two 'Next release'
pseudo-release entries.
* :support:`15` Add :doc:`/concepts` to flesh out some assumptions not
adequately explained in :doc:`/usage`.
* :release:`0.3.0 <2013-11-21>`
* :feature:`11` Fix up styling so changelogs don't look suboptimal under `the
new Read The Docs theme
`_. Still
looks OK under their old theme too!
* :support:`0` Move to actual Sphinx docs so we can use ourselves.
* :support:`0` Created a basic test suite to protect against regressions.
* :bug:`9 major` Clean up additional 'unreleased' display/organization
behavior, including making sure ALL unreleased issues show up as
'unreleased'. Thanks to Donald Stufft for the report.
* :feature:`1` (also :issue:`3`, :issue:`10`) Allow using ``-`` or ``0`` as a
dummy issue 'number', which will result in no issue number/link being
displayed. Thanks to Markus Zapke-Gründemann and Hynek Schlawack for patches
& discussion.
* This feature lets you categorize changes that aren't directly related
to issues in your tracker. It's an improvement over, and replacement
for, the previous "vanilla bullet list items are treated as bugs"
behavior.
* Said behavior (non-role-prefixed bullet list items turning into
regular bugs) is being retained as there's not a lot to gain from
deactivating it.
* :release:`0.2.4 <2013.10.04>`
* :support:`0 backported` Handful of typos, doc tweaks & addition of a
.gitignore file. Thanks to Markus Zapke-Gründemann.
* :bug:`0` Fix duplicate display of "bare" (not prefixed with an issue role)
changelog entries. Thanks again to Markus.
* :support:`0 backported` Edited the README/docs to be clearer about how
Releases works/operates.
* :support:`0 backported` Explicitly documented how non-role-prefixed line
items are preserved.
* :bug:`0` Updated non-role-prefixed line items so they get prefixed with a
'[Bug]' signifier (since they are otherwise treated as bugfix items.)
* :release:`0.2.3 <2013.09.16>`
* :bug:`0` Fix a handful of bugs in release assignment logic.
* :release:`0.2.2 <2013.09.15>`
* :bug:`0` Ensured Python 3 compatibility.
* :release:`0.2.1 <2013.09.15>`
* :bug:`0` Fixed a stupid bug causing invalid issue hyperlinks.
* :release:`0.2.0 <2013.09.15>`
* :feature:`0` Basic functionality.
releases-1.0.0/docs/concepts.rst 0000644 0000765 0000024 00000016225 12276521173 017555 0 ustar jforcier staff 0000000 0000000 ========
Concepts
========
Basic conceptual info about how Releases organizes and thinks about issues and
releases. For details on formatting/etc (e.g. so you can interpret the examples
below), see :doc:`/usage`.
Issue and release types
=======================
* Issues are always one of three types: **features**, **bug fixes** or
**support items**.
* **Features** are (typically larger) changes adding new behavior.
* **Bug fixes** are (typically minor) changes addressing incorrect
behavior, crashes, etc.
* **Support items** vary in size but are usually non-code-related changes,
such as documentation or packaging updates.
* Releases also happen to come in three flavors:
* **Major releases** are backwards incompatible releases, often with
large/sweeping changes to a codebase.
* They increment the first version number only, e.g. ``1.0.0``.
* **Feature releases** (sometimes called **minor** or **secondary**) are
backwards compatible with the previous major release, and focus on adding
new functionality (code, or support, or both.) They sometimes include
major/complex bug fixes which are too risky to include in a bugfix
release.
* The second version number is incremented for these, e.g. ``1.1.0``.
* **Bugfix releases** (sometimes called **tertiary**) focus on fixing
incorrect behavior while minimizing the risk of creating more bugs.
Rarely, they will include small new features deemed important enough to
backport from their 'native' feature release.
* These releases increment the third/final version number, e.g.
``1.1.1``.
Release organization
====================
We parse changelog timelines so the resulting per-release issue lists honor the
above descriptions. Here are the core rules, with examples. See :doc:`/usage`
for details on formatting/etc.
* **By default, bugfixes go into bugfix releases, features and support items go
into feature releases.**
* Input::
* :release:`1.1.0 `
* :release:`1.0.1 `
* :support:`4` Updated our test runner
* :bug:`3` Another bugfix
* :feature:`2` Implemented new feature
* :bug:`1` Fixed a bug
* :release:`1.0.0 `
* Result:
* ``1.0.1``: bug #1, bug #3
* ``1.1.0``: feature #2, support #4
* **Bugfixes are assumed to backport to all stable release lines by default,
and are displayed as such.** However, this can be overridden on a per-release
and/or per-bug basis - see later bullet points.
* Input::
* :release:`1.1.1 `
* :release:`1.0.2 `
* :bug:`3` Fixed another bug, onoes
* :release:`1.1.0 `
* :release:`1.0.1 `
* :feature:`2` Implemented new feature
* :bug:`1` Fixed a bug
* :release:`1.0.0 `
* Result:
* ``1.0.1``: bug #1
* ``1.1.0``: feature #2
* ``1.0.2``: bug #3
* ``1.1.1``: bug #3
* **Bugfixes marked 'major' go into feature releases instead.**
* Input::
* :release:`1.1.0 `
* :release:`1.0.1 `
* :bug:`3 major` Big bugfix with lots of changes
* :feature:`2` Implemented new feature
* :bug:`1` Fixed a bug
* :release:`1.0.0 `
* Result:
* ``1.0.1``: bug #1
* ``1.1.0``: feature #2, bug #3
* **Features or support items marked 'backported' appear in both bugfix and
feature releases.**
* Input::
* :release:`1.1.0 `
* :release:`1.0.1 `
* :bug:`4` Fixed another bug
* :feature:`3` Regular feature
* :feature:`2 backported` Small new feature worth backporting
* :bug:`1` Fixed a bug
* :release:`1.0.0 `
* Result:
* ``1.0.1``: bug #1, feature #2, bug #4
* ``1.1.0``: feature #2, feature #3
* **Releases implicitly include all issues from their own, and prior, release
lines.** (Again, unless the release explicitly states otherwise - see below.)
* For example, in the below changelog (remembering that changelogs are
written in descending order from newest to oldest entry) the code
released as ``1.1.0`` includes the changes from bugs #1 and #3, in
addition to its explicitly stated contents of feature #2::
* :release:`1.1.0 `
* :release:`1.0.1 `
* :bug:`3` Another bugfix
* :feature:`2` Implemented new feature
* :bug:`1` Fixed a bug
* :release:`1.0.0 `
* Again, to be explicit, the rendered changelog displays this breakdown:
* ``1.0.1``: bug #1, bug #3
* ``1.1.0``: feature #2
But it's *implied* that ``1.1.0`` includes the contents of ``1.0.1``
because it released afterwards/simultaneously and is a higher release
line.
* **Releases may be told explicitly which issues to include** (using a
comma-separated list.) This is useful for the rare bugfix that gets
backported beyond the actively supported release lines.
For example, below shows a project whose lifecycle is "release 1.0; release
1.1 and drop active support for 1.0; put out a special 1.0.x release."
Without the explicit issue list for 1.0.1, Releases would roll up all
bugfixes, including the two that didn't actually apply to the 1.0 line.
* Input::
* :release:`1.0.1 ` 1, 5
* :release:`1.1.1 `
* :bug:`5` Bugfix that applied back to 1.0.
* :bug:`4` Bugfix that didn't apply to 1.0, only 1.1
* :bug:`3` Bugfix that didn't apply to 1.0, only 1.1
* :release:`1.1.0 `
* :feature:`2` Implemented new feature
* :bug:`1` Fixed a 1.0.0 bug
* :release:`1.0.0 `
* Result:
* ``1.1.0``: feature #2
* ``1.1.1``: bugs #3, #4 and #5
* ``1.0.1``: bugs #1 and #5 only
* **Bugfix issues may be told explicitly which release line they 'start' in.**
This is useful for bugs that don't go back all the way to the oldest actively
supported line - it keeps them from showing up in "too-old" releases.
The below example includes a project actively supporting 1.5, 1.6 and 1.7
release lines, with a couple of bugfixes that only applied to 1.6+.
* Input::
* :release:`1.7.1 `
* :release:`1.6.2 `
* :release:`1.5.3 `
* :bug:`50` Bug applying to all lines
* :bug:`42 (1.6+)` A bug only applying to the new feature in 1.6
* :release:`1.7.0 `
* :release:`1.6.1 `
* :release:`1.5.2 `
* :feature:`25` Another new feature
* :bug:`35` Bug that applies to all lines
* :bug:`34` Bug that applies to all lines
* :release:`1.6.0 `
* :release:`1.5.1 `
* :feature:`22` Some new feature
* :bug:`20` Bugfix
* :release:`1.5.0 `
* Result:
* ``1.5.1``: bug #20
* ``1.6.0``: feature #22
* ``1.5.2``: bugs #34, #35
* ``1.6.1``: bugs #34, #35
* ``1.7.0``: feature #25
* ``1.5.3``: bug #50 only
* ``1.6.2``: bugs #50 and #42
* ``1.7.1``: bugs #50 and #42
releases-1.0.0/docs/conf.py 0000644 0000765 0000024 00000001155 12343675014 016477 0 ustar jforcier staff 0000000 0000000 from datetime import datetime
import os
import sys
import sphinx_rtd_theme
extensions = []
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
project = u'Releases'
year = datetime.now().year
copyright = u'%d Jeff Forcier' % year
# Ensure project directory is on PYTHONPATH for version, autodoc access
sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..')))
exclude_patterns = ['_build']
# RTD theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Dogfood
extensions.append('releases')
releases_github_path = 'bitprophet/releases'
releases-1.0.0/docs/index.rst 0000644 0000765 0000024 00000000242 12402217165 017030 0 ustar jforcier staff 0000000 0000000 ========
Releases
========
.. include:: ../README.rst
Table of Contents
=================
.. toctree::
:maxdepth: 2
concepts
usage
changelog
releases-1.0.0/docs/usage.rst 0000644 0000765 0000024 00000013533 12616727312 017043 0 ustar jforcier staff 0000000 0000000 =====
Usage
=====
To use Releases, mimic the format seen in `its own changelog
`_ or in
`Fabric's changelog
`_.
Specifically:
* Install ``releases`` and update your Sphinx ``conf.py`` to include it in the
``extensions`` list setting: ``extensions = ['releases']``.
* Also set the ``releases_release_uri`` and ``releases_issue_uri`` top
level options - they determine the targets of the issue & release links
in the HTML output. Both should have an unevaluated ``%s`` where the
release/issue number would go.
* Alternately, if your project is hosted on Github, set the
``releases_github_path`` setting instead, to e.g.
``account/project``. Releases will then use an appropriate Github
URL for both releases and issues.
* If ``releases_release_uri`` or ``releases_issue_uri`` are *also*
configured, they will be preferred over ``releases_github_path``.
(If only one is configured, the other link type will continue using
``releases_github_path``.)
* See `Fabric's docs/conf.py
`_
for an example.
* You may optionally set ``releases_debug = True`` to see debug output
while building your docs.
* Create a Sphinx document named ``changelog.rst`` containing a bulleted list
somewhere at its topmost level.
* If you wish to use a different document name, use another config option
(as per previous bullet point), ``releases_document_name``. E.g.
``releases_document_name = "CHANGES"`` would cause Releases to mutate a
file called ``CHANGES.rst`` instead of ``changelog.rst``.
* Elements before or after this bulleted list will be untouched by
Releases, allowing you to place e.g. paragraphs, comments etc at the top
(or bottom) of the document.
* List items are to be ordered chronologically with the newest ones on top.
* As you fix issues, put them on the top of the list.
* As you cut releases, put those on the top of the list and they will
include the issues below them.
* Issues with no releases above them will end up in a specially marked
"Unreleased" section of the rendered changelog.
* Bullet list items should use the ``support``, ``feature`` or ``bug``
roles to mark issues, or ``release`` to mark a release. These special roles
must be the first element in each list item.
* Line-items that do not start with any issue role will be considered bugs
(both in terms of inclusion in releases, and formatting) and, naturally,
will not be given a hyperlink.
* Issue roles are of the form ``:type:`number[ keyword]```. Specifically:
* ``number`` is used to generate the link to the actual issue in your issue
tracker (going by the ``releases_issue_uri`` option). It's used for both
the link target & (part of) the link text.
* If ``number`` is given as ``-`` or ``0`` (as opposed to a "real" issue
number), no issue link will be generated. You can use this for items
without a related issue.
* Keywords are optional and may be one of:
* ``backported``: Given on *support* or *feature* issues to denote
backporting to bugfix releases; such issues will show up in both
release types. E.g. placing ``:support:`123 backported``` in your
changelog below releases '1.1.1' and '1.2.0' will cause it to appear
in both of those releases' lists.
* ``major``: Given on *bug* issues to denote inclusion in feature,
instead of bugfix, releases. E.g. placing ``:bug:`22 major``` below
releases '1.1.1' and '1.2.0' will cause it to appear in '1.2.0'
**only**.
* ``(N.N+)`` where ``N.N`` is a valid release line, e.g. ``1.1`` or
``2.10``: Given on *bug* issues to denote minimum release line. E.g.
when actively backporting most bugs to release lines 1.2, 1.3 and
1.4, you might specify ``:bug:`55 (1.3+)``` to note that bug 55 only
applies to releases in 1.3 and above - not 1.2.
* Regular Sphinx content may be given after issue roles and will be preserved
as-is when rendering. For example, in ``:bug:`123` Fixed a bug, thanks
`@somebody`!``, the rendered changelog will preserve/render "Fixed a bug,
thanks ``@somebody``!" after the issue link.
* Release roles are of the form ``:release:`number ```.
* You may place a comma-separated (whitespace optional) list of issue
numbers after the release role, and this will limit the issues included
in that release to that explicit list.
* Otherwise, releases include all relevant issues as outlined above and
in :doc:`/concepts`.
Then build your docs; in the rendered output, ``changelog.html`` should show
issues grouped by release, as per the above rules. Examples: `Releases' own
rendered changelog
`_, `Fabric's
rendered changelog `_.
Optional styling additions
==========================
If you have any nontrivial changelog entries (e.g. whose description spans
multiple paragraphs or includes their own bulleted lists, etc) you may run into
`docutils' rather enthusiastic bulleted list massaging
`_
which can then make your releases look different from one another.
To help combat this, it may be useful to add the following rule to the Sphinx
theme you're using::
div#changelog > div.section > ul > li > p:only-child {
margin-bottom: 0;
}
.. note::
Some themes, like `Alabaster `_,
may already include this style rule.
releases-1.0.0/LICENSE 0000644 0000765 0000024 00000002442 12451615262 015254 0 ustar jforcier staff 0000000 0000000 Copyright (c) 2015, Jeff Forcier
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
releases-1.0.0/MANIFEST.in 0000644 0000765 0000024 00000000265 12402211651 015774 0 ustar jforcier staff 0000000 0000000 include LICENSE
include dev-requirements.txt
include tasks.py
recursive-include docs *
recursive-exclude docs/_build *
recursive-include tests *
recursive-exclude tests *.pyc *.pyo
releases-1.0.0/PKG-INFO 0000644 0000765 0000024 00000001675 12616745107 015360 0 ustar jforcier staff 0000000 0000000 Metadata-Version: 1.1
Name: releases
Version: 1.0.0
Summary: A Sphinx extension for changelog manipulation
Home-page: https://github.com/bitprophet/releases
Author: Jeff Forcier
Author-email: jeff@bitprophet.org
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Unix
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
releases-1.0.0/README.rst 0000644 0000765 0000024 00000002670 12241471342 015735 0 ustar jforcier staff 0000000 0000000 .. image:: https://secure.travis-ci.org/bitprophet/releases.png?branch=master
:target: https://travis-ci.org/bitprophet/releases
What is Releases?
=================
Releases is a `Sphinx `_ extension designed to help you
keep a source control friendly, merge friendly changelog file & turn it into
useful, human readable HTML output.
Specifically:
* The source format (kept in your Sphinx tree as ``changelog.rst``) is a
stream-like timeline that plays well with source control & only requires one
entry per change (even for changes that exist in multiple release lines).
* The output (when you have the extension installed and run your Sphinx build
command) is a traditional looking changelog page with a section for every
release; multi-release issues are copied automatically into each release.
* By default, feature and support issues are only displayed under feature
releases, and bugs are only displayed under bugfix releases. This can be
overridden on a per-issue basis.
Some background on why this tool was created can be found in `this blog post
`_.
For more documentation, including detailed installation and usage information,
please see http://releases.readthedocs.org.
.. note::
You can install the `development version
`_
via ``pip install releases==dev``.
releases-1.0.0/releases/ 0000755 0000765 0000024 00000000000 12616745107 016055 5 ustar jforcier staff 0000000 0000000 releases-1.0.0/releases/__init__.py 0000644 0000765 0000024 00000045771 12616744017 020203 0 ustar jforcier staff 0000000 0000000 import itertools
import re
import sys
from functools import partial
from distutils.version import LooseVersion
from docutils import nodes, utils
from .models import Issue, ISSUE_TYPES, Release
def _log(txt, config):
"""
Log debug output if debug setting is on.
Intended to be partial'd w/ config at top of functions. Meh.
"""
if config.releases_debug:
sys.stderr.write(str(txt) + "\n")
sys.stderr.flush()
def issue_nodelist(name, link=None):
which = '[%s]' % (
ISSUE_TYPES[name], name.capitalize()
)
signifier = [nodes.raw(text=which, format='html')]
hyperlink = [nodes.inline(text=" "), link] if link else []
trail = [] if link else [nodes.inline(text=" ")]
return signifier + hyperlink + [nodes.inline(text=":")] + trail
release_line_re = re.compile(r'\((\d+\.\d+)\+\)') # e.g. '(1.2+)'
def issues_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
"""
Use: :issue|bug|feature|support:`ticket_number`
When invoked as :issue:, turns into just a "#NN" hyperlink to
`releases_issue_uri`.
When invoked otherwise, turns into "[Type] <#NN hyperlink>: ".
May give a 'ticket number' of ' backported' to indicate a
backported feature or support ticket. This extra info will be stripped out
prior to parsing. May also give 'major' in the same vein, implying the bug
was a major bug released in a feature release. May give a 'ticket number'
of ``-`` or ``0`` to generate no hyperlink.
"""
# Old-style 'just the issue link' behavior
issue_no, _, ported = utils.unescape(text).partition(' ')
# Lol @ access back to Sphinx
config = inliner.document.settings.env.app.config
if issue_no not in ('-', '0'):
if config.releases_issue_uri:
# TODO: deal with % vs .format()
ref = config.releases_issue_uri % issue_no
elif config.releases_github_path:
ref = "https://github.com/{0}/issues/{1}".format(
config.releases_github_path, issue_no)
link = nodes.reference(rawtext, '#' + issue_no, refuri=ref, **options)
else:
link = None
issue_no = None # So it doesn't gum up dupe detection later
# Additional 'new-style changelog' stuff
if name in ISSUE_TYPES:
nodelist = issue_nodelist(name, link)
line = None
# Sanity check
if ported not in ('backported', 'major', ''):
match = release_line_re.match(ported)
if not match:
err = "Gave unknown issue metadata '{0} for issue no. {1}"
raise ValueError(err.format(ported, issue_no))
else:
line = match.groups()[0]
# Create temporary node w/ data & final nodes to publish
node = Issue(
number=issue_no,
type_=name,
nodelist=nodelist,
backported=(ported == 'backported'),
major=(ported == 'major'),
line=line,
)
return [node], []
# Return old style info for 'issue' for older changelog entries
else:
return [link], []
def release_nodes(text, slug, date, config):
# Doesn't seem possible to do this "cleanly" (i.e. just say "make me a
# title and give it these HTML attributes during render time) so...fuckit.
# We were already doing fully raw elements elsewhere anyway. And who cares
# about a PDF of a changelog? :x
if config.releases_release_uri:
# TODO: % vs .format()
uri = config.releases_release_uri % slug
elif config.releases_github_path:
uri = "https://github.com/{0}/tree/{1}".format(
config.releases_github_path, slug)
link = '{1}'.format(uri, text)
datespan = ''
if date:
datespan = ' {0}'.format(date)
header = '{0}{1}
'.format(
link, datespan)
return nodes.section('',
nodes.raw(rawtext='', text=header, format='html'),
ids=[text]
)
year_arg_re = re.compile(r'^(.+?)\s*(?$', re.DOTALL)
def release_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
"""
Invoked as :release:`N.N.N `.
Turns into useful release header + link to GH tree for the tag.
"""
# Make sure year has been specified
match = year_arg_re.match(text)
if not match:
msg = inliner.reporter.error("Must specify release date!")
return [inliner.problematic(rawtext, rawtext, msg)], [msg]
number, date = match.group(1), match.group(2)
# Lol @ access back to Sphinx
config = inliner.document.settings.env.app.config
nodelist = [release_nodes(number, number, date, config)]
# Return intermediate node
node = Release(number=number, date=date, nodelist=nodelist)
return [node], []
def get_line(obj):
# 1.2.7 -> 1.2
return '.'.join(obj.number.split('.')[:-1])
def append_unreleased_entries(app, lines, releases):
"""
Entries not yet released get special 'release' entries (that lack an
actual release object).
"""
log = partial(_log, config=app.config)
for which in ('bugfix', 'feature'):
nodelist = [release_nodes(
"Next %s release" % which,
'master',
None,
app.config
)]
line = 'unreleased_%s' % which
log("Creating '%s' faux-release with %r" % (line, lines[line]))
releases.append({
'obj': Release(number=line, date=None, nodelist=nodelist),
'entries': lines[line]
})
def reorder_release_entries(releases):
"""
Mutate ``releases`` so the entrylist in each is ordered by feature/bug/etc.
"""
order = {'feature': 0, 'bug': 1, 'support': 2}
for release in releases:
entries = release['entries'][:]
release['entries'] = sorted(entries, key=lambda x: order[x.type])
def construct_entry_with_release(focus, issues, lines, log, releases, rest):
"""
Releases 'eat' the entries in their line's list and get added to the
final data structure. They also inform new release-line 'buffers'.
Release lines, once the release obj is removed, should be empty or a
comma-separated list of issue numbers.
"""
line = get_line(focus)
log("release for line %r" % line)
# Check for explicitly listed issues first
explicit = None
if rest[0].children:
explicit = [x.strip() for x in rest[0][0].split(',')]
# Do those by themselves since they override all other logic
if explicit:
log("Explicit issues requested: %r" % (explicit,))
# First scan global issue dict, dying if not found
missing = [i for i in explicit if i not in issues]
if missing:
raise ValueError(
"Couldn't find issue(s) #{0} in the changelog!".format(
', '.join(missing)))
# Obtain objects from global list
entries = []
for i in explicit:
for flattened_issue_item in itertools.chain(issues[i]):
entries.append(flattened_issue_item)
# Create release
log("entries in this release: %r" % (entries,))
releases.append({
'obj': focus,
'entries': entries,
})
# Introspect entries to determine which buckets they should get
# removed from
for obj in entries:
if obj.type == 'bug':
# Major bugfix: remove from unreleased_feature
if obj.major:
log("Removing #%s from unreleased" % obj.number)
lines['unreleased_feature'].remove(obj)
# Regular bugfix: remove from bucket for this release's
# line + unreleased_bugfix
else:
if obj in lines['unreleased_bugfix']:
log("Removing #%s from unreleased" % obj.number)
lines['unreleased_bugfix'].remove(obj)
if obj in lines[line]:
log("Removing #%s from %s" % (obj.number, line))
lines[line].remove(obj)
# Regular feature/support: remove from unreleased_feature
# Backported feature/support: remove from bucket for this
# release's line (if applicable) + unreleased_feature
else:
log("Removing #%s from unreleased" % obj.number)
lines['unreleased_feature'].remove(obj)
if obj in lines.get(line, []):
lines[line].remove(obj)
# Implicit behavior otherwise
else:
# New release line/branch detected. Create it & dump unreleased
# features.
if line not in lines:
log("not seen prior, making feature release")
lines[line] = []
entries = [
x
for x in lines['unreleased_feature']
if x.type in ('feature', 'support') or x.major
]
releases.append({
'obj': focus,
'entries': entries
})
lines['unreleased_feature'] = []
# Existing line -> empty out its bucket into new release.
# Skip 'major' bugs as those "belong" to the next release (and will
# also be in 'unreleased_feature' - so safe to nuke the entire
# line)
else:
log("pre-existing, making bugfix release")
entries = [x for x in lines[line] if not x.major]
log("entries in this release: %r" % (entries,))
releases.append({
'obj': focus,
'entries': entries,
})
lines[line] = []
# Clean out the items we just released from
# 'unreleased_bugfix'. (Can't nuke it because there might
# be some unreleased bugs for other release lines.)
for x in entries:
if x in lines['unreleased_bugfix']:
lines['unreleased_bugfix'].remove(x)
def construct_entry_without_release(focus, issues, lines, log, rest):
# Handle rare-but-valid non-issue-attached line items, which are
# always bugs. (They are their own description.)
if not isinstance(focus, Issue):
# First, sanity check for potential mistakes resulting in an issue node
# being buried within something else.
buried = focus.traverse(Issue)
if buried:
msg = """
Found issue node ({0!r}) buried inside another node:
{1}
Please double-check your ReST syntax! There is probably text in the above
output that will show you which part of your changelog to look at.
For example, indentation problems can accidentally generate nested definition
lists.
"""
raise ValueError(msg.format(buried[0], str(buried[0].parent)))
# OK, it looks legit - make it a bug.
log("Found line item w/ no real issue object, creating bug")
focus = Issue(
type_='bug',
nodelist=issue_nodelist('bug'),
description=nodes.list_item('', nodes.paragraph('', '', focus)),
)
else:
focus.attributes['description'] = rest
# Add to global list or die trying
issues[focus.number] = issues.get(focus.number, []) + [focus]
if focus.type == 'bug':
# Major bugs go into unreleased_feature
if focus.major:
lines['unreleased_feature'].append(focus)
log("Adding to unreleased_feature")
# Regular bugs go into per-line buckets ('major' bugs do
# not) as well as unreleased_bugfix. Adjust for bugs with a
# 'line' (minimum line no.) attribute.
else:
bug_lines = [x for x in lines if x != 'unreleased_feature']
if focus.line:
bug_lines = [x for x in bug_lines
if (x != 'unreleased_bugfix'
and LooseVersion(x) >= LooseVersion(focus.line))]
bug_lines = bug_lines + ['unreleased_bugfix']
for line in bug_lines:
log("Adding to %r" % line)
lines[line].append(focus)
else:
# Backported feature/support items go into all lines, including
# both 'unreleased' lists
if focus.backported:
for line in lines:
log("Adding to release line %r" % line)
lines[line].append(focus)
# Non-backported feature/support items go into feature releases
# only.
else:
log("Adding to unreleased_feature")
lines['unreleased_feature'].append(focus)
def construct_releases(entries, app):
log = partial(_log, config=app.config)
# Walk from back to front, consuming entries & copying them into
# per-release buckets as releases are encountered. Store releases in order.
releases = []
lines = {'unreleased_bugfix': [], 'unreleased_feature': []}
# Also keep a master hash of issues by number to detect duplicates & assist
# in explicitly defined release lists.
issues = {}
for obj in reversed(entries):
# Issue object is always found in obj (LI) index 0 (first, often only
# P) and is the 1st item within that (index 0 again).
# Preserve all other contents of 'obj'.
focus = obj[0].pop(0)
rest = obj
log(repr(focus))
# Releases 'eat' the entries in their line's list and get added to the
# final data structure. They also inform new release-line 'buffers'.
# Release lines, once the release obj is removed, should be empty or a
# comma-separated list of issue numbers.
if isinstance(focus, Release):
construct_entry_with_release(
focus, issues, lines, log, releases, rest
)
# Entries get copied into release line buckets as follows:
# * Features and support go into 'unreleased_feature' for use in new
# feature releases.
# * Bugfixes go into all release lines (so they can be printed in >1
# bugfix release as appropriate) as well as 'unreleased_bugfix' (so
# they can be displayed prior to release'). Caveats include bugs marked
# 'major' (they go into unreleased_feature instead) or with 'N.N+'
# (meaning they only go into release line buckets for that release and
# up.)
# * Support/feature entries marked as 'backported' go into all
# release lines as well, on the assumption that they were released to
# all active branches.
# * The 'rest' variable (which here is the bug description, vitally
# important!) is preserved by stuffing it into the focus (issue)
# object - it will get unpacked by construct_nodes() later.
else:
construct_entry_without_release(focus, issues, lines, log, rest)
append_unreleased_entries(app, lines, releases)
reorder_release_entries(releases)
return releases
def construct_nodes(releases):
result = []
# Reverse the list again so the final display is newest on top
for d in reversed(releases):
if not d['entries']:
continue
obj = d['obj']
entries = []
for entry in d['entries']:
# Use nodes.Node.deepcopy to deepcopy the description
# node. If this is not done, multiple references to the same
# object (e.g. a reference object in the description of #649, which
# is then copied into 2 different release lists) will end up in the
# doctree, which makes subsequent parse steps very angry (index()
# errors).
desc = entry['description'].deepcopy()
# Additionally, expand any other issue roles found in the
# description - sometimes we refer to related issues inline. (They
# can't be left as issue() objects at render time since that's
# undefined.)
# Use [:] slicing to avoid mutation during the loops.
for index, node in enumerate(desc[:]):
for subindex, subnode in enumerate(node[:]):
if isinstance(subnode, Issue):
desc[index][subindex:subindex+1] = subnode['nodelist']
# Rework this entry to insert the now-rendered issue nodes in front
# of the 1st paragraph of the 'description' nodes (which should be
# the preserved LI + nested paragraph-or-more from original
# markup.)
# FIXME: why is there no "prepend a list" method?
for node in reversed(entry['nodelist']):
desc[0].insert(0, node)
entries.append(desc)
# Entry list
list_ = nodes.bullet_list('', *entries)
# Insert list into release nodelist (as it's a section)
obj['nodelist'][0].append(list_)
# Release header
header = nodes.paragraph('', '', *obj['nodelist'])
result.extend(header)
return result
class BulletListVisitor(nodes.NodeVisitor):
def __init__(self, document, app):
nodes.NodeVisitor.__init__(self, document)
self.found_changelog = False
self.app = app
def visit_bullet_list(self, node):
# The first found bullet list (which should be the first one at the top
# level of the document) is the changelog.
if not self.found_changelog:
self.found_changelog = True
# Walk + parse into release mapping
releases = construct_releases(node.children, self.app)
# Construct new set of nodes to replace the old, and we're done
node.replace_self(construct_nodes(releases))
def unknown_visit(self, node):
pass
def generate_changelog(app, doctree):
# Don't scan/mutate documents that don't match the configured document name
# (which by default is changelog.rst).
if app.env.docname != app.config.releases_document_name:
return
# Find the first bullet-list node & replace it with our organized/parsed
# elements.
changelog_visitor = BulletListVisitor(doctree, app)
doctree.walk(changelog_visitor)
def setup(app):
# Issue base URI setting: releases_issue_uri
# E.g. 'https://github.com/fabric/fabric/issues/'
app.add_config_value(name='releases_issue_uri', default=None,
rebuild='html')
# Release-tag base URI setting: releases_release_uri
# E.g. 'https://github.com/fabric/fabric/tree/'
app.add_config_value(name='releases_release_uri', default=None,
rebuild='html')
# Convenience Github version of above
app.add_config_value(name='releases_github_path', default=None,
rebuild='html')
# Which document to use as the changelog
app.add_config_value(name='releases_document_name', default='changelog',
rebuild='html')
# Debug output
app.add_config_value(name='releases_debug', default=False, rebuild='html')
# Register intermediate roles
for x in list(ISSUE_TYPES) + ['issue']:
app.add_role(x, issues_role)
app.add_role('release', release_role)
# Hook in our changelog transmutation at appropriate step
app.connect('doctree-read', generate_changelog)
releases-1.0.0/releases/_version.py 0000644 0000765 0000024 00000000120 12616744204 020241 0 ustar jforcier staff 0000000 0000000 __version_info__ = (1, 0, 0)
__version__ = '.'.join(map(str, __version_info__))
releases-1.0.0/releases/models.py 0000644 0000765 0000024 00000002202 12343415221 017672 0 ustar jforcier staff 0000000 0000000 from docutils import nodes
# Issue type list (keys) + color values
ISSUE_TYPES = {
'bug': 'A04040',
'feature': '40A056',
'support': '4070A0',
}
class Issue(nodes.Element):
@property
def type(self):
return self['type_']
@property
def backported(self):
return self.get('backported', False)
@property
def major(self):
return self.get('major', False)
@property
def number(self):
return self.get('number', None)
@property
def line(self):
return self.get('line', None)
def __repr__(self):
flag = ''
if self.backported:
flag = 'backported'
elif self.major:
flag = 'major'
elif self.line:
flag = self.line + '+'
if flag:
flag = ' ({0})'.format(flag)
return '<{issue.type} #{issue.number}{flag}>'.format(issue=self,
flag=flag)
class Release(nodes.Element):
@property
def number(self):
return self['number']
def __repr__(self):
return ''.format(self.number)
releases-1.0.0/releases.egg-info/ 0000755 0000765 0000024 00000000000 12616745107 017547 5 ustar jforcier staff 0000000 0000000 releases-1.0.0/releases.egg-info/dependency_links.txt 0000644 0000765 0000024 00000000001 12616745107 023615 0 ustar jforcier staff 0000000 0000000
releases-1.0.0/releases.egg-info/PKG-INFO 0000644 0000765 0000024 00000001675 12616745107 020655 0 ustar jforcier staff 0000000 0000000 Metadata-Version: 1.1
Name: releases
Version: 1.0.0
Summary: A Sphinx extension for changelog manipulation
Home-page: https://github.com/bitprophet/releases
Author: Jeff Forcier
Author-email: jeff@bitprophet.org
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Unix
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
releases-1.0.0/releases.egg-info/SOURCES.txt 0000644 0000765 0000024 00000000607 12616745107 021436 0 ustar jforcier staff 0000000 0000000 LICENSE
MANIFEST.in
README.rst
dev-requirements.txt
setup.cfg
setup.py
tasks.py
docs/.changelog.rst.swp
docs/changelog.rst
docs/concepts.rst
docs/conf.py
docs/index.rst
docs/usage.rst
releases/__init__.py
releases/_version.py
releases/models.py
releases.egg-info/PKG-INFO
releases.egg-info/SOURCES.txt
releases.egg-info/dependency_links.txt
releases.egg-info/top_level.txt
tests/changelog.py releases-1.0.0/releases.egg-info/top_level.txt 0000644 0000765 0000024 00000000011 12616745107 022271 0 ustar jforcier staff 0000000 0000000 releases
releases-1.0.0/setup.cfg 0000644 0000765 0000024 00000000130 12616745107 016065 0 ustar jforcier staff 0000000 0000000 [bdist_wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
releases-1.0.0/setup.py 0000644 0000765 0000024 00000002265 12243514510 015755 0 ustar jforcier staff 0000000 0000000 #!/usr/bin/env python
from setuptools import setup
# Version info -- read without importing
_locals = {}
with open('releases/_version.py') as fp:
exec(fp.read(), None, _locals)
version = _locals['__version__']
setup(
name='releases',
version=version,
description='A Sphinx extension for changelog manipulation',
author='Jeff Forcier',
author_email='jeff@bitprophet.org',
url='https://github.com/bitprophet/releases',
packages=['releases'],
classifiers=[
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Unix',
'Operating System :: POSIX',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Software Development',
'Topic :: Software Development :: Build Tools',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules',
],
)
releases-1.0.0/tasks.py 0000644 0000765 0000024 00000000724 12616731213 015745 0 ustar jforcier staff 0000000 0000000 from invocations import docs
from invocations.testing import test
from invocations.packaging import release
from invoke import Collection
from invoke import run
from invoke import task
@task(help={
'pty': "Whether to run tests under a pseudo-tty",
})
def integration(pty=True):
"""Runs integration tests."""
cmd = 'inv test -o --tests=integration'
run(cmd + ('' if pty else ' --no-pty'), pty=pty)
ns = Collection(test, integration, release, docs)
releases-1.0.0/tests/ 0000755 0000765 0000024 00000000000 12616745107 015414 5 ustar jforcier staff 0000000 0000000 releases-1.0.0/tests/changelog.py 0000644 0000765 0000024 00000043326 12616744032 017721 0 ustar jforcier staff 0000000 0000000 from tempfile import mkdtemp
from shutil import rmtree
import six
from spec import Spec, eq_, raises
from mock import Mock
from docutils.nodes import (
reference, bullet_list, list_item, raw, paragraph, Text,
)
from sphinx.application import Sphinx
import sphinx
from releases import (
Issue,
issues_role,
Release,
release_role,
construct_releases,
construct_nodes,
)
from releases import setup as releases_setup # avoid unittest crap
def _app(**kwargs):
# Create a real Sphinx app, with stupid temp dirs because it assumes.
# Helps catch things like "testing a config option but forgot
# app.add_config_value()"
src, dst, doctree = mkdtemp(), mkdtemp(), mkdtemp()
try:
# STFU Sphinx :(
Sphinx._log = lambda self, message, wfile, nonl=False: None
app = Sphinx(
srcdir=src,
confdir=None,
outdir=dst,
doctreedir=doctree,
buildername='html',
)
finally:
[rmtree(x) for x in (src, doctree)]
releases_setup(app)
# Mock out the config within. More horrible assumptions by Sphinx :(
config = {
'releases_release_uri': 'foo_%s',
'releases_issue_uri': 'bar_%s',
'releases_debug': False,
}
# Allow tinkering with document filename
if 'docname' in kwargs:
app.env.temp_data['docname'] = kwargs.pop('docname')
# Allow config overrides
for name in kwargs:
config['releases_{0}'.format(name)] = kwargs[name]
# Stitch together as the sphinx app init() usually does w/ real conf files
app.config._raw_config = config
# init_values() requires a 'warn' runner on Sphinx 1.3+, give it no-op.
init_args = []
if sphinx.version_info[:2] > (1, 2):
init_args = [lambda x: x]
app.config.init_values(*init_args)
return app
def _inliner(app=None):
app = app or _app()
return Mock(document=Mock(settings=Mock(env=Mock(app=app))))
# Obtain issue() object w/o wrapping all parse steps
def _issue(type_, number, **kwargs):
text = str(number)
if kwargs.get('backported', False):
text += " backported"
if kwargs.get('major', False):
text += " major"
if kwargs.get('line', None):
text += " (%s+)" % kwargs['line']
app = kwargs.get('app', None)
return issues_role(
name=type_,
rawtext='',
text=text,
lineno=None,
inliner=_inliner(app=app),
)[0][0]
def _entry(i):
"""
Easy wrapper for issue/release objects.
Default is to give eg an issue/release object that gets wrapped in a LI->P.
May give your own (non-issue/release) object to skip auto wrapping. (Useful
since _entry() is often called a few levels deep.)
"""
if not isinstance(i, (Issue, Release)):
return i
return list_item('', paragraph('', '', i))
def _release(number, **kwargs):
app = kwargs.get('app', None)
nodes = release_role(
name=None,
rawtext='',
text='%s <2013-11-20>' % number,
lineno=None,
inliner=_inliner(app=app),
)[0]
return list_item('', paragraph('', '', *nodes))
def _release_list(*entries):
entries = list(entries) # lol tuples
# Translate simple objs into changelog-friendly ones
for index, item in enumerate(entries):
if isinstance(item, six.string_types):
entries[index] = _release(item)
else:
entries[index] = _entry(item)
# Insert initial/empty 1st release to start timeline
entries.append(_release('1.0.0'))
return entries
def _changelog2dict(changelog):
d = {}
for r in changelog:
d[r['obj'].number] = r['entries']
return d
def _releases(*entries, **kwargs):
app = kwargs.get('app', None) or _app()
return construct_releases(_release_list(*entries), app)
def _setup_issues(self):
self.f = _issue('feature', '12')
self.s = _issue('support', '5')
self.b = _issue('bug', '15')
self.mb = _issue('bug', '200', major=True)
self.bf = _issue('feature', '27', backported=True)
self.bs = _issue('support', '29', backported=True)
class releases(Spec):
"""
Organization of issues into releases (parsing)
"""
def setup(self):
_setup_issues(self)
def _expect_entries(self, all_entries, in_, not_in):
# Grab 2nd release as 1st is the empty 'beginning of time' one
entries = _releases(*all_entries)[1]['entries']
eq_(len(entries), len(in_))
for x in in_:
assert x in entries
for x in not_in:
assert x not in entries
def feature_releases_include_features_and_support_not_bugs(self):
self._expect_entries(
['1.1.0', self.f, self.b, self.s],
[self.f, self.s],
[self.b]
)
def feature_releases_include_major_bugs(self):
self._expect_entries(
['1.1.0', self.f, self.b, self.mb],
[self.f, self.mb],
[self.b]
)
def bugfix_releases_include_bugs(self):
self._expect_entries(
['1.0.2', self.f, self.b, self.mb],
[self.b],
[self.mb, self.f],
)
def bugfix_releases_include_backported_features(self):
self._expect_entries(
['1.0.2', self.bf, self.b, self.s],
[self.b, self.bf],
[self.s]
)
def bugfix_releases_include_backported_support(self):
self._expect_entries(
['1.0.2', self.f, self.b, self.s, self.bs],
[self.b, self.bs],
[self.s, self.f]
)
def unmarked_bullet_list_items_treated_as_bugs(self):
fake = list_item('', paragraph('', '', raw('', 'whatever')))
releases = _releases('1.0.2', self.f, fake)
entries = releases[1]['entries']
eq_(len(entries), 1)
assert self.f not in entries
assert isinstance(entries[0], Issue)
eq_(entries[0].number, None)
def unreleased_items_go_in_unreleased_releases(self):
releases = _releases(self.f, self.b)
# Should have two unreleased lists, one feature w/ feature, one bugfix
# w/ bugfix.
bugfix, feature = releases[1:]
eq_(len(feature['entries']), 1)
eq_(len(bugfix['entries']), 1)
assert self.f in feature['entries']
assert self.b in bugfix['entries']
eq_(feature['obj'].number, 'unreleased_feature')
eq_(bugfix['obj'].number, 'unreleased_bugfix')
def issues_consumed_by_releases_are_not_in_unreleased(self):
releases = _releases('1.0.2', self.f, self.b, self.s, self.bs)
release = releases[1]['entries']
unreleased = releases[-1]['entries']
assert self.b in release
assert self.b not in unreleased
def oddly_ordered_bugfix_releases_and_unreleased_list(self):
# Release set up w/ non-contiguous feature+bugfix releases; catches
# funky problems with 'unreleased' buckets
b2 = _issue('bug', '2')
f3 = _issue('feature', '3')
changelog = _releases(
'1.1.1', '1.0.2', self.f, b2, '1.1.0', f3, self.b
)
assert f3 in changelog[1]['entries']
assert b2 in changelog[2]['entries']
assert b2 in changelog[3]['entries']
def release_line_bugfix_specifier(self):
b50 = _issue('bug', '50')
b42 = _issue('bug', '42', line='1.1')
f25 = _issue('feature', '25')
b35 = _issue('bug', '35')
b34 = _issue('bug', '34')
f22 = _issue('feature', '22')
b20 = _issue('bug', '20')
c = _changelog2dict(_releases(
'1.2.1', '1.1.2', '1.0.3',
b50, b42,
'1.2.0', '1.1.1', '1.0.2',
f25, b35, b34,
'1.1.0', '1.0.1',
f22, b20
))
for rel, issues in (
('1.0.1', [b20]),
('1.1.0', [f22]),
('1.0.2', [b34, b35]),
('1.1.1', [b34, b35]),
('1.2.0', [f25]),
('1.0.3', [b50]), # the crux - is not b50 + b42
('1.1.2', [b50, b42]),
('1.2.1', [b50, b42]),
):
eq_(set(c[rel]), set(issues))
def releases_can_specify_issues_explicitly(self):
# Build regular list-o-entries
b2 = _issue('bug', '2')
b3 = _issue('bug', '3')
changelog = _release_list(
'1.0.1', '1.1.1', b3, b2, self.b, '1.1.0', self.f
)
# Modify 1.0.1 release to be speshul
changelog[0][0].append(Text("2, 3"))
rendered = construct_releases(changelog, _app())
# 1.0.1 includes just 2 and 3, not bug 1
one_0_1 = rendered[3]['entries']
one_1_1 = rendered[2]['entries']
assert self.b not in one_0_1
assert b2 in one_0_1
assert b3 in one_0_1
# 1.1.1 includes all 3 (i.e. the explicitness of 1.0.1 didn't affect
# the 1.1 line bucket.)
assert self.b in one_1_1
assert b2 in one_1_1
assert b3 in one_1_1
def explicit_release_list_split_works_with_unicode(self):
b = _issue('bug', '17')
changelog = _release_list('1.0.1', b)
changelog[0][0].append(Text(six.text_type('17')))
# When using naive method calls, this explodes
construct_releases(changelog, _app())
def explicit_feature_release_features_are_removed_from_unreleased(self):
f1 = _issue('feature', '1')
f2 = _issue('feature', '2')
changelog = _release_list('1.1.0', f1, f2)
# Ensure that 1.1.0 specifies feature 2
changelog[0][0].append(Text("2"))
rendered = _changelog2dict(construct_releases(changelog, _app()))
# 1.1.0 should have feature 2 only
assert f2 in rendered['1.1.0']
assert f1 not in rendered['1.1.0']
# unreleased feature list should still get/see feature 1
assert f1 in rendered['unreleased_feature']
# now-released feature 2 should not be in unreleased_feature
assert f2 not in rendered['unreleased_feature']
def explicit_bugfix_releases_get_removed_from_unreleased(self):
b1 = _issue('bug', '1')
b2 = _issue('bug', '2')
changelog = _release_list('1.0.1', b1, b2)
# Ensure that 1.0.1 specifies bug 2
changelog[0][0].append(Text('2'))
rendered = construct_releases(changelog, _app())
# 1.0.1 should have bug 2 only
assert b2 in rendered[1]['entries']
assert b1 not in rendered[1]['entries']
# unreleased bug list should still get/see bug 1
assert b1 in rendered[2]['entries']
@raises(ValueError)
def explicit_releases_error_on_unfound_issues(self):
# Just a release - result will have 1.0.0, 1.0.1, and unreleased
changelog = _release_list('1.0.1')
# No issues listed -> this clearly doesn't exist in any buckets
changelog[1][0].append(Text("25"))
# This should asplode
construct_releases(changelog, _app())
def duplicate_issue_numbers_adds_two_issue_items(self):
test_changelog = _releases('1.0.1', self.b, self.b)
test_changelog = _changelog2dict(test_changelog)
eq_(len(test_changelog['1.0.1']), 2)
def duplicate_zeroes_dont_error(self):
cl = _releases('1.0.1', _issue('bug', '0'), _issue('bug', '0'))
cl = _changelog2dict(cl)
assert len(cl['1.0.1']) == 2
def issues_are_sorted_by_type_within_releases(self):
b1 = _issue('bug', '123', major=True)
b2 = _issue('bug', '124', major=True)
s1 = _issue('support', '25')
s2 = _issue('support', '26')
f1 = _issue('feature', '3455')
f2 = _issue('feature', '3456')
# Semi random definitely-not-in-desired-order order
changelog = _changelog2dict(_releases('1.1', b1, s1, s2, f1, b2, f2))
# Order should be feature, bug, support. While it doesn't REALLY
# matter, assert that within each category the order matches the old
# 'reverse chronological' order.
eq_(changelog['1.1'], [f2, f1, b2, b1, s2, s1])
def _obj2name(obj):
cls = obj if isinstance(obj, type) else obj.__class__
return cls.__name__.split('.')[-1]
def _expect_type(node, cls):
type_ = _obj2name(node)
name = _obj2name(cls)
msg = "Expected %r to be a %s, but it's a %s" % (node, name, type_)
assert isinstance(node, cls), msg
class nodes(Spec):
"""
Expansion/extension of docutils nodes (rendering)
"""
def setup(self):
_setup_issues(self)
def _generate(self, *entries, **kwargs):
app = kwargs.get('app', None)
nodes = construct_nodes(_releases(*entries, app=app))
# By default, yield the contents of the bullet list.
return nodes if kwargs.get('raw', False) else nodes[0][1][0]
def _test_link(self, kwargs, type_, expected):
app = _app(**kwargs)
nodes = construct_nodes(construct_releases([
_release('1.0.2', app=app),
_entry(_issue('bug', 15, app=app)),
_release('1.0.0'),
], app=app))
if type_ == 'release':
header = nodes[0][0][0].astext()
assert expected in header
elif type_ == 'issue':
link = nodes[0][1][0][0][2]
eq_(link['refuri'], expected)
else:
raise Exception("Gave unknown type_ kwarg to _test_link()!")
def issues_with_numbers_appear_as_number_links(self):
self._test_link({}, 'issue', 'bar_15')
def releases_appear_as_header_links(self):
self._test_link({}, 'release', 'foo_1.0.2')
def links_will_use_github_option_if_defined(self):
kwargs = {
'release_uri': None,
'issue_uri': None,
'github_path': 'foo/bar',
}
for type_, expected in (
('issue', 'https://github.com/foo/bar/issues/15'),
('release', 'https://github.com/foo/bar/tree/1.0.2'),
):
self._test_link(kwargs, type_, expected)
def issue_links_prefer_explicit_setting_over_github_setting(self):
kwargs = {
'release_uri': None,
'issue_uri': 'explicit_issue_%s',
'github_path': 'foo/bar',
}
self._test_link(kwargs, 'issue', 'explicit_issue_15')
def release_links_prefer_explicit_setting_over_github_setting(self):
kwargs = {
'release_uri': 'explicit_release_%s',
'issue_uri': None,
'github_path': 'foo/bar',
}
self._test_link(kwargs, 'release', 'explicit_release_1.0.2')
def _assert_prefix(self, entries, expectation):
assert expectation in self._generate(*entries)[0][0][0]
def bugs_marked_as_bugs(self):
self._assert_prefix(['1.0.2', self.b], 'Bug')
def features_marked_as_features(self):
self._assert_prefix(['1.1.0', self.f], 'Feature')
def support_marked_as_support(self):
self._assert_prefix(['1.1.0', self.s], 'Support')
def dashed_issues_appear_as_unlinked_issues(self):
node = self._generate('1.0.2', _issue('bug', '-'))
assert not isinstance(node[0][2], reference)
def zeroed_issues_appear_as_unlinked_issues(self):
node = self._generate('1.0.2', _issue('bug', '0'))
assert not isinstance(node[0][2], reference)
def un_prefixed_list_items_appear_as_unlinked_bugs(self):
fake = list_item('', paragraph('', '', raw('', 'whatever')))
node = self._generate('1.0.2', fake)
assert 'Bug' in str(node[0][0])
assert 'whatever' in str(node[0][3])
def issues_remain_wrapped_in_unordered_list_nodes(self):
node = self._generate('1.0.2', self.b, raw=True)[0][1]
_expect_type(node, bullet_list)
_expect_type(node[0], list_item)
def release_headers_have_local_style_tweaks(self):
node = self._generate('1.0.2', self.b, raw=True)[0][0]
_expect_type(node, raw)
# Header w/ bottom margin
assert '