From 157f49839f42bf2ec006246b2cf18f833882820c Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Thu, 22 Apr 2021 22:48:48 -0700 Subject: [PATCH] `yaclog show` and `yaclog format` commands --- CHANGELOG.md | 13 ++++++++-- yaclog/changelog.py | 18 +++++++------ yaclog/cli/__main__.py | 57 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a0e62a..dcde008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog + All notable changes to this project will be documented in this file +## Unreleased + +### Added + +- `yaclog` tool for manipulating changelogs from the command line + - `init` command to make a new changelog + - `format` command to reformat the changelog + - `show` command to show changes from the changelog + ## 0.2.0 - 2021-04-19 ### Added @@ -12,8 +22,7 @@ All notable changes to this project will be documented in this file - Updated package metadata - Rewrote parser to use a 2-step method that is more flexible. - Parser can now handle code blocks. - - Parser can now handle setext-style headers and H2s not conforming to the - schema. + - Parser can now handle setext-style headers and H2s not conforming to the schema. ## 0.1.0 - 2021-04-16 diff --git a/yaclog/changelog.py b/yaclog/changelog.py index 464b293..941b8dd 100644 --- a/yaclog/changelog.py +++ b/yaclog/changelog.py @@ -48,7 +48,7 @@ def _join_markdown(segments: List[str]) -> str: text: List[str] = [] last_bullet = False for segment in segments: - is_bullet = bullet_regex.match(segment) and '\n' not in segment + is_bullet = bullet_regex.match(segment) if not is_bullet or not last_bullet: text.append('') @@ -76,7 +76,7 @@ class VersionEntry: for section, entries in self.sections.items(): if section: if md: - segments.append(f'## {section.title()}') + segments.append(f'### {section.title()}') else: segments.append(f'{section.upper()}:') @@ -107,7 +107,7 @@ class VersionEntry: return ' '.join(segments) def text(self, md: bool = True) -> str: - return self.body(md) + '\n\n' + self.header(md) + return self.header(md) + '\n\n' + self.body(md) def __str__(self) -> str: return self.header(False) @@ -115,9 +115,9 @@ class VersionEntry: class Changelog: def __init__(self, path: os.PathLike = None): - self.path = path - self.header = '' - self.versions = [] + self.path: os.PathLike = path + self.header: str = '' + self.versions: List[VersionEntry] = [] self.links = {} if not os.path.exists(path): @@ -256,7 +256,6 @@ class Changelog: # strip whitespace from header self.header = _join_markdown(header_segments) - self.links = self.links def write(self, path: os.PathLike = None): if path is None: @@ -274,7 +273,10 @@ class Changelog: v_links[version.name] = version.link fp.write(version.text()) - fp.write('\n\n') + fp.write('\n') + + if version != self.versions[-1] or len(v_links) > 0: + fp.write('\n') for link_id, link in v_links.items(): fp.write(f'[{link_id.lower()}]: {link}\n') diff --git a/yaclog/cli/__main__.py b/yaclog/cli/__main__.py index 20d5c04..cfcb313 100644 --- a/yaclog/cli/__main__.py +++ b/yaclog/cli/__main__.py @@ -20,33 +20,68 @@ import os.path @click.group() -@click.option('--path', envvar='YACLOG_PATH', default='CHANGELOG.md', - type=click.Path(dir_okay=False, writable=True, readable=True)) +@click.option('--path', envvar='YACLOG_PATH', default='CHANGELOG.md', show_default=True, + type=click.Path(dir_okay=False, writable=True, readable=True), + help='Location for the changelog file') @click.version_option() @click.pass_context def cli(ctx, path): - ctx.obj = path - + """Manipulate markdown changelog files""" if not (ctx.invoked_subcommand == 'init' or os.path.exists(path)): # if the path doesnt exist, then ask if we can create it - if click.confirm(f'Changelog file {path} does not exist. Would you like to create it?'): + if click.confirm(f'Changelog file {path} does not exist. Would you like to create it?', abort=True): ctx.invoke('init') - else: - return + + ctx.obj = yaclog.read(path) -@cli.command('init') +@cli.command() @click.pass_context def init(ctx): + """Create a new changelog file""" path = ctx.parent.params['path'] - if os.path.exists(path) and not click.confirm( - f'Changelog file {path} already exists. Would you like to overwrite it?'): - return + if os.path.exists(path): + click.confirm(f'Changelog file {path} already exists. Would you like to overwrite it?', abort=True) yaclog.Changelog(path).write() print(f'Created new changelog file at {path}') +@cli.command('format') +@click.pass_obj +def reformat(obj: yaclog.Changelog): + """Reformat the changelog file""" + obj.write() + print(f'Reformatted changelog file at {obj.path}') + + +@cli.command(short_help='Show changes from the changelog file') +@click.option('--all', '-a', 'all_versions', is_flag=True, help='show all versions') +@click.argument('versions', type=str, nargs=-1) +@click.pass_obj +def show(obj: yaclog.Changelog, all_versions, versions): + """Show the changes for VERSIONS + + VERSIONS is a list of versions to print. If not given, the most recent version is used + """ + if all_versions: + with open(obj.path, 'r') as fp: + click.echo_via_pager(fp.read()) + else: + if len(versions): + v_list = [] + for v_name in versions: + matches = [v for v in obj.versions if v.name == v_name] + if len(matches) == 0: + raise click.BadArgumentUsage(f'version "{v_name}" not found in changelog') + v_list += matches + else: + v_list = [obj.versions[0]] + + for v in v_list: + click.echo(v.text()) + + if __name__ == '__main__': cli()