parent
044be06e7e
commit
ad5e4f04c7
@ -0,0 +1,20 @@
|
||||
<h1 align="center">
|
||||
Codex Skills
|
||||
</h1>
|
||||
|
||||
Optional Codex skills stored in-repo so they can be shared with the community.
|
||||
|
||||
### Quick navigation
|
||||
- You are here: `codex_skills/`
|
||||
- [Repo overview](../README.md) | [Dashboards](../config/dashboards/README.md) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
|
||||
|
||||
## Skills
|
||||
|
||||
- `homeassistant-dashboard-designer/`
|
||||
- A constrained, button-card-first Home Assistant Lovelace dashboard design system + a small YAML lint helper.
|
||||
|
||||
### Notes
|
||||
- These skills are documentation + helper scripts only; they should not contain tokens, secrets, or environment-specific credentials.
|
||||
- Codex loads skills from your local Codex home. See each skill's README for install steps.
|
||||
|
||||
<a name="bottom" href="https://github.com/CCOSTAN/Home-AssistantConfig#logo"><img align="right" border="0" src="https://raw.githubusercontent.com/CCOSTAN/Home-AssistantConfig/master/config/www/custom_ui/floorplan/images/branding/up_arrow.png" width="25" ></a>
|
||||
@ -0,0 +1,57 @@
|
||||
<h1 align="center">
|
||||
Home Assistant Dashboard Designer (Codex Skill)
|
||||
</h1>
|
||||
|
||||
This directory contains the `homeassistant-dashboard-designer` Codex skill, stored in-repo so it can be shared with the community.
|
||||
|
||||
### Quick navigation
|
||||
- You are here: `codex_skills/homeassistant-dashboard-designer/`
|
||||
- [Repo overview](../../README.md) | [Codex skills](../README.md) | [Dashboards](../../config/dashboards/README.md) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
- Enforces a constrained Lovelace design system (button-card first, minimal card-mod, grid/vertical-stack layout rules).
|
||||
- Encourages centralized templates and deterministic YAML output.
|
||||
- Treats Stitch MCP output as *inspiration only* and translates ideas into safe Lovelace YAML.
|
||||
|
||||
Skill source of truth inside this folder:
|
||||
- `SKILL.md`
|
||||
- `agents/openai.yaml`
|
||||
- `references/dashboard_rules.md`
|
||||
- `scripts/validate_lovelace_view.py`
|
||||
|
||||
## Install (Local Codex)
|
||||
|
||||
Codex loads skills from your local Codex skills directory.
|
||||
|
||||
1. Copy this folder into your Codex skills directory as:
|
||||
- `~/.codex/skills/homeassistant-dashboard-designer/` (Linux/macOS)
|
||||
- `%USERPROFILE%\\.codex\\skills\\homeassistant-dashboard-designer\\` (Windows)
|
||||
2. Restart your Codex session/editor so it re-indexes skills.
|
||||
|
||||
## Stitch MCP Install (Design Inspiration)
|
||||
|
||||
Stitch is optional and used only for design inspiration. To enable it, add a Stitch MCP server entry to your Codex config.
|
||||
|
||||
1. Set an environment variable with your API key:
|
||||
- `STITCH_API_KEY=<your_key>`
|
||||
2. Add this to your `~/.codex/config.toml`:
|
||||
|
||||
```toml
|
||||
[mcp_servers.stitch]
|
||||
url = "https://stitch.googleapis.com/mcp"
|
||||
env_http_headers = { "X-Goog-Api-Key" = "STITCH_API_KEY" }
|
||||
http_headers = { "Accept" = "application/json" }
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Invoke in chat:
|
||||
- `$homeassistant-dashboard-designer`
|
||||
|
||||
Then provide the structured intent block described in `SKILL.md` (dashboard intent, view name, entity map, and layout constraints).
|
||||
|
||||
### Notes
|
||||
- This skill intentionally contains no secrets. Configure MCP credentials via environment variables in your local Codex setup.
|
||||
|
||||
<a name="bottom" href="https://github.com/CCOSTAN/Home-AssistantConfig#logo"><img align="right" border="0" src="https://raw.githubusercontent.com/CCOSTAN/Home-AssistantConfig/master/config/www/custom_ui/floorplan/images/branding/up_arrow.png" width="25" ></a>
|
||||
@ -0,0 +1,143 @@
|
||||
---
|
||||
name: homeassistant-dashboard-designer
|
||||
description: "Design, update, and refactor Home Assistant Lovelace dashboards (YAML views/partials) with a constrained, machine-safe design system: button-card-first structure, minimal card-mod styling, optional flex-horseshoe + mini-graph telemetry, strict grid/vertical-stack layout rules, centralized templates, deterministic ordering, and config validation. Use for Home Assistant dashboard work (especially config/dashboards/**), when refactoring views, adding infra/home/energy/environment panels, or translating Stitch design inspiration into safe Lovelace YAML."
|
||||
---
|
||||
|
||||
# Home Assistant Dashboard Designer
|
||||
|
||||
Design dashboards as systems: predictable structure, reusable templates, minimal drift. Treat Stitch as inspiration only; translate to safe Lovelace YAML.
|
||||
|
||||
## Input Contract (Ask For This If Missing)
|
||||
|
||||
Request structured intent, not raw YAML:
|
||||
|
||||
```yaml
|
||||
dashboard_type: infra # infra|home|energy|environment
|
||||
view_name: "Infrastructure Overview"
|
||||
target_paths:
|
||||
dashboard_yaml: "config/dashboards/infrastructure/dashboard.yaml"
|
||||
view_yaml: "config/dashboards/infrastructure/views/07_docker_containers.yaml"
|
||||
entities:
|
||||
- name: "Proxmox01"
|
||||
cpu: "sensor.proxmox01_cpu"
|
||||
memory: "sensor.proxmox01_mem"
|
||||
constraints:
|
||||
desktop_columns: 4
|
||||
mobile_columns: 2
|
||||
notes:
|
||||
- "Preserve existing templates; minimize diff."
|
||||
```
|
||||
|
||||
If updating an existing view, also ask:
|
||||
|
||||
- Which view file (single view dict) is the source of truth?
|
||||
- Any "don't touch" areas/templates?
|
||||
- Any fallback cards already in use that must remain?
|
||||
|
||||
## Output Contract (Always Produce)
|
||||
|
||||
- One valid Lovelace view (single view dict)
|
||||
- Deterministic ordering (stable ordering of cards/sections)
|
||||
- Section comments (short, consistent)
|
||||
- Zero per-card styling variance (no one-off styles; use templates/snippets)
|
||||
- Fallback usage explicitly justified (YAML comments adjacent to the fallback card)
|
||||
|
||||
## Card Priority Model
|
||||
|
||||
Attempt Tier 1 first. Use Tier 2 only if Tier 1 cannot express the requirement safely.
|
||||
|
||||
Tier 1 (primary):
|
||||
- `custom:button-card` (structure + tiles + headers + containers)
|
||||
- `card-mod` (shared/global polish only; shared snippets OK)
|
||||
- `custom:flex-horseshoe-card` (single-metric radial telemetry)
|
||||
- `custom:mini-graph-card` (single-metric trends)
|
||||
|
||||
Tier 2 (fallback, must justify in comments):
|
||||
- `entities`, `markdown`, `history-graph`, `conditional`
|
||||
- `iframe` (last resort)
|
||||
|
||||
## Layout Rules (Hard Constraints)
|
||||
|
||||
- Allowed layout containers: `grid`, `vertical-stack`
|
||||
- Maximum nesting depth: 2 (example: `grid` -> `vertical-stack` -> cards)
|
||||
- No `horizontal-stack` inside grid cells (prefer: more grid columns, or vertical-stack)
|
||||
- No freeform positioning
|
||||
- No layout logic embedded in `card-mod`
|
||||
|
||||
Note: If the repo/view uses Home Assistant `type: sections`, treat `sections` as the top-level structure and enforce the same container rules inside each section (sections should contain only `grid`/`vertical-stack` cards and their children).
|
||||
|
||||
## Design System Rules (Implementation Discipline)
|
||||
|
||||
### Button Card (Primary Renderer)
|
||||
|
||||
- Use `custom:button-card` as the structural primitive.
|
||||
- Templates required. Do not inline per-card `styles:`; keep styling inside centralized templates/snippets.
|
||||
- Logic via variables or template JS only (avoid duplicating logic per-card).
|
||||
|
||||
### card-mod (Styling Constraint Layer)
|
||||
|
||||
- Allowed only for shared/global polish:
|
||||
- padding normalization
|
||||
- border radius control
|
||||
- shadow suppression
|
||||
- typography consistency
|
||||
- No entity-specific styling
|
||||
- No per-card visual experimentation
|
||||
|
||||
### Flex Horseshoe Card
|
||||
|
||||
- One primary metric per card.
|
||||
- Explicit color thresholds.
|
||||
- No mixed units.
|
||||
|
||||
### Mini Graph Card
|
||||
|
||||
- One metric per graph.
|
||||
- Consistent time windows per dashboard.
|
||||
- Minimal legends (only when required).
|
||||
|
||||
## Template Architecture (Required)
|
||||
|
||||
- Views reference templates only (for `custom:button-card`: every instance must specify `template:`).
|
||||
- Templates accept variables; views pass variables.
|
||||
- Do not create new templates unless explicitly instructed.
|
||||
|
||||
Repo reality check (important):
|
||||
- If the repo already has established templates (for example, Infrastructure uses `bearstone_*`), use what exists.
|
||||
- If the spec's "required template categories" are not present, map to the closest existing template and call out the gap, or ask for explicit permission to add templates.
|
||||
|
||||
## Stitch MCP (Inspiration Only)
|
||||
|
||||
Use Stitch only to learn:
|
||||
- visual hierarchy (headers, grouping, density)
|
||||
- spacing rhythm (padding/gaps)
|
||||
- grouping patterns (panels, sections)
|
||||
|
||||
Never treat Stitch output as implementation code.
|
||||
|
||||
When prompting Stitch, include these constraints verbatim:
|
||||
|
||||
```
|
||||
Grid-only layout. No absolute positioning. No JS frameworks. No custom HTML/CSS outside card-mod.
|
||||
Card types limited to: custom:button-card, card-mod, custom:flex-horseshoe-card, custom:mini-graph-card, and HA core fallback cards only if unavoidable.
|
||||
Max layout nesting depth: 2. No horizontal-stack inside grid cells.
|
||||
```
|
||||
|
||||
## Workflow (Do This In Order)
|
||||
|
||||
1. Read the target dashboard/view/partials/templates to understand existing patterns and avoid drift.
|
||||
2. Determine intent: `infra` (NOC), `home`, `energy`, or `environment`. Keep one intent per view.
|
||||
3. Validate entities and services before editing (prefer Home Assistant MCP live context; otherwise ask user to confirm entity IDs).
|
||||
4. Draft layout with constraints: a top-level `grid` and optional `vertical-stack` groups.
|
||||
5. Implement using Tier 1 cards first; reuse existing templates; avoid one-off styles.
|
||||
6. If fallback cards are necessary, add an inline comment explaining why Tier 1 cannot satisfy the requirement.
|
||||
7. Validate:
|
||||
- Run Home Assistant config validation (`ha core check` or `homeassistant --script check_config`) when available.
|
||||
- Optionally run `scripts/validate_lovelace_view.py` from this skill against the changed view file to catch violations early.
|
||||
8. Failure behavior:
|
||||
- If requirements can't be met: state the violated rule and propose a compliant alternative.
|
||||
- If validation fails: stop, surface the error output, and propose corrected YAML. Do not leave invalid config applied.
|
||||
|
||||
## References
|
||||
|
||||
- Read `references/dashboard_rules.md` when you need the full constraint set and repo-specific mapping notes.
|
||||
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Home Assistant Dashboard Designer"
|
||||
short_description: "Design/refactor HA Lovelace dashboards safely"
|
||||
default_prompt: "Use $homeassistant-dashboard-designer to design, update, or refactor a Home Assistant Lovelace dashboard view using button-card templates + safe grid/stack layouts."
|
||||
@ -0,0 +1,119 @@
|
||||
# Dashboard Rules (Full)
|
||||
|
||||
This file is reference material for $homeassistant-dashboard-designer. Prefer following the workflow in `../SKILL.md`, and read this only when you need details.
|
||||
|
||||
## Card Priority Model
|
||||
|
||||
Tier 1 (primary, attempt first):
|
||||
- `custom:button-card`
|
||||
- `card-mod` (shared/global styling only)
|
||||
- `custom:flex-horseshoe-card`
|
||||
- `custom:mini-graph-card`
|
||||
|
||||
Tier 2 (fallback, only when Tier 1 cannot satisfy the requirement; justify inline):
|
||||
- `entities`
|
||||
- `markdown`
|
||||
- `history-graph`
|
||||
- `conditional`
|
||||
- `iframe` (last resort)
|
||||
|
||||
## Layout Rules (Hard Constraints)
|
||||
|
||||
- Allowed layout containers: `grid`, `vertical-stack`
|
||||
- Maximum nesting depth: 2
|
||||
- No `horizontal-stack` inside grid cells
|
||||
- No freeform positioning
|
||||
- No layout logic embedded in `card-mod`
|
||||
|
||||
Sections-mode note:
|
||||
- If a view uses `type: sections`, treat `sections` as the top-level structure and enforce the same container rules inside each section.
|
||||
|
||||
## Template Architecture (Required)
|
||||
|
||||
Intent: centralize visuals and logic into templates; views pass only variables and remain uniform.
|
||||
|
||||
Rules:
|
||||
- `custom:button-card` must use templates (no one-off per-card styles).
|
||||
- Templates accept variables.
|
||||
- Do not hardcode entity IDs inside templates.
|
||||
- Do not create new templates without explicit instruction.
|
||||
|
||||
Reality note for CCOSTAN/Home-AssistantConfig:
|
||||
- Infrastructure templates already exist in:
|
||||
- `config/dashboards/infrastructure/templates/button_card_templates.yaml`
|
||||
- Existing template naming uses `bearstone_*` and includes styling inside templates (this is OK).
|
||||
- When asked to implement new conceptual categories (e.g., "status_pill"), first map to existing templates (e.g., `bearstone_infra_chip*`). If no suitable match exists, ask before adding templates.
|
||||
|
||||
## Design System (Discipline)
|
||||
|
||||
### Button Card
|
||||
|
||||
Use it for:
|
||||
- Infrastructure tiles
|
||||
- Status pills
|
||||
- Section headers
|
||||
- Control footers
|
||||
- Card containers
|
||||
|
||||
Rules:
|
||||
- Templates required.
|
||||
- Avoid per-card `styles:` keys.
|
||||
- Avoid per-card `card_mod:` blocks.
|
||||
|
||||
### card-mod
|
||||
|
||||
Allowed only for shared/global polish:
|
||||
- padding normalization
|
||||
- border radius control
|
||||
- shadow suppression
|
||||
- typography consistency
|
||||
|
||||
Not allowed:
|
||||
- entity-specific styling
|
||||
- per-card visual experimentation
|
||||
|
||||
### Flex Horseshoe Card
|
||||
|
||||
Use it for:
|
||||
- Temperature
|
||||
- Battery
|
||||
- Load
|
||||
- Utilization
|
||||
- Percentage-based metrics
|
||||
|
||||
Rules:
|
||||
- One primary metric per card.
|
||||
- Explicit color thresholds.
|
||||
- No mixed units.
|
||||
|
||||
### Mini Graph Card
|
||||
|
||||
Rules:
|
||||
- One metric per graph.
|
||||
- Consistent time windows per dashboard (keep uniform unless user asks otherwise).
|
||||
- Minimal legends (only if required for interpretation).
|
||||
|
||||
## Stitch MCP Integration (Inspiration Only)
|
||||
|
||||
Stitch is advisory, never authoritative.
|
||||
|
||||
When using Stitch:
|
||||
- Provide feasibility constraints (cards + layout)
|
||||
- Use outputs only to inform hierarchy/grouping/density/spacing
|
||||
- Translate into approved Lovelace YAML
|
||||
|
||||
Required constraints to include in Stitch prompt:
|
||||
|
||||
```
|
||||
Grid-only layout. No absolute positioning. No JS frameworks. No custom HTML/CSS outside card-mod.
|
||||
Card types limited to: custom:button-card, card-mod, custom:flex-horseshoe-card, custom:mini-graph-card, and HA core fallback cards only if unavoidable.
|
||||
Max layout nesting depth: 2. No horizontal-stack inside grid cells.
|
||||
```
|
||||
|
||||
## Repo-Specific Dashboard YAML Rules (config/dashboards/**)
|
||||
|
||||
If working in this repo's `config/dashboards/` tree:
|
||||
- Do not edit `config/.storage` (runtime state).
|
||||
- Includes must use absolute container paths starting with `/config/`.
|
||||
- Views are one file per view, and the dashboard file uses `!include_dir_list`.
|
||||
- Files under `config/dashboards/**/*.yaml` must include the standard `@CCOSTAN` header block.
|
||||
@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Lightweight validator for Lovelace view YAML files.
|
||||
|
||||
Goal: catch common violations of the homeassistant-dashboard-designer constraints
|
||||
before Home Assistant reloads.
|
||||
|
||||
This is NOT a full Lovelace schema validator.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class _Tagged(str):
|
||||
pass
|
||||
|
||||
|
||||
class _Loader(yaml.SafeLoader):
|
||||
pass
|
||||
|
||||
|
||||
def _construct_undefined(loader: _Loader, node: yaml.Node) -> Any:
|
||||
# Preserve unknown tags (e.g., !include, !include_dir_list) as opaque strings.
|
||||
if isinstance(node, yaml.ScalarNode):
|
||||
return _Tagged(f"{node.tag} {node.value}")
|
||||
if isinstance(node, yaml.SequenceNode):
|
||||
seq = loader.construct_sequence(node)
|
||||
return _Tagged(f"{node.tag} {seq!r}")
|
||||
if isinstance(node, yaml.MappingNode):
|
||||
mapping = loader.construct_mapping(node)
|
||||
return _Tagged(f"{node.tag} {mapping!r}")
|
||||
return _Tagged(f"{node.tag}")
|
||||
|
||||
|
||||
_Loader.add_constructor(None, _construct_undefined) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
level: str # "ERROR" | "WARN"
|
||||
path: str
|
||||
message: str
|
||||
|
||||
|
||||
ALLOWED_LAYOUT = {"grid", "vertical-stack"}
|
||||
DISALLOWED_CARD_TYPES = {"horizontal-stack"}
|
||||
|
||||
# Cards allowed without justification (design-system tiering is enforced in SKILL.md; this is a lint).
|
||||
ALLOWED_CARD_TYPES = {
|
||||
"grid",
|
||||
"vertical-stack",
|
||||
"custom:button-card",
|
||||
"custom:flex-horseshoe-card",
|
||||
"custom:mini-graph-card",
|
||||
# Tier-2 fallbacks (validator does not enforce justification comments).
|
||||
"entities",
|
||||
"markdown",
|
||||
"history-graph",
|
||||
"conditional",
|
||||
"iframe",
|
||||
}
|
||||
|
||||
|
||||
def _is_mapping(v: Any) -> bool:
|
||||
return isinstance(v, dict)
|
||||
|
||||
|
||||
def _is_sequence(v: Any) -> bool:
|
||||
return isinstance(v, list)
|
||||
|
||||
|
||||
def _card_type(card: dict[str, Any]) -> str | None:
|
||||
t = card.get("type")
|
||||
if isinstance(t, str):
|
||||
return t
|
||||
return None
|
||||
|
||||
|
||||
def _validate_layout_depth(
|
||||
*,
|
||||
node: Any,
|
||||
cur_depth: int,
|
||||
max_depth: int,
|
||||
node_path: str,
|
||||
findings: list[Finding],
|
||||
) -> None:
|
||||
if not _is_mapping(node):
|
||||
return
|
||||
|
||||
ctype = _card_type(node)
|
||||
if ctype in ALLOWED_LAYOUT:
|
||||
cur_depth += 1
|
||||
if cur_depth > max_depth:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path=node_path,
|
||||
message=f"Layout nesting depth {cur_depth} exceeds max {max_depth}.",
|
||||
)
|
||||
)
|
||||
|
||||
cards = node.get("cards")
|
||||
if _is_sequence(cards):
|
||||
for idx, child in enumerate(cards):
|
||||
_validate_layout_depth(
|
||||
node=child,
|
||||
cur_depth=cur_depth,
|
||||
max_depth=max_depth,
|
||||
node_path=f"{node_path}.cards[{idx}]",
|
||||
findings=findings,
|
||||
)
|
||||
|
||||
|
||||
def _walk_cards(node: Any, node_path: str, findings: list[Finding]) -> None:
|
||||
if _is_mapping(node):
|
||||
ctype = _card_type(node)
|
||||
if ctype in DISALLOWED_CARD_TYPES:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path=node_path,
|
||||
message=f"Disallowed card type: {ctype}",
|
||||
)
|
||||
)
|
||||
if ctype and ctype not in ALLOWED_CARD_TYPES:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="WARN",
|
||||
path=node_path,
|
||||
message=f"Unknown/unlisted card type: {ctype}",
|
||||
)
|
||||
)
|
||||
|
||||
if ctype == "custom:button-card":
|
||||
tmpl = node.get("template")
|
||||
if tmpl is None:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path=node_path,
|
||||
message="custom:button-card must set template: (string or list).",
|
||||
)
|
||||
)
|
||||
if "styles" in node:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path=node_path,
|
||||
message="Per-card styles: are not allowed; move styling into centralized templates.",
|
||||
)
|
||||
)
|
||||
if "card_mod" in node:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path=node_path,
|
||||
message="Per-card card_mod: is not allowed on button-card instances; use shared snippets or templates.",
|
||||
)
|
||||
)
|
||||
|
||||
cards = node.get("cards")
|
||||
if _is_sequence(cards):
|
||||
for idx, child in enumerate(cards):
|
||||
_walk_cards(child, f"{node_path}.cards[{idx}]", findings)
|
||||
|
||||
elif _is_sequence(node):
|
||||
for idx, child in enumerate(node):
|
||||
_walk_cards(child, f"{node_path}[{idx}]", findings)
|
||||
|
||||
|
||||
def _load_yaml(path: Path) -> Any:
|
||||
txt = path.read_text(encoding="utf-8")
|
||||
return yaml.load(txt, Loader=_Loader)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("view_yaml", type=Path, help="Path to a Lovelace view YAML file")
|
||||
ap.add_argument("--max-layout-depth", type=int, default=2)
|
||||
ap.add_argument("--strict", action="store_true", help="Treat WARN findings as errors (non-zero exit).")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
if not args.view_yaml.exists():
|
||||
print(f"ERROR: file not found: {args.view_yaml}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
try:
|
||||
doc = _load_yaml(args.view_yaml)
|
||||
except Exception as e:
|
||||
print(f"ERROR: failed to parse YAML: {e}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
findings: list[Finding] = []
|
||||
|
||||
# The repo uses "one YAML file per view (single view dict)".
|
||||
if not _is_mapping(doc):
|
||||
findings.append(Finding(level="ERROR", path="$", message="View file must be a single YAML mapping (one view dict)."))
|
||||
else:
|
||||
# Support both classic views (`cards:`) and sections views (`type: sections`, `sections:`).
|
||||
if "cards" in doc:
|
||||
_validate_layout_depth(
|
||||
node=doc,
|
||||
cur_depth=0,
|
||||
max_depth=args.max_layout_depth,
|
||||
node_path="$",
|
||||
findings=findings,
|
||||
)
|
||||
_walk_cards(doc.get("cards", []), "$.cards", findings)
|
||||
elif doc.get("type") == "sections" and "sections" in doc:
|
||||
sections = doc.get("sections")
|
||||
if isinstance(sections, _Tagged):
|
||||
findings.append(
|
||||
Finding(
|
||||
level="WARN",
|
||||
path="$.sections",
|
||||
message="sections is an !include/unknown tag; validator cannot inspect included sections content.",
|
||||
)
|
||||
)
|
||||
elif _is_sequence(sections):
|
||||
for sidx, section in enumerate(sections):
|
||||
spath = f"$.sections[{sidx}]"
|
||||
if not _is_mapping(section):
|
||||
findings.append(Finding(level="ERROR", path=spath, message="Section must be a mapping."))
|
||||
continue
|
||||
|
||||
_validate_layout_depth(
|
||||
node=section,
|
||||
cur_depth=0,
|
||||
max_depth=args.max_layout_depth,
|
||||
node_path=spath,
|
||||
findings=findings,
|
||||
)
|
||||
_walk_cards(section.get("cards", []), f"{spath}.cards", findings)
|
||||
else:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path="$.sections",
|
||||
message="sections must be a list (or an !include tag).",
|
||||
)
|
||||
)
|
||||
else:
|
||||
findings.append(
|
||||
Finding(
|
||||
level="ERROR",
|
||||
path="$",
|
||||
message="View dict must contain cards: or be type: sections with sections:.",
|
||||
)
|
||||
)
|
||||
|
||||
errors = [f for f in findings if f.level == "ERROR"]
|
||||
warns = [f for f in findings if f.level == "WARN"]
|
||||
|
||||
for f in errors + warns:
|
||||
print(f"{f.level}: {f.path}: {f.message}")
|
||||
|
||||
if errors:
|
||||
return 1
|
||||
if warns and args.strict:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Loading…
Reference in new issue