Skip to content

Commit 007077a

Browse files
committed
Refactor mapper classes into their bases
This separate used to exist to support parsing multiple languages, which we no longer do.
1 parent a6558dc commit 007077a

File tree

17 files changed

+374
-567
lines changed

17 files changed

+374
-567
lines changed

autoapi/mappers/python/astroid_utils.py renamed to autoapi/_astroid_utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44

55
import astroid
66
import astroid.nodes
7-
87
import astroid.nodes.node_classes
9-
import sphinx.util.logging
10-
11-
_LOGGER = sphinx.util.logging.getLogger(__name__)
128

139

1410
def resolve_import_alias(name, import_names):

autoapi/mappers/python/mapper.py renamed to autoapi/_mapper.py

Lines changed: 151 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import collections
22
import copy
3+
import fnmatch
34
import operator
45
import os
56
import re
67

8+
from jinja2 import Environment, FileSystemLoader
9+
import sphinx
710
import sphinx.environment
811
from sphinx.errors import ExtensionError
912
import sphinx.util
13+
import sphinx.util.logging
1014
from sphinx.util.console import colorize
1115
from sphinx.util.display import status_iterator
1216
import sphinx.util.docstrings
13-
import sphinx.util.logging
17+
from sphinx.util.osutil import ensuredir
1418

15-
from ..base import SphinxMapperBase
16-
from .parser import Parser
17-
from .objects import (
19+
from ._parser import Parser
20+
from ._objects import (
1821
PythonClass,
1922
PythonFunction,
2023
PythonModule,
@@ -26,6 +29,7 @@
2629
PythonException,
2730
TopLevelPythonPythonMapper,
2831
)
32+
from .settings import OWN_PAGE_LEVELS, TEMPLATE_DIR
2933

3034
LOGGER = sphinx.util.logging.getLogger(__name__)
3135

@@ -219,13 +223,11 @@ def _link_objs(value):
219223
return result[:-2]
220224

221225

222-
class PythonSphinxMapper(SphinxMapperBase):
223-
"""AutoAPI domain handler for Python
224-
225-
Parses directly from Python files.
226+
class Mapper:
227+
"""Base class for mapping `PythonMapperBase` objects to Sphinx.
226228
227229
Args:
228-
app: Sphinx application passed in as part of the extension
230+
app: Sphinx application instance
229231
"""
230232

231233
_OBJ_MAP = {
@@ -244,13 +246,142 @@ class PythonSphinxMapper(SphinxMapperBase):
244246
}
245247

246248
def __init__(self, app, template_dir=None, dir_root=None, url_root=None):
247-
super().__init__(app, template_dir, dir_root, url_root)
249+
self.app = app
250+
251+
template_paths = [TEMPLATE_DIR]
252+
253+
if template_dir:
254+
# Put at the front so it's loaded first
255+
template_paths.insert(0, template_dir)
256+
257+
self.jinja_env = Environment(
258+
loader=FileSystemLoader(template_paths),
259+
trim_blocks=True,
260+
lstrip_blocks=True,
261+
)
262+
263+
def _wrapped_prepare(value):
264+
return value
265+
266+
self.jinja_env.filters["prepare_docstring"] = _wrapped_prepare
267+
if self.app.config.autoapi_prepare_jinja_env:
268+
self.app.config.autoapi_prepare_jinja_env(self.jinja_env)
269+
270+
own_page_level = self.app.config.autoapi_own_page_level
271+
desired_page_level = OWN_PAGE_LEVELS.index(own_page_level)
272+
self.own_page_types = set(OWN_PAGE_LEVELS[: desired_page_level + 1])
273+
274+
self.dir_root = dir_root
275+
self.url_root = url_root
276+
277+
# Mapping of {filepath -> raw data}
278+
self.paths = collections.OrderedDict()
279+
# Mapping of {object id -> Python Object}
280+
self.objects_to_render = collections.OrderedDict()
281+
# Mapping of {object id -> Python Object}
282+
self.all_objects = collections.OrderedDict()
283+
# Mapping of {namespace id -> Python Object}
284+
self.namespaces = collections.OrderedDict()
248285

249286
self.jinja_env.filters["link_objs"] = _link_objs
250287
self._use_implicit_namespace = (
251288
self.app.config.autoapi_python_use_implicit_namespaces
252289
)
253290

291+
@staticmethod
292+
def find_files(patterns, dirs, ignore):
293+
if not ignore:
294+
ignore = []
295+
296+
pattern_regexes = []
297+
for pattern in patterns:
298+
regex = re.compile(fnmatch.translate(pattern).replace(".*", "(.*)"))
299+
pattern_regexes.append((pattern, regex))
300+
301+
for _dir in dirs:
302+
for root, _, filenames in os.walk(_dir):
303+
seen = set()
304+
for pattern, pattern_re in pattern_regexes:
305+
for filename in fnmatch.filter(filenames, pattern):
306+
skip = False
307+
308+
match = re.match(pattern_re, filename)
309+
norm_name = match.groups()
310+
if norm_name in seen:
311+
continue
312+
313+
# Skip ignored files
314+
for ignore_pattern in ignore:
315+
if fnmatch.fnmatch(
316+
os.path.join(root, filename), ignore_pattern
317+
):
318+
LOGGER.info(
319+
colorize("bold", "[AutoAPI] ")
320+
+ colorize(
321+
"darkgreen", f"Ignoring {root}/{filename}"
322+
)
323+
)
324+
skip = True
325+
326+
if skip:
327+
continue
328+
329+
# Make sure the path is full
330+
if not os.path.isabs(filename):
331+
filename = os.path.join(root, filename)
332+
333+
yield filename
334+
seen.add(norm_name)
335+
336+
def add_object(self, obj):
337+
"""Add object to local and app environment storage
338+
339+
Args:
340+
obj: Instance of a AutoAPI object
341+
"""
342+
display = obj.display
343+
if display and obj.type in self.own_page_types:
344+
self.objects_to_render[obj.id] = obj
345+
346+
self.all_objects[obj.id] = obj
347+
child_stack = list(obj.children)
348+
while child_stack:
349+
child = child_stack.pop()
350+
self.all_objects[child.id] = child
351+
if display and child.type in self.own_page_types:
352+
self.objects_to_render[child.id] = child
353+
child_stack.extend(getattr(child, "children", ()))
354+
355+
def output_rst(self, source_suffix):
356+
for _, obj in status_iterator(
357+
self.objects_to_render.items(),
358+
colorize("bold", "[AutoAPI] ") + "Rendering Data... ",
359+
length=len(self.objects_to_render),
360+
verbosity=1,
361+
stringify_func=(lambda x: x[0]),
362+
):
363+
rst = obj.render(is_own_page=True)
364+
if not rst:
365+
continue
366+
367+
output_dir = obj.output_dir(self.dir_root)
368+
ensuredir(output_dir)
369+
output_path = output_dir / obj.output_filename()
370+
path = f"{output_path}{source_suffix}"
371+
with open(path, "wb+") as detail_file:
372+
detail_file.write(rst.encode("utf-8"))
373+
374+
if self.app.config.autoapi_add_toctree_entry:
375+
self._output_top_rst()
376+
377+
def _output_top_rst(self):
378+
# Render Top Index
379+
top_level_index = os.path.join(self.dir_root, "index.rst")
380+
pages = [obj for obj in self.objects_to_render.values() if obj.display]
381+
with open(top_level_index, "wb") as top_level_file:
382+
content = self.jinja_env.get_template("index.rst")
383+
top_level_file.write(content.render(pages=pages).encode("utf-8"))
384+
254385
def _need_to_load(self, files):
255386
last_files = getattr(self.app.env, "autoapi_source_files", [])
256387
self.app.env.autoapi_source_files = files
@@ -361,7 +492,14 @@ def map(self, options=None):
361492
self._hide_yo_kids()
362493
self.app.env.autoapi_annotations = {}
363494

364-
super().map(options)
495+
for _, data in status_iterator(
496+
self.paths.items(),
497+
colorize("bold", "[AutoAPI] ") + "Mapping Data... ",
498+
length=len(self.paths),
499+
stringify_func=(lambda x: x[0]),
500+
):
501+
for obj in self.create_class(data, options=options):
502+
self.add_object(obj)
365503

366504
top_level_objects = {
367505
obj.id: obj
@@ -383,7 +521,7 @@ def map(self, options=None):
383521
self.app.env.autoapi_objects = self.objects_to_render
384522
self.app.env.autoapi_all_objects = self.all_objects
385523

386-
def create_class(self, data, options=None, **kwargs):
524+
def create_class(self, data, options=None):
387525
"""Create a class from the passed in data
388526
389527
Args:
@@ -402,13 +540,10 @@ def create_class(self, data, options=None, **kwargs):
402540
jinja_env=self.jinja_env,
403541
app=self.app,
404542
url_root=self.url_root,
405-
**kwargs,
406543
)
407544

408545
for child_data in data.get("children", []):
409-
for child_obj in self.create_class(
410-
child_data, options=options, **kwargs
411-
):
546+
for child_obj in self.create_class(child_data, options=options):
412547
obj.children.append(child_obj)
413548

414549
# Some objects require children to establish their docstring

0 commit comments

Comments
 (0)