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 .*.swp
.*sw? .*sw?
/cover/ /cover/
/releasenotes/notes/reno.cache

View File

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

View File

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

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',), (('--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')),
] ]

View File

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

View File

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

View File

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