Skip to content

Commit 2b27f95

Browse files
authored
DocGen: add complex categories (#126)
1 parent 393796a commit 2b27f95

File tree

10 files changed

+301
-19
lines changed

10 files changed

+301
-19
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from __future__ import annotations
5+
6+
from pathlib import Path
7+
from typing import Any, Dict, List, Optional
8+
from dataclasses import dataclass, field
9+
10+
from aws_doc_sdk_examples_tools import metadata_errors
11+
from .metadata_errors import (
12+
MetadataErrors,
13+
)
14+
15+
16+
@dataclass
17+
class TitleInfo:
18+
title: Optional[str] = field(default=None)
19+
title_abbrev: Optional[str] = field(default=None)
20+
synopsis: Optional[str] = field(default=None)
21+
title_suffixes: str | Dict[str, str] = field(default_factory=dict)
22+
23+
@classmethod
24+
def from_yaml(cls, yaml: Dict[str, str] | None) -> Optional[TitleInfo]:
25+
if yaml is None:
26+
return None
27+
28+
title = yaml.get("title")
29+
title_suffixes: str | Dict[str, str] = yaml.get("title_suffixes", {})
30+
title_abbrev = yaml.get("title_abbrev")
31+
synopsis = yaml.get("synopsis")
32+
33+
return cls(
34+
title=title,
35+
title_suffixes=title_suffixes,
36+
title_abbrev=title_abbrev,
37+
synopsis=synopsis,
38+
)
39+
40+
41+
@dataclass
42+
class CategoryWithNoDisplayError(metadata_errors.MetadataError):
43+
def message(self):
44+
return "Category has no display value"
45+
46+
47+
@dataclass
48+
class Category:
49+
key: str
50+
display: str
51+
defaults: Optional[TitleInfo] = field(default=None)
52+
overrides: Optional[TitleInfo] = field(default=None)
53+
description: Optional[str] = field(default=None)
54+
55+
def validate(self, errors: MetadataErrors):
56+
if not self.display:
57+
errors.append(CategoryWithNoDisplayError(id=self.key))
58+
59+
@classmethod
60+
def from_yaml(
61+
cls, key: str, yaml: Dict[str, Any]
62+
) -> tuple[Category, MetadataErrors]:
63+
errors = MetadataErrors()
64+
display = str(yaml.get("display"))
65+
defaults = TitleInfo.from_yaml(yaml.get("defaults"))
66+
overrides = TitleInfo.from_yaml(yaml.get("overrides"))
67+
description = yaml.get("description")
68+
69+
return (
70+
cls(
71+
key=key,
72+
display=display,
73+
defaults=defaults,
74+
overrides=overrides,
75+
description=description,
76+
),
77+
errors,
78+
)
79+
80+
81+
def parse(
82+
file: Path, yaml: Dict[str, Any]
83+
) -> tuple[List[str], Dict[str, Category], MetadataErrors]:
84+
categories: Dict[str, Category] = {}
85+
errors = MetadataErrors()
86+
87+
standard_cats = yaml.get("standard_categories", [])
88+
# Work around inconsistency where some tools use 'Actions' and DocGen uses 'Api' to refer to single-action examples.
89+
for i in range(len(standard_cats)):
90+
if standard_cats[i] == "Actions":
91+
standard_cats[i] = "Api"
92+
for key, yaml_cat in yaml.get("categories", {}).items():
93+
if yaml_cat is None:
94+
errors.append(metadata_errors.MissingCategoryBody(id=key, file=file))
95+
else:
96+
category, cat_errs = Category.from_yaml(key, yaml_cat)
97+
categories[key] = category
98+
for error in cat_errs:
99+
error.file = file
100+
error.id = key
101+
errors.extend(cat_errs)
102+
103+
return standard_cats, categories, errors
104+
105+
106+
if __name__ == "__main__":
107+
from pprint import pp
108+
import yaml
109+
110+
path = Path(__file__).parent / "config" / "categories.yaml"
111+
with open(path) as file:
112+
meta = yaml.safe_load(file)
113+
standard_cats, cats, errs = parse(path, meta)
114+
pp(standard_cats)
115+
pp(cats)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from pathlib import Path
5+
from typing import Dict, List, Tuple
6+
import pytest
7+
import yaml
8+
9+
from aws_doc_sdk_examples_tools import metadata_errors
10+
from .categories import (
11+
parse,
12+
Category,
13+
TitleInfo,
14+
)
15+
16+
17+
def load(
18+
path: str,
19+
) -> Tuple[List[str], Dict[str, Category], metadata_errors.MetadataErrors]:
20+
root = Path(__file__).parent
21+
filename = root / "test_resources" / path
22+
with open(filename) as file:
23+
meta = yaml.safe_load(file)
24+
return parse(filename, meta)
25+
26+
27+
def test_empty_categories():
28+
_, _, errs = load("empty_categories.yaml")
29+
assert [*errs] == [
30+
metadata_errors.MissingCategoryBody(
31+
file=Path(__file__).parent / "test_resources/empty_categories.yaml",
32+
id="EmptyCat",
33+
)
34+
]
35+
36+
37+
def test_categories():
38+
_, categories, _ = load("categories.yaml")
39+
assert categories == {
40+
"Actions": Category(
41+
key="Actions",
42+
display="Actions test",
43+
overrides=TitleInfo(
44+
title="Title override",
45+
title_suffixes={
46+
"cli": " with a CLI",
47+
"sdk": " with an &AWS; SDK",
48+
"sdk_cli": " with an &AWS; SDK or CLI",
49+
},
50+
title_abbrev="Title abbrev override",
51+
synopsis="synopsis test.",
52+
),
53+
description="test description.",
54+
),
55+
"Basics": Category(
56+
key="Basics",
57+
display="Basics",
58+
defaults=TitleInfo(
59+
title="Title default",
60+
title_abbrev="Title abbrev default",
61+
),
62+
description="default description.",
63+
),
64+
"TributaryLite": Category(
65+
key="TributaryLite",
66+
display="Tea light",
67+
description="light your way.",
68+
),
69+
}
70+
71+
72+
if __name__ == "__main__":
73+
pytest.main([__file__, "-vv"])
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
standard_categories: ["Hello", "Basics", "Actions", "Scenarios"]
2+
categories:
3+
Hello:
4+
display: "Hello"
5+
overrides:
6+
title: "Hello {{.ServiceEntity.Short}}"
7+
title_abbrev: "Hello {{.ServiceEntity.Short}}"
8+
synopsis: "get started using {{.ServiceEntity.Short}}."
9+
Actions:
10+
display: "Actions"
11+
overrides:
12+
title: "Use <code>{{.Action}}</code>"
13+
title_suffixes:
14+
cli: " with a CLI"
15+
sdk: " with an &AWS; SDK"
16+
sdk_cli: " with an &AWS; SDK or CLI"
17+
title_abbrev: "<code>{{.Action}}</code>"
18+
synopsis: "use <code>{{.Action}}</code>."
19+
description: "are code excerpts from larger programs and must be run in context. While actions
20+
show you how to call individual service functions, you can see actions in context in their related scenarios."
21+
Basics:
22+
display: "Basics"
23+
defaults:
24+
title: "Learn the basics of {{.ServiceEntity.Short}} with an &AWS; SDK"
25+
title_abbrev: "Learn the basics"
26+
description: "are code examples that show you how to perform the essential operations within a service."
27+
Scenarios:
28+
display: "Scenarios"
29+
description: "are code examples that show you how to accomplish specific tasks by
30+
calling multiple functions within a service or combined with other &AWS-services;."
31+
TributaryLite:
32+
display: "&AWS; community contributions"
33+
description: "are examples that were created and are maintained by multiple teams across &AWS;.
34+
To provide feedback, use the mechanism provided in the linked repositories."

aws_doc_sdk_examples_tools/doc_gen.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
# from os import glob
1414

15+
from .categories import Category, parse as parse_categories
1516
from .metadata import (
1617
Example,
1718
DocFilenames,
@@ -55,6 +56,8 @@ class DocGen:
5556
validation: ValidationConfig = field(default_factory=ValidationConfig)
5657
sdks: Dict[str, Sdk] = field(default_factory=dict)
5758
services: Dict[str, Service] = field(default_factory=dict)
59+
standard_categories: List[str] = field(default_factory=list)
60+
categories: Dict[str, Category] = field(default_factory=dict)
5861
snippets: Dict[str, Snippet] = field(default_factory=dict)
5962
snippet_files: Set[str] = field(default_factory=set)
6063
examples: Dict[str, Example] = field(default_factory=dict)
@@ -201,6 +204,19 @@ def for_root(
201204
except Exception:
202205
pass
203206

207+
try:
208+
categories_path = config / "categories.yaml"
209+
with categories_path.open(encoding="utf-8") as file:
210+
meta = yaml.safe_load(file)
211+
standard_categories, categories, errs = parse_categories(
212+
categories_path, meta
213+
)
214+
self.standard_categories = standard_categories
215+
self.categories = categories
216+
self.errors.extend(errs)
217+
except Exception:
218+
pass
219+
204220
try:
205221
entities_config_path = config / "entities.yaml"
206222
with entities_config_path.open(encoding="utf-8") as file:
@@ -236,6 +252,7 @@ def process_metadata(self, path: Path) -> "DocGen":
236252
yaml.safe_load(file),
237253
self.sdks,
238254
self.services,
255+
self.standard_categories,
239256
self.cross_blocks,
240257
self.validation,
241258
self.root,
@@ -268,6 +285,8 @@ def validate(self):
268285
sdk.validate(self.errors)
269286
for service in self.services.values():
270287
service.validate(self.errors)
288+
for category in self.categories.values():
289+
category.validate(self.errors)
271290
for example in self.examples.values():
272291
example.validate(self.errors, self.root)
273292
validate_metadata(self.root, self.validation.strict_titles, self.errors)
@@ -339,6 +358,7 @@ def parse_examples(
339358
yaml: Dict[str, Any],
340359
sdks: Dict[str, Sdk],
341360
services: Dict[str, Service],
361+
standard_categories: List[str],
342362
blocks: Set[str],
343363
validation: Optional[ValidationConfig],
344364
root: Optional[Path] = None,
@@ -350,12 +370,13 @@ def parse_examples(
350370
example, example_errors = example_from_yaml(
351371
yaml[id], sdks, services, blocks, validation, root or file.parent
352372
)
353-
check_id_format(
354-
id,
355-
example.services,
356-
validation.strict_titles and example.category == "Api",
357-
example_errors,
358-
)
373+
if example.category in standard_categories:
374+
check_id_format(
375+
id,
376+
example.services,
377+
validation.strict_titles and example.category == "Api",
378+
example_errors,
379+
)
359380
for error in example_errors:
360381
error.file = file
361382
error.id = id
@@ -411,7 +432,7 @@ def get_doc_filenames(example_id: str, example: Example) -> Optional[DocFilename
411432
)
412433
)
413434
else:
414-
anchor = "actions" if example.category == "Actions" else "scenarios"
435+
anchor = "actions" if example.category == "Api" else "scenarios"
415436
sdk_pages[language.property][version.sdk_version] = SDKPageVersion(
416437
actions_scenarios={
417438
service_id: f"{base_url}/{language.property}_{version.sdk_version}_{service_id}_code_examples.html#{anchor}"

aws_doc_sdk_examples_tools/metadata_errors.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,12 @@ def message(self):
370370
return f"URL {self.url} is missing a title"
371371

372372

373+
@dataclass
374+
class MissingCategoryBody(MetadataParseError):
375+
def message(self):
376+
return "category definition missing body"
377+
378+
373379
@dataclass
374380
class ExampleMergeMismatchedId(MetadataError):
375381
other_id: str = ""

0 commit comments

Comments
 (0)