mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Move utils/roles/applications/files.py to utils/roles/mapping.py and add a data-driven role-type detector in utils/roles/type.py. Each entry in ROLE_FILES carries per-role-type allowed/mandatory flags plus marker entries that drive get_role_types(); a role MAY belong to several types (e.g. an application that also ships a system service). Add cli/meta/roles/mapping with files / types / markers subcommands so contributors can query the schema in text or JSON. Move cli/meta/applications/ to cli/meta/roles/applications/ and adjust PROJECT_ROOT parents indices accordingly. Move docs/contributing/design/services/ to docs/contributing/design/role/services/ and document the mapping in docs/contributing/design/role/mapping.md per documentation.md. Replace the narrow test_app_only_files with a generalist tests/integration/roles/meta/test_mapping.py that walks every role and every ROLE_FILES entry and reports forbidden files, missing mandatory files, forbidden entries, and missing mandatory entries in one structured fail report. meta/info.yml and meta/users.yml are now allowed across every role type. Create the seven mandatory files surfaced by the generalist test (README.md for desk-keepassxc, desk-obs, web-svc-cdn following readme_md.md, plus tasks/main.yml stubs for svc-db-memcached, svc-db-redis, sys-svc-proxy, web-app-socialhome). Audit and rewrite touched markdown for documentation.md compliance: replace em-dashes with fluent sentences, drop SPOT self-labels, fix all broken relative links introduced by the services/ move. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
2.0 KiB
Python
65 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
from .resolver import CombinedResolver
|
|
from .role_introspection import require_role_exists
|
|
|
|
|
|
def print_tree(start_role: str) -> None:
|
|
"""
|
|
Print an ASCII tree showing [run_after] and [dependencies] for each role.
|
|
|
|
Avoid infinite loops by:
|
|
- marking already-expanded nodes
|
|
- detecting cycles on the current stack
|
|
"""
|
|
require_role_exists(start_role)
|
|
|
|
resolver = CombinedResolver()
|
|
|
|
expanded: set[str] = set()
|
|
stack: list[str] = []
|
|
|
|
def show_node(node: str, prefix: str, is_last: bool) -> None:
|
|
connector = "└── " if is_last else "├── "
|
|
if prefix == "":
|
|
print(node)
|
|
else:
|
|
print(prefix + connector + node)
|
|
|
|
if node in stack:
|
|
print(prefix + (" " if is_last else "│ ") + "↩︎ (cycle)")
|
|
return
|
|
|
|
if node in expanded:
|
|
print(prefix + (" " if is_last else "│ ") + "… (already shown)")
|
|
return
|
|
|
|
expanded.add(node)
|
|
stack.append(node)
|
|
|
|
edges = resolver.edges_for(node)
|
|
|
|
groups: list[tuple[str, list[str]]] = []
|
|
if edges.run_after:
|
|
groups.append(("run_after", edges.run_after))
|
|
if edges.dependencies:
|
|
groups.append(("dependencies", edges.dependencies))
|
|
if edges.services:
|
|
groups.append(("services", edges.services))
|
|
|
|
base_indent = prefix + (" " if is_last else "│ ")
|
|
|
|
for gi, (gname, children) in enumerate(groups):
|
|
g_last = gi == (len(groups) - 1)
|
|
g_connector = "└── " if g_last else "├── "
|
|
print(base_indent + g_connector + f"[{gname}]")
|
|
|
|
child_prefix = base_indent + (" " if g_last else "│ ")
|
|
for ci, child in enumerate(children):
|
|
c_last = ci == (len(children) - 1)
|
|
show_node(child, child_prefix, c_last)
|
|
|
|
stack.pop()
|
|
|
|
show_node(start_role, prefix="", is_last=True)
|