1"""
2    sphinx.ext.autosummary
3    ~~~~~~~~~~~~~~~~~~~~~~
4
5    Sphinx extension that adds an autosummary:: directive, which can be
6    used to generate function/method/attribute/etc. summary lists, similar
7    to those output eg. by Epydoc and other API doc generation tools.
8
9    An :autolink: role is also provided.
10
11    autosummary directive
12    ---------------------
13
14    The autosummary directive has the form::
15
16        .. autosummary::
17           :nosignatures:
18           :toctree: generated/
19
20           module.function_1
21           module.function_2
22           ...
23
24    and it generates an output table (containing signatures, optionally)
25
26        ========================  =============================================
27        module.function_1(args)   Summary line from the docstring of function_1
28        module.function_2(args)   Summary line from the docstring
29        ...
30        ========================  =============================================
31
32    If the :toctree: option is specified, files matching the function names
33    are inserted to the toctree with the given prefix:
34
35        generated/module.function_1
36        generated/module.function_2
37        ...
38
39    Note: The file names contain the module:: or currentmodule:: prefixes.
40
41    .. seealso:: autosummary_generate.py
42
43
44    autolink role
45    -------------
46
47    The autolink role functions as ``:obj:`` when the name referred can be
48    resolved to a Python object, and otherwise it becomes simple emphasis.
49    This can be used as the default role to make links 'smart'.
50
51    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
52    :license: BSD, see LICENSE for details.
53"""
54
55import inspect
56import os
57import posixpath
58import re
59import sys
60import warnings
61from os import path
62from types import ModuleType
63from typing import Any, Dict, List, Tuple, cast
64
65from docutils import nodes
66from docutils.nodes import Element, Node, system_message
67from docutils.parsers.rst import directives
68from docutils.parsers.rst.states import Inliner, RSTStateMachine, Struct, state_classes
69from docutils.statemachine import StringList
70
71import sphinx
72from sphinx import addnodes
73from sphinx.application import Sphinx
74from sphinx.config import Config
75from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
76from sphinx.environment import BuildEnvironment
77from sphinx.environment.adapters.toctree import TocTree
78from sphinx.ext.autodoc import INSTANCEATTR, Documenter
79from sphinx.ext.autodoc.directive import DocumenterBridge, Options
80from sphinx.ext.autodoc.importer import import_module
81from sphinx.ext.autodoc.mock import mock
82from sphinx.locale import __
83from sphinx.pycode import ModuleAnalyzer, PycodeError
84from sphinx.util import logging, rst
85from sphinx.util.docutils import (NullReporter, SphinxDirective, SphinxRole, new_document,
86                                  switch_source_input)
87from sphinx.util.matching import Matcher
88from sphinx.writers.html import HTMLTranslator
89
90if False:
91    # For type annotation
92    from typing import Type  # for python3.5.1
93
94
95logger = logging.getLogger(__name__)
96
97
98periods_re = re.compile(r'\.(?:\s+)')
99literal_re = re.compile(r'::\s*$')
100
101WELL_KNOWN_ABBREVIATIONS = ('et al.', ' i.e.',)
102
103
104# -- autosummary_toc node ------------------------------------------------------
105
106class autosummary_toc(nodes.comment):
107    pass
108
109
110def process_autosummary_toc(app: Sphinx, doctree: nodes.document) -> None:
111    """Insert items described in autosummary:: to the TOC tree, but do
112    not generate the toctree:: list.
113    """
114    warnings.warn('process_autosummary_toc() is deprecated',
115                  RemovedInSphinx50Warning, stacklevel=2)
116    env = app.builder.env
117    crawled = {}
118
119    def crawl_toc(node: Element, depth: int = 1) -> None:
120        crawled[node] = True
121        for j, subnode in enumerate(node):
122            try:
123                if (isinstance(subnode, autosummary_toc) and
124                        isinstance(subnode[0], addnodes.toctree)):
125                    TocTree(env).note(env.docname, subnode[0])
126                    continue
127            except IndexError:
128                continue
129            if not isinstance(subnode, nodes.section):
130                continue
131            if subnode not in crawled:
132                crawl_toc(subnode, depth + 1)
133    crawl_toc(doctree)
134
135
136def autosummary_toc_visit_html(self: nodes.NodeVisitor, node: autosummary_toc) -> None:
137    """Hide autosummary toctree list in HTML output."""
138    raise nodes.SkipNode
139
140
141def autosummary_noop(self: nodes.NodeVisitor, node: Node) -> None:
142    pass
143
144
145# -- autosummary_table node ----------------------------------------------------
146
147class autosummary_table(nodes.comment):
148    pass
149
150
151def autosummary_table_visit_html(self: HTMLTranslator, node: autosummary_table) -> None:
152    """Make the first column of the table non-breaking."""
153    try:
154        table = cast(nodes.table, node[0])
155        tgroup = cast(nodes.tgroup, table[0])
156        tbody = cast(nodes.tbody, tgroup[-1])
157        rows = cast(List[nodes.row], tbody)
158        for row in rows:
159            col1_entry = cast(nodes.entry, row[0])
160            par = cast(nodes.paragraph, col1_entry[0])
161            for j, subnode in enumerate(list(par)):
162                if isinstance(subnode, nodes.Text):
163                    new_text = subnode.astext().replace(" ", "\u00a0")
164                    par[j] = nodes.Text(new_text)
165    except IndexError:
166        pass
167
168
169# -- autodoc integration -------------------------------------------------------
170
171# current application object (used in `get_documenter()`).
172_app = None  # type: Sphinx
173
174
175class FakeDirective(DocumenterBridge):
176    def __init__(self) -> None:
177        settings = Struct(tab_width=8)
178        document = Struct(settings=settings)
179        env = BuildEnvironment()
180        env.config = Config()
181        state = Struct(document=document)
182        super().__init__(env, None, Options(), 0, state)
183
184
185def get_documenter(app: Sphinx, obj: Any, parent: Any) -> "Type[Documenter]":
186    """Get an autodoc.Documenter class suitable for documenting the given
187    object.
188
189    *obj* is the Python object to be documented, and *parent* is an
190    another Python object (e.g. a module or a class) to which *obj*
191    belongs to.
192    """
193    from sphinx.ext.autodoc import DataDocumenter, ModuleDocumenter
194
195    if inspect.ismodule(obj):
196        # ModuleDocumenter.can_document_member always returns False
197        return ModuleDocumenter
198
199    # Construct a fake documenter for *parent*
200    if parent is not None:
201        parent_doc_cls = get_documenter(app, parent, None)
202    else:
203        parent_doc_cls = ModuleDocumenter
204
205    if hasattr(parent, '__name__'):
206        parent_doc = parent_doc_cls(FakeDirective(), parent.__name__)
207    else:
208        parent_doc = parent_doc_cls(FakeDirective(), "")
209
210    # Get the corrent documenter class for *obj*
211    classes = [cls for cls in app.registry.documenters.values()
212               if cls.can_document_member(obj, '', False, parent_doc)]
213    if classes:
214        classes.sort(key=lambda cls: cls.priority)
215        return classes[-1]
216    else:
217        return DataDocumenter
218
219
220# -- .. autosummary:: ----------------------------------------------------------
221
222class Autosummary(SphinxDirective):
223    """
224    Pretty table containing short signatures and summaries of functions etc.
225
226    autosummary can also optionally generate a hidden toctree:: node.
227    """
228
229    required_arguments = 0
230    optional_arguments = 0
231    final_argument_whitespace = False
232    has_content = True
233    option_spec = {
234        'caption': directives.unchanged_required,
235        'toctree': directives.unchanged,
236        'nosignatures': directives.flag,
237        'recursive': directives.flag,
238        'template': directives.unchanged,
239    }
240
241    def run(self) -> List[Node]:
242        self.bridge = DocumenterBridge(self.env, self.state.document.reporter,
243                                       Options(), self.lineno, self.state)
244
245        names = [x.strip().split()[0] for x in self.content
246                 if x.strip() and re.search(r'^[~a-zA-Z_]', x.strip()[0])]
247        items = self.get_items(names)
248        nodes = self.get_table(items)
249
250        if 'toctree' in self.options:
251            dirname = posixpath.dirname(self.env.docname)
252
253            tree_prefix = self.options['toctree'].strip()
254            docnames = []
255            excluded = Matcher(self.config.exclude_patterns)
256            filename_map = self.config.autosummary_filename_map
257            for name, sig, summary, real_name in items:
258                real_name = filename_map.get(real_name, real_name)
259                docname = posixpath.join(tree_prefix, real_name)
260                docname = posixpath.normpath(posixpath.join(dirname, docname))
261                if docname not in self.env.found_docs:
262                    if excluded(self.env.doc2path(docname, None)):
263                        msg = __('autosummary references excluded document %r. Ignored.')
264                    else:
265                        msg = __('autosummary: stub file not found %r. '
266                                 'Check your autosummary_generate setting.')
267
268                    logger.warning(msg, real_name, location=self.get_source_info())
269                    continue
270
271                docnames.append(docname)
272
273            if docnames:
274                tocnode = addnodes.toctree()
275                tocnode['includefiles'] = docnames
276                tocnode['entries'] = [(None, docn) for docn in docnames]
277                tocnode['maxdepth'] = -1
278                tocnode['glob'] = None
279                tocnode['caption'] = self.options.get('caption')
280
281                nodes.append(autosummary_toc('', '', tocnode))
282
283        if 'toctree' not in self.options and 'caption' in self.options:
284            logger.warning(__('A captioned autosummary requires :toctree: option. ignored.'),
285                           location=nodes[-1])
286
287        return nodes
288
289    def import_by_name(self, name: str, prefixes: List[str]) -> Tuple[str, Any, Any, str]:
290        with mock(self.config.autosummary_mock_imports):
291            try:
292                return import_by_name(name, prefixes)
293            except ImportError as exc:
294                # check existence of instance attribute
295                try:
296                    return import_ivar_by_name(name, prefixes)
297                except ImportError:
298                    pass
299
300                raise exc  # re-raise ImportError if instance attribute not found
301
302    def create_documenter(self, app: Sphinx, obj: Any,
303                          parent: Any, full_name: str) -> "Documenter":
304        """Get an autodoc.Documenter class suitable for documenting the given
305        object.
306
307        Wraps get_documenter and is meant as a hook for extensions.
308        """
309        doccls = get_documenter(app, obj, parent)
310        return doccls(self.bridge, full_name)
311
312    def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]:
313        """Try to import the given names, and return a list of
314        ``[(name, signature, summary_string, real_name), ...]``.
315        """
316        prefixes = get_import_prefixes_from_env(self.env)
317
318        items = []  # type: List[Tuple[str, str, str, str]]
319
320        max_item_chars = 50
321
322        for name in names:
323            display_name = name
324            if name.startswith('~'):
325                name = name[1:]
326                display_name = name.split('.')[-1]
327
328            try:
329                real_name, obj, parent, modname = self.import_by_name(name, prefixes=prefixes)
330            except ImportError:
331                logger.warning(__('autosummary: failed to import %s'), name,
332                               location=self.get_source_info())
333                continue
334
335            self.bridge.result = StringList()  # initialize for each documenter
336            full_name = real_name
337            if not isinstance(obj, ModuleType):
338                # give explicitly separated module name, so that members
339                # of inner classes can be documented
340                full_name = modname + '::' + full_name[len(modname) + 1:]
341            # NB. using full_name here is important, since Documenters
342            #     handle module prefixes slightly differently
343            documenter = self.create_documenter(self.env.app, obj, parent, full_name)
344            if not documenter.parse_name():
345                logger.warning(__('failed to parse name %s'), real_name,
346                               location=self.get_source_info())
347                items.append((display_name, '', '', real_name))
348                continue
349            if not documenter.import_object():
350                logger.warning(__('failed to import object %s'), real_name,
351                               location=self.get_source_info())
352                items.append((display_name, '', '', real_name))
353                continue
354            if documenter.options.members and not documenter.check_module():
355                continue
356
357            # try to also get a source code analyzer for attribute docs
358            try:
359                documenter.analyzer = ModuleAnalyzer.for_module(
360                    documenter.get_real_modname())
361                # parse right now, to get PycodeErrors on parsing (results will
362                # be cached anyway)
363                documenter.analyzer.find_attr_docs()
364            except PycodeError as err:
365                logger.debug('[autodoc] module analyzer failed: %s', err)
366                # no source file -- e.g. for builtin and C modules
367                documenter.analyzer = None
368
369            # -- Grab the signature
370
371            try:
372                sig = documenter.format_signature(show_annotation=False)
373            except TypeError:
374                # the documenter does not support ``show_annotation`` option
375                sig = documenter.format_signature()
376
377            if not sig:
378                sig = ''
379            else:
380                max_chars = max(10, max_item_chars - len(display_name))
381                sig = mangle_signature(sig, max_chars=max_chars)
382
383            # -- Grab the summary
384
385            documenter.add_content(None)
386            summary = extract_summary(self.bridge.result.data[:], self.state.document)
387
388            items.append((display_name, sig, summary, real_name))
389
390        return items
391
392    def get_table(self, items: List[Tuple[str, str, str, str]]) -> List[Node]:
393        """Generate a proper list of table nodes for autosummary:: directive.
394
395        *items* is a list produced by :meth:`get_items`.
396        """
397        table_spec = addnodes.tabular_col_spec()
398        table_spec['spec'] = r'\X{1}{2}\X{1}{2}'
399
400        table = autosummary_table('')
401        real_table = nodes.table('', classes=['longtable'])
402        table.append(real_table)
403        group = nodes.tgroup('', cols=2)
404        real_table.append(group)
405        group.append(nodes.colspec('', colwidth=10))
406        group.append(nodes.colspec('', colwidth=90))
407        body = nodes.tbody('')
408        group.append(body)
409
410        def append_row(*column_texts: str) -> None:
411            row = nodes.row('')
412            source, line = self.state_machine.get_source_and_line()
413            for text in column_texts:
414                node = nodes.paragraph('')
415                vl = StringList()
416                vl.append(text, '%s:%d:<autosummary>' % (source, line))
417                with switch_source_input(self.state, vl):
418                    self.state.nested_parse(vl, 0, node)
419                    try:
420                        if isinstance(node[0], nodes.paragraph):
421                            node = node[0]
422                    except IndexError:
423                        pass
424                    row.append(nodes.entry('', node))
425            body.append(row)
426
427        for name, sig, summary, real_name in items:
428            qualifier = 'obj'
429            if 'nosignatures' not in self.options:
430                col1 = ':%s:`%s <%s>`\\ %s' % (qualifier, name, real_name, rst.escape(sig))
431            else:
432                col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name)
433            col2 = summary
434            append_row(col1, col2)
435
436        return [table_spec, table]
437
438    def warn(self, msg: str) -> None:
439        warnings.warn('Autosummary.warn() is deprecated',
440                      RemovedInSphinx40Warning, stacklevel=2)
441        logger.warning(msg)
442
443    @property
444    def genopt(self) -> Options:
445        warnings.warn('Autosummary.genopt is deprecated',
446                      RemovedInSphinx40Warning, stacklevel=2)
447        return self.bridge.genopt
448
449    @property
450    def warnings(self) -> List[Node]:
451        warnings.warn('Autosummary.warnings is deprecated',
452                      RemovedInSphinx40Warning, stacklevel=2)
453        return []
454
455    @property
456    def result(self) -> StringList:
457        warnings.warn('Autosummary.result is deprecated',
458                      RemovedInSphinx40Warning, stacklevel=2)
459        return self.bridge.result
460
461
462def strip_arg_typehint(s: str) -> str:
463    """Strip a type hint from argument definition."""
464    return s.split(':')[0].strip()
465
466
467def mangle_signature(sig: str, max_chars: int = 30) -> str:
468    """Reformat a function signature to a more compact form."""
469    # Strip return type annotation
470    s = re.sub(r"\)\s*->\s.*$", ")", sig)
471
472    # Remove parenthesis
473    s = re.sub(r"^\((.*)\)$", r"\1", s).strip()
474
475    # Strip literals (which can contain things that confuse the code below)
476    s = re.sub(r"\\\\", "", s)      # escaped backslash (maybe inside string)
477    s = re.sub(r"\\'", "", s)       # escaped single quote
478    s = re.sub(r'\\"', "", s)       # escaped double quote
479    s = re.sub(r"'[^']*'", "", s)   # string literal (w/ single quote)
480    s = re.sub(r'"[^"]*"', "", s)   # string literal (w/ double quote)
481
482    # Strip complex objects (maybe default value of arguments)
483    while re.search(r'\([^)]*\)', s):   # contents of parenthesis (ex. NamedTuple(attr=...))
484        s = re.sub(r'\([^)]*\)', '', s)
485    while re.search(r'<[^>]*>', s):     # contents of angle brackets (ex. <object>)
486        s = re.sub(r'<[^>]*>', '', s)
487    while re.search(r'{[^}]*}', s):     # contents of curly brackets (ex. dict)
488        s = re.sub(r'{[^}]*}', '', s)
489
490    # Parse the signature to arguments + options
491    args = []  # type: List[str]
492    opts = []  # type: List[str]
493
494    opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)\s*=\s*")
495    while s:
496        m = opt_re.search(s)
497        if not m:
498            # The rest are arguments
499            args = s.split(', ')
500            break
501
502        opts.insert(0, m.group(2))
503        s = m.group(1)[:-2]
504
505    # Strip typehints
506    for i, arg in enumerate(args):
507        args[i] = strip_arg_typehint(arg)
508
509    for i, opt in enumerate(opts):
510        opts[i] = strip_arg_typehint(opt)
511
512    # Produce a more compact signature
513    sig = limited_join(", ", args, max_chars=max_chars - 2)
514    if opts:
515        if not sig:
516            sig = "[%s]" % limited_join(", ", opts, max_chars=max_chars - 4)
517        elif len(sig) < max_chars - 4 - 2 - 3:
518            sig += "[, %s]" % limited_join(", ", opts,
519                                           max_chars=max_chars - len(sig) - 4 - 2)
520
521    return "(%s)" % sig
522
523
524def extract_summary(doc: List[str], document: Any) -> str:
525    """Extract summary from docstring."""
526    def parse(doc: List[str], settings: Any) -> nodes.document:
527        state_machine = RSTStateMachine(state_classes, 'Body')
528        node = new_document('', settings)
529        node.reporter = NullReporter()
530        state_machine.run(doc, node)
531
532        return node
533
534    # Skip a blank lines at the top
535    while doc and not doc[0].strip():
536        doc.pop(0)
537
538    # If there's a blank line, then we can assume the first sentence /
539    # paragraph has ended, so anything after shouldn't be part of the
540    # summary
541    for i, piece in enumerate(doc):
542        if not piece.strip():
543            doc = doc[:i]
544            break
545
546    if doc == []:
547        return ''
548
549    # parse the docstring
550    node = parse(doc, document.settings)
551    if not isinstance(node[0], nodes.paragraph):
552        # document starts with non-paragraph: pick up the first line
553        summary = doc[0].strip()
554    else:
555        # Try to find the "first sentence", which may span multiple lines
556        sentences = periods_re.split(" ".join(doc))
557        if len(sentences) == 1:
558            summary = sentences[0].strip()
559        else:
560            summary = ''
561            for i in range(len(sentences)):
562                summary = ". ".join(sentences[:i + 1]).rstrip(".") + "."
563                node[:] = []
564                node = parse(doc, document.settings)
565                if summary.endswith(WELL_KNOWN_ABBREVIATIONS):
566                    pass
567                elif not node.traverse(nodes.system_message):
568                    # considered as that splitting by period does not break inline markups
569                    break
570
571    # strip literal notation mark ``::`` from tail of summary
572    summary = literal_re.sub('.', summary)
573
574    return summary
575
576
577def limited_join(sep: str, items: List[str], max_chars: int = 30,
578                 overflow_marker: str = "...") -> str:
579    """Join a number of strings to one, limiting the length to *max_chars*.
580
581    If the string overflows this limit, replace the last fitting item by
582    *overflow_marker*.
583
584    Returns: joined_string
585    """
586    full_str = sep.join(items)
587    if len(full_str) < max_chars:
588        return full_str
589
590    n_chars = 0
591    n_items = 0
592    for j, item in enumerate(items):
593        n_chars += len(item) + len(sep)
594        if n_chars < max_chars - len(overflow_marker):
595            n_items += 1
596        else:
597            break
598
599    return sep.join(list(items[:n_items]) + [overflow_marker])
600
601
602# -- Importing items -----------------------------------------------------------
603
604def get_import_prefixes_from_env(env: BuildEnvironment) -> List[str]:
605    """
606    Obtain current Python import prefixes (for `import_by_name`)
607    from ``document.env``
608    """
609    prefixes = [None]  # type: List[str]
610
611    currmodule = env.ref_context.get('py:module')
612    if currmodule:
613        prefixes.insert(0, currmodule)
614
615    currclass = env.ref_context.get('py:class')
616    if currclass:
617        if currmodule:
618            prefixes.insert(0, currmodule + "." + currclass)
619        else:
620            prefixes.insert(0, currclass)
621
622    return prefixes
623
624
625def import_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]:
626    """Import a Python object that has the given *name*, under one of the
627    *prefixes*.  The first name that succeeds is used.
628    """
629    tried = []
630    for prefix in prefixes:
631        try:
632            if prefix:
633                prefixed_name = '.'.join([prefix, name])
634            else:
635                prefixed_name = name
636            obj, parent, modname = _import_by_name(prefixed_name)
637            return prefixed_name, obj, parent, modname
638        except ImportError:
639            tried.append(prefixed_name)
640    raise ImportError('no module named %s' % ' or '.join(tried))
641
642
643def _import_by_name(name: str) -> Tuple[Any, Any, str]:
644    """Import a Python object given its full name."""
645    try:
646        name_parts = name.split('.')
647
648        # try first interpret `name` as MODNAME.OBJ
649        modname = '.'.join(name_parts[:-1])
650        if modname:
651            try:
652                mod = import_module(modname)
653                return getattr(mod, name_parts[-1]), mod, modname
654            except (ImportError, IndexError, AttributeError):
655                pass
656
657        # ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ...
658        last_j = 0
659        modname = None
660        for j in reversed(range(1, len(name_parts) + 1)):
661            last_j = j
662            modname = '.'.join(name_parts[:j])
663            try:
664                import_module(modname)
665            except ImportError:
666                continue
667
668            if modname in sys.modules:
669                break
670
671        if last_j < len(name_parts):
672            parent = None
673            obj = sys.modules[modname]
674            for obj_name in name_parts[last_j:]:
675                parent = obj
676                obj = getattr(obj, obj_name)
677            return obj, parent, modname
678        else:
679            return sys.modules[modname], None, modname
680    except (ValueError, ImportError, AttributeError, KeyError) as e:
681        raise ImportError(*e.args) from e
682
683
684def import_ivar_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]:
685    """Import an instance variable that has the given *name*, under one of the
686    *prefixes*.  The first name that succeeds is used.
687    """
688    try:
689        name, attr = name.rsplit(".", 1)
690        real_name, obj, parent, modname = import_by_name(name, prefixes)
691        qualname = real_name.replace(modname + ".", "")
692        analyzer = ModuleAnalyzer.for_module(modname)
693        if (qualname, attr) in analyzer.find_attr_docs():
694            return real_name + "." + attr, INSTANCEATTR, obj, modname
695    except (ImportError, ValueError, PycodeError):
696        pass
697
698    raise ImportError
699
700
701# -- :autolink: (smart default role) -------------------------------------------
702
703def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner,
704                  options: Dict = {}, content: List[str] = []
705                  ) -> Tuple[List[Node], List[system_message]]:
706    """Smart linking role.
707
708    Expands to ':obj:`text`' if `text` is an object that can be imported;
709    otherwise expands to '*text*'.
710    """
711    warnings.warn('autolink_role() is deprecated.', RemovedInSphinx40Warning, stacklevel=2)
712    env = inliner.document.settings.env
713    pyobj_role = env.get_domain('py').role('obj')
714    objects, msg = pyobj_role('obj', rawtext, etext, lineno, inliner, options, content)
715    if msg != []:
716        return objects, msg
717
718    assert len(objects) == 1
719    pending_xref = cast(addnodes.pending_xref, objects[0])
720    prefixes = get_import_prefixes_from_env(env)
721    try:
722        name, obj, parent, modname = import_by_name(pending_xref['reftarget'], prefixes)
723    except ImportError:
724        literal = cast(nodes.literal, pending_xref[0])
725        objects[0] = nodes.emphasis(rawtext, literal.astext(), classes=literal['classes'])
726
727    return objects, msg
728
729
730class AutoLink(SphinxRole):
731    """Smart linking role.
732
733    Expands to ':obj:`text`' if `text` is an object that can be imported;
734    otherwise expands to '*text*'.
735    """
736    def run(self) -> Tuple[List[Node], List[system_message]]:
737        pyobj_role = self.env.get_domain('py').role('obj')
738        objects, errors = pyobj_role('obj', self.rawtext, self.text, self.lineno,
739                                     self.inliner, self.options, self.content)
740        if errors:
741            return objects, errors
742
743        assert len(objects) == 1
744        pending_xref = cast(addnodes.pending_xref, objects[0])
745        try:
746            # try to import object by name
747            prefixes = get_import_prefixes_from_env(self.env)
748            import_by_name(pending_xref['reftarget'], prefixes)
749        except ImportError:
750            literal = cast(nodes.literal, pending_xref[0])
751            objects[0] = nodes.emphasis(self.rawtext, literal.astext(),
752                                        classes=literal['classes'])
753
754        return objects, errors
755
756
757def get_rst_suffix(app: Sphinx) -> str:
758    def get_supported_format(suffix: str) -> Tuple[str, ...]:
759        parser_class = app.registry.get_source_parsers().get(suffix)
760        if parser_class is None:
761            return ('restructuredtext',)
762        return parser_class.supported
763
764    suffix = None  # type: str
765    for suffix in app.config.source_suffix:
766        if 'restructuredtext' in get_supported_format(suffix):
767            return suffix
768
769    return None
770
771
772def process_generate_options(app: Sphinx) -> None:
773    genfiles = app.config.autosummary_generate
774
775    if genfiles is True:
776        env = app.builder.env
777        genfiles = [env.doc2path(x, base=None) for x in env.found_docs
778                    if os.path.isfile(env.doc2path(x))]
779    elif genfiles is False:
780        pass
781    else:
782        ext = list(app.config.source_suffix)
783        genfiles = [genfile + (ext[0] if not genfile.endswith(tuple(ext)) else '')
784                    for genfile in genfiles]
785
786        for entry in genfiles[:]:
787            if not path.isfile(path.join(app.srcdir, entry)):
788                logger.warning(__('autosummary_generate: file not found: %s'), entry)
789                genfiles.remove(entry)
790
791    if not genfiles:
792        return
793
794    suffix = get_rst_suffix(app)
795    if suffix is None:
796        logger.warning(__('autosummary generats .rst files internally. '
797                          'But your source_suffix does not contain .rst. Skipped.'))
798        return
799
800    from sphinx.ext.autosummary.generate import generate_autosummary_docs
801
802    imported_members = app.config.autosummary_imported_members
803    with mock(app.config.autosummary_mock_imports):
804        generate_autosummary_docs(genfiles, suffix=suffix, base_path=app.srcdir,
805                                  app=app, imported_members=imported_members,
806                                  overwrite=app.config.autosummary_generate_overwrite,
807                                  encoding=app.config.source_encoding)
808
809
810def setup(app: Sphinx) -> Dict[str, Any]:
811    # I need autodoc
812    app.setup_extension('sphinx.ext.autodoc')
813    app.add_node(autosummary_toc,
814                 html=(autosummary_toc_visit_html, autosummary_noop),
815                 latex=(autosummary_noop, autosummary_noop),
816                 text=(autosummary_noop, autosummary_noop),
817                 man=(autosummary_noop, autosummary_noop),
818                 texinfo=(autosummary_noop, autosummary_noop))
819    app.add_node(autosummary_table,
820                 html=(autosummary_table_visit_html, autosummary_noop),
821                 latex=(autosummary_noop, autosummary_noop),
822                 text=(autosummary_noop, autosummary_noop),
823                 man=(autosummary_noop, autosummary_noop),
824                 texinfo=(autosummary_noop, autosummary_noop))
825    app.add_directive('autosummary', Autosummary)
826    app.add_role('autolink', AutoLink())
827    app.connect('builder-inited', process_generate_options)
828    app.add_config_value('autosummary_context', {}, True)
829    app.add_config_value('autosummary_filename_map', {}, 'html')
830    app.add_config_value('autosummary_generate', [], True, [bool])
831    app.add_config_value('autosummary_generate_overwrite', True, False)
832    app.add_config_value('autosummary_mock_imports',
833                         lambda config: config.autodoc_mock_imports, 'env')
834    app.add_config_value('autosummary_imported_members', [], False, [bool])
835
836    return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
837