88
99from .api import Document , FileItem , GlobItem , SiteMap , TocTree , UrlItem
1010
11+ DEFAULT_SUBTREES_KEY = "parts"
12+ DEFAULT_ITEMS_KEY = "sections"
13+
1114FILE_KEY = "file"
1215GLOB_KEY = "glob"
1316URL_KEY = "url"
@@ -50,100 +53,109 @@ def parse_toc_data(data: Dict[str, Any]) -> SiteMap:
5053
5154
5255def _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
0 commit comments