diff --git a/doc/source/conf.py b/doc/source/conf.py index 5c7b0bc..67ed674 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -25,7 +25,6 @@ else: has_theme = True -sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be @@ -34,6 +33,7 @@ extensions = [ 'sphinx.ext.autodoc', # 'sphinx.ext.intersphinx', 'reno.sphinxext', + 'reno._exts.show_reno_config', ] if has_theme: diff --git a/doc/source/user/usage.rst b/doc/source/user/usage.rst index 3aa69ed..944a03a 100644 --- a/doc/source/user/usage.rst +++ b/doc/source/user/usage.rst @@ -220,102 +220,7 @@ using command-line switches. For example: The following options are configurable: -`notesdir` - The notes subdirectory within the `relnotesdir` where the notes live. - - Defaults to ``notes``. - -`collapse_pre_releases` - Should pre-release versions be merged into the final release of the same - number (`1.0.0.0a1` notes appear under `1.0.0`). - - Defaults to ``True``. - -`stop_at_branch_base` - Should the scanner stop at the base of a branch (True) or go ahead and scan - the entire history (False)? - - Defaults to ``True``. - -`branch` - The git branch to scan. If a stable branch is specified but does not exist, - reno attempts to automatically convert that to an "end-of-life" tag. For - example, ``origin/stable/liberty`` would be converted to ``liberty-eol``. - - Defaults to the "current" branch checked out. - -`earliest_version` - The earliest version to be included. This is usually the lowest version - number, and is meant to be the oldest version. If unset, all versions will be - scanned. - - Defaults to ``None``. - -`template` - The template used by reno new to create a note. - -`release_tag_re` - The regex pattern used to match the repo tags representing a valid release - version. The pattern is compiled with the verbose and unicode flags enabled. - - Defaults to ``((?:[\d.ab]|rc)+)``. - -`pre_release_tag_re` - The regex pattern used to check if a valid release version tag is also a - valid pre-release version. The pattern is compiled with the verbose and - unicode flags enabled. The pattern must define a group called `pre_release` - that matches the pre-release part of the tag and any separator, e.g for - pre-release version `12.0.0.0rc1` the default RE pattern will identify - `.0rc1` as the value of the group 'pre_release'. - - Defaults to ``(?P\.\d+(?:[ab]|rc)+\d*)$``. - -`branch_name_re` - The pattern for names for branches that are relevant when scanning history to - determine where to stop, to find the "base" of a branch. Other branches are - ignored. - - Defaults to ``stable/.+``. - -`sections` - The identifiers and names of permitted sections in the release notes, in the - order in which the final report will be generated. A prelude section will - always be automatically inserted before the first element of this list. - -`prelude_section_name` - The name of the prelude section in the note template. Note that the - value for this must be a single word, but can have underscores. The - value is displayed in titlecase in the report after replacing - underscores with spaces. - - Defaults to ``prelude`` - -`ignore_null_merges` - OpenStack used to use null-merges to bring final release tags from - stable branches back into the master branch. This confuses the - regular traversal because it makes that stable branch appear to be - part of master and/or the later stable branch. This option allows us - to ignore those. - - When this option is set to True, any merge commits with no changes - and in which the second or later parent is tagged are considered - "null-merges" that bring the tag information into the current branch - but nothing else. - - Defaults to ``True``. - -`ignore_notes` - A list of filenames or UIDs for notes that should be ignored by the - reno scanner. It is most useful to set this when a note is edited on - the wrong branch, making it appear to be part of a release that it - is not. - - .. warning:: - - Setting the option in the main configuration file makes it apply - to all branches. To ignore a note in the HTML build, use the - ``ignore-notes`` parameter to the ``release-notes`` sphinx - directive. +.. show-reno-config:: Debugging ========= diff --git a/reno/_exts/__init__.py b/reno/_exts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/reno/_exts/show_reno_config.py b/reno/_exts/show_reno_config.py new file mode 100644 index 0000000..29afd1b --- /dev/null +++ b/reno/_exts/show_reno_config.py @@ -0,0 +1,75 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from docutils import nodes +from docutils.parsers import rst +from docutils.statemachine import ViewList + +from sphinx.util.nodes import nested_parse_with_titles + +from reno import config +import six + + +def _multi_line_string(s, indent=''): + output_lines = s.splitlines() + if not output_lines[0].strip(): + output_lines = output_lines[1:] + for l in output_lines: + yield indent + l + + +def _format_option_help(options): + "Produce RST lines for the configuration options." + for opt in options: + yield '``{}``'.format(opt.name) + for l in _multi_line_string(opt.help, ' '): + yield l + yield '' + if isinstance(opt.default, six.string_types) and '\n' in opt.default: + # Multi-line string + yield ' Defaults to' + yield '' + yield ' ::' + yield '' + for l in _multi_line_string(opt.default, ' '): + yield l + else: + yield ' Defaults to ``{!r}``'.format(opt.default) + yield '' + + +class ShowConfigDirective(rst.Directive): + + option_spec = {} + + has_content = True + + def run(self): + env = self.state.document.settings.env + app = env.app + + result = ViewList() + source_name = '<' + __name__ + '>' + for line in _format_option_help(config._OPTIONS): + app.info(line) + result.append(line, source_name) + + node = nodes.section() + node.document = self.state.document + nested_parse_with_titles(self.state, result, node) + + return node.children + + +def setup(app): + app.add_directive('show-reno-config', ShowConfigDirective) diff --git a/reno/config.py b/reno/config.py index 9eb11c4..b45a03a 100644 --- a/reno/config.py +++ b/reno/config.py @@ -26,71 +26,74 @@ Opt = collections.namedtuple('Opt', 'name default help') _OPTIONS = [ Opt('notesdir', defaults.NOTES_SUBDIR, - textwrap.dedent(""" + textwrap.dedent("""\ The notes subdirectory within the relnotesdir where the notes live. """)), Opt('collapse_pre_releases', True, - textwrap.dedent(""" + textwrap.dedent("""\ Should pre-release versions be merged into the final release of the same number (1.0.0.0a1 notes appear under 1.0.0). """)), Opt('stop_at_branch_base', True, - textwrap.dedent(""" + textwrap.dedent("""\ Should the scanner stop at the base of a branch (True) or go ahead and scan the entire history (False)? """)), Opt('branch', None, - textwrap.dedent(""" - # The git branch to scan. Defaults to the "current" branch - # checked out. + textwrap.dedent("""\ + The git branch to scan. Defaults to the "current" branch + checked out. If a stable branch is specified but does not + exist, reno attempts to automatically convert that to an + "end-of-life" tag. For example, ``origin/stable/liberty`` + would be converted to ``liberty-eol``. """)), Opt('earliest_version', None, - textwrap.dedent(""" - # The earliest version to be included. This is usually the - # lowest version number, and is meant to be the oldest - # version. + textwrap.dedent("""\ + The earliest version to be included. This is usually the + lowest version number, and is meant to be the oldest + version. If unset, all versions will be scanned. """)), Opt('template', defaults.TEMPLATE.format(defaults.PRELUDE_SECTION_NAME), - textwrap.dedent(""" - # The template used by reno new to create a note. + textwrap.dedent("""\ + The template used by reno new to create a note. """)), Opt('release_tag_re', - textwrap.dedent(''' + textwrap.dedent('''\ ((?:[\d.ab]|rc)+) # digits, a, b, and rc cover regular and # pre-releases '''), - textwrap.dedent(""" - # The RE pattern used to match the repo tags representing a valid - # release version. The pattern is compiled with the verbose and unicode - # flags enabled. + textwrap.dedent("""\ + The regex pattern used to match the repo tags representing a + valid release version. The pattern is compiled with the + verbose and unicode flags enabled. """)), Opt('pre_release_tag_re', - textwrap.dedent(''' + textwrap.dedent('''\ (?P\.\d+(?:[ab]|rc)+\d*)$ '''), - textwrap.dedent(""" - # The RE pattern used to check if a valid release version tag is also a - # valid pre-release version. The pattern is compiled with the verbose - # and unicode flags enabled. The pattern must define a group called - # 'pre_release' that matches the pre-release part of the tag and any - # separator, e.g for pre-release version '12.0.0.0rc1' the default RE - # pattern will identify '.0rc1' as the value of the group - # 'pre_release'. + textwrap.dedent("""\ + The regex pattern used to check if a valid release version tag + is also a valid pre-release version. The pattern is compiled + with the verbose and unicode flags enabled. The pattern must + define a group called 'pre_release' that matches the + pre-release part of the tag and any separator, e.g for + pre-release version '12.0.0.0rc1' the default pattern will + identify '.0rc1' as the value of the group 'pre_release'. """)), Opt('branch_name_re', 'stable/.+', - textwrap.dedent(""" - # The pattern for names for branches that are relevant when - # scanning history to determine where to stop, to find the - # "base" of a branch. Other branches are ignored. + textwrap.dedent("""\ + The pattern for names for branches that are relevant when + scanning history to determine where to stop, to find the + "base" of a branch. Other branches are ignored. """)), Opt('sections', @@ -104,42 +107,46 @@ _OPTIONS = [ ['fixes', 'Bug Fixes'], ['other', 'Other Notes'], ], - textwrap.dedent(""" - # The identifiers and names of permitted sections in the - # release notes, in the order in which the final report will - # be generated. A prelude section will always be automatically - # inserted before the first element of this list. + textwrap.dedent("""\ + The identifiers and names of permitted sections in the + release notes, in the order in which the final report will + be generated. A prelude section will always be automatically + inserted before the first element of this list. """)), Opt('prelude_section_name', defaults.PRELUDE_SECTION_NAME, - textwrap.dedent(""" - # The name of the prelude section in the note template. This - # allows users to rename the section to, for example, - # 'release_summary' or 'project_wide_general_announcements', - # which is displayed in titlecase in the report after - # replacing underscores with spaces. + textwrap.dedent("""\ + The name of the prelude section in the note template. This + allows users to rename the section to, for example, + 'release_summary' or 'project_wide_general_announcements', + which is displayed in titlecase in the report after + replacing underscores with spaces. """)), Opt('ignore_null_merges', True, - textwrap.dedent(""" - # When this option is set to True, any merge commits with no - # changes and in which the second or later parent is tagged - # are considered "null-merges" that bring the tag information - # into the current branch but nothing else. - # - # OpenStack used to use null-merges to bring final release - # tags from stable branches back into the master branch. This - # confuses the regular traversal because it makes that stable - # branch appear to be part of master and/or the later stable - # branch. This option allows us to ignore those. + textwrap.dedent("""\ + When this option is set to True, any merge commits with no + changes and in which the second or later parent is tagged + are considered "null-merges" that bring the tag information + into the current branch but nothing else. + + OpenStack used to use null-merges to bring final release + tags from stable branches back into the master branch. This + confuses the regular traversal because it makes that stable + branch appear to be part of master and/or the later stable + branch. This option allows us to ignore those. """)), Opt('ignore_notes', [], - textwrap.dedent(""" - # Note files to be ignored. It's useful to be able to ignore a - # file if it is edited on the wrong branch. Notes should be - # specified by their filename or UID. Setting the value in the - # configuration file makes it apply to all branches. + textwrap.dedent("""\ + Note files to be ignored. It's useful to be able to ignore a + file if it is edited on the wrong branch. Notes should be + specified by their filename or UID. + + Setting the option in the main configuration file makes it + apply to all branches. To ignore a note in the HTML build, use + the ``ignore-notes`` parameter to the ``release-notes`` sphinx + directive. """)), ] diff --git a/reno/tests/test_exts.py b/reno/tests/test_exts.py new file mode 100644 index 0000000..85445ca --- /dev/null +++ b/reno/tests/test_exts.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import textwrap + +from reno._exts import show_reno_config +from reno import config +from reno.tests import base + + +class TestMultiLineString(base.TestCase): + + def test_no_indent(self): + input = textwrap.dedent("""\ + The notes subdirectory within the relnotesdir where the + notes live. + """) + expected = '\n'.join([ + 'The notes subdirectory within the relnotesdir where the', + 'notes live.', + ]) + actual = '\n'.join(show_reno_config._multi_line_string(input)) + self.assertEqual(expected, actual) + + def test_with_indent(self): + input = textwrap.dedent("""\ + The notes subdirectory within the relnotesdir where the + notes live. + """) + expected = '\n'.join([ + ' The notes subdirectory within the relnotesdir where the', + ' notes live.', + ]) + actual = '\n'.join(show_reno_config._multi_line_string(input, ' ')) + self.assertEqual(expected, actual) + + def test_first_line_blank(self): + input = textwrap.dedent(""" + The notes subdirectory within the relnotesdir where the + notes live. + """) + expected = '\n'.join([ + ' The notes subdirectory within the relnotesdir where the', + ' notes live.', + ]) + actual = '\n'.join(show_reno_config._multi_line_string(input, ' ')) + self.assertEqual(expected, actual) + + +class TestFormatOptionHelp(base.TestCase): + + def test_simple_default(self): + opt = config.Opt( + 'notesdir', 'path/to/notes', + textwrap.dedent("""\ + The notes subdirectory within the relnotesdir where the + notes live. + """), + ) + actual = '\n'.join(show_reno_config._format_option_help([opt])) + expected = textwrap.dedent("""\ + ``notesdir`` + The notes subdirectory within the relnotesdir where the + notes live. + + Defaults to ``'path/to/notes'`` + """) + self.assertEqual(expected, actual) + + def test_bool_default(self): + opt = config.Opt( + 'collapse_pre_releases', True, + textwrap.dedent("""\ + Should pre-release versions be merged into the final release + of the same number (1.0.0.0a1 notes appear under 1.0.0). + """), + ) + actual = '\n'.join(show_reno_config._format_option_help([opt])) + expected = textwrap.dedent("""\ + ``collapse_pre_releases`` + Should pre-release versions be merged into the final release + of the same number (1.0.0.0a1 notes appear under 1.0.0). + + Defaults to ``True`` + """) + self.assertEqual(expected, actual) + + def test_multiline_default(self): + opt = config.Opt( + 'release_tag_re', + textwrap.dedent('''\ + ((?:[\d.ab]|rc)+) # digits, a, b, and rc cover regular and + # pre-releases + '''), + textwrap.dedent("""\ + The regex pattern used to match the repo tags representing a + valid release version. The pattern is compiled with the + verbose and unicode flags enabled. + """), + ) + actual = '\n'.join(show_reno_config._format_option_help([opt])) + expected = textwrap.dedent("""\ + ``release_tag_re`` + The regex pattern used to match the repo tags representing a + valid release version. The pattern is compiled with the + verbose and unicode flags enabled. + + Defaults to + + :: + + ((?:[\d.ab]|rc)+) # digits, a, b, and rc cover regular and + # pre-releases + """) + self.assertEqual(expected, actual)