Step 1 of the control-marking task, and deliberately server-only: no handle is drawn yet. This project already shipped a range handle for `profile.interval`, which compileGeometry ignores, so the control dragged and changed nothing. The consumer comes first now. Two kinds join the taxonomy — `junction-crosswalk-inset` and `junction-stop-line-offset`, both on the existing `junction-approach` anchor. The solver writes them onto the approach entry, `applyDirectJunctionPlans` carries them onto the compiled approach, and `compileControlMarkings` reads them in place of the module constants it used for every junction. They move markings without reshaping the junction, so unlike width and cutback they deliberately do not trigger a boundary recompute. `applyJunctionConstraint` becomes an explicit switch. Its trailing `else` had meant every kind that was not approach-width fell through to the cutback validator, so a new kind would have been silently validated and written as a cutback. The same non-exhaustive shape in the test fixture's `valueFor` is fixed the same way, and now throws for an unnamed kind rather than answering with a corner radius. design.md's taxonomy is updated with it — a test asserts the two cannot drift, which is what caught the omission. Measured on a 41-road workspace with 8 crossings: both constraints change their marking geometry, neither drags the other, and out-of-range blocks instead of clamping. That measurement is not in the suite: the synthetic junction resolves `junction_inset_m` to 0 because its crossing never binds to a plan, and the committed OSM fixture has no crossings at all. The tests assert the wiring the handles will depend on — values reaching the approach entry, distinct branches, blocking diagnostics — and the gap is recorded in the test itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
46 lines
1.1 KiB
Python
Executable File
46 lines
1.1 KiB
Python
Executable File
"""
|
|
Terminal output utilities: colors and structured logging.
|
|
|
|
Single source of truth for Colors and log_* functions
|
|
used across all Trellis scripts.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
class Colors:
|
|
"""ANSI color codes for terminal output."""
|
|
|
|
RED = "\033[0;31m"
|
|
GREEN = "\033[0;32m"
|
|
YELLOW = "\033[1;33m"
|
|
BLUE = "\033[0;34m"
|
|
CYAN = "\033[0;36m"
|
|
DIM = "\033[2m"
|
|
NC = "\033[0m" # No Color / Reset
|
|
|
|
|
|
def colored(text: str, color: str) -> str:
|
|
"""Apply ANSI color to text."""
|
|
return f"{color}{text}{Colors.NC}"
|
|
|
|
|
|
def log_info(msg: str) -> None:
|
|
"""Print info-level message with [INFO] prefix."""
|
|
print(f"{Colors.BLUE}[INFO]{Colors.NC} {msg}")
|
|
|
|
|
|
def log_success(msg: str) -> None:
|
|
"""Print success message with [SUCCESS] prefix."""
|
|
print(f"{Colors.GREEN}[SUCCESS]{Colors.NC} {msg}")
|
|
|
|
|
|
def log_warn(msg: str) -> None:
|
|
"""Print warning message with [WARN] prefix."""
|
|
print(f"{Colors.YELLOW}[WARN]{Colors.NC} {msg}")
|
|
|
|
|
|
def log_error(msg: str) -> None:
|
|
"""Print error message with [ERROR] prefix."""
|
|
print(f"{Colors.RED}[ERROR]{Colors.NC} {msg}")
|