diff --git a/milestones/README.md b/milestones/README.md index 95ca73c..bcf8c23 100644 --- a/milestones/README.md +++ b/milestones/README.md @@ -5,11 +5,13 @@ repositories that install `docgen` and maintain their own demo bundle. The library no longer ships an in-repo dogfood; consumers are the integration test of record. -**Active:** **[visual-map-field-strings.md](visual-map-field-strings.md)** — -`visual_map` type/scene/source and `segment_names` values must be YAML -strings at config load. +**Active:** **[path-config-strings.md](path-config-strings.md)** — +`env_file` / `repo_root` / `dirs.*` must be YAML strings at config load. **Shipped:** +- **[visual-map-field-strings.md](visual-map-field-strings.md)** — + `visual_map` type/scene/source and `segment_names` values must be YAML + strings (#108). - **[generation-model-strings.md](generation-model-strings.md)** — `narration_from_source` / `manim_scene_generation` model and prompt keys must be YAML strings (#107). diff --git a/milestones/path-config-strings.md b/milestones/path-config-strings.md new file mode 100644 index 0000000..ed92f3e --- /dev/null +++ b/milestones/path-config-strings.md @@ -0,0 +1,38 @@ +# Milestone: env_file / repo_root / dirs paths must be strings + +**Status:** Active +**PR:** [#109](https://github.com/jmjava/documentation-generator/pull/109) +**Depends on:** `milestones/visual-map-field-strings.md` (PR #108) + +## Problem + +`manim.manim_path` is already a string at config load. These path keys +were not: + +1. **`env_file` / `repo_root`** — a YAML list loaded successfully, then + `Path / list` TypeError’d when CLI applied the env file or the wizard + resolved `repo_root`. +2. **`dirs.narration` / `audio` / `animations` / `recordings` / `hints`** + — a list TypeError’d during `Config.__post_init__` Path joins instead + of raising `ConfigError`. + +## Goal + +Fail closed at `Config.from_yaml` with `ConfigError`. Missing keys still +use defaults (`narration/`, git-discovered repo root, no `env_file`). + +## Done when + +- [x] Present `env_file` / `repo_root` must be non-empty YAML strings. +- [x] Present `dirs.narration` / `audio` / `animations` / `recordings` / + `hints` must be non-empty YAML strings. +- [x] Tests for list values of those keys. +- [x] `ruff check src/ tests/` +- [x] `pytest tests/` +- [x] `docgen benchmark` (no clock change) + +## Out of scope + +- `wizard.default_guidance` type gating is separate. +- Per-segment generation `system_prompt` / `class_name` are separate. +- `pages` output directory keys are separate. diff --git a/milestones/visual-map-field-strings.md b/milestones/visual-map-field-strings.md index 7715e52..4654810 100644 --- a/milestones/visual-map-field-strings.md +++ b/milestones/visual-map-field-strings.md @@ -1,7 +1,7 @@ # Milestone: visual_map type/scene/source and segment_names values must be strings -**Status:** Active -**PR:** [#108](https://github.com/jmjava/documentation-generator/pull/108) +**Status:** Shipped +**PR:** #108 **Depends on:** `milestones/generation-model-strings.md` (PR #107), `milestones/visual-map-row-types.md` (PR #86), `milestones/segment-id-strings.md` (PR #87) diff --git a/src/docgen/config.py b/src/docgen/config.py index 334821a..4a52508 100644 --- a/src/docgen/config.py +++ b/src/docgen/config.py @@ -164,7 +164,11 @@ class Config: hints_dir: Path = field(init=False) def __post_init__(self) -> None: + src = self._source_label() dirs = self._block("dirs") + for dkey in ("narration", "audio", "animations", "recordings", "hints"): + if dirs.get(dkey) is not None: + require_yaml_string(dirs[dkey], label=f"dirs.{dkey}", source=src) self.narration_dir = self.base_dir / dirs.get("narration", "narration") self.audio_dir = self.base_dir / dirs.get("audio", "audio") self.animations_dir = self.base_dir / dirs.get("animations", "animations") @@ -347,6 +351,10 @@ def __post_init__(self) -> None: f"{src}: manim_scene_generation.scene_spec_system_prompt must be a " f"YAML string, not {type(msg['scene_spec_system_prompt']).__name__}" ) + if self.raw.get("env_file") is not None: + require_yaml_string(self.raw["env_file"], label="env_file", source=src) + if self.raw.get("repo_root") is not None: + require_yaml_string(self.raw["repo_root"], label="repo_root", source=src) ocr = self._sub_block(validation, "ocr", label="validation.ocr") if ocr.get("error_patterns") is not None: string_list_block( diff --git a/tests/test_config.py b/tests/test_config.py index eaa301d..2d0f738 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -631,3 +631,24 @@ def test_from_yaml_list_segment_names_value_raises(tmp_path: Path) -> None: p.write_text('segment_names:\n "01":\n - 01-intro\n', encoding="utf-8") with pytest.raises(ConfigError, match="segment_names.01 must be a YAML string"): Config.from_yaml(p) + + +def test_from_yaml_list_env_file_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("env_file:\n - .env\n", encoding="utf-8") + with pytest.raises(ConfigError, match="env_file must be a YAML string"): + Config.from_yaml(p) + + +def test_from_yaml_list_repo_root_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("repo_root:\n - ..\n", encoding="utf-8") + with pytest.raises(ConfigError, match="repo_root must be a YAML string"): + Config.from_yaml(p) + + +def test_from_yaml_list_dirs_narration_raises(tmp_path: Path) -> None: + p = tmp_path / "docgen.yaml" + p.write_text("dirs:\n narration:\n - narration\n", encoding="utf-8") + with pytest.raises(ConfigError, match="dirs.narration must be a YAML string"): + Config.from_yaml(p)