Skip to content

Commit 07493ae

Browse files
committed
👌 IMPROVE: Parsing code
Make "parts" and "sections" key names variable (as `DEFAULT_SUBTREES_KEY`, `DEFAULT_ITEMS_KEY`).
1 parent 029e954 commit 07493ae

File tree

3 files changed

+100
-80
lines changed

3 files changed

+100
-80
lines changed

sphinx_external_toc/parsing.py

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
from .api import Document, FileItem, GlobItem, SiteMap, TocTree, UrlItem
1010

11+
DEFAULT_SUBTREES_KEY = "parts"
12+
DEFAULT_ITEMS_KEY = "sections"
13+
1114
FILE_KEY = "file"
1215
GLOB_KEY = "glob"
1316
URL_KEY = "url"
@@ -50,100 +53,109 @@ def parse_toc_data(data: Dict[str, Any]) -> SiteMap:
5053

5154

5255
def _parse_doc_item(
53-
data: Dict[str, Any], defaults: Dict[str, Any], path: str, file_key: str = FILE_KEY
56+
data: Dict[str, Any],
57+
defaults: Dict[str, Any],
58+
path: str,
59+
*,
60+
subtrees_key: str = DEFAULT_SUBTREES_KEY,
61+
items_key: str = DEFAULT_ITEMS_KEY,
62+
file_key: str = FILE_KEY,
5463
) -> Tuple[Document, Sequence[Dict[str, Any]]]:
5564
"""Parse a single doc item."""
5665
if file_key not in data:
5766
raise MalformedError(f"'{file_key}' key not found: '{path}'")
58-
if "sections" in data:
59-
# this is a shorthand for defining a single part
60-
if "parts" in data:
61-
raise MalformedError(f"Both 'sections' and 'parts' found: '{path}'")
62-
parts_data = [{"sections": data["sections"], **data.get("options", {})}]
63-
elif "parts" in data:
64-
parts_data = data["parts"]
65-
if not (isinstance(parts_data, Sequence) and parts_data):
66-
raise MalformedError(f"parts not a non-empty list: '{path}'")
67+
if items_key in data:
68+
# this is a shorthand for defining a single subtree
69+
if subtrees_key in data:
70+
raise MalformedError(
71+
f"Both '{subtrees_key}' and '{items_key}' found: '{path}'"
72+
)
73+
subtrees_data = [{items_key: data[items_key], **data.get("options", {})}]
74+
elif subtrees_key in data:
75+
subtrees_data = data[subtrees_key]
76+
if not (isinstance(subtrees_data, Sequence) and subtrees_data):
77+
raise MalformedError(f"'{subtrees_key}' not a non-empty list: '{path}'")
6778
else:
68-
parts_data = []
79+
subtrees_data = []
6980

7081
_known_link_keys = {FILE_KEY, GLOB_KEY, URL_KEY}
7182

72-
parts = []
73-
for part_idx, part in enumerate(parts_data):
83+
toctrees = []
84+
for toc_idx, toc_data in enumerate(subtrees_data):
7485

75-
if not (isinstance(part, Mapping) and "sections" in part):
86+
if not (isinstance(toc_data, Mapping) and items_key in toc_data):
7687
raise MalformedError(
77-
f"part not a mapping containing 'sections' key: '{path}{part_idx}'"
88+
f"part not a mapping containing '{items_key}' key: '{path}{toc_idx}'"
7889
)
7990

80-
section_data = part["sections"]
91+
items_data = toc_data[items_key]
8192

82-
if not (isinstance(section_data, Sequence) and section_data):
83-
raise MalformedError(f"sections not a non-empty list: '{path}{part_idx}'")
93+
if not (isinstance(items_data, Sequence) and items_data):
94+
raise MalformedError(
95+
f"'{items_key}' not a non-empty list: '{path}{toc_idx}'"
96+
)
8497

