diff --git a/pyproject.toml b/pyproject.toml index fb49d01..d3ff801 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ pyyaml = "^6.0.2" pydantic = "^2.11.3" rich = "^14.0.0" gitpython = "^3.1.44" +typer = "^0.16.0" [tool.poetry.group.dev.dependencies] twine = "^6.1.0" diff --git a/src/hdlbuild/cli.py b/src/hdlbuild/cli.py index 394a5cc..a0a0a19 100644 --- a/src/hdlbuild/cli.py +++ b/src/hdlbuild/cli.py @@ -1,32 +1,32 @@ -import argparse +import typer from importlib.metadata import version, PackageNotFoundError -from hdlbuild.commands import register_commands -def get_version(): +from hdlbuild.commands.build import cli as build_cli +from hdlbuild.commands.clean import cli as clean_cli +from hdlbuild.commands.dep import cli as dep_cli +from hdlbuild.commands.test import cli as test_cli +from hdlbuild.commands.init import cli as init_cli + +def get_version() -> str: try: - return version("hdlbuild") # Paketname aus pyproject.toml + return version("hdlbuild") except PackageNotFoundError: return "unknown" +app = typer.Typer( + rich_help_panel="ℹ️ HDLBuild – FPGA‑Build‑Tool", + help=f"hdlbuild v{get_version()} – Build‑Management for FPGA projects" +) + +# Unter‑Kommandos registrieren (entspricht add_subparsers) +app.add_typer(build_cli, name="build", help="Build the project") +app.add_typer(clean_cli, name="clean", help="Clean build artifacts") +app.add_typer(dep_cli, name="dep", help="Resolve dependencies") +app.add_typer(test_cli, name="test", help="Run simulations/testbenches") +app.add_typer(init_cli, name="init", help="Initialize project") + def main(): - version_str = get_version() - parser = argparse.ArgumentParser( - description=f"hdlbuild v{version_str} - Build management tool for FPGA projects", - formatter_class=argparse.RawTextHelpFormatter - ) - - subparsers = parser.add_subparsers( - title="Commands", - description="Available commands", - dest="command", - required=True - ) - - # Register all commands - register_commands(subparsers) - - args = parser.parse_args() - args.func(args) + app() if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/hdlbuild/commands/__init__.py b/src/hdlbuild/commands/__init__.py deleted file mode 100644 index e754fc7..0000000 --- a/src/hdlbuild/commands/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .commands import register_commands \ No newline at end of file diff --git a/src/hdlbuild/commands/build.py b/src/hdlbuild/commands/build.py index 373c471..d896b15 100644 --- a/src/hdlbuild/commands/build.py +++ b/src/hdlbuild/commands/build.py @@ -1,30 +1,34 @@ -from hdlbuild.tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth -from hdlbuild.utils.console_utils import ConsoleUtils -from hdlbuild.utils.directory_manager import ensure_directories_exist -from hdlbuild.utils.project_loader import load_project_config +import typer -class BuildCommand: - def __init__(self): - self.console_utils = ConsoleUtils("hdlbuild") +from hdlbuild.tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth +from hdlbuild.utils.console_utils import ConsoleUtils +from hdlbuild.utils.directory_manager import ensure_directories_exist +from hdlbuild.utils.project_loader import load_project_config - def register(self, subparsers): - parser = subparsers.add_parser("build", help="Start the build process") - parser.add_argument( - "target", - nargs="?", - choices=["synth"], - help="Specify 'synth' to only synthesize the design (optional)" - ) - parser.set_defaults(func=self.execute) +cli = typer.Typer(rich_help_panel="🔨 Build Commands") - def execute(self, args): - """Starts the build process.""" - self.project = load_project_config() - if args.target == "synth": - self.console_utils.print("Starting synth process...") - ensure_directories_exist(True) - xilinx_ise_synth(self.project) - else: - self.console_utils.print("Starting build process...") - ensure_directories_exist(True) - xilinx_ise_all(self.project) \ No newline at end of file +@cli.callback(invoke_without_command=True) +def build( + target: str = typer.Argument( + None, + help="Optional: 'synth' to run synthesis only", + show_default=False, + rich_help_panel="🔨 Build Commands", + ) +) -> None: + """ + Run the full build flow or synthesis only. + + * `hdlbuild build` → full flow + * `hdlbuild build synth` → synthesis only + """ + console = ConsoleUtils("hdlbuild") + project = load_project_config() + + ensure_directories_exist(True) + if target == "synth": + console.print("Starting synthesis …") + xilinx_ise_synth(project) + else: + console.print("Starting full build …") + xilinx_ise_all(project) diff --git a/src/hdlbuild/commands/clean.py b/src/hdlbuild/commands/clean.py index 3e8e43a..7208b5a 100644 --- a/src/hdlbuild/commands/clean.py +++ b/src/hdlbuild/commands/clean.py @@ -1,27 +1,35 @@ -from hdlbuild.utils.console_utils import ConsoleUtils -from hdlbuild.utils.directory_manager import clear_build_directories, clear_directories +import typer -class CleanCommand: - def __init__(self): - self.console_utils = ConsoleUtils("hdlbuild") +from hdlbuild.utils.console_utils import ConsoleUtils +from hdlbuild.utils.directory_manager import clear_build_directories, clear_directories - def register(self, subparsers): - parser = subparsers.add_parser("clean", help="Clean build artifacts") - parser.add_argument( - "target", - nargs="?", - choices=["all"], - help="Specify 'all' to clean everything (optional)" - ) - parser.set_defaults(func=self.execute) +cli = typer.Typer(rich_help_panel="🧹 Clean Commands") - def execute(self, args): - """Cleans the build artifacts.""" - if args.target == "all": - self.console_utils.print("Starting clean all process...") - clear_directories() - self.console_utils.print("All cleaned.") - else: - self.console_utils.print("Clearing build artifacts...") - clear_build_directories() - self.console_utils.print("Build artifacts cleaned.") \ No newline at end of file +@cli.callback(invoke_without_command=True) +def clean( + target: str = typer.Argument( + None, + help="Optional: 'all' → wipe *all* artefacts, otherwise only the build directory", + show_default=False, + ) +) -> None: + """ + Remove build artefacts (`build/*`) or *everything* (`all`). + + Examples + -------- + ```bash + hdlbuild clean # build/* and temporary files only + hdlbuild clean all # also caches, logs, etc. + ``` + """ + console = ConsoleUtils("hdlbuild") + + if target == "all": + console.print("Starting clean‑all …") + clear_directories() + console.print("All artefacts removed.") + else: + console.print("Removing build artefacts …") + clear_build_directories() + console.print("Build artefacts removed.") diff --git a/src/hdlbuild/commands/commands.py b/src/hdlbuild/commands/commands.py deleted file mode 100644 index 80d1472..0000000 --- a/src/hdlbuild/commands/commands.py +++ /dev/null @@ -1,19 +0,0 @@ -from hdlbuild.commands.build import BuildCommand -from hdlbuild.commands.clean import CleanCommand -from hdlbuild.commands.dep import DepCommand -from hdlbuild.commands.init import InitCommand -from hdlbuild.commands.test import TestCommand - - -def register_commands(subparsers): - """Registers all available commands.""" - commands = [ - CleanCommand(), - BuildCommand(), - DepCommand(), - TestCommand(), - InitCommand(), - ] - - for command in commands: - command.register(subparsers) \ No newline at end of file diff --git a/src/hdlbuild/commands/dep.py b/src/hdlbuild/commands/dep.py index c0930d3..e9945cb 100644 --- a/src/hdlbuild/commands/dep.py +++ b/src/hdlbuild/commands/dep.py @@ -1,17 +1,23 @@ -from hdlbuild.dependencies.resolver import DependencyResolver -from hdlbuild.utils.console_utils import ConsoleUtils -from hdlbuild.utils.project_loader import load_project_config +import typer -class DepCommand: - def __init__(self): - self.console_utils = ConsoleUtils("hdlbuild") +from hdlbuild.dependencies.resolver import DependencyResolver +from hdlbuild.utils.console_utils import ConsoleUtils +from hdlbuild.utils.project_loader import load_project_config - def register(self, subparsers): - parser = subparsers.add_parser("dep", help="Start the dependencies process") - parser.set_defaults(func=self.execute) +cli = typer.Typer(rich_help_panel="🔗 Dependency Commands") - def execute(self, args): - """Starts the dependencies process.""" - self.project = load_project_config() - self.console_utils.print("Starting dependencies process...") - DependencyResolver(self.project).resolve_all() \ No newline at end of file +@cli.callback(invoke_without_command=True) +def dep() -> None: + """ + Resolve all project dependencies. + + ```bash + hdlbuild dep + ``` + """ + console = ConsoleUtils("hdlbuild") + project = load_project_config() + + console.print("Resolving dependencies …") + DependencyResolver(project).resolve_all() + console.print("Dependencies resolved.") diff --git a/src/hdlbuild/commands/init.py b/src/hdlbuild/commands/init.py index 607bd66..c66a98d 100644 --- a/src/hdlbuild/commands/init.py +++ b/src/hdlbuild/commands/init.py @@ -1,36 +1,35 @@ -from pathlib import Path import shutil -from hdlbuild.dependencies.resolver import DependencyResolver +from pathlib import Path +import typer + from hdlbuild.utils.console_utils import ConsoleUtils -class InitCommand: - def __init__(self): - self.console_utils = ConsoleUtils("hdlbuild") +cli = typer.Typer(rich_help_panel="🆕 Init Commands") - def register(self, subparsers): - parser = subparsers.add_parser("init", help="Initialize a new HDLBuild project") - parser.set_defaults(func=self.execute) +@cli.callback(invoke_without_command=True) +def init() -> None: + """ + Initialise a new HDLBuild project in the current directory. - def execute(self, args): - """Initialize a new HDLBuild project.""" - project_dir = Path.cwd() + Copies `.gitignore` and `project.yml` from the template folder. + """ + console = ConsoleUtils("hdlbuild") + project_dir = Path.cwd() - # Correctly resolve path to templates directory - script_dir = Path(__file__).parent.resolve() - template_dir = (script_dir / ".." / "templates").resolve() + script_dir = Path(__file__).parent.resolve() + template_dir = (script_dir / ".." / "templates").resolve() - # Files to copy - files = [ - ("gitignore.template", ".gitignore"), - ("project.yml.template", "project.yml"), - ] + files = [ + ("gitignore.template", ".gitignore"), + ("project.yml.template", "project.yml"), + ] - for template_name, target_name in files: - template_path = template_dir / template_name - target_path = project_dir / target_name + for template_name, target_name in files: + template_path = template_dir / template_name + target_path = project_dir / target_name - if not target_path.exists(): - shutil.copy(template_path, target_path) - self.console_utils.print(f"Created {target_name}") - else: - self.console_utils.print(f"{target_name} already exists, skipping.") + if not target_path.exists(): + shutil.copy(template_path, target_path) + console.print(f"Created {target_name}") + else: + console.print(f"{target_name} already exists – skipping.") diff --git a/src/hdlbuild/commands/test.py b/src/hdlbuild/commands/test.py index a198617..a9c9ef0 100644 --- a/src/hdlbuild/commands/test.py +++ b/src/hdlbuild/commands/test.py @@ -1,23 +1,31 @@ -from hdlbuild.tools.xilinx_ise.isim import build_testbench, run_testbench -from hdlbuild.utils.console_utils import ConsoleUtils -from hdlbuild.utils.project_loader import load_project_config +import typer -class TestCommand: - def __init__(self): - self.console_utils = ConsoleUtils("hdlbuild") +from hdlbuild.tools.xilinx_ise.isim import build_testbench, run_testbench +from hdlbuild.utils.console_utils import ConsoleUtils +from hdlbuild.utils.project_loader import load_project_config - def register(self, subparsers): - parser = subparsers.add_parser("test", help="Start the Tests process") - parser.add_argument( - "target", - nargs="?", - help="Select the target to test" - ) - parser.set_defaults(func=self.execute) +cli = typer.Typer(rich_help_panel="🧪 Test Commands") - def execute(self, args): - """Starts the test process.""" - self.project = load_project_config() - self.console_utils.print("Starting test process...") - build_testbench(self.project, args.target) - run_testbench(self.project, args.target) \ No newline at end of file +@cli.callback(invoke_without_command=True) +def test( + target: str = typer.Argument( + None, + help="Name of the test target (leave empty to run all)", + show_default=False, + ) +) -> None: + """ + Build and run testbenches. + + ```bash + hdlbuild test # run all TBs + hdlbuild test alu # run TB 'alu' only + ``` + """ + console = ConsoleUtils("hdlbuild") + project = load_project_config() + + console.print("Starting test flow …") + build_testbench(project, target) + run_testbench(project, target) + console.print("Tests finished.")