Merged in refactor/migrate-to-typer-framework (pull request #65)

refactor: replace global command class to typer functions

Approved-by: Jonathan Teran
This commit is contained in:
JonathanGitFlow
2026-05-16 22:01:32 +00:00
committed by jt
6 changed files with 66 additions and 103 deletions
+3
View File
@@ -9,3 +9,6 @@ dist/
build/ build/
**/__pycache__/ **/__pycache__/
**/*.egg-info/ **/*.egg-info/
# MacOS
.DS_Store
+1 -1
View File
@@ -1 +1 @@
3.10 3.14
+1 -3
View File
@@ -1,4 +1,4 @@
image: python:3.10 image: python:3.14
pipelines: pipelines:
branches: branches:
@@ -10,7 +10,5 @@ pipelines:
script: script:
- pip install uv - pip install uv
- uv run git-flow tag --token=$BEARER - uv run git-flow tag --token=$BEARER
- git status
- git log --oneline -5
- uv build - uv build
- uv publish --token=$PYPI_TOKEN - uv publish --token=$PYPI_TOKEN
+2 -2
View File
@@ -6,9 +6,9 @@ build-backend = "setuptools.build_meta"
name = "git-flow-envs" name = "git-flow-envs"
description = "Git workflow automation to follow best practices when working with multiple environments" description = "Git workflow automation to follow best practices when working with multiple environments"
dynamic = ["version"] dynamic = ["version"]
requires-python = ">= 3.10" requires-python = ">= 3.14"
dependencies = [ dependencies = [
"requests" "requests", "typer"
] ]
authors = [ authors = [
{name = "Alan Facundo Biglieri", email = "abiglieri@renatre.org.ar"}, {name = "Alan Facundo Biglieri", email = "abiglieri@renatre.org.ar"},
+9 -41
View File
@@ -1,6 +1,8 @@
from argparse import Namespace, ArgumentParser from argparse import Namespace, ArgumentParser
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from os.path import isfile from os.path import isfile
import rich
from rich.prompt import Prompt, Confirm
from git_flow import ( from git_flow import (
BRANCH_TYPES, BRANCH_TYPES,
@@ -44,16 +46,16 @@ class Command(ABC):
) )
def success(self, msg: str): def success(self, msg: str):
self._print(TYPE_SUCCESS, msg) rich.print(f":white_check_mark: [green]{msg}[/green]")
def warning(self, msg: str): def warning(self, msg: str):
self._print(TYPE_WARNING, msg) rich.print(f":warning: [yellow]{msg}[/yellow]")
def error(self, msg: str): def error(self, msg: str):
self._print(TYPE_ERROR, msg) rich.print(f":x: [red]{msg}[/red]")
def info(self, msg: str): def info(self, msg: str):
self._print(TYPE_INFO, msg) rich.print(f":information: [blue]{msg}[/blue]")
def prompt( def prompt(
self, self,
@@ -65,11 +67,7 @@ class Command(ABC):
onetime = not persistent onetime = not persistent
while onetime or persistent: while onetime or persistent:
if default: result = Prompt.ask(prompt)
prompt += f" [{default}]"
prompt += ": "
result = input(prompt)
result = result.strip() if strip else result result = result.strip() if strip else result
if result: if result:
@@ -82,40 +80,10 @@ class Command(ABC):
return result return result
def confirm(self, question: str, default: bool = True): def confirm(self, question: str, default: bool = True):
suffix = " [Y/n]: " if default else " [y/N]: " return Confirm.ask(question, default=default)
answer = input(question + suffix)
return default if len(answer) == 0 else answer.startswith("y")
def choice(self, prompt: str, options: list[str]): def choice(self, prompt: str, options: list[str]):
print(prompt) return Prompt.ask(prompt, choices = options)
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
def setup_parser(self, parser: ArgumentParser) -> ArgumentParser: def setup_parser(self, parser: ArgumentParser) -> ArgumentParser:
return parser return parser
+44 -50
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import locale 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 import GitFlowError
from git_flow.command.base import Command from git_flow.command.base import Command
@@ -19,62 +21,56 @@ REPOSITORY_TOKEN_PATH = ".repository-token"
LOCALE = "es_AR.UTF-8" LOCALE = "es_AR.UTF-8"
class GitFlowCommand(Command): app = typer.Typer()
commands: tuple[Command, ...]
def __init__(self, *args: Command) -> None:
self.commands = args
def name(self) -> str: @app.command()
return "git-flow" def init():
run(InitCommand())
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: @app.command()
subparsers = parser.add_subparsers( def new():
title="comandos", dest="command", required=True 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,
) )
for command in self.commands: run(BranchCommand(), args)
subparser = subparsers.add_parser(
command.name(), description=command.description()
)
command.setup_parser(subparser)
return parser
def get_parser(self) -> ArgumentParser: def run(command: Command, args: Namespace = Namespace()):
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() command.init()
return command.run(args)
def main():
try: try:
locale.setlocale(locale.LC_ALL, LOCALE) command.run(args)
except locale.Error as e:
print_warning(f"No se pudo configurar el locale '{LOCALE}': {e}")
command = GitFlowCommand(
InitCommand(),
NewCommand(),
CommitCommand(),
MergeCommand(),
TagCommand(),
ReleaseCommand(),
BranchCommand()
)
try:
command.run()
except GitFlowError as e: except GitFlowError as e:
command.error(str(e)) command.error(str(e))
except Exception as e: except Exception as e:
@@ -83,8 +79,6 @@ def main():
print() print()
command.error("Ejecución abortada") command.error("Ejecución abortada")
return
def main():
if __name__ == "__main__": app()
main()