Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions src/archml/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"""Entry point for the ArchML command-line interface."""

import argparse
import re
import sys
from pathlib import Path

from archml.compiler.build import CompilerError, compile_files
from archml.compiler.build import CompilerError, SourceImportKey, compile_files
from archml.validation.checks import validate
from archml.workspace.config import WorkspaceConfigError, load_workspace_config

Expand Down Expand Up @@ -144,6 +145,13 @@ def _cmd_init(args: argparse.Namespace) -> int:
if not name:
print("Error: mnemonic name cannot be empty.", file=sys.stderr)
return 1
if not re.match(r"^[a-z][a-z0-9_-]*$", name):
print(
f"Error: invalid mnemonic name '{name}': must start with a lowercase letter "
"followed by lowercase letters, digits, hyphens, or underscores.",
file=sys.stderr,
)
return 1

workspace_dir = Path(args.workspace_dir).resolve()
workspace_dir.mkdir(parents=True, exist_ok=True)
Expand All @@ -157,7 +165,7 @@ def _cmd_init(args: argparse.Namespace) -> int:
return 1

workspace_yaml.write_text(
f"build-directory: {_DEFAULT_BUILD_DIR}\nsource-imports:\n - name: {name}\n local-path: .\n",
f"name: {name}\nbuild-directory: {_DEFAULT_BUILD_DIR}\nsource-imports:\n - name: {name}\n local-path: .\n",
encoding="utf-8",
)

Expand Down Expand Up @@ -189,9 +197,6 @@ def _cmd_check(args: argparse.Namespace) -> int:
directory = root
workspace_yaml = directory / ".archml-workspace.yaml"

# The empty-string key represents the workspace root for non-mnemonic imports.
source_import_map: dict[str, Path] = {"": directory}

try:
config = load_workspace_config(workspace_yaml)
except WorkspaceConfigError as exc:
Expand All @@ -201,25 +206,37 @@ def _cmd_check(args: argparse.Namespace) -> int:
build_dir = directory / config.build_directory
sync_dir = directory / config.remote_sync_directory

# Build the source import map: SourceImportKey(repo, mnemonic) -> absolute base path.
# Local mnemonics use config.name as repo; remote repos use "@name".
source_import_map: dict[SourceImportKey, Path] = {}

for imp in config.source_imports:
if isinstance(imp, LocalPathImport):
source_import_map[imp.name] = (directory / imp.local_path).resolve()
source_import_map[SourceImportKey(config.name, imp.name)] = (directory / imp.local_path).resolve()
elif isinstance(imp, GitPathImport):
repo_dir = (sync_dir / imp.name).resolve()
if repo_dir.exists():
source_import_map[f"@{imp.name}"] = repo_dir
remote_workspace_yaml = repo_dir / ".archml-workspace.yaml"
if remote_workspace_yaml.exists():
try:
remote_config = load_workspace_config(remote_workspace_yaml)
for remote_imp in remote_config.source_imports:
if isinstance(remote_imp, LocalPathImport):
mnemonic_path = (repo_dir / remote_imp.local_path).resolve()
source_import_map[f"@{imp.name}/{remote_imp.name}"] = mnemonic_path
source_import_map[SourceImportKey(f"@{imp.name}", remote_imp.name)] = mnemonic_path
except WorkspaceConfigError as exc:
print(f"Warning: could not load workspace config from remote '{imp.name}': {exc}")

archml_files = [f for f in directory.rglob("*.archml") if build_dir not in f.parents and sync_dir not in f.parents]
# Scan only files under local mnemonic paths (repo == config.name, i.e. not remote).
local_mnemonic_paths = {base_path for key, base_path in source_import_map.items() if key.repo == config.name}
seen_files: set[Path] = set()
archml_files: list[Path] = []
for base_path in sorted(local_mnemonic_paths):
for f in base_path.rglob("*.archml"):
if f not in seen_files and build_dir not in f.parents and sync_dir not in f.parents:
seen_files.add(f)
archml_files.append(f)

if not archml_files:
print("No .archml files found in the workspace.")
return 0
Expand Down
Loading