Source code for generate_changelog.cli

"""Command line interface for generate_changelog."""
import functools
import json
from enum import Enum
from pathlib import Path
from typing import Callable, Optional

import typer
from git import Repo

from generate_changelog.commits import get_context_from_tags
from generate_changelog.configuration import DEFAULT_CONFIG_FILE_NAMES, Configuration, write_default_config
from generate_changelog.release_hint import suggest_release_type

app = typer.Typer()


[docs] class OutputOption(str, Enum): """Types of output available.""" release_hint = "release-hint" notes = "notes" all = "all"
[docs] def version_callback(value: bool) -> None: """Display the version and exit.""" import generate_changelog if value: typer.echo(generate_changelog.__version__) raise typer.Exit()
[docs] def generate_config_callback(value: bool) -> None: """Generate a default configuration file.""" if not value: # pragma: no cover return f = Path.cwd() / Path(DEFAULT_CONFIG_FILE_NAMES[0]) file_path = f.expanduser().resolve() if file_path.exists(): overwrite = typer.confirm(f"{file_path} already exists. Overwrite it?") if not overwrite: typer.echo("Aborting configuration file generation.") typer.Abort() write_default_config(f) typer.echo(f"The configuration file was written to {f}.") raise typer.Exit()
[docs] @app.command() def main( version: Optional[bool] = typer.Option( None, "--version", help="Show program's version number and exit", callback=version_callback, is_eager=True ), generate_config: Optional[bool] = typer.Option( None, "--generate-config", help="Generate a default configuration file", callback=generate_config_callback, ), config_file: Optional[Path] = typer.Option( None, "--config", "-c", help="Path to the config file.", envvar="CHANGELOG_CONFIG_FILE" ), repository_path: Optional[Path] = typer.Option( None, "--repo-path", "-r", help="Path to the repository, if not within the current directory" ), starting_tag: Optional[str] = typer.Option(None, "--starting-tag", "-t", help="Tag to generate a changelog from."), output: Optional[OutputOption] = typer.Option(None, "--output", "-o", help="What output to generate."), skip_output_pipeline: bool = typer.Option( False, "--skip-output-pipeline", help="Do not execute the output pipeline in the configuration." ), branch_override: Optional[str] = typer.Option( None, "--branch-override", "-b", help="Override the current branch for release hint decisions." ), ) -> None: """Generate a change log from git commits.""" from generate_changelog import templating from generate_changelog.pipeline import pipeline_factory echo_func = functools.partial(echo, quiet=bool(output)) config = get_user_config(config_file, echo_func) if repository_path: # pragma: no cover repository = Repo(repository_path) else: repository = Repo(search_parent_directories=True) if repository.head.is_detached: current_branch = repository.head else: current_branch = repository.active_branch # get starting tag based configuration if not passed in if not starting_tag and config.starting_tag_pipeline: start_tag_pipeline = pipeline_factory(config.starting_tag_pipeline, **config.variables) starting_tag = start_tag_pipeline.run() if not starting_tag: echo_func("No starting tag found. Generating entire change log.") else: echo_func(f"Generating change log from tag: '{starting_tag}'.") version_contexts = get_context_from_tags(repository, config, starting_tag) branch_name = branch_override or current_branch.name release_hint = suggest_release_type(branch_name, version_contexts, config) # use the output pipeline to deal with the rendered change log. has_starting_tag = bool(starting_tag) rendered_chglog = templating.render_changelog(version_contexts, config, has_starting_tag) if not skip_output_pipeline: echo_func("Executing output pipeline.") output_pipeline = pipeline_factory(config.output_pipeline, **config.variables) output_pipeline.run(rendered_chglog.full) if output == OutputOption.release_hint: typer.echo(release_hint) elif output == OutputOption.notes: if rendered_chglog.notes: typer.echo(rendered_chglog.notes) else: typer.echo(rendered_chglog.full) elif output == OutputOption.all: notes = rendered_chglog.notes or rendered_chglog.full out = {"release_hint": release_hint, "notes": notes} typer.echo(json.dumps(out)) else: typer.echo("Done.") raise typer.Exit()
[docs] def get_user_config(config_file: Optional[Path], echo_func: Callable) -> Configuration: """ Get the default configuration and update it with the user's config file. Args: config_file: The path to the user's configuration file echo_func: The function to call to echo output Returns: The configuration object """ from generate_changelog.configuration import get_config config = get_config() user_config = config_file or next( (Path.cwd() / Path(name) for name in DEFAULT_CONFIG_FILE_NAMES if (Path.cwd() / Path(name)).exists()), None ) if user_config: echo_func(f"Using configuration file: {user_config}") config.update_from_file(user_config) else: echo_func("No configuration file found. Using default configuration.") return config
[docs] def echo(message: str, quiet: bool = False) -> None: """ Display a message to the user. Args: message: The message to send to the user quiet: Do it quietly """ if not quiet: typer.echo(message)
typer_click_object = typer.main.get_command(app) if __name__ == "__main__": app()