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:
Doug Hellmann 2016-03-07 17:44:25 -05:00
parent 0b459b8337
commit 9cb8c4bf1b
8 changed files with 176 additions and 59 deletions

1
.gitignore vendored
View File

@ -53,3 +53,4 @@ ChangeLog
.*.swp
.*sw?
/cover/
/releasenotes/notes/reno.cache

View File

@ -12,10 +12,6 @@
from __future__ import print_function
from reno import scanner
import yaml
_SECTION_ORDER = [
('features', 'New Features'),
@ -41,7 +37,7 @@ def _indent_for_list(text, prefix=' '):
]) + '\n'
def format_report(reporoot, scanner_output, versions_to_include, title=None):
def format_report(loader, versions_to_include, title=None):
report = []
if 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.
file_contents = {}
for version in versions_to_include:
for filename, sha in scanner_output[version]:
body = scanner.get_file_at_commit(
reporoot,
filename,
sha,
)
y = yaml.safe_load(body)
file_contents[filename] = y
for filename, sha in loader[version]:
body = loader.parse_note_file(filename, sha)
file_contents[filename] = body
for version in versions_to_include:
report.append(version)
@ -67,7 +58,7 @@ def format_report(reporoot, scanner_output, versions_to_include, title=None):
report.append('')
# Add the preludes.
notefiles = scanner_output[version]
notefiles = loader[version]
for n, sha in notefiles:
if 'prelude' in file_contents[n]:
report.append(file_contents[n]['prelude'])

View File

@ -14,7 +14,7 @@ from __future__ import print_function
import logging
from reno import scanner
from reno import loader
from reno import utils
LOG = logging.getLogger(__name__)
@ -26,17 +26,19 @@ def list_cmd(args):
reporoot = args.reporoot.rstrip('/') + '/'
notesdir = utils.get_notes_dir(args)
collapse = args.collapse_pre_releases
notes = scanner.get_notes_by_version(
reporoot, notesdir, args.branch,
ldr = loader.Loader(
reporoot=reporoot,
notesdir=notesdir,
branch=args.branch,
collapse_pre_releases=collapse,
earliest_version=args.earliest_version,
)
if args.version:
versions = args.version
else:
versions = notes.keys()
versions = ldr.versions
for version in versions:
notefiles = notes[version]
notefiles = ldr[version]
print(version)
for n, sha in notefiles:
if n.startswith(reporoot):

109
reno/loader.py Normal file
View File

@ -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)

View File

@ -39,6 +39,10 @@ _query_args = [
(('--earliest-version',),
dict(default=None,
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')),
]

View File

@ -13,7 +13,7 @@
from __future__ import print_function
from reno import formatter
from reno import scanner
from reno import loader
from reno import utils
@ -22,18 +22,19 @@ def report_cmd(args):
reporoot = args.reporoot.rstrip('/') + '/'
notesdir = utils.get_notes_dir(args)
collapse = args.collapse_pre_releases
notes = scanner.get_notes_by_version(
reporoot, notesdir, args.branch,
ldr = loader.Loader(
reporoot=reporoot,
notesdir=notesdir,
branch=args.branch,
collapse_pre_releases=collapse,
earliest_version=args.earliest_version,
)
if args.version:
versions = args.version
else:
versions = notes.keys()
versions = ldr.versions
text = formatter.format_report(
reporoot,
notes,
ldr,
versions,
title='Release Notes',
)

View File

@ -14,7 +14,7 @@ import os.path
from reno import defaults
from reno import formatter
from reno import scanner
from reno import loader
from docutils import nodes
from docutils.parsers import rst
@ -59,8 +59,10 @@ class ReleaseNotesDirective(rst.Directive):
info('scanning %s for %s release notes' %
(os.path.join(reporoot, notesdir), branch or 'current branch'))
notes = scanner.get_notes_by_version(
reporoot, notesdir, branch,
ldr = loader.Loader(
reporoot=reporoot,
notesdir=notesdir,
branch=branch,
collapse_pre_releases=collapse,
earliest_version=earliest_version,
)
@ -70,10 +72,9 @@ class ReleaseNotesDirective(rst.Directive):
for v in version_opt.split(',')
]
else:
versions = notes.keys()
versions = ldr.versions
text = formatter.format_report(
reporoot,
notes,
ldr,
versions,
title=title,
)

View File

@ -12,12 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import textwrap
from reno import formatter
from reno import loader
from reno.tests import base
from oslotest import mockpatch
import mock
class TestFormatter(base.TestCase):
@ -30,19 +29,20 @@ class TestFormatter(base.TestCase):
versions = ['0.0.0', '1.0.0']
note_bodies = {
'note1': textwrap.dedent("""
prelude: >
This is the prelude.
"""),
'note2': textwrap.dedent("""
issues:
- This is the first issue.
- This is the second issue.
"""),
'note3': textwrap.dedent("""
features:
- We added a feature!
""")
'note1': {
'prelude': 'This is the prelude.',
},
'note2': {
'issues': [
'This is the first issue.',
'This is the second issue.',
],
},
'note3': {
'features': [
'We added a feature!',
],
},
}
def _get_note_body(self, reporoot, filename, sha):
@ -50,15 +50,26 @@ class TestFormatter(base.TestCase):
def setUp(self):
super(TestFormatter, self).setUp()
self.useFixture(
mockpatch.Patch('reno.scanner.get_file_at_commit',
new=self._get_note_body)
)
def _load(ldr):
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):
result = formatter.format_report(
reporoot=None,
scanner_output=self.scanner_output,
loader=self.ldr,
versions_to_include=self.versions,
title='This is the title',
)
@ -66,8 +77,7 @@ class TestFormatter(base.TestCase):
def test_versions(self):
result = formatter.format_report(
reporoot=None,
scanner_output=self.scanner_output,
loader=self.ldr,
versions_to_include=self.versions,
title='This is the title',
)
@ -76,8 +86,7 @@ class TestFormatter(base.TestCase):
def test_without_title(self):
result = formatter.format_report(
reporoot=None,
scanner_output=self.scanner_output,
loader=self.ldr,
versions_to_include=self.versions,
title=None,
)
@ -85,8 +94,7 @@ class TestFormatter(base.TestCase):
def test_section_order(self):
result = formatter.format_report(
reporoot=None,
scanner_output=self.scanner_output,
loader=self.ldr,
versions_to_include=self.versions,
title=None,
)