8
8
9
9
from .api import Document , FileItem , GlobItem , SiteMap , TocTree , UrlItem
10
10
11
+ DEFAULT_SUBTREES_KEY = "parts"
12
+ DEFAULT_ITEMS_KEY = "sections"
13
+
11
14
FILE_KEY = "file"
12
15
GLOB_KEY = "glob"
13
16
URL_KEY = "url"
@@ -50,100 +53,109 @@ def parse_toc_data(data: Dict[str, Any]) -> SiteMap:
50
53
51
54
52
55
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 ,
54
63
) -> Tuple [Document , Sequence [Dict [str , Any ]]]:
55
64
"""Parse a single doc item."""
56
65
if file_key not in data :
57
66
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 } '" )
67
78
else :
68
- parts_data = []
79
+ subtrees_data = []
69
80
70
81
_known_link_keys = {FILE_KEY , GLOB_KEY , URL_KEY }
71
82
72
- parts = []
73
- for part_idx , part in enumerate (parts_data ):
83
+ toctrees = []
84
+ for toc_idx , toc_data in enumerate (subtrees_data ):
74
85
75
- if not (isinstance (part , Mapping ) and "sections" in part ):
86
+ if not (isinstance (toc_data , Mapping ) and items_key in toc_data ):
76
87
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 } '"
78
89
)
79
90
80
- section_data = part [ "sections" ]
91
+ items_data = toc_data [ items_key ]
81
92
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
+ )
84
97
85
98
# 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 ):
88
101
89
- if not isinstance (section , Mapping ):
102
+ if not isinstance (item_data , Mapping ):
90
103
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 } '"
92
105
)
93
106
94
- link_keys = _known_link_keys .intersection (section )
107
+ link_keys = _known_link_keys .intersection (item_data )
108
+
109
+ # validation checks
95
110
if not link_keys :
96
111
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 } '"
99
114
)
100
115
if not len (link_keys ) == 1 :
101
116
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 } "
104
119
)
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
+ )
105
127
106
128
if link_keys == {FILE_KEY }:
107
- sections .append (FileItem (section [FILE_KEY ]))
129
+ items .append (FileItem (item_data [FILE_KEY ]))
108
130
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 ]))
115
132
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" )))
122
134
123
135
# 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 }
125
137
for key in defaults :
126
138
if key not in keywords :
127
139
keywords [key ] = defaults [key ]
128
140
129
141
try :
130
- toc_item = TocTree (items = sections , ** keywords )
142
+ toc_item = TocTree (items = items , ** keywords )
131
143
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 )
134
146
135
147
try :
136
148
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
138
150
)
139
151
except TypeError as exc :
140
152
raise MalformedError (f"doc validation: { path } " ) from exc
141
153
142
154
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
147
159
]
148
160
149
161
return (
@@ -185,6 +197,8 @@ def _docitem_to_dict(
185
197
site_map : SiteMap ,
186
198
* ,
187
199
skip_defaults : bool = True ,
200
+ subtrees_key : str = DEFAULT_SUBTREES_KEY ,
201
+ items_key : str = DEFAULT_ITEMS_KEY ,
188
202
file_key : str = FILE_KEY ,
189
203
parsed_docnames : Optional [Set [str ]] = None ,
190
204
) -> Dict [str , Any ]:
@@ -204,7 +218,7 @@ def _docitem_to_dict(
204
218
if not doc_item .subtrees :
205
219
return data
206
220
207
- def _parse_section (item ):
221
+ def _parse_item (item ):
208
222
if isinstance (item , FileItem ):
209
223
if item in site_map :
210
224
return _docitem_to_dict (
@@ -222,20 +236,26 @@ def _parse_section(item):
222
236
return {URL_KEY : item .url }
223
237
raise TypeError (item )
224
238
225
- data ["parts" ] = []
239
+ data [subtrees_key ] = []
226
240
fields = attr .fields_dict (TocTree )
227
- for part in doc_item .subtrees :
241
+ for toctree in doc_item .subtrees :
228
242
# 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
233
247
}
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
+ }
240
260
241
261
return data
0 commit comments