diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 5299777..445e252 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -162,6 +162,15 @@ Notes are output in the order they are found when scanning the git history of the branch using topological ordering. This is deterministic, but not necessarily predictable or mutable. +Checking Notes +============== + +Run ``reno lint `` to test the existing +release notes files against some rules for catching common +mistakes. The command exits with an error code if there are any +mistakes, so it can be used in a build pipeline to force some +correctness. + Configuring Reno ================ diff --git a/releasenotes/notes/add-linter-ce0a861ade64baf2.yaml b/releasenotes/notes/add-linter-ce0a861ade64baf2.yaml new file mode 100644 index 0000000..b36983a --- /dev/null +++ b/releasenotes/notes/add-linter-ce0a861ade64baf2.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a ``lint`` command for checking the contents and names of the + release notes files against some basic validation rules. \ No newline at end of file diff --git a/reno/linter.py b/reno/linter.py new file mode 100644 index 0000000..5071842 --- /dev/null +++ b/reno/linter.py @@ -0,0 +1,54 @@ +# 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 __future__ import print_function + +import glob +import logging +import os.path + +from reno import loader +from reno import scanner + +LOG = logging.getLogger(__name__) + + +def lint_cmd(args, conf): + "Check some common mistakes" + LOG.debug('starting lint') + notesdir = os.path.join(conf.reporoot, conf.notespath) + notes = glob.glob(os.path.join(notesdir, '*.yaml')) + + error = 0 + load = loader.Loader(conf, ignore_cache=True) + + allowed_section_names = ['prelude'] + [s[0] for s in conf.sections] + + uids = {} + for f in notes: + LOG.debug('examining %s', f) + uid = scanner._get_unique_id(f) + uids.setdefault(uid, []).append(f) + + content = load.parse_note_file(f, None) + for section_name in content.keys(): + if section_name not in allowed_section_names: + LOG.warning('unrecognized section name %s in %s', + section_name, f) + error = 1 + + for uid, names in sorted(uids.items()): + if len(names) > 1: + LOG.warning('UID collision: %s', names) + error = 1 + + return error diff --git a/reno/main.py b/reno/main.py index dd062fe..3237661 100644 --- a/reno/main.py +++ b/reno/main.py @@ -18,6 +18,7 @@ from reno import cache from reno import config from reno import create from reno import defaults +from reno import linter from reno import lister from reno import report @@ -173,6 +174,18 @@ def main(argv=sys.argv[1:]): _build_query_arg_group(do_cache) do_cache.set_defaults(func=cache.cache_cmd) + do_linter = subparsers.add_parser( + 'lint', + help='check some common mistakes', + ) + do_linter.add_argument( + 'reporoot', + default='.', + nargs='?', + help='root of the git repository', + ) + do_linter.set_defaults(func=linter.lint_cmd) + args = parser.parse_args(argv) conf = config.Config(args.reporoot, args.relnotesdir) conf.override_from_parsed_args(args) diff --git a/tox.ini b/tox.ini index 7b45ac9..88a0101 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,9 @@ commands = coverage report --show-missing [testenv:pep8] -commands = flake8 +commands = + flake8 + reno -q lint [testenv:venv] commands = {posargs}