Skip to content

Commit a997ee0

Browse files
authored
Add a schema for validating .doc_config.yaml (#7)
* Add a schema for validating .doc_config.yaml This is a simple JSON Schema for validating the .doc_config.yaml files. Additionally, add a mode for concatenating the `index.adoc` file for an example page from fragments in the current directory -- this is anticipated based on examination for the tar-and-transfer example, which probably needs to be broken down into several files. Writing the config to allow for this was an initial trial of the schema's internal consistency and usability. As a side-effect of the refactor, the 'readme_is_index' config was replaced with a config of the form {"copy": "README.adoc"} which will probably be the norm for examples, but about which we can be flexible in the future. With the schema in place, updates to the doc build script include the removal of type checks, as we can rely on the schema to do this work. * Minor tweaks per review
1 parent 1ab3400 commit a997ee0

File tree

6 files changed

+152
-50
lines changed

6 files changed

+152
-50
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ repos:
2626
rev: 0.33.0
2727
hooks:
2828
- id: check-github-workflows
29+
- id: check-jsonschema
30+
args: ["--schemafile", "support/doc_config_schema.json"]
31+
files: "\\.doc_config\\.yaml"
32+
types: [yaml]
2933

3034
- repo: "https://github.com/sirosen/texthooks"
3135
rev: 0.6.8

support/build-doc-bundle.py

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,13 @@ def build_all_files() -> t.Iterator[tuple[str, bytes]]:
6868
config = load_config(config_file)
6969
all_example_configs.append(config)
7070

71-
if not config.readme_is_index:
72-
_abort("doc builds currently only support readme_is_index behavior")
73-
7471
for sub_path in ("definition.json", "input_schema.json", "sample_input.json"):
7572
with open(source_dir / sub_path, "rb") as fp:
7673
yield f"{config.example_dir}/{sub_path}", fp.read()
77-
with open(source_dir / "README.adoc", "rb") as fp:
78-
content = fp.read()
79-
if config.append_source_blocks:
80-
content = append_source_blocks(content)
81-
content = prepend_preamble(config, content)
82-
yield f"{config.example_dir}/index.adoc", content
74+
75+
yield f"{config.example_dir}/index.adoc", render_example_index_doc(
76+
source_dir, config
77+
)
8378
yield "index.adoc", build_index_doc(all_example_configs)
8479

8580

@@ -88,6 +83,30 @@ def find_build_configs() -> t.Iterator[pathlib.Path]:
8883
yield pathlib.Path(item)
8984

9085

86+
def render_example_index_doc(
87+
source_dir: pathlib.Path, config: ExampleDocBuildConfig
88+
) -> bytes:
89+
content: bytes
90+
if config.index_source.mode == "copy":
91+
if len(config.index_source.filenames) != 1:
92+
raise ValueError("A 'copy' config cannot have multiple filenames.")
93+
content = (source_dir / config.index_source.filenames[0]).read_bytes()
94+
elif config.index_source.mode == "concat":
95+
content = b"".join(
96+
(source_dir / filename).read_bytes()
97+
for filename in config.index_source.filenames
98+
)
99+
else:
100+
raise NotImplementedError(
101+
f"Unsupported index_source mode: {config.index_source.mode}"
102+
)
103+
104+
if config.append_source_blocks:
105+
content = append_source_blocks(content)
106+
content = prepend_preamble(config, content)
107+
return content
108+
109+
91110
def prepend_preamble(config: ExampleDocBuildConfig, content: bytes) -> bytes:
92111
return (
93112
textwrap.dedent(
@@ -171,55 +190,51 @@ def build_index_doc(configs: list[ExampleDocBuildConfig]) -> bytes:
171190
return INDEX_TEMPLATE.render({"examples": sorted_configs}).encode("utf-8")
172191

173192

193+
def load_config(config_file: pathlib.Path) -> ExampleDocBuildConfig:
194+
with open(config_file, "rb") as fp:
195+
raw_config_data = yaml.safe_load(fp)
196+
197+
if not isinstance(raw_config_data, dict):
198+
_abort(f"cannot fetch yaml data from {config_file}, non-dict config?")
199+
200+
return ExampleDocBuildConfig._load(raw_config_data)
201+
202+
174203
@dataclasses.dataclass
175204
class ExampleDocBuildConfig:
176205
title: str
177206
short_description: str
178207
example_dir: str
179-
readme_is_index: bool
208+
index_source: IndexSourceConfig
180209
append_source_blocks: bool
181210
menu_weight: int
182211

212+
@classmethod
213+
def _load(cls, data: dict[str, t.Any]) -> t.Self:
183214

184-
def load_config(config_file: pathlib.Path) -> ExampleDocBuildConfig:
185-
filename: str = str(config_file)
186-
with open(config_file, "rb") as fp:
187-
raw_config_data = yaml.load(fp, Loader=yaml.Loader)
188-
189-
title: str = _require_yaml_type(filename, raw_config_data, "title", str)
190-
short_description: str = _require_yaml_type(
191-
filename, raw_config_data, "short_description", str
192-
)
193-
194-
example_dir: str = _require_yaml_type(filename, raw_config_data, "example_dir", str)
195-
readme_is_index: bool = _require_yaml_type(
196-
filename, raw_config_data, "readme_is_index", bool
197-
)
198-
append_source_blocks: bool = _require_yaml_type(
199-
filename, raw_config_data, "append_source_blocks", bool
200-
)
201-
menu_weight: int = _require_yaml_type(filename, raw_config_data, "menu_weight", int)
202-
203-
return ExampleDocBuildConfig(
204-
title=title,
205-
short_description=short_description,
206-
example_dir=example_dir,
207-
readme_is_index=readme_is_index,
208-
append_source_blocks=append_source_blocks,
209-
menu_weight=menu_weight,
210-
)
211-
215+
return cls(
216+
title=data["title"],
217+
short_description=data["short_description"],
218+
example_dir=data["example_dir"],
219+
index_source=IndexSourceConfig._load(data["index_source"]),
220+
append_source_blocks=data["append_source_blocks"],
221+
menu_weight=data["menu_weight"],
222+
)
212223

213-
T = t.TypeVar("T")
214224

215-
216-
def _require_yaml_type(filename: str, data: t.Any, key: str, typ: type[T]) -> T:
217-
if not isinstance(data, dict):
218-
_abort("cannot fetch yaml data, non-dict config?")
219-
value = data.get(key)
220-
if not isinstance(value, typ):
221-
_abort(f"{filename}::$.{key} must be of type '{typ.__name__}'")
222-
return value
225+
@dataclasses.dataclass
226+
class IndexSourceConfig:
227+
mode: t.Literal["copy", "concat"]
228+
filenames: list[str]
229+
230+
@classmethod
231+
def _load(cls, data: dict[str, t.Any]) -> t.Self:
232+
if "copy" in data:
233+
return cls(mode="copy", filenames=[data["copy"]])
234+
elif "concat" in data:
235+
return cls(mode="concat", filenames=[data["concat"]["files"]])
236+
else:
237+
_abort("Unexpected error: index_source did not have copy or concat")
223238

224239

225240
def _abort(message: str) -> t.NoReturn:

support/doc_config_schema.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"title": ".doc_config.yaml Validation Schema",
3+
"description": "This schema describes the shape of our internal doc configs. It is used to validate configs so that the build script doesn't need to do this checking.",
4+
"$schema": "https://json-schema.org/draft/2020-12/schema",
5+
"properties": {
6+
"title": {
7+
"type": "string"
8+
},
9+
"short_description": {
10+
"type": "string"
11+
},
12+
"example_dir": {
13+
"type": "string"
14+
},
15+
"index_source": {
16+
"oneOf": [
17+
{
18+
"$ref": "#/$defs/index_source_copy"
19+
},
20+
{
21+
"$ref": "#/$defs/index_source_concat"
22+
}
23+
]
24+
},
25+
"append_source_blocks": {
26+
"type": "boolean"
27+
},
28+
"menu_weight": {
29+
"type": "integer"
30+
}
31+
},
32+
"required": [
33+
"title",
34+
"short_description",
35+
"example_dir",
36+
"index_source",
37+
"append_source_blocks",
38+
"menu_weight"
39+
],
40+
"additionalProperties": false,
41+
"$defs": {
42+
"index_source_copy": {
43+
"type": "object",
44+
"properties": {
45+
"copy": {
46+
"type": "string"
47+
}
48+
},
49+
"required": [
50+
"copy"
51+
],
52+
"additionalProperties": false
53+
},
54+
"index_source_concat": {
55+
"type": "object",
56+
"properties": {
57+
"concat": {
58+
"type": "object",
59+
"properties": {
60+
"files": {
61+
"$ref": "#/$defs/string_array",
62+
"minItems": 1
63+
}
64+
},
65+
"required": [
66+
"files"
67+
],
68+
"additionalProperties": false
69+
}
70+
},
71+
"additionalProperties": false
72+
},
73+
"string_array": {
74+
"type": "array",
75+
"items": {
76+
"type": "string"
77+
}
78+
}
79+
}
80+
}

transfer_examples/move/.doc_config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ short_description: |
88
be copied to the destination and then deleted from the source.
99
1010
example_dir: 'move_flow'
11-
readme_is_index: true
11+
index_source:
12+
copy: "README.adoc"
1213
append_source_blocks: true
1314

1415
menu_weight: 100

transfer_examples/transfer_after_approval/.doc_config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ short_description: |
55
destination Guest Collection.
66
77
example_dir: 'transfer_after_approval'
8-
readme_is_index: true
8+
index_source:
9+
copy: "README.adoc"
910
append_source_blocks: true
1011

1112
menu_weight: 300

transfer_examples/two_hop/.doc_config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ short_description: |
55
destination. Remove from intermediate after completion.
66
77
example_dir: 'two_hop'
8-
readme_is_index: true
8+
index_source:
9+
copy: "README.adoc"
910
append_source_blocks: true
1011

1112
menu_weight: 200

0 commit comments

Comments
 (0)