use the cache file instead of scanner when possible
Define a loader API on top of the cache and scanner APIs and update the list, report, and sphinxext modules to use that instead of calling the scanner directly. Change-Id: I2899c2ae9bb46919a375ffe4f195b239cff389ef Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
parent
0b459b8337
commit
9cb8c4bf1b
|
@ -53,3 +53,4 @@ ChangeLog
|
||||||
.*.swp
|
.*.swp
|
||||||
.*sw?
|
.*sw?
|
||||||
/cover/
|
/cover/
|
||||||
|
/releasenotes/notes/reno.cache
|
||||||
|
|
|
@ -12,10 +12,6 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from reno import scanner
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
_SECTION_ORDER = [
|
_SECTION_ORDER = [
|
||||||
('features', 'New Features'),
|
('features', 'New Features'),
|
||||||
|
@ -41,7 +37,7 @@ def _indent_for_list(text, prefix=' '):
|
||||||
]) + '\n'
|
]) + '\n'
|
||||||
|
|
||||||
|
|
||||||
def format_report(reporoot, scanner_output, versions_to_include, title=None):
|
def format_report(loader, versions_to_include, title=None):
|
||||||
report = []
|
report = []
|
||||||
if title:
|
if title:
|
||||||
report.append('=' * len(title))
|
report.append('=' * len(title))
|
||||||
|
@ -52,14 +48,9 @@ def format_report(reporoot, scanner_output, versions_to_include, title=None):
|
||||||
# Read all of the notes files.
|
# Read all of the notes files.
|
||||||
file_contents = {}
|
file_contents = {}
|
||||||
for version in versions_to_include:
|
for version in versions_to_include:
|
||||||
for filename, sha in scanner_output[version]:
|
for filename, sha in loader[version]:
|
||||||
body = scanner.get_file_at_commit(
|
body = loader.parse_note_file(filename, sha)
|
||||||
reporoot,
|
file_contents[filename] = body
|
||||||
filename,
|
|
||||||
sha,
|
|
||||||
)
|
|
||||||
y = yaml.safe_load(body)
|
|
||||||
file_contents[filename] = y
|
|
||||||
|
|
||||||
for version in versions_to_include:
|
for version in versions_to_include:
|
||||||
report.append(version)
|
report.append(version)
|
||||||
|
@ -67,7 +58,7 @@ def format_report(reporoot, scanner_output, versions_to_include, title=None):
|
||||||
report.append('')
|
report.append('')
|
||||||
|
|
||||||
# Add the preludes.
|
# Add the preludes.
|
||||||
notefiles = scanner_output[version]
|
notefiles = loader[version]
|
||||||
for n, sha in notefiles:
|
for n, sha in notefiles:
|
||||||
if 'prelude' in file_contents[n]:
|
if 'prelude' in file_contents[n]:
|
||||||
report.append(file_contents[n]['prelude'])
|
report.append(file_contents[n]['prelude'])
|
||||||
|
|
|
@ -14,7 +14,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from reno import scanner
|
from reno import loader
|
||||||
from reno import utils
|
from reno import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -26,17 +26,19 @@ def list_cmd(args):
|
||||||
reporoot = args.reporoot.rstrip('/') + '/'
|
reporoot = args.reporoot.rstrip('/') + '/'
|
||||||
notesdir = utils.get_notes_dir(args)
|
notesdir = utils.get_notes_dir(args)
|
||||||
collapse = args.collapse_pre_releases
|
collapse = args.collapse_pre_releases
|
||||||
notes = scanner.get_notes_by_version(
|
ldr = loader.Loader(
|
||||||
reporoot, notesdir, args.branch,
|
reporoot=reporoot,
|
||||||
|
notesdir=notesdir,
|
||||||
|
branch=args.branch,
|
||||||
collapse_pre_releases=collapse,
|
collapse_pre_releases=collapse,
|
||||||
earliest_version=args.earliest_version,
|
earliest_version=args.earliest_version,
|
||||||
)
|
)
|
||||||
if args.version:
|
if args.version:
|
||||||
versions = args.version
|
versions = args.version
|
||||||
else:
|
else:
|
||||||
versions = notes.keys()
|
versions = ldr.versions
|
||||||
for version in versions:
|
for version in versions:
|
||||||
notefiles = notes[version]
|
notefiles = ldr[version]
|
||||||
print(version)
|
print(version)
|
||||||
for n, sha in notefiles:
|
for n, sha in notefiles:
|
||||||
if n.startswith(reporoot):
|
if n.startswith(reporoot):
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
# 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 logging
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from reno import scanner
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_filename(reporoot, notesdir):
|
||||||
|
return os.path.join(reporoot, notesdir, 'reno.cache')
|
||||||
|
|
||||||
|
|
||||||
|
class Loader(object):
|
||||||
|
"Load the release notes for a given repository."
|
||||||
|
|
||||||
|
def __init__(self, reporoot, notesdir, branch=None,
|
||||||
|
collapse_pre_releases=True,
|
||||||
|
earliest_version=None,
|
||||||
|
ignore_cache=False):
|
||||||
|
"""Initialize a Loader.
|
||||||
|
|
||||||
|
The versions are presented in reverse chronological order.
|
||||||
|
|
||||||
|
Notes files are associated with the earliest version for which
|
||||||
|
they were available, regardless of whether they changed later.
|
||||||
|
|
||||||
|
:param reporoot: Path to the root of the git repository.
|
||||||
|
:type reporoot: str
|
||||||
|
:param notesdir: The directory under *reporoot* with the release notes.
|
||||||
|
:type notesdir: str
|
||||||
|
:param branch: The name of the branch to scan. Defaults to current.
|
||||||
|
:type branch: str
|
||||||
|
:param collapse_pre_releases: When true, merge pre-release versions
|
||||||
|
into the final release, if it is present.
|
||||||
|
:type collapse_pre_releases: bool
|
||||||
|
:param earliest_version: The oldest version to include.
|
||||||
|
:type earliest_version: str
|
||||||
|
:param ignore_cache: Do not load a cache file if it is present.
|
||||||
|
:type ignore_cache: bool
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._reporoot = reporoot
|
||||||
|
self._notesdir = notesdir
|
||||||
|
self._branch = branch
|
||||||
|
self._collapse_pre_releases = collapse_pre_releases
|
||||||
|
self._earliest_version = earliest_version
|
||||||
|
self._ignore_cache = ignore_cache
|
||||||
|
|
||||||
|
self._cache = None
|
||||||
|
self._scanner_output = None
|
||||||
|
self._cache_filename = get_cache_filename(reporoot, notesdir)
|
||||||
|
|
||||||
|
self._load_data()
|
||||||
|
|
||||||
|
def _load_data(self):
|
||||||
|
cache_file_exists = os.path.exists(self._cache_filename)
|
||||||
|
|
||||||
|
if self._ignore_cache and cache_file_exists:
|
||||||
|
LOG.debug('ignoring cache file %s', self._cache_filename)
|
||||||
|
|
||||||
|
if (not self._ignore_cache) and cache_file_exists:
|
||||||
|
with open(self._cache_filename, 'r') as f:
|
||||||
|
self._cache = yaml.safe_load(f.read())
|
||||||
|
# Save the cached scanner output to the same attribute
|
||||||
|
# it would be in if we had loaded it "live". This
|
||||||
|
# simplifies some of the logic in the other methods.
|
||||||
|
self._scanner_output = {
|
||||||
|
n['version']: n['files']
|
||||||
|
for n in self._cache['notes']
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
self._scanner_output = scanner.get_notes_by_version(
|
||||||
|
reporoot=self._reporoot,
|
||||||
|
notesdir=self._notesdir,
|
||||||
|
branch=self._branch,
|
||||||
|
collapse_pre_releases=self._collapse_pre_releases,
|
||||||
|
earliest_version=self._earliest_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def versions(self):
|
||||||
|
"A list of all of the versions found."
|
||||||
|
return list(self._scanner_output.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, version):
|
||||||
|
"Return data about the files that should go into a given version."
|
||||||
|
return self._scanner_output[version]
|
||||||
|
|
||||||
|
def parse_note_file(self, filename, sha):
|
||||||
|
"Return the data structure encoded in the note file."
|
||||||
|
if self._cache:
|
||||||
|
return self._cache['file-contents'][filename]
|
||||||
|
else:
|
||||||
|
body = scanner.get_file_at_commit(self._reporoot, filename, sha)
|
||||||
|
return yaml.safe_load(body)
|
|
@ -39,6 +39,10 @@ _query_args = [
|
||||||
(('--earliest-version',),
|
(('--earliest-version',),
|
||||||
dict(default=None,
|
dict(default=None,
|
||||||
help='stop when this version is reached in the history')),
|
help='stop when this version is reached in the history')),
|
||||||
|
(('--ignore-cache',),
|
||||||
|
dict(default=False,
|
||||||
|
action='store_true',
|
||||||
|
help='if there is a cache file present, do not use it')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from reno import formatter
|
from reno import formatter
|
||||||
from reno import scanner
|
from reno import loader
|
||||||
from reno import utils
|
from reno import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,18 +22,19 @@ def report_cmd(args):
|
||||||
reporoot = args.reporoot.rstrip('/') + '/'
|
reporoot = args.reporoot.rstrip('/') + '/'
|
||||||
notesdir = utils.get_notes_dir(args)
|
notesdir = utils.get_notes_dir(args)
|
||||||
collapse = args.collapse_pre_releases
|
collapse = args.collapse_pre_releases
|
||||||
notes = scanner.get_notes_by_version(
|
ldr = loader.Loader(
|
||||||
reporoot, notesdir, args.branch,
|
reporoot=reporoot,
|
||||||
|
notesdir=notesdir,
|
||||||
|
branch=args.branch,
|
||||||
collapse_pre_releases=collapse,
|
collapse_pre_releases=collapse,
|
||||||
earliest_version=args.earliest_version,
|
earliest_version=args.earliest_version,
|
||||||
)
|
)
|
||||||
if args.version:
|
if args.version:
|
||||||
versions = args.version
|
versions = args.version
|
||||||
else:
|
else:
|
||||||
versions = notes.keys()
|
versions = ldr.versions
|
||||||
text = formatter.format_report(
|
text = formatter.format_report(
|
||||||
reporoot,
|
ldr,
|
||||||
notes,
|
|
||||||
versions,
|
versions,
|
||||||
title='Release Notes',
|
title='Release Notes',
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import os.path
|
||||||
|
|
||||||
from reno import defaults
|
from reno import defaults
|
||||||
from reno import formatter
|
from reno import formatter
|
||||||
from reno import scanner
|
from reno import loader
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers import rst
|
from docutils.parsers import rst
|
||||||
|
@ -59,8 +59,10 @@ class ReleaseNotesDirective(rst.Directive):
|
||||||
info('scanning %s for %s release notes' %
|
info('scanning %s for %s release notes' %
|
||||||
(os.path.join(reporoot, notesdir), branch or 'current branch'))
|
(os.path.join(reporoot, notesdir), branch or 'current branch'))
|
||||||
|
|
||||||
notes = scanner.get_notes_by_version(
|
ldr = loader.Loader(
|
||||||
reporoot, notesdir, branch,
|
reporoot=reporoot,
|
||||||
|
notesdir=notesdir,
|
||||||
|
branch=branch,
|
||||||
collapse_pre_releases=collapse,
|
collapse_pre_releases=collapse,
|
||||||
earliest_version=earliest_version,
|
earliest_version=earliest_version,
|
||||||
)
|
)
|
||||||
|
@ -70,10 +72,9 @@ class ReleaseNotesDirective(rst.Directive):
|
||||||
for v in version_opt.split(',')
|
for v in version_opt.split(',')
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
versions = notes.keys()
|
versions = ldr.versions
|
||||||
text = formatter.format_report(
|
text = formatter.format_report(
|
||||||
reporoot,
|
ldr,
|
||||||
notes,
|
|
||||||
versions,
|
versions,
|
||||||
title=title,
|
title=title,
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,12 +12,11 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from reno import formatter
|
from reno import formatter
|
||||||
|
from reno import loader
|
||||||
from reno.tests import base
|
from reno.tests import base
|
||||||
|
|
||||||
from oslotest import mockpatch
|
import mock
|
||||||
|
|
||||||
|
|
||||||
class TestFormatter(base.TestCase):
|
class TestFormatter(base.TestCase):
|
||||||
|
@ -30,19 +29,20 @@ class TestFormatter(base.TestCase):
|
||||||
versions = ['0.0.0', '1.0.0']
|
versions = ['0.0.0', '1.0.0']
|
||||||
|
|
||||||
note_bodies = {
|
note_bodies = {
|
||||||
'note1': textwrap.dedent("""
|
'note1': {
|
||||||
prelude: >
|
'prelude': 'This is the prelude.',
|
||||||
This is the prelude.
|
},
|
||||||
"""),
|
'note2': {
|
||||||
'note2': textwrap.dedent("""
|
'issues': [
|
||||||
issues:
|
'This is the first issue.',
|
||||||
- This is the first issue.
|
'This is the second issue.',
|
||||||
- This is the second issue.
|
],
|
||||||
"""),
|
},
|
||||||
'note3': textwrap.dedent("""
|
'note3': {
|
||||||
features:
|
'features': [
|
||||||
- We added a feature!
|
'We added a feature!',
|
||||||
""")
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_note_body(self, reporoot, filename, sha):
|
def _get_note_body(self, reporoot, filename, sha):
|
||||||
|
@ -50,15 +50,26 @@ class TestFormatter(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestFormatter, self).setUp()
|
super(TestFormatter, self).setUp()
|
||||||
self.useFixture(
|
|
||||||
mockpatch.Patch('reno.scanner.get_file_at_commit',
|
def _load(ldr):
|
||||||
new=self._get_note_body)
|
ldr._scanner_output = self.scanner_output
|
||||||
)
|
ldr._cache = {
|
||||||
|
'file-contents': self.note_bodies
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch('reno.loader.Loader._load_data', _load):
|
||||||
|
self.ldr = loader.Loader(
|
||||||
|
reporoot='reporoot',
|
||||||
|
notesdir='notesdir',
|
||||||
|
branch=None,
|
||||||
|
collapse_pre_releases=None,
|
||||||
|
earliest_version=None,
|
||||||
|
ignore_cache=False,
|
||||||
|
)
|
||||||
|
|
||||||
def test_with_title(self):
|
def test_with_title(self):
|
||||||
result = formatter.format_report(
|
result = formatter.format_report(
|
||||||
reporoot=None,
|
loader=self.ldr,
|
||||||
scanner_output=self.scanner_output,
|
|
||||||
versions_to_include=self.versions,
|
versions_to_include=self.versions,
|
||||||
title='This is the title',
|
title='This is the title',
|
||||||
)
|
)
|
||||||
|
@ -66,8 +77,7 @@ class TestFormatter(base.TestCase):
|
||||||
|
|
||||||
def test_versions(self):
|
def test_versions(self):
|
||||||
result = formatter.format_report(
|
result = formatter.format_report(
|
||||||
reporoot=None,
|
loader=self.ldr,
|
||||||
scanner_output=self.scanner_output,
|
|
||||||
versions_to_include=self.versions,
|
versions_to_include=self.versions,
|
||||||
title='This is the title',
|
title='This is the title',
|
||||||
)
|
)
|
||||||
|
@ -76,8 +86,7 @@ class TestFormatter(base.TestCase):
|
||||||
|
|
||||||
def test_without_title(self):
|
def test_without_title(self):
|
||||||
result = formatter.format_report(
|
result = formatter.format_report(
|
||||||
reporoot=None,
|
loader=self.ldr,
|
||||||
scanner_output=self.scanner_output,
|
|
||||||
versions_to_include=self.versions,
|
versions_to_include=self.versions,
|
||||||
title=None,
|
title=None,
|
||||||
)
|
)
|
||||||
|
@ -85,8 +94,7 @@ class TestFormatter(base.TestCase):
|
||||||
|
|
||||||
def test_section_order(self):
|
def test_section_order(self):
|
||||||
result = formatter.format_report(
|
result = formatter.format_report(
|
||||||
reporoot=None,
|
loader=self.ldr,
|
||||||
scanner_output=self.scanner_output,
|
|
||||||
versions_to_include=self.versions,
|
versions_to_include=self.versions,
|
||||||
title=None,
|
title=None,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue