Skip to content

Commit 0ebb0e8

Browse files
committed
♻️ REFACTOR: API naming
Renamed to be more general: - `DocItem` -> `Document` - `DocItem.parts` -> `Document.subtrees` - `TocItem` -> `TocTree` - `TocItem.sections` -> `TocTree.items`
1 parent 141da25 commit 0ebb0e8

13 files changed

+350
-347
lines changed

README.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ sections:
321321

322322
## API
323323

324-
The ToC file is parsed to a `SiteMap`, which is a `MutableMapping` subclass, with keys representing docnames mapping to a `DocItem` that stores information on the toctrees it should contain:
324+
The ToC file is parsed to a `SiteMap`, which is a `MutableMapping` subclass, with keys representing docnames mapping to a `Document` that stores information on the toctrees it should contain:
325325

326326
```python
327327
import yaml
@@ -334,21 +334,23 @@ yaml.dump(site_map.as_json())
334334
Would produce e.g.
335335

336336
```yaml
337-
_root: intro
338-
doc1:
339-
docname: doc1
340-
parts: []
341-
title: null
342-
intro:
343-
docname: intro
344-
parts:
345-
- caption: Part Caption
346-
numbered: true
347-
reversed: false
348-
sections:
349-
- doc1
350-
titlesonly: true
351-
title: null
337+
root: intro
338+
documents:
339+
doc1:
340+
docname: doc1
341+
subtrees: []
342+
title: null
343+
intro:
344+
docname: intro
345+
subtrees:
346+
- caption: Part Caption
347+
numbered: true
348+
reversed: false
349+
items:
350+
- doc1
351+
titlesonly: true
352+
title: null
353+
meta: {}
352354
```
353355

354356
## Development Notes

sphinx_external_toc/api.py

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class UrlItem:
2828

2929

3030
@attr.s(slots=True)
31-
class TocItem:
31+
class TocTree:
3232
"""An individual toctree within a document."""
3333

3434
# TODO validate uniqueness of docnames (at least one item)
35-
sections: List[Union[GlobItem, FileItem, UrlItem]] = attr.ib(
35+
items: List[Union[GlobItem, FileItem, UrlItem]] = attr.ib(
3636
validator=deep_iterable(
3737
instance_of((GlobItem, FileItem, UrlItem)), instance_of(list)
3838
)
@@ -49,47 +49,43 @@ class TocItem:
4949
titlesonly: bool = attr.ib(True, kw_only=True, validator=instance_of(bool))
5050

5151
def files(self) -> List[str]:
52-
return [
53-
str(section) for section in self.sections if isinstance(section, FileItem)
54-
]
52+
return [str(item) for item in self.items if isinstance(item, FileItem)]
5553

5654
def globs(self) -> List[str]:
57-
return [
58-
str(section) for section in self.sections if isinstance(section, GlobItem)
59-
]
55+
return [str(item) for item in self.items if isinstance(item, GlobItem)]
6056

6157

6258
@attr.s(slots=True)
63-
class DocItem:
59+
class Document:
6460
"""A document in the site map."""
6561

6662
docname: str = attr.ib(validator=instance_of(str))
6763
title: Optional[str] = attr.ib(None, validator=optional(instance_of(str)))
6864
# TODO validate uniqueness of docnames across all parts (and none should be the docname)
69-
parts: List[TocItem] = attr.ib(
70-
factory=list, validator=deep_iterable(instance_of(TocItem), instance_of(list))
65+
subtrees: List[TocTree] = attr.ib(
66+
factory=list, validator=deep_iterable(instance_of(TocTree), instance_of(list))
7167
)
7268

7369
def child_files(self) -> List[str]:
7470
"""Return all children files."""
75-
return [name for part in self.parts for name in part.files()]
71+
return [name for tree in self.subtrees for name in tree.files()]
7672

7773
def child_globs(self) -> List[str]:
7874
"""Return all children globs."""
79-
return [name for part in self.parts for name in part.globs()]
75+
return [name for tree in self.subtrees for name in tree.globs()]
8076

8177

8278
class SiteMap(MutableMapping):
8379
"""A mapping of documents to their toctrees (or None if terminal)."""
8480

85-
def __init__(self, root: DocItem, meta: Optional[Dict[str, Any]] = None) -> None:
86-
self._docs: Dict[str, DocItem] = {}
81+
def __init__(self, root: Document, meta: Optional[Dict[str, Any]] = None) -> None:
82+
self._docs: Dict[str, Document] = {}
8783
self[root.docname] = root
88-
self._root: DocItem = root
84+
self._root: Document = root
8985
self._meta: Dict[str, Any] = meta or {}
9086

9187
@property
92-
def root(self) -> DocItem:
88+
def root(self) -> Document:
9389
"""Return the root document."""
9490
return self._root
9591

@@ -102,10 +98,10 @@ def globs(self) -> Set[str]:
10298
"""Return set of all globs present across all toctrees."""
10399
return {glob for item in self._docs.values() for glob in item.child_globs()}
104100

105-
def __getitem__(self, docname: str) -> DocItem:
101+
def __getitem__(self, docname: str) -> Document:
106102
return self._docs[docname]
107103

108-
def __setitem__(self, docname: str, item: DocItem) -> None:
104+
def __setitem__(self, docname: str, item: Document) -> None:
109105
assert item.docname == docname
110106
self._docs[docname] = item
111107

@@ -130,17 +126,12 @@ def _serializer(inst: Any, field: attr.Attribute, value: Any) -> Any:
130126
return str(value)
131127
return value
132128

133-
def as_json(
134-
self, root_key: str = "_root", meta_key: str = "_meta"
135-
) -> Dict[str, Any]:
129+
def as_json(self) -> Dict[str, Any]:
136130
"""Return JSON serialized site-map representation."""
137-
dct = {
138-
k: attr.asdict(v, value_serializer=self._serializer) if v else v
139-
for k, v in self._docs.items()
131+
doc_dict = {
132+
k: attr.asdict(self._docs[k], value_serializer=self._serializer)
133+
if self._docs[k]
134+
else self._docs[k]
135+
for k in sorted(self._docs)
140136
}
141-
assert root_key not in dct
142-
dct[root_key] = self.root.docname
143-
if self.meta:
144-
assert meta_key not in dct
145-
dct[meta_key] = self.meta
146-
return dct
137+
return {"root": self.root.docname, "documents": doc_dict, "meta": self.meta}

sphinx_external_toc/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def main():
2323
def parse_toc(toc_file):
2424
"""Parse a ToC file to a site-map YAML."""
2525
site_map = parse_toc_yaml(toc_file)
26-
click.echo(yaml.dump(site_map.as_json()))
26+
click.echo(yaml.dump(site_map.as_json(), sort_keys=False, default_flow_style=False))
2727

2828

2929
@main.command("create-site")

sphinx_external_toc/events.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from sphinx.util.docutils import SphinxDirective
1515
from sphinx.util.matching import Matcher, patfilter, patmatch
1616

17-
from .api import DocItem, FileItem, GlobItem, SiteMap, UrlItem
17+
from .api import Document, FileItem, GlobItem, SiteMap, UrlItem
1818
from .parsing import parse_toc_yaml
1919

2020
logger = logging.getLogger(__name__)
@@ -166,9 +166,9 @@ def insert_toctrees(app: Sphinx, doctree: nodes.document) -> None:
166166
)
167167

168168
site_map: SiteMap = app.env.external_site_map
169-
doc_item: Optional[DocItem] = site_map.get(app.env.docname)
169+
doc_item: Optional[Document] = site_map.get(app.env.docname)
170170

171-
if doc_item is None or not doc_item.parts:
171+
if doc_item is None or not doc_item.subtrees:
172172
if toc_placeholders:
173173
create_warning(
174174
app,
@@ -199,7 +199,7 @@ def insert_toctrees(app: Sphinx, doctree: nodes.document) -> None:
199199

200200
node_list: List[nodes.Element] = []
201201

202-
for toctree in doc_item.parts:
202+
for toctree in doc_item.subtrees:
203203

204204
subnode = toctree_node()
205205
subnode["parent"] = app.env.docname
@@ -211,7 +211,7 @@ def insert_toctrees(app: Sphinx, doctree: nodes.document) -> None:
211211
# TODO this wasn't in the original code,
212212
# but alabaster theme intermittently raised `KeyError('rawcaption')`
213213
subnode["rawcaption"] = toctree.caption or ""
214-
subnode["glob"] = any(isinstance(entry, GlobItem) for entry in toctree.sections)
214+
subnode["glob"] = any(isinstance(entry, GlobItem) for entry in toctree.items)
215215
subnode["hidden"] = False if toc_placeholders else toctree.hidden
216216
subnode["includehidden"] = False
217217
subnode["numbered"] = (
@@ -223,7 +223,7 @@ def insert_toctrees(app: Sphinx, doctree: nodes.document) -> None:
223223
wrappernode = nodes.compound(classes=["toctree-wrapper"])
224224
wrappernode.append(subnode)
225225

226-
for entry in toctree.sections:
226+
for entry in toctree.items:
227227

228228
if isinstance(entry, UrlItem):
229229

sphinx_external_toc/parsing.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import attr
77
import yaml
88

9-
from .api import DocItem, FileItem, GlobItem, SiteMap, TocItem, UrlItem
9+
from .api import Document, FileItem, GlobItem, SiteMap, TocTree, UrlItem
1010

1111
FILE_KEY = "file"
1212
GLOB_KEY = "glob"
@@ -51,7 +51,7 @@ def parse_toc_data(data: Dict[str, Any]) -> SiteMap:
5151

5252
def _parse_doc_item(
5353
data: Dict[str, Any], defaults: Dict[str, Any], path: str, file_key: str = FILE_KEY
54-
) -> Tuple[DocItem, Sequence[Dict[str, Any]]]:
54+
) -> Tuple[Document, Sequence[Dict[str, Any]]]:
5555
"""Parse a single doc item."""
5656
if file_key not in data:
5757
raise MalformedError(f"'{file_key}' key not found: '{path}'")
@@ -127,13 +127,15 @@ def _parse_doc_item(
127127
keywords[key] = defaults[key]
128128

129129
try:
130-
toc_item = TocItem(sections=sections, **keywords)
130+
toc_item = TocTree(items=sections, **keywords)
131131
except TypeError as exc:
132132
raise MalformedError(f"toctree validation: {path}{part_idx}") from exc
133133
parts.append(toc_item)
134134

135135
try:
136-
doc_item = DocItem(docname=data[file_key], title=data.get("title"), parts=parts)
136+
doc_item = Document(
137+
docname=data[file_key], title=data.get("title"), subtrees=parts
138+
)
137139
except TypeError as exc:
138140
raise MalformedError(f"doc validation: {path}") from exc
139141

@@ -179,7 +181,7 @@ def create_toc_dict(site_map: SiteMap, *, skip_defaults: bool = True) -> Dict[st
179181

180182

181183
def _docitem_to_dict(
182-
doc_item: DocItem,
184+
doc_item: Document,
183185
site_map: SiteMap,
184186
*,
185187
skip_defaults: bool = True,
@@ -199,7 +201,7 @@ def _docitem_to_dict(
199201
if doc_item.title is not None:
200202
data["title"] = doc_item.title
201203

202-
if not doc_item.parts:
204+
if not doc_item.subtrees:
203205
return data
204206

205207
def _parse_section(item):
@@ -221,15 +223,15 @@ def _parse_section(item):
221223
raise TypeError(item)
222224

223225
data["parts"] = []
224-
fields = attr.fields_dict(TocItem)
225-
for part in doc_item.parts:
226+
fields = attr.fields_dict(TocTree)
227+
for part in doc_item.subtrees:
226228
# only add these keys if their value is not the default
227229
part_data = {
228230
key: getattr(part, key)
229231
for key in ("caption", "numbered", "reversed", "titlesonly")
230232
if (not skip_defaults) or getattr(part, key) != fields[key].default
231233
}
232-
part_data["sections"] = [_parse_section(s) for s in part.sections]
234+
part_data["sections"] = [_parse_section(s) for s in part.items]
233235
data["parts"].append(part_data)
234236

235237
# apply shorthand if possible

sphinx_external_toc/tools.py

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

88
import yaml
99

10-
from .api import DocItem, FileItem, SiteMap, TocItem
10+
from .api import Document, FileItem, SiteMap, TocTree
1111
from .parsing import MalformedError, parse_toc_yaml
1212

1313

@@ -129,15 +129,15 @@ def create_site_map_from_path(
129129
# we add all files to the site map, even if they don't have descendants
130130
# so we may later change their title
131131
for root_file in root_files:
132-
site_map[root_file] = DocItem(root_file)
132+
site_map[root_file] = Document(root_file)
133133

134134
# while there are subfolders add them to the site-map
135135
while indexed_folders:
136136
sub_path, child_index, child_files, child_folders = indexed_folders.pop(0)
137137
for child_file in child_files:
138138
child_docname = (sub_path / child_file).relative_to(root_path).as_posix()
139139
assert child_docname not in site_map
140-
site_map[child_docname] = DocItem(child_docname)
140+
site_map[child_docname] = Document(child_docname)
141141
doc_item, new_indexed_folders = _doc_item_from_path(
142142
root_path,
143143
sub_path,
@@ -164,7 +164,7 @@ def _doc_item_from_path(
164164
default_index: str,
165165
ignore_matches: Sequence[str],
166166
):
167-
"""Return the ``DocItem`` and children folders that contain an index."""
167+
"""Return the ``Document`` and children folders that contain an index."""
168168
file_items = [
169169
FileItem((folder / name).relative_to(root).as_posix())
170170
for name in other_docnames
@@ -186,9 +186,9 @@ def _doc_item_from_path(
186186
FileItem((sub_folder / child_index).relative_to(root).as_posix())
187187
)
188188

189-
doc_item = DocItem(
189+
doc_item = Document(
190190
docname=(folder / index_docname).relative_to(root).as_posix(),
191-
parts=[TocItem(sections=file_items + index_items)] # type: ignore[arg-type]
191+
subtrees=[TocTree(items=file_items + index_items)] # type: ignore[arg-type]
192192
if (file_items or index_items)
193193
else [],
194194
)

0 commit comments

Comments
 (0)