1111DEFAULT_SUBTREES_KEY = "subtrees"
1212DEFAULT_ITEMS_KEY = "items"
1313FILE_FORMAT_KEY = "format"
14+ ROOT_KEY = "root"
1415FILE_KEY = "file"
1516GLOB_KEY = "glob"
1617URL_KEY = "url"
@@ -97,7 +98,7 @@ def parse_toc_data(data: Dict[str, Any]) -> SiteMap:
9798 defaults : Dict [str , Any ] = {** file_format .toc_defaults , ** data .get ("defaults" , {})}
9899
99100 doc_item , docs_list = _parse_doc_item (
100- data , defaults , "/" , depth = 0 , file_key = "root" , file_format = file_format
101+ data , defaults , "/" , depth = 0 , is_root = True , file_format = file_format
101102 )
102103
103104 site_map = SiteMap (
@@ -118,11 +119,12 @@ def _parse_doc_item(
118119 * ,
119120 depth : int ,
120121 file_format : FileFormat ,
121- file_key : str = FILE_KEY ,
122- ) -> Tuple [Document , Sequence [Dict [str , Any ]]]:
122+ is_root : bool = False ,
123+ ) -> Tuple [Document , Sequence [Tuple [ str , Dict [str , Any ] ]]]:
123124 """Parse a single doc item."""
125+ file_key = ROOT_KEY if is_root else FILE_KEY
124126 if file_key not in data :
125- raise MalformedError (f"'{ file_key } ' key not found: '{ path } '" )
127+ raise MalformedError (f"'{ file_key } ' key not found @ '{ path } '" )
126128
127129 subtrees_key = file_format .get_subtrees_key (depth )
128130 items_key = file_format .get_items_key (depth )
@@ -142,20 +144,23 @@ def _parse_doc_item(
142144 if not allowed_keys .issuperset (data .keys ()):
143145 unknown_keys = set (data .keys ()).difference (allowed_keys )
144146 raise MalformedError (
145- f"Unknown keys found: { unknown_keys !r} , allowed: { allowed_keys !r} : '{ path } '"
147+ f"Unknown keys found: { unknown_keys !r} , allowed: { allowed_keys !r} @ '{ path } '"
146148 )
147149
150+ shorthand_used = False
148151 if items_key in data :
149152 # this is a shorthand for defining a single subtree
150153 if subtrees_key in data :
151154 raise MalformedError (
152- f"Both '{ subtrees_key } ' and '{ items_key } ' found: '{ path } '"
155+ f"Both '{ subtrees_key } ' and '{ items_key } ' found @ '{ path } '"
153156 )
154157 subtrees_data = [{items_key : data [items_key ], ** data .get ("options" , {})}]
158+ shorthand_used = True
155159 elif subtrees_key in data :
156160 subtrees_data = data [subtrees_key ]
157161 if not (isinstance (subtrees_data , Sequence ) and subtrees_data ):
158- raise MalformedError (f"'{ subtrees_key } ' not a non-empty list: '{ path } '" )
162+ raise MalformedError (f"'{ subtrees_key } ' not a non-empty list @ '{ path } '" )
163+ path = f"{ path } { subtrees_key } /"
159164 else :
160165 subtrees_data = []
161166
@@ -164,46 +169,46 @@ def _parse_doc_item(
164169 toctrees = []
165170 for toc_idx , toc_data in enumerate (subtrees_data ):
166171
172+ toc_path = path if shorthand_used else f"{ path } { toc_idx } /"
173+
167174 if not (isinstance (toc_data , Mapping ) and items_key in toc_data ):
168175 raise MalformedError (
169- f"subtree not a mapping containing '{ items_key } ' key: ' { path } { toc_idx } '"
176+ f"item not a mapping containing '{ items_key } ' key @ ' { toc_path } '"
170177 )
171178
172179 items_data = toc_data [items_key ]
173180
174181 if not (isinstance (items_data , Sequence ) and items_data ):
175- raise MalformedError (
176- f"'{ items_key } ' not a non-empty list: '{ path } { toc_idx } '"
177- )
182+ raise MalformedError (f"'{ items_key } ' not a non-empty list @ '{ toc_path } '" )
178183
179184 # generate items list
180185 items : List [Union [GlobItem , FileItem , UrlItem ]] = []
181186 for item_idx , item_data in enumerate (items_data ):
182187
183188 if not isinstance (item_data , Mapping ):
184189 raise MalformedError (
185- f"' { items_key } ' item not a mapping type: ' { path } { toc_idx } /{ item_idx } '"
190+ f"item not a mapping type @ ' { toc_path } { items_key } /{ item_idx } '"
186191 )
187192
188193 link_keys = _known_link_keys .intersection (item_data )
189194
190195 # validation checks
191196 if not link_keys :
192197 raise MalformedError (
193- f"' { items_key } ' item does not contain one of "
194- f"{ _known_link_keys !r} : ' { path } { toc_idx } /{ item_idx } '"
198+ f"item does not contain one of "
199+ f"{ _known_link_keys !r} @ ' { toc_path } { items_key } /{ item_idx } '"
195200 )
196201 if not len (link_keys ) == 1 :
197202 raise MalformedError (
198- f"' { items_key } ' item contains incompatible keys "
199- f"{ link_keys !r} : { path } { toc_idx } /{ item_idx } "
203+ f"item contains incompatible keys "
204+ f"{ link_keys !r} @ ' { toc_path } { items_key } /{ item_idx } ' "
200205 )
201206 for item_key in (GLOB_KEY , URL_KEY ):
202207 for other_key in (subtrees_key , items_key ):
203208 if link_keys == {item_key } and other_key in item_data :
204209 raise MalformedError (
205- f"' { items_key } ' item contains incompatible keys "
206- f"'{ item_key } ' and '{ other_key } ': { path } { toc_idx } /{ item_idx } "
210+ f"item contains incompatible keys "
211+ f"'{ item_key } ' and '{ other_key } ' @ ' { toc_path } { items_key } /{ item_idx } ' "
207212 )
208213
209214 if link_keys == {FILE_KEY }:
@@ -222,7 +227,7 @@ def _parse_doc_item(
222227 try :
223228 toc_item = TocTree (items = items , ** keywords )
224229 except TypeError as exc :
225- raise MalformedError (f"toctree validation: { path } { toc_idx } " ) from exc
230+ raise MalformedError (f"toctree validation @ ' { toc_path } ' " ) from exc
226231 toctrees .append (toc_item )
227232
228233 try :
@@ -232,21 +237,27 @@ def _parse_doc_item(
232237 except TypeError as exc :
233238 raise MalformedError (f"doc validation: { path } " ) from exc
234239
235- docs_data = [
236- item_data
237- for toc_data in subtrees_data
238- for item_data in toc_data [items_key ]
240+ # list of docs that need to be parsed recursively (and path)
241+ docs_to_be_parsed_list = [
242+ (
243+ f"{ path } /{ items_key } /{ ii } /"
244+ if shorthand_used
245+ else f"{ path } { ti } /{ items_key } /{ ii } /" ,
246+ item_data ,
247+ )
248+ for ti , toc_data in enumerate (subtrees_data )
249+ for ii , item_data in enumerate (toc_data [items_key ])
239250 if FILE_KEY in item_data
240251 ]
241252
242253 return (
243254 doc_item ,
244- docs_data ,
255+ docs_to_be_parsed_list ,
245256 )
246257
247258
248259def _parse_docs_list (
249- docs_list : Sequence [Dict [str , Any ]],
260+ docs_list : Sequence [Tuple [ str , Dict [str , Any ] ]],
250261 site_map : SiteMap ,
251262 defaults : Dict [str , Any ],
252263 path : str ,
@@ -255,11 +266,10 @@ def _parse_docs_list(
255266 file_format : FileFormat ,
256267):
257268 """Parse a list of docs."""
258- for doc_data in docs_list :
259- docname = doc_data ["file" ]
269+ for child_path , doc_data in docs_list :
270+ docname = doc_data [FILE_KEY ]
260271 if docname in site_map :
261- raise MalformedError (f"document file used multiple times: { docname } " )
262- child_path = f"{ path } { docname } /"
272+ raise MalformedError (f"document file used multiple times: '{ docname } '" )
263273 child_item , child_docs_list = _parse_doc_item (
264274 doc_data , defaults , child_path , depth = depth , file_format = file_format
265275 )
@@ -280,13 +290,13 @@ def create_toc_dict(site_map: SiteMap, *, skip_defaults: bool = True) -> Dict[st
280290 try :
281291 file_format = FILE_FORMATS [site_map .file_format or "default" ]
282292 except KeyError :
283- raise KeyError (f"File format not recognised: '{ site_map .file_format } '" )
293+ raise KeyError (f"File format not recognised @ '{ site_map .file_format } '" )
284294 data = _docitem_to_dict (
285295 site_map .root ,
286296 site_map ,
287297 depth = 0 ,
288298 skip_defaults = skip_defaults ,
289- file_key = "root" ,
299+ is_root = True ,
290300 file_format = file_format ,
291301 )
292302 if site_map .meta :
@@ -304,14 +314,15 @@ def _docitem_to_dict(
304314 depth : int ,
305315 file_format : FileFormat ,
306316 skip_defaults : bool = True ,
307- file_key : str = FILE_KEY ,
317+ is_root : bool = False ,
308318 parsed_docnames : Optional [Set [str ]] = None ,
309319) -> Dict [str , Any ]:
310320 """
311321
312322 :param skip_defaults: do not add key/values for values that are already the default
313323
314324 """
325+ file_key = ROOT_KEY if is_root else FILE_KEY
315326 subtrees_key = file_format .get_subtrees_key (depth )
316327 items_key = file_format .get_items_key (depth )
317328
0 commit comments