diff --git a/chipnut/__init__.py b/chipnut/__init__.py index e69de29..f102a9c 100644 --- a/chipnut/__init__.py +++ b/chipnut/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/chipnut/__main__.py b/chipnut/__main__.py index 05e9774..2ff8abe 100644 --- a/chipnut/__main__.py +++ b/chipnut/__main__.py @@ -1,9 +1,5 @@ from .cli import CLI - -from .config import ConfigError, load_config - -from .commands.setup import is_chipnut_workspace, find_template_conflicts, copy_template_files -from .commands.compiler import compile as compile_project +from .commands import setup def help_text(): return [ @@ -12,96 +8,8 @@ def help_text(): def main() -> int: cli = CLI(help_callback=help_text) - - cli.command( - "init", - help="Create an empty chipnut workspace or reinitialize with --force", - description=( - "Initialize a chipnut development workspace by creating a Nutfile and default project files. " - "If a Nutfile already exists, initialization should fail unless --force is used." - ), - ).flag( - "force", - "f", - help="Reinitialize even when a Nutfile already exists", - ).arg( - "path", - nargs="?", - default=".", - help="Directory to initialize (default: current directory)", - ).handle(cmd_init) - - cli.command( - "build", - help="Compile the project", - description=( - "Compile the project according to the configuration in the Nutfile. " - ), - ).option( - "config", - "c", - default="Nutfile", - help="Path to Nutfile (default: ./Nutfile)", - ).option( - "build-folder", - "o", - default=None, - help="Override output build folder from config", - ).option( - "name", - "n", - default=None, - help="Override output file name from config", - ).handle(cmd_build) - + setup(cli) return cli.run() -def cmd_build(args, flags) -> int: - config_path = flags["config"] - build_folder_override = flags["build_folder"] - file_name_override = flags["name"] - - if config_path == "Nutfile" and not is_chipnut_workspace(): - print("Error: No Nutfile found. Please run 'chipnut init' to create a workspace.") - return 1 - - try: - config = load_config(config_path) - except FileNotFoundError: - print(f"Error: Config file not found: {config_path}") - return 1 - except ConfigError as error: - print(f"Error: Invalid Nutfile: {error}") - return 1 - - try: - compile_project( - config, - build_folder=build_folder_override, - file_name=file_name_override, - ) - except (RuntimeError, OSError) as error: - print(f"Error: Build failed: {error}") - return 1 - - return 0 - -def cmd_init(args, flags) -> int: - dest_dir = args[0] - force = flags["force"] - - conflicts = find_template_conflicts(dest_dir) - if conflicts and not force: - print("Some files already exist that would be overwritten:") - for file in conflicts: - print(f" {file}") - - print("Use --force to overwrite existing files.") - return 1 - - copy_template_files(dest_dir, force=force, checked=True) - - return 0 - if __name__ == "__main__": raise SystemExit(main()) diff --git a/chipnut/commands/__init__.py b/chipnut/commands/__init__.py index e69de29..10576cc 100644 --- a/chipnut/commands/__init__.py +++ b/chipnut/commands/__init__.py @@ -0,0 +1,8 @@ +from ..cli import CLI + +from .setup import setup as cmd_setup +from .compiler import setup as cmd_build + +def setup(cli: CLI): + cmd_setup(cli) + cmd_build(cli) diff --git a/chipnut/commands/compiler.py b/chipnut/commands/compiler.py index 46cdcfd..ed448d7 100644 --- a/chipnut/commands/compiler.py +++ b/chipnut/commands/compiler.py @@ -2,12 +2,14 @@ from dataclasses import dataclass from pathlib import Path from typing import Optional +import os import shutil -from ..config import Config +from ..cli import CLI +from ..config import Config, ConfigError, load_config from ..paths import get_templates_path from ..utils import zip_dir -from ..logger import logger + @dataclass(frozen=True) class CompileContext: @@ -18,28 +20,27 @@ class CompileContext: output_name: str -_ACTIVE_CONTEXT: Optional[CompileContext] = None - class Compiler: + def __init__(self, context: CompileContext): + self.context = context + def run(self) -> None: return None -def get_compile_context() -> CompileContext: - if _ACTIVE_CONTEXT is None: - raise RuntimeError("No active compile context. Call only during compile().") - return _ACTIVE_CONTEXT def _resolve_project_path(project_root: Path, value: Path) -> Path: if value.is_absolute(): return value return (project_root / value).resolve() + def _format_to_filename(name: str) -> str: formatted = name.strip().lower().replace(" ", "_") if not formatted.endswith(".nutsi"): formatted += ".nutsi" return formatted + def _clear_directory(directory: Path) -> None: for child in directory.iterdir(): if child.is_dir(): @@ -47,6 +48,11 @@ def _clear_directory(directory: Path) -> None: else: child.unlink() + +def _is_chipnut_workspace(path: str = ".") -> bool: + return os.path.isfile(os.path.join(path, "Nutfile")) + + def prepare_build_directory(build_dir: Path) -> Path: if build_dir.exists() and not build_dir.is_dir(): raise RuntimeError(f"Build path exists but is not a directory: {build_dir}") @@ -70,6 +76,7 @@ def prepare_build_directory(build_dir: Path) -> Path: return build_dir + def stage_project_sources(project_root: Path, entry_path: Path, build_dir: Path) -> None: if entry_path.is_absolute(): raise RuntimeError( @@ -99,9 +106,9 @@ def stage_project_sources(project_root: Path, entry_path: Path, build_dir: Path) ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.nutsi"), ) + def generate_nutsi_file(build_dir: Path, file_name: str) -> Path: output_path = build_dir / file_name - zip_dir( str(build_dir), str(output_path), @@ -113,6 +120,7 @@ def generate_nutsi_file(build_dir: Path, file_name: str) -> Path: ) return output_path + def _create_context(config: Config, build_folder: Optional[str], file_name: Optional[str]) -> CompileContext: project_root = config.path.parent.resolve() entry_path = Path(config.build.entry) @@ -134,18 +142,66 @@ def _create_context(config: Config, build_folder: Optional[str], file_name: Opti output_name=output_name, ) -def compile(config: Config, build_folder: Optional[str] = None, file_name: Optional[str] = None) -> Path: - global _ACTIVE_CONTEXT +def compile(config: Config, build_folder: Optional[str] = None, file_name: Optional[str] = None) -> Path: context = _create_context(config, build_folder=build_folder, file_name=file_name) prepared_dir = prepare_build_directory(context.build_dir) stage_project_sources(context.project_root, context.entry_path, prepared_dir) + Compiler(context).run() + return generate_nutsi_file(prepared_dir, context.output_name) + + +def main(args, flags) -> int: + config_path = flags["config"] + build_folder_override = flags["build_folder"] + file_name_override = flags["name"] + + if config_path == "Nutfile" and not _is_chipnut_workspace(): + print("Error: No Nutfile found. Please run 'chipnut init' to create a workspace.") + return 1 - _ACTIVE_CONTEXT = context try: - Compiler().run() - finally: - _ACTIVE_CONTEXT = None + config = load_config(config_path) + except FileNotFoundError: + print(f"Error: Config file not found: {config_path}") + return 1 + except ConfigError as error: + print(f"Error: Invalid Nutfile: {error}") + return 1 - archive_path = generate_nutsi_file(prepared_dir, context.output_name) - return archive_path + try: + compile( + config, + build_folder=build_folder_override, + file_name=file_name_override, + ) + except (RuntimeError, OSError) as error: + print(f"Error: Build failed: {error}") + return 1 + + return 0 + + +def setup(cli: CLI): + cli.command( + "build", + help="Compile the project", + description=( + "Compile the project according to the configuration in the Nutfile. " + ), + ).option( + "config", + "c", + default="Nutfile", + help="Path to Nutfile (default: ./Nutfile)", + ).option( + "build-folder", + "o", + default=None, + help="Override output build folder from config", + ).option( + "name", + "n", + default=None, + help="Override output file name from config", + ).handle(main) diff --git a/chipnut/commands/setup.py b/chipnut/commands/setup.py index 467a0cd..9be855b 100644 --- a/chipnut/commands/setup.py +++ b/chipnut/commands/setup.py @@ -1,4 +1,5 @@ from ..paths import get_templates_path +from ..cli import CLI import shutil import os @@ -43,5 +44,38 @@ def copy_template_files(dest_dir: str, force: bool = False, checked: bool = Fals dirs_exist_ok=True ) -def is_chipnut_workspace(path: str = ".") -> bool: - return os.path.isfile(os.path.join(path, "Nutfile")) +def main(args, flags) -> int: + dest_dir = args[0] + force = flags["force"] + + conflicts = find_template_conflicts(dest_dir) + if conflicts and not force: + print("Some files already exist that would be overwritten:") + for file in conflicts: + print(f" {file}") + + print("Use --force to overwrite existing files.") + return 1 + + copy_template_files(dest_dir, force=force, checked=True) + + return 0 + +def setup(cli: CLI): + cli.command( + "init", + help="Create an empty chipnut workspace or reinitialize with --force", + description=( + "Initialize a chipnut development workspace by creating a Nutfile and default project files. " + "If a Nutfile already exists, initialization should fail unless --force is used." + ), + ).flag( + "force", + "f", + help="Reinitialize even when a Nutfile already exists", + ).arg( + "path", + nargs="?", + default=".", + help="Directory to initialize (default: current directory)", + ).handle(main)