Skip to content

Commit

Permalink
Changed framework from Typer to Click
Browse files Browse the repository at this point in the history
  • Loading branch information
coordt committed Nov 24, 2024
1 parent 06d7a45 commit f08d922
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 106 deletions.
5 changes: 1 addition & 4 deletions generate_changelog/_attr_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ def attribute_docstrings(obj: type) -> dict:
nodes = list(ast_class.body)
docstrings = {}

for (
a,
b,
) in pairs(nodes):
for a, b in pairs(nodes):
if isinstance(a, ast.AnnAssign) and isinstance(a.target, ast.Name) and a.simple:
name = a.target.id
elif isinstance(a, ast.Assign) and len(a.targets) == 1 and isinstance(a.targets[0], ast.Name):
Expand Down
7 changes: 3 additions & 4 deletions generate_changelog/actions/file_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from dataclasses import dataclass
from pathlib import Path

import typer
import rich_click as click

from generate_changelog.actions import register_builtin
from generate_changelog.configuration import StrOrCallable
Expand All @@ -30,8 +30,7 @@ def __call__(self, *args, **kwargs) -> str:
filepath.touch()

if not filepath.exists():
typer.echo(f"The file '{filepath}' does not exist.", err=True)
raise typer.Exit(1)
raise click.UsageError(f"The file '{filepath}' does not exist.")

return filepath.read_text() or ""

Expand All @@ -55,7 +54,7 @@ def __call__(self, input_text: StrOrCallable) -> StrOrCallable:
@register_builtin
def stdout(content: str) -> str:
"""Write content to stdout."""
typer.echo(content)
click.echo(content)
return content


Expand Down
5 changes: 4 additions & 1 deletion generate_changelog/actions/matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ class MetadataMatch:
operator: in
value: ["fix", "refactor", "update"]
Valid operators: ``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``is``, ``is not``, ``in``, ``not in``
Valid operators: `==`, `!=`, `<`, `>`, `>=`, `<=`, `is`, `is not`, `in`, `not in`
Attributes:
operator_map: A map of operator names to operators
Args:
attribute: The name of the metadata key whose value will be evaluated
Expand Down
2 changes: 1 addition & 1 deletion generate_changelog/actions/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def bash(script: str, environment: Optional[dict] = None) -> str:

command = ["bash", "--noprofile", "--norc", "-eo", "pipefail", script_path]

result = subprocess.run(
result = subprocess.run( # NOQA: S603
command,
env=environment,
encoding="utf-8",
Expand Down
146 changes: 65 additions & 81 deletions generate_changelog/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,79 @@

import functools
import json
from enum import Enum
from pathlib import Path
from typing import Callable, Optional

import typer
import rich_click as click
from click.core import Context, Parameter
from git import Repo

from generate_changelog import __version__
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()


class OutputOption(str, Enum):
"""Types of output available."""

release_hint = "release-hint"
notes = "notes"
all = "all"


def version_callback(value: bool) -> None:
"""Display the version and exit."""
import generate_changelog

if value:
typer.echo(generate_changelog.__version__)
raise typer.Exit()


def generate_config_callback(value: bool) -> None:
def generate_config_callback(ctx: Context, param: Parameter, 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?")
overwrite = click.confirm(f"{file_path} already exists. Overwrite it?")
if not overwrite:
typer.echo("Aborting configuration file generation.")
typer.Abort()
click.echo("Aborting configuration file generation.")
click.Abort()
write_default_config(f)
typer.echo(f"The configuration file was written to {f}.")
raise typer.Exit()


@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."
),
click.echo(f"The configuration file was written to {f}.")
ctx.exit()


@click.command(
context_settings={
"help_option_names": ["-h", "--help"],
},
add_help_option=True,
)
@click.option(
"--generate-config",
is_flag=True,
help="Generate a default configuration file",
callback=generate_config_callback,
is_eager=True,
expose_value=False,
)
@click.option(
"--config",
"-c",
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
help="Path to the config file.",
envvar="CHANGELOG_CONFIG_FILE",
)
@click.option("--repo-path", "-r", help="Path to the repository, if not within the current directory")
@click.option("--starting-tag", "-t", help="Tag to generate a changelog from.")
@click.option("--output", "-o", type=click.Choice(["release-hint", "notes", "all"]), help="What output to generate.")
@click.option("--skip-output-pipeline", is_flag=True, help="Do not execute the output pipeline in the configuration.")
@click.option("--branch-override", "-b", help="Override the current branch for release hint decisions.")
@click.version_option(version=__version__)
def cli(
config: Optional[Path],
repo_path: Optional[Path],
starting_tag: Optional[str],
output: Optional[str],
skip_output_pipeline: bool,
branch_override: Optional[str],
) -> 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)
configuration = get_user_config(config, echo_func)

if repository_path: # pragma: no cover
repository = Repo(repository_path)
if repo_path: # pragma: no cover
repository = Repo(repo_path)
else:
repository = Repo(search_parent_directories=True)

Expand All @@ -92,45 +83,45 @@ def main(
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)
# get starting tag based on configuration if not passed in
if not starting_tag and configuration.starting_tag_pipeline:
start_tag_pipeline = pipeline_factory(configuration.starting_tag_pipeline, **configuration.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)
version_contexts = get_context_from_tags(repository, configuration, starting_tag)

branch_name = branch_override or current_branch.name
release_hint = suggest_release_type(branch_name, version_contexts, config)
release_hint = suggest_release_type(branch_name, version_contexts, configuration)

# 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)
rendered_chglog = templating.render_changelog(version_contexts, configuration, has_starting_tag)

if not skip_output_pipeline:
echo_func("Executing output pipeline.")
output_pipeline = pipeline_factory(config.output_pipeline, **config.variables)
output_pipeline = pipeline_factory(configuration.output_pipeline, **configuration.variables)
output_pipeline.run(rendered_chglog.full)

if output == OutputOption.release_hint:
typer.echo(release_hint)
elif output == OutputOption.notes:
if output == "release_hint":
click.echo(release_hint)
elif output == "notes":
if rendered_chglog.notes:
typer.echo(rendered_chglog.notes)
click.echo(rendered_chglog.notes)
else:
typer.echo(rendered_chglog.full)
elif output == OutputOption.all:
click.echo(rendered_chglog.full)
elif output == "all":
notes = rendered_chglog.notes or rendered_chglog.full
out = {"release_hint": release_hint, "notes": notes}
typer.echo(json.dumps(out))
click.echo(json.dumps(out))
else:
typer.echo("Done.")
click.echo("Done.")

raise typer.Exit()
# raise click.Exit()


def get_user_config(config_file: Optional[Path], echo_func: Callable) -> Configuration:
Expand Down Expand Up @@ -167,11 +158,4 @@ def echo(message: str, quiet: bool = False) -> None:
quiet: Do it quietly
"""
if not quiet:
typer.echo(message)


typer_click_object = typer.main.get_command(app)


if __name__ == "__main__":
app()
click.echo(message)
12 changes: 5 additions & 7 deletions generate_changelog/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from dataclasses import asdict, dataclass, field
from pathlib import Path

import typer
import rich_click as click
from ruamel.yaml import YAML

yaml = YAML()
Expand Down Expand Up @@ -244,17 +244,15 @@ def update_from_file(self, filename: Path) -> None:
filename: Path to the YAML file
Raises:
Exit: if the path does not exist or is a directory
click.UsageError: if the path does not exist or is a directory
"""
file_path = filename.expanduser().resolve()

if not file_path.exists():
typer.echo(f"'{filename}' does not exist.", err=True)
raise typer.Exit(1)
raise click.UsageError(f"'{filename}' does not exist.")

if not file_path.is_file():
typer.echo(f"'{filename}' is not a file.", err=True)
raise typer.Exit(1)
raise click.UsageError(f"'{filename}' is not a file.")

content = file_path.read_text()
values = yaml.load(content)
Expand Down Expand Up @@ -297,7 +295,7 @@ def write_default_config(filename: Path) -> None:
"""
from ruamel.yaml.comments import CommentedMap

from ._attr_docs import attribute_docstrings
from generate_changelog._attr_docs import attribute_docstrings

file_path = filename.expanduser().resolve()
default_config = get_default_config()
Expand Down
8 changes: 4 additions & 4 deletions generate_changelog/data_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ def deep_merge(*dicts: dict) -> dict:
"""

def merge_into(d1: dict, d2: dict) -> dict:
for key in d2:
for key, val in d2.items():
if key not in d1 or not isinstance(d1[key], dict):
d1[key] = copy.deepcopy(d2[key])
d1[key] = copy.deepcopy(val)
else:
d1[key] = merge_into(d1[key], d2[key])
d1[key] = merge_into(d1[key], val)
return d1

return reduce(merge_into, dicts, {})
Expand Down Expand Up @@ -65,7 +65,7 @@ def comprehensive_merge(*args: Any) -> Any: # NOQA: C901
"""

def merge_into(d1: Any, d2: Any) -> Any:
if type(d1) != type(d2):
if type(d1) is not type(d2):
raise ValueError(f"Cannot merge {type(d2)} into {type(d1)}.")

if isinstance(d1, list):
Expand Down
4 changes: 2 additions & 2 deletions generate_changelog/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_section_pattern() -> str:
Get the version section pattern for the changelog.
Raises:
MissingConfiguration: If the ``starting_tag_pipeline`` configuration is missing or incorrect.
MissingConfigurationError: If the ``starting_tag_pipeline`` configuration is missing or incorrect.
Returns:
The version section pattern.
Expand Down Expand Up @@ -96,7 +96,7 @@ def get_changelog_path() -> Path:
Return the path to the changelog.
Raises:
MissingConfiguration: If the ``starting_tag_pipeline`` configuration is missing or incorrect.
MissingConfigurationError: If the ``starting_tag_pipeline`` configuration is missing or incorrect.
Returns:
The path to the changelog.
Expand Down
5 changes: 3 additions & 2 deletions generate_changelog/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def resolve_name(obj: Any, name: str, default: Any = None) -> Any:
The value at the resolved name or the default value.
Raises:
TypeError, AttributeError: If accessing the property raises one of these exceptions.
TypeError: If accessing the property raises one of these exceptions.
AttributeError: If accessing the property raises one of these exceptions.
"""
lookups = name.split(".")
current = obj
Expand All @@ -110,7 +111,7 @@ def resolve_name(obj: Any, name: str, default: Any = None) -> Any:
): # un-subscript-able object
return default
return current
except Exception: # NOQA: BLE001 pragma: no cover
except Exception: # NOQA: BLE001 pragma: no cover
return default


Expand Down

0 comments on commit f08d922

Please sign in to comment.