From fec30cd1e838d95034f7cf74b2f7fa8c98a2e00b Mon Sep 17 00:00:00 2001 From: Jonathan Teran Carballo Date: Sat, 16 May 2026 18:59:45 -0300 Subject: [PATCH] refactor: replace global command class to typer functions --- .gitignore | 3 + .python-version | 2 +- bitbucket-pipelines.yml | 4 +- pyproject.toml | 4 +- src/git_flow/command/base.py | 50 +++-------------- src/git_flow/main.py | 106 +++++++++++++++++------------------ 6 files changed, 66 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index 5718d26..a54b6f9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ dist/ build/ **/__pycache__/ **/*.egg-info/ + +# MacOS +.DS_Store \ No newline at end of file diff --git a/.python-version b/.python-version index c8cfe39..6324d40 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10 +3.14 diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 5d48553..f8dffbb 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -1,4 +1,4 @@ -image: python:3.10 +image: python:3.14 pipelines: branches: @@ -10,7 +10,5 @@ pipelines: script: - pip install uv - uv run git-flow tag --token=$BEARER - - git status - - git log --oneline -5 - uv build - uv publish --token=$PYPI_TOKEN diff --git a/pyproject.toml b/pyproject.toml index ca97f52..81cd782 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,9 @@ build-backend = "setuptools.build_meta" name = "git-flow-envs" description = "Git workflow automation to follow best practices when working with multiple environments" dynamic = ["version"] -requires-python = ">= 3.10" +requires-python = ">= 3.14" dependencies = [ - "requests" + "requests", "typer" ] authors = [ {name = "Alan Facundo Biglieri", email = "abiglieri@renatre.org.ar"}, diff --git a/src/git_flow/command/base.py b/src/git_flow/command/base.py index 2e51eee..5d67230 100644 --- a/src/git_flow/command/base.py +++ b/src/git_flow/command/base.py @@ -1,6 +1,8 @@ from argparse import Namespace, ArgumentParser from abc import ABC, abstractmethod from os.path import isfile +import rich +from rich.prompt import Prompt, Confirm from git_flow import ( BRANCH_TYPES, @@ -44,16 +46,16 @@ class Command(ABC): ) def success(self, msg: str): - self._print(TYPE_SUCCESS, msg) + rich.print(f":white_check_mark: [green]{msg}[/green]") def warning(self, msg: str): - self._print(TYPE_WARNING, msg) + rich.print(f":warning: [yellow]{msg}[/yellow]") def error(self, msg: str): - self._print(TYPE_ERROR, msg) + rich.print(f":x: [red]{msg}[/red]") def info(self, msg: str): - self._print(TYPE_INFO, msg) + rich.print(f":information: [blue]{msg}[/blue]") def prompt( self, @@ -65,11 +67,7 @@ class Command(ABC): onetime = not persistent while onetime or persistent: - if default: - prompt += f" [{default}]" - - prompt += ": " - result = input(prompt) + result = Prompt.ask(prompt) result = result.strip() if strip else result if result: @@ -82,40 +80,10 @@ class Command(ABC): return result def confirm(self, question: str, default: bool = True): - suffix = " [Y/n]: " if default else " [y/N]: " - answer = input(question + suffix) - - return default if len(answer) == 0 else answer.startswith("y") + return Confirm.ask(question, default=default) def choice(self, prompt: str, options: list[str]): - print(prompt) - - for i, option in enumerate(options): - print(f"\t{i+1}. {option}") - - selection = None - - while selection is None: - answer = input(f"Seleccione una opción [1-{len(options)}] o escribala: ") - - if answer.isdigit(): - answer = int(answer) - - if 1 <= answer and answer <= len(options): - selection = options[answer - 1] - - if not self.confirm( - f"Seleccionó la opción {answer} ({selection}), ¿es correcto?" - ): - selection = None - else: - self.error(f"La opción {answer} está fuera del rango permitido.") - elif answer in options: - selection = answer - else: - self.error(f"La opción '{answer}' es inválida.") - - return selection + return Prompt.ask(prompt, choices = options) def setup_parser(self, parser: ArgumentParser) -> ArgumentParser: return parser diff --git a/src/git_flow/main.py b/src/git_flow/main.py index 0dadbce..8db1184 100755 --- a/src/git_flow/main.py +++ b/src/git_flow/main.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 import locale -from argparse import Namespace, ArgumentParser +from argparse import Namespace +from typing import Optional +import typer from git_flow import GitFlowError from git_flow.command.base import Command @@ -19,62 +21,56 @@ REPOSITORY_TOKEN_PATH = ".repository-token" LOCALE = "es_AR.UTF-8" -class GitFlowCommand(Command): - commands: tuple[Command, ...] - - def __init__(self, *args: Command) -> None: - self.commands = args - - def name(self) -> str: - return "git-flow" - - def description(self) -> str: - return """Git flow es una herramienta para automatizar un workflow siguiendo conventional commits, conventional branch y semver.""" - - def setup_parser(self, parser: ArgumentParser) -> ArgumentParser: - subparsers = parser.add_subparsers( - title="comandos", dest="command", required=True - ) - - for command in self.commands: - subparser = subparsers.add_parser( - command.name(), description=command.description() - ) - command.setup_parser(subparser) - - return parser - - def get_parser(self) -> ArgumentParser: - parser = ArgumentParser(self.name(), description=self.description()) - - return self.setup_parser(parser) - - def run(self, args: Namespace = Namespace()): - args = self.get_parser().parse_args() - - for command in self.commands: - if command.name() == args.command: - command.init() - return command.run(args) +app = typer.Typer() -def main(): - try: - locale.setlocale(locale.LC_ALL, LOCALE) - except locale.Error as e: - print_warning(f"No se pudo configurar el locale '{LOCALE}': {e}") +@app.command() +def init(): + run(InitCommand()) - command = GitFlowCommand( - InitCommand(), - NewCommand(), - CommitCommand(), - MergeCommand(), - TagCommand(), - ReleaseCommand(), - BranchCommand() + +@app.command() +def new(): + run(NewCommand()) + + +@app.command() +def commit(): + run(CommitCommand()) + + +@app.command() +def merge(): + run(MergeCommand()) + + +@app.command() +def tag(token: Optional[str] = None): + run(TagCommand(), Namespace(token = token)) + + +@app.command() +def release(group: Optional[str] = None): + run(ReleaseCommand(), Namespace(group = group)) + + +@app.command() +def branch(env: Optional[str] = None, trash: bool = False, wip: bool = False, all: bool = False): + args = Namespace( + environment = env, + trash = trash, + wip = wip, + all = all, ) + + run(BranchCommand(), args) + + +def run(command: Command, args: Namespace = Namespace()): + command.init() + try: - command.run() + command.run(args) except GitFlowError as e: command.error(str(e)) except Exception as e: @@ -83,8 +79,6 @@ def main(): print() command.error("Ejecución abortada") - return - -if __name__ == "__main__": - main() +def main(): + app()