1- import os
2- import fnmatch
31from collections import OrderedDict , namedtuple
2+ import fnmatch
3+ import os
4+ import pathlib
45import re
56
6- import anyascii
7- from docutils .parsers .rst import convert_directive_function
87from jinja2 import Environment , FileSystemLoader , TemplateNotFound
98import sphinx
109import sphinx .util
1312from sphinx .util .osutil import ensuredir
1413import sphinx .util .logging
1514
16- from ..settings import API_ROOT , TEMPLATE_DIR
15+ from ..settings import TEMPLATE_DIR
1716
1817LOGGER = sphinx .util .logging .getLogger (__name__ )
1918_OWN_PAGE_LEVELS = [
2423 "function" ,
2524 "method" ,
2625 "property" ,
27- "attribute" ,
2826 "data" ,
27+ "attribute" ,
2928]
3029
3130Path = namedtuple ("Path" , ["absolute" , "relative" ])
3231
3332
3433class PythonMapperBase :
35-
3634 """Base object for JSON -> Python object mapping.
3735
3836 Subclasses of this object will handle their language specific JSON input,
3937 and map that onto this standard Python object.
4038 Subclasses may also include language-specific attributes on this object.
4139
42- Arguments:
43-
4440 Args:
4541 obj: JSON object representing this object
4642 jinja_env: A template environment for rendering this object
4743
48- Required attributes:
49-
5044 Attributes:
5145 id (str): A globally unique identifier for this object.
5246 Generally a fully qualified name, including namespace.
@@ -56,25 +50,21 @@ class PythonMapperBase:
5650 children (list): Children of this object
5751 parameters (list): Parameters to this object
5852 methods (list): Methods on this object
59-
60- Optional attributes:
61-
6253 """
6354
6455 language = "base"
6556 type = "base"
66- # Create a page in the output for this object.
67- top_level_object = False
6857 _RENDER_LOG_LEVEL = "VERBOSE"
6958
70- def __init__ (self , obj , jinja_env , app , options = None ):
59+ def __init__ (self , obj , jinja_env , app , url_root , options = None ):
7160 self .app = app
7261 self .obj = obj
7362 self .options = options
7463 self .jinja_env = jinja_env
75- self .url_root = os . path . join ( "/" , API_ROOT )
64+ self .url_root = url_root
7665
7766 self .name = None
67+ self .qual_name = None
7868 self .id = None
7969
8070 def __getstate__ (self ):
@@ -104,7 +94,7 @@ def rendered(self):
10494 def get_context_data (self ):
10595 own_page_level = self .app .config .autoapi_own_page_level
10696 desired_page_level = _OWN_PAGE_LEVELS .index (own_page_level )
107- own_page_types = set (_OWN_PAGE_LEVELS [:desired_page_level + 1 ])
97+ own_page_types = set (_OWN_PAGE_LEVELS [: desired_page_level + 1 ])
10898
10999 return {
110100 "autoapi_options" : self .app .config .autoapi_options ,
@@ -128,28 +118,19 @@ def short_name(self):
128118 """Shorten name property"""
129119 return self .name .split ("." )[- 1 ]
130120
131- @property
132- def pathname (self ):
133- """Sluggified path for filenames
121+ def output_dir (self , root ):
122+ """The directory to render this object."""
123+ module = self .id [: - (len ("." + self .qual_name ))]
124+ parts = [root ] + module .split ("." )
125+ return pathlib .PosixPath (* parts )
134126
135- Slugs to a filename using the follow steps
127+ def output_filename (self ):
128+ """The name of the file to render into, without a file suffix."""
129+ filename = self .qual_name
130+ if filename == "index" :
131+ filename = ".index"
136132
137- * Decode unicode to approximate ascii
138- * Remove existing hyphens
139- * Substitute hyphens for non-word characters
140- * Break up the string as paths
141- """
142- slug = self .name
143- slug = anyascii .anyascii (slug )
144- slug = slug .replace ("-" , "" )
145- slug = re .sub (r"[^\w\.]+" , "-" , slug ).strip ("-" )
146- return os .path .join (* slug .split ("." ))
147-
148- def include_dir (self , root ):
149- """Return directory of file"""
150- parts = [root ]
151- parts .extend (self .pathname .split (os .path .sep ))
152- return "/" .join (parts )
133+ return filename
153134
154135 @property
155136 def include_path (self ):
@@ -158,9 +139,7 @@ def include_path(self):
158139 This is used in ``toctree`` directives, as Sphinx always expects Unix
159140 path separators
160141 """
161- parts = [self .include_dir (root = self .url_root )]
162- parts .append ("index" )
163- return "/" .join (parts )
142+ return str (self .output_dir (self .url_root ) / self .output_filename ())
164143
165144 @property
166145 def display (self ):
@@ -180,14 +159,13 @@ def ref_directive(self):
180159
181160
182161class SphinxMapperBase :
183-
184162 """Base class for mapping `PythonMapperBase` objects to Sphinx.
185163
186164 Args:
187165 app: Sphinx application instance
188166 """
189167
190- def __init__ (self , app , template_dir = None , url_root = None ):
168+ def __init__ (self , app , template_dir = None , dir_root = None , url_root = None ):
191169 self .app = app
192170
193171 template_paths = [TEMPLATE_DIR ]
@@ -211,8 +189,9 @@ def _wrapped_prepare(value):
211189
212190 own_page_level = self .app .config .autoapi_own_page_level
213191 desired_page_level = _OWN_PAGE_LEVELS .index (own_page_level )
214- self .own_page_types = set (_OWN_PAGE_LEVELS [:desired_page_level + 1 ])
192+ self .own_page_types = set (_OWN_PAGE_LEVELS [: desired_page_level + 1 ])
215193
194+ self .dir_root = dir_root
216195 self .url_root = url_root
217196
218197 # Mapping of {filepath -> raw data}
@@ -300,14 +279,17 @@ def add_object(self, obj):
300279 Args:
301280 obj: Instance of a AutoAPI object
302281 """
303- if obj .type in self .own_page_types :
282+ display = obj .display
283+ if display and obj .type in self .own_page_types :
304284 self .objects_to_render [obj .id ] = obj
305285
306286 self .all_objects [obj .id ] = obj
307287 child_stack = list (obj .children )
308288 while child_stack :
309289 child = child_stack .pop ()
310290 self .all_objects [child .id ] = child
291+ if display and child .type in self .own_page_types :
292+ self .objects_to_render [child .id ] = child
311293 child_stack .extend (getattr (child , "children" , ()))
312294
313295 def map (self , options = None ):
@@ -329,81 +311,32 @@ def create_class(self, data, options=None, **kwargs):
329311 """
330312 raise NotImplementedError
331313
332- def output_child_rst (self , obj , obj_parent , detail_dir , source_suffix ):
333-
334- if not obj .display :
335- return
336-
337- # Skip nested cases like functions in functions or clases in clases
338- if obj .type == obj_parent .type :
339- return
340-
341- obj_child_page_level = _OWN_PAGE_LEVELS .index (obj .type )
342- desired_page_level = _OWN_PAGE_LEVELS .index (self .app .config .autoapi_own_page_level )
343- is_own_page = obj_child_page_level <= desired_page_level
344- if not is_own_page :
345- return
346-
347- obj_child_rst = obj .render (
348- is_own_page = is_own_page ,
349- )
350- if not obj_child_rst :
351- return
352-
353- function_page_level = _OWN_PAGE_LEVELS .index ("function" )
354- is_level_beyond_function = function_page_level < desired_page_level
355- if obj .type in ["exception" , "class" ]:
356- if not is_level_beyond_function :
357- outfile = f"{ obj .short_name } { source_suffix } "
358- path = os .path .join (detail_dir , outfile )
359- else :
360- outdir = os .path .join (detail_dir , obj .short_name )
361- ensuredir (outdir )
362- path = os .path .join (outdir , f"index{ source_suffix } " )
363- else :
364- is_parent_in_detail_dir = detail_dir .endswith (obj_parent .short_name )
365- outdir = detail_dir if is_parent_in_detail_dir else os .path .join (detail_dir , obj_parent .short_name )
366- ensuredir (outdir )
367- path = os .path .join (outdir , f"{ obj .short_name } { source_suffix } " )
368-
369- with open (path , "wb+" ) as obj_child_detail_file :
370- obj_child_detail_file .write (obj_child_rst .encode ("utf-8" ))
371-
372- for obj_child in obj .children :
373- child_detail_dir = os .path .join (detail_dir , obj .name )
374- self .output_child_rst (obj_child , obj , child_detail_dir , source_suffix )
375-
376- def output_rst (self , root , source_suffix ):
314+ def output_rst (self , source_suffix ):
377315 for _ , obj in status_iterator (
378316 self .objects_to_render .items (),
379317 colorize ("bold" , "[AutoAPI] " ) + "Rendering Data... " ,
380318 length = len (self .objects_to_render ),
381319 verbosity = 1 ,
382320 stringify_func = (lambda x : x [0 ]),
383321 ):
384- if not obj .display :
385- continue
386-
387322 rst = obj .render (is_own_page = True )
388323 if not rst :
389324 continue
390325
391- detail_dir = obj .include_dir (root = root )
392- ensuredir (detail_dir )
393- path = os .path .join (detail_dir , f"index{ source_suffix } " )
326+ output_dir = obj .output_dir (self .dir_root )
327+ ensuredir (output_dir )
328+ output_path = output_dir / obj .output_filename ()
329+ path = f"{ output_path } { source_suffix } "
394330 with open (path , "wb+" ) as detail_file :
395331 detail_file .write (rst .encode ("utf-8" ))
396-
397- for child in obj .children :
398- self .output_child_rst (child , obj , detail_dir , source_suffix )
399332
400333 if self .app .config .autoapi_add_toctree_entry :
401- self ._output_top_rst (root )
334+ self ._output_top_rst ()
402335
403- def _output_top_rst (self , root ):
336+ def _output_top_rst (self ):
404337 # Render Top Index
405- top_level_index = os .path .join (root , "index.rst" )
406- pages = self .objects_to_render .values ()
338+ top_level_index = os .path .join (self . dir_root , "index.rst" )
339+ pages = [ obj for obj in self .objects_to_render .values () if obj . display ]
407340 with open (top_level_index , "wb" ) as top_level_file :
408341 content = self .jinja_env .get_template ("index.rst" )
409342 top_level_file .write (content .render (pages = pages ).encode ("utf-8" ))
0 commit comments