8598
# generate sections list
86-
sections: List[Union[GlobItem, FileItem, UrlItem]] = []
87-
for sect_idx, section in enumerate(section_data):
99+
items: List[Union[GlobItem, FileItem, UrlItem]] = []
100+
for item_idx, item_data in enumerate(items_data):
88101

89-
if not isinstance(section, Mapping):
102+
if not isinstance(item_data, Mapping):
90103
raise MalformedError(
91-
f"toctree section not a mapping type: '{path}{part_idx}/{sect_idx}'"
104+
f"'{items_key}' item not a mapping type: '{path}{toc_idx}/{item_idx}'"
92105
)
93106

94-
link_keys = _known_link_keys.intersection(section)
107+
link_keys = _known_link_keys.intersection(item_data)
108+
109+
# validation checks
95110
if not link_keys:
96111
raise MalformedError(
97-
"toctree section does not contain one of "
98-
f"{_known_link_keys!r}: '{path}{part_idx}/{sect_idx}'"
112+
f"'{items_key}' item does not contain one of "
113+
f"{_known_link_keys!r}: '{path}{toc_idx}/{item_idx}'"
99114
)
100115
if not len(link_keys) == 1:
101116
raise MalformedError(
102-
"toctree section contains incompatible keys "
103-
f"{link_keys!r}: {path}{part_idx}/{sect_idx}"
117+
f"'{items_key}' item contains incompatible keys "
118+
f"{link_keys!r}: {path}{toc_idx}/{item_idx}"
104119
)
120+
for item_key in (GLOB_KEY, URL_KEY):
121+
for other_key in (subtrees_key, items_key):
122+
if link_keys == {item_key} and other_key in item_data:
123+
raise MalformedError(
124+
f"'{items_key}' item contains incompatible keys "
125+
f"'{item_key}' and '{other_key}': {path}{toc_idx}/{item_idx}"
126+
)
105127

106128
if link_keys == {FILE_KEY}:
107-
sections.append(FileItem(section[FILE_KEY]))
129+
items.append(FileItem(item_data[FILE_KEY]))
108130
elif link_keys == {GLOB_KEY}:
109-
if "sections" in section or "parts" in section:
110-
raise MalformedError(
111-
"toctree section contains incompatible keys "
112-
f"{GLOB_KEY} and parts/sections: {path}{part_idx}/{sect_idx}"
113-
)
114-
sections.append(GlobItem(section[GLOB_KEY]))
131+
items.append(GlobItem(item_data[GLOB_KEY]))
115132
elif link_keys == {URL_KEY}:
116-
if "sections" in section or "parts" in section:
117-
raise MalformedError(
118-
"toctree section contains incompatible keys "
119-
f"{URL_KEY} and parts/sections: {path}{part_idx}/{sect_idx}"
120-
)
121-
sections.append(UrlItem(section[URL_KEY], section.get("title")))
133+
items.append(UrlItem(item_data[URL_KEY], item_data.get("title")))
122134

123135
# generate toc key-word arguments
124-
keywords = {k: part[k] for k in TOCTREE_OPTIONS if k in part}
136+
keywords = {k: toc_data[k] for k in TOCTREE_OPTIONS if k in toc_data}
125137
for key in defaults:
126138
if key not in keywords:
127139
keywords[key] = defaults[key]
128140

129141
try:
130-
toc_item = TocTree(items=sections, **keywords)
142+
toc_item = TocTree(items=items, **keywords)
131143
except TypeError as exc:
132-
raise MalformedError(f"toctree validation: {path}{part_idx}") from exc
133-
parts.append(toc_item)
144+
raise MalformedError(f"toctree validation: {path}{toc_idx}") from exc
145+
toctrees.append(toc_item)
134146

135147
try:
136148
doc_item = Document(
137-
docname=data[file_key], title=data.get("title"), subtrees=parts
149+
docname=data[file_key], title=data.get("title"), subtrees=toctrees
138150
)
139151
except TypeError as exc:
140152
raise MalformedError(f"doc validation: {path}") from exc
141153

