from __future__ import annotations
import importlib
import logging
from enum import Enum
# from typing import TYPE_CHECKING
import click
from rich.console import Console
from rich.logging import RichHandler
import semantic_release
from semantic_release import globals
from semantic_release.cli.cli_context import CliContextObj
from semantic_release.cli.config import GlobalCommandLineOptions
from semantic_release.cli.const import DEFAULT_CONFIG_FILE
from semantic_release.cli.util import rprint
# if TYPE_CHECKING:
# pass
FORMAT = "[%(module)s.%(funcName)s] %(message)s"
[docs]
class Cli(click.MultiCommand):
"""Root MultiCommand for the semantic-release CLI"""
[docs]
class SubCmds(Enum):
"""Subcommand import definitions"""
# SUBCMD_FUNCTION_NAME => MODULE_WITH_FUNCTION
CHANGELOG = f"{__package__}.changelog"
GENERATE_CONFIG = f"{__package__}.generate_config"
VERSION = f"{__package__}.version"
PUBLISH = f"{__package__}.publish"
[docs]
def list_commands(self, _ctx: click.Context) -> list[str]:
# Used for shell-completion
return [subcmd.lower().replace("_", "-") for subcmd in Cli.SubCmds.__members__]
[docs]
def get_command(self, _ctx: click.Context, name: str) -> click.Command | None:
subcmd_name = name.lower().replace("-", "_")
try:
subcmd_def: Cli.SubCmds = Cli.SubCmds.__dict__[subcmd_name.upper()]
module_path = subcmd_def.value
subcmd_module = importlib.import_module(module_path)
return getattr(subcmd_module, subcmd_name)
except (KeyError, ModuleNotFoundError, AttributeError):
return None
@click.command(
cls=Cli,
context_settings={
"help_option_names": ["-h", "--help"],
},
)
@click.version_option(
version=semantic_release.__version__,
prog_name="semantic-release",
help="Show the version of semantic-release and exit",
)
@click.option(
"-c",
"--config",
"config_file",
default=DEFAULT_CONFIG_FILE,
help="Specify a configuration file for semantic-release to use",
type=click.Path(),
)
@click.option("--noop", "noop", is_flag=True, help="Run semantic-release in no-op mode")
@click.option(
"-v",
"--verbose",
"verbosity",
help="Set logging verbosity",
default=0,
count=True,
show_default=True,
type=click.IntRange(0, 2, clamp=True),
)
@click.option(
"--strict",
"strict",
is_flag=True,
default=False,
help="Enable strict mode",
)
@click.pass_context
def main(
ctx: click.Context,
config_file: str = DEFAULT_CONFIG_FILE,
verbosity: int = 0,
noop: bool = False,
strict: bool = False,
) -> None:
"""
Python Semantic Release
Automated Semantic Versioning based on version 2.0.0 of the Semantic Versioning
specification, which can be found at https://semver.org/spec/v2.0.0.html.
Detect the next semantically correct version for a project based on the Git
history, create and publish a changelog to a remote VCS, build a project.
For more information, visit https://python-semantic-release.readthedocs.io/
"""
console = Console(stderr=True)
log_level = [logging.WARNING, logging.INFO, logging.DEBUG][verbosity]
logging.basicConfig(
level=log_level,
format=FORMAT,
datefmt="[%X]",
handlers=[
RichHandler(
console=console, rich_tracebacks=True, tracebacks_suppress=[click]
),
],
)
logger = logging.getLogger(__name__)
logger.debug("logging level set to: %s", logging.getLevelName(log_level))
if log_level == logging.DEBUG:
globals.debug = True
if noop:
rprint(
":shield: [bold cyan]You are running in no-operation mode, because the "
"'--noop' flag was supplied"
)
cli_options = GlobalCommandLineOptions(
noop=noop, verbosity=verbosity, config_file=config_file, strict=strict
)
logger.debug("global cli options: %s", cli_options)
ctx.obj = CliContextObj(ctx, logger, cli_options)