Technolgy file archetecture revised with dictionary input method
This commit is contained in:
+130
-45
@@ -1,5 +1,8 @@
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Any, Optional
|
||||
|
||||
import matplotlib
|
||||
|
||||
@@ -7,7 +10,27 @@ matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import nazca as nd
|
||||
|
||||
from generate_handbook import DEFAULT_PACKAGE_ROOT, DEFAULT_SRC_ROOT, public_top_level_members
|
||||
from generate_handbook import DEFAULT_SRC_ROOT
|
||||
|
||||
|
||||
_PRIMITIVE_BUILDER_PATH = Path(__file__).resolve().parent / "tests" / "build_all_primitives.py"
|
||||
_PRIMITIVE_BUILDER_SPEC = importlib.util.spec_from_file_location(
|
||||
"mxpic_build_all_primitives",
|
||||
_PRIMITIVE_BUILDER_PATH,
|
||||
)
|
||||
if _PRIMITIVE_BUILDER_SPEC is None or _PRIMITIVE_BUILDER_SPEC.loader is None:
|
||||
raise ImportError(f"Could not load primitive builder: {_PRIMITIVE_BUILDER_PATH}")
|
||||
|
||||
_primitive_builder = importlib.util.module_from_spec(_PRIMITIVE_BUILDER_SPEC)
|
||||
_PRIMITIVE_BUILDER_SPEC.loader.exec_module(_primitive_builder)
|
||||
|
||||
PRIMITIVES_PACKAGE = _primitive_builder.PRIMITIVES_PACKAGE
|
||||
bootstrap_technology = _primitive_builder.bootstrap_technology
|
||||
build_context = _primitive_builder.build_context
|
||||
build_kwargs = _primitive_builder.build_kwargs
|
||||
discover_primitive_classes = _primitive_builder.discover_primitive_classes
|
||||
get_cell = _primitive_builder.get_cell
|
||||
safe_name = _primitive_builder.safe_name
|
||||
|
||||
|
||||
PALETTE = {
|
||||
@@ -23,9 +46,63 @@ PALETTE = {
|
||||
}
|
||||
|
||||
|
||||
USE_COLOR = sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
|
||||
COLORS = {
|
||||
"bold": "\033[1m",
|
||||
"cyan": "\033[36m",
|
||||
"green": "\033[32m",
|
||||
"red": "\033[31m",
|
||||
"yellow": "\033[33m",
|
||||
"reset": "\033[0m",
|
||||
}
|
||||
STATUS_STYLES = {
|
||||
"info": ("▶", "cyan", "Info"),
|
||||
"generated": ("✓", "green", "Generated"),
|
||||
"skipped": ("○", "yellow", "Skipped"),
|
||||
"failed": ("✗", "red", "Failed"),
|
||||
}
|
||||
STATUS_FALLBACK_SYMBOLS = {
|
||||
"info": ">",
|
||||
"generated": "+",
|
||||
"skipped": "-",
|
||||
"failed": "x",
|
||||
}
|
||||
|
||||
|
||||
def colorize(text: str, color: str) -> str:
|
||||
if not USE_COLOR:
|
||||
return text
|
||||
return f"{COLORS[color]}{text}{COLORS['reset']}"
|
||||
|
||||
|
||||
def can_print(text: str) -> bool:
|
||||
encoding = sys.stdout.encoding or "utf-8"
|
||||
if not encoding.lower().replace("-", "").startswith("utf"):
|
||||
return text.isascii()
|
||||
|
||||
try:
|
||||
text.encode(encoding)
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def status_item(status: str, count: Optional[int] = None) -> str:
|
||||
symbol, color, label = STATUS_STYLES[status]
|
||||
if not can_print(symbol):
|
||||
symbol = STATUS_FALLBACK_SYMBOLS[status]
|
||||
|
||||
suffix = f": {count}" if count is not None else ""
|
||||
return colorize(f"{symbol} {label}{suffix}", color)
|
||||
|
||||
|
||||
def print_status(status: str, message: str) -> None:
|
||||
print(f"{status_item(status)} {message}")
|
||||
|
||||
|
||||
def apply_mxpic_colors() -> None:
|
||||
"""Apply the mxPIC color palette to Nazca when the layers are available."""
|
||||
print("Applying mxPIC layer colors...")
|
||||
print_status("info", "Applying mxPIC layer colors...")
|
||||
for layer_id, color in PALETTE.items():
|
||||
try:
|
||||
nd.set_layercolor(layer=layer_id, color=color)
|
||||
@@ -47,11 +124,17 @@ def module_name_for(py_file: Path, src_root: Path, package_root: str) -> str:
|
||||
return f"{package_root}.{'.'.join(relative_path.parts)}"
|
||||
|
||||
|
||||
def generate_image_for_class(target_dir: Path, class_name: str, component_class: type) -> str:
|
||||
def generate_image_for_class(
|
||||
target_dir: Path,
|
||||
class_name: str,
|
||||
component_class: type,
|
||||
device_name: Optional[str] = None,
|
||||
context: Optional[dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""Instantiate one component class and save its cell image."""
|
||||
nd.clear_layout()
|
||||
instance = component_class()
|
||||
cell = getattr(instance, "cell", None)
|
||||
kwargs = build_kwargs(component_class, device_name, context) if context else {}
|
||||
instance = component_class(**kwargs)
|
||||
cell = get_cell(instance) if context else getattr(instance, "cell", None)
|
||||
if cell is None:
|
||||
return "skipped"
|
||||
|
||||
@@ -64,68 +147,70 @@ def generate_image_for_class(target_dir: Path, class_name: str, component_class:
|
||||
|
||||
|
||||
def generate_component_images(
|
||||
img_root: Path = Path("images/components"),
|
||||
src_root: Path = DEFAULT_SRC_ROOT,
|
||||
package_root: str = DEFAULT_PACKAGE_ROOT,
|
||||
img_root: Path = Path("docs/source/images"),
|
||||
src_root: Path = DEFAULT_SRC_ROOT / "primitives",
|
||||
package_root: str = PRIMITIVES_PACKAGE,
|
||||
) -> dict[str, int]:
|
||||
print("Starting mxPIC component image generation...")
|
||||
print(colorize("mxPIC primitive image generation", "bold"))
|
||||
|
||||
img_root = Path(img_root)
|
||||
src_root = Path(src_root)
|
||||
counts = {"generated": 0, "skipped": 0, "failed": 0}
|
||||
|
||||
if not src_root.exists():
|
||||
print(f"Source directory not found: {src_root}")
|
||||
print_status("failed", f"Source directory not found: {src_root}")
|
||||
counts["failed"] += 1
|
||||
return counts
|
||||
|
||||
img_root.mkdir(parents=True, exist_ok=True)
|
||||
remove_generated_pngs(img_root)
|
||||
nd.clear_layout()
|
||||
bootstrap_technology()
|
||||
apply_mxpic_colors()
|
||||
context = build_context()
|
||||
|
||||
for py_file in sorted(src_root.rglob("*.py")):
|
||||
if py_file.name == "__init__.py":
|
||||
continue
|
||||
for index, component_class in enumerate(discover_primitive_classes(), start=1):
|
||||
class_name = component_class.__name__
|
||||
module_name = component_class.__module__
|
||||
module_suffix = module_name.removeprefix(f"{package_root}.")
|
||||
target_dir = img_root / Path(*module_suffix.split(".")[:-1])
|
||||
|
||||
module_name = module_name_for(py_file, src_root, package_root)
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
device_name = safe_name("IMG", index, class_name)
|
||||
result = generate_image_for_class(
|
||||
target_dir,
|
||||
class_name,
|
||||
component_class,
|
||||
device_name=device_name,
|
||||
context=context,
|
||||
)
|
||||
except Exception as error:
|
||||
print(f"Skipped module import {module_name}: {error}")
|
||||
counts["skipped"] += 1
|
||||
plt.close()
|
||||
print_status("failed", f"{module_name}.{class_name}: {error}")
|
||||
counts["failed"] += 1
|
||||
continue
|
||||
|
||||
class_names, _ = public_top_level_members(py_file)
|
||||
for class_name in class_names:
|
||||
component_class = getattr(module, class_name, None)
|
||||
if component_class is None:
|
||||
print(f"Skipped missing class {module_name}.{class_name}")
|
||||
counts["skipped"] += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
target_dir = img_root / py_file.relative_to(src_root).parent
|
||||
result = generate_image_for_class(target_dir, class_name, component_class)
|
||||
except Exception as error:
|
||||
plt.close()
|
||||
print(f"Failed image for {module_name}.{class_name}: {error}")
|
||||
counts["failed"] += 1
|
||||
continue
|
||||
|
||||
counts[result] += 1
|
||||
if result == "generated":
|
||||
print(f"Generated: {target_dir / f'{class_name}.png'}")
|
||||
else:
|
||||
print(f"Skipped class without cell: {module_name}.{class_name}")
|
||||
counts[result] += 1
|
||||
if result == "generated":
|
||||
print_status("generated", str(target_dir / f"{class_name}.png"))
|
||||
else:
|
||||
print_status("skipped", f"{module_name}.{class_name} has no cell")
|
||||
|
||||
print(
|
||||
"Image generation complete. "
|
||||
f"Generated: {counts['generated']} | "
|
||||
f"Skipped: {counts['skipped']} | "
|
||||
f"Failures: {counts['failed']}"
|
||||
"\n"
|
||||
+ colorize("Image generation complete.", "bold")
|
||||
+ " "
|
||||
+ status_item("generated", counts["generated"])
|
||||
+ " | "
|
||||
+ status_item("skipped", counts["skipped"])
|
||||
+ " | "
|
||||
+ status_item("failed", counts["failed"])
|
||||
)
|
||||
if counts["failed"]:
|
||||
raise RuntimeError(f"{counts['failed']} primitive image(s) failed to build")
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_component_images(img_root=Path("docs/source/mxpic/components"))
|
||||
generate_component_images()
|
||||
|
||||
Reference in New Issue
Block a user