142154
docs_data = [
143-
section
144-
for part in parts_data
145-
for section in part["sections"]
146-
if FILE_KEY in section
155+
item_data
156+
for toc_data in subtrees_data
157+
for item_data in toc_data[items_key]
158+
if FILE_KEY in item_data
147159
]
148160

149161
return (
@@ -185,6 +197,8 @@ def _docitem_to_dict(
185197
site_map: SiteMap,
186198
*,
187199
skip_defaults: bool = True,
200+
subtrees_key: str = DEFAULT_SUBTREES_KEY,
201+
items_key: str = DEFAULT_ITEMS_KEY,
188202
file_key: str = FILE_KEY,
189203
parsed_docnames: Optional[Set[str]] = None,
190204
) -> Dict[str, Any]:
@@ -204,7 +218,7 @@ def _docitem_to_dict(
204218
if not doc_item.subtrees:
205219
return data
206220

207-
def _parse_section(item):
221+
def _parse_item(item):
208222
if isinstance(item, FileItem):
209223
if item in site_map:
210224
return _docitem_to_dict(
@@ -222,20 +236,26 @@ def _parse_section(item):
222236
return {URL_KEY: item.url}
223237
raise TypeError(item)
224238

225-
data["parts"] = []
239+
data[subtrees_key] = []
226240
fields = attr.fields_dict(TocTree)
227-
for part in doc_item.subtrees:
241+
for toctree in doc_item.subtrees:
228242
# only add these keys if their value is not the default
229-
part_data = {
230-
key: getattr(part, key)
231-
for key in ("caption", "numbered", "reversed", "titlesonly")
232-
if (not skip_defaults) or getattr(part, key) != fields[key].default
243+
toctree_data = {
244+
key: getattr(toctree, key)
245+
for key in TOCTREE_OPTIONS
246+
if (not skip_defaults) or getattr(toctree, key) != fields[key].default
233247
}
234-
part_data["sections"] = [_parse_section(s) for s in part.items]
235-
data["parts"].append(part_data)
236-
237-
# apply shorthand if possible
238-
if len(data["parts"]) == 1 and list(data["parts"][0]) == ["sections"]:
239-
data["sections"] = data.pop("parts")[0]["sections"]
248+
toctree_data[items_key] = [_parse_item(s) for s in toctree.items]
249+
data[subtrees_key].append(toctree_data)
250+
251+
# apply shorthand if possible (one toctree in subtrees)
252+
if len(data[subtrees_key]) == 1 and items_key in data[subtrees_key][0]:
253+
old_toctree_data = data.pop(subtrees_key)[0]
254+
data[items_key] = old_toctree_data[items_key]
255+
# move options to options key
256+
if len(old_toctree_data) > 1:
257+
data["options"] = {
258+
k: v for k, v in old_toctree_data.items() if k != items_key
259+
}
240260

241261
return data
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
meta:
22
regress: intro
3-
parts:
4-
- caption: Part Caption
3+
options:
4+
caption: Part Caption
55
numbered: true
6-
sections:
7-
- file: doc1
8-
- file: doc2
9-
- file: doc3
10-
sections:
11-
- file: subfolder/doc4
12-
- url: https://example.com
136
root: intro
7+
sections:
8+
- file: doc1
9+
- file: doc2
10+
- file: doc3
11+
sections:
12+
- file: subfolder/doc4
13+
- url: https://example.com
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
meta:
22
regress: intro
3-
parts:
4-
- numbered: true
5-
sections:
6-
- file: doc1
7-
- file: doc2
8-
- file: doc3
9-
sections:
10-
- file: doc4
11-
- url: https://example.com
3+
options:
4+
numbered: true
125
root: intro
6+
sections:
7+
- file: doc1
8+
- file: doc2
9+
- file: doc3
10+
sections:
11+
- file: doc4
12+
- url: https://example.com

0 commit comments

Comments
 (0)