From 2a3bdcc8208b4016e62b15898dfb4a207c659749 Mon Sep 17 00:00:00 2001 From: Nathan Woodrow Date: Fri, 13 Sep 2019 16:47:29 +1000 Subject: [PATCH] Add pdoc to generate API docs --- ext_libs/Scripts/pdoc | 506 ++++++++++++ ext_libs/pdoc/__init__.py | 1206 +++++++++++++++++++++++++++++ ext_libs/pdoc/templates/LICENSE | 19 + ext_libs/pdoc/templates/README.md | 3 + ext_libs/pdoc/templates/css.mako | 928 ++++++++++++++++++++++ ext_libs/pdoc/templates/html.mako | 484 ++++++++++++ ext_libs/pdoc/templates/text.mako | 147 ++++ 7 files changed, 3293 insertions(+) create mode 100644 ext_libs/Scripts/pdoc create mode 100644 ext_libs/pdoc/__init__.py create mode 100644 ext_libs/pdoc/templates/LICENSE create mode 100644 ext_libs/pdoc/templates/README.md create mode 100644 ext_libs/pdoc/templates/css.mako create mode 100644 ext_libs/pdoc/templates/html.mako create mode 100644 ext_libs/pdoc/templates/text.mako diff --git a/ext_libs/Scripts/pdoc b/ext_libs/Scripts/pdoc new file mode 100644 index 000000000..1a3a5454f --- /dev/null +++ b/ext_libs/Scripts/pdoc @@ -0,0 +1,506 @@ +#!c:\osgeo4~1\bin\python3.exe + +from __future__ import absolute_import, division, print_function +import argparse +try: + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +except ImportError: + from http.server import BaseHTTPRequestHandler, HTTPServer +import codecs +import datetime +import imp +import os +import os.path as path +import pkgutil +import re +import subprocess +import sys +import tempfile + +import pdoc + +# `xrange` is `range` with Python3. +try: + xrange = xrange +except NameError: + xrange = range + +version_suffix = '%d.%d' % (sys.version_info[0], sys.version_info[1]) +default_http_dir = path.join(tempfile.gettempdir(), 'pdoc-%s' % version_suffix) + +parser = argparse.ArgumentParser( + description='Automatically generate API docs for Python modules.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) +aa = parser.add_argument +aa('module_name', type=str, nargs='?', + help='The Python module name. This may be an import path resolvable in ' + 'the current environment, or a file path to a Python module or ' + 'package.') +aa('ident_name', type=str, nargs='?', + help='When specified, only identifiers containing the name given ' + 'will be shown in the output. Search is case sensitive. ' + 'Has no effect when --http is set.') +aa('--version', action='store_true', + help='Print the version of pdoc and exit.') +aa('--html', action='store_true', + help='When set, the output will be HTML formatted.') +aa('--html-dir', type=str, default='.', + help='The directory to output HTML files to. This option is ignored when ' + 'outputting documentation as plain text.') +aa('--html-no-source', action='store_true', + help='When set, source code will not be viewable in the generated HTML. ' + 'This can speed up the time required to document large modules.') +aa('--overwrite', action='store_true', + help='Overwrites any existing HTML files instead of producing an error.') +aa('--all-submodules', action='store_true', + help='When set, every submodule will be included, regardless of whether ' + '__all__ is set and contains the submodule.') +aa('--external-links', action='store_true', + help='When set, identifiers to external modules are turned into links. ' + 'This is automatically set when using --http.') +aa('--template-dir', type=str, default=None, + help='Specify a directory containing Mako templates. ' + 'Alternatively, put your templates in $XDG_CONFIG_HOME/pdoc and ' + 'pdoc will automatically find them.') +aa('--link-prefix', type=str, default='', + help='A prefix to use for every link in the generated documentation. ' + 'No link prefix results in all links being relative. ' + 'Has no effect when combined with --http.') +aa('--only-pypath', action='store_true', + help='When set, only modules in your PYTHONPATH will be documented.') +aa('--http', action='store_true', + help='When set, pdoc will run as an HTTP server providing documentation ' + 'of all installed modules. Only modules found in PYTHONPATH will be ' + 'listed.') +aa('--http-dir', type=str, default=default_http_dir, + help='The directory to cache HTML documentation when running as an HTTP ' + 'server.') +aa('--http-host', type=str, default='localhost', + help='The host on which to run the HTTP server.') +aa('--http-port', type=int, default=8080, + help='The port on which to run the HTTP server.') +aa('--http-html', action='store_true', + help='Internal use only. Do not set.') +args = parser.parse_args() + + +class WebDoc (BaseHTTPRequestHandler): + def do_HEAD(self): + if self.path != '/': + out = self.html() + if out is None: + self.send_response(404) + self.end_headers() + return + + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_GET(self): + if self.path == '/': + modules = [] + for (imp, name, ispkg) in pkgutil.iter_modules(pdoc.import_path): + if name == 'setup' and not ispkg: + continue + modules.append((name, quick_desc(imp, name, ispkg))) + modules = sorted(modules, key=lambda x: x[0].lower()) + + out = pdoc.tpl_lookup.get_template('/html.mako') + out = out.render(modules=modules, link_prefix=args.link_prefix) + out = out.strip() + elif self.path.endswith('.ext'): + # External links are a bit weird. You should view them as a giant + # hack. Basically, the idea is to "guess" where something lives + # when documenting another module and hope that guess can actually + # track something down in a more global context. + # + # The idea here is to start specific by looking for HTML that + # exists that matches the full external path given. Then trim off + # one component at the end and try again. + # + # If no HTML is found, then we ask `pdoc` to do its thang on the + # parent module in the external path. If all goes well, that + # module will then be able to find the external identifier. + + import_path = self.path[:-4].lstrip('/') + resolved = self.resolve_ext(import_path) + if resolved is None: # Try to generate the HTML... + _eprint('Generating HTML for %s on the fly...' % import_path) + try: + process_html_out(import_path.split('.')[0]) + except subprocess.CalledProcessError as e: + _eprint('Could not run "%s" (exit code: %d): %s' % + (' '.join(e.cmd), e.returncode, e.output)) + + # Try looking once more. + resolved = self.resolve_ext(import_path) + if resolved is None: # All hope is lost. + self.send_response(404) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.echo('External identifier %s not found.' + % import_path) + return + self.send_response(302) + self.send_header('Location', resolved) + self.end_headers() + return + else: + out = self.html() + if out is None: + self.send_response(404) + self.send_header('Content-type', 'text/html') + self.end_headers() + + err = 'Module %s not found.' % self.import_path + self.echo(err) + return + + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.echo(out) + + def echo(self, s): + self.wfile.write(s.encode('utf-8')) + + def html(self): + """ + Retrieves and sends the HTML belonging to the path given in + URL. This method is smart and will look for HTML files already + generated and account for whether they are stale compared to + the source code. + """ + + # Deny favico shortcut early. + if self.path == '/favicon.ico': + return None + + # Look for the module file without importing it. + impath = self.import_path + try: + mfile = pkgutil.get_loader(self.import_path).get_filename() + except Exception as e: + _eprint('Could not load %s: %s' % (impath, e)) + mfile = None + + # Check the file system first before trying an import. + fp = self.file_path + if last_modified(mfile) < last_modified(fp) and os.access(fp, os.R_OK): + with codecs.open(fp, 'r', 'utf-8') as f: + return f.read() + + # Regenerate the HTML by shelling out to pdoc. + # This ensures that the documentation is generated from a fresh import. + _eprint('Generating HTML for %s' % impath) + try: + process_html_out(impath) + except subprocess.CalledProcessError as e: + _eprint('Could not run "%s" (exit code: %d): %s' % + (' '.join(e.cmd), e.returncode, e.output)) + return None + + with codecs.open(self.file_path, 'r', 'utf-8') as f: + return f.read() + + def resolve_ext(self, import_path): + def exists(p): + p = path.join(args.html_dir, p) + pkg = path.join(p, pdoc.html_package_name) + mod = p + pdoc.html_module_suffix + + if path.isfile(pkg): + return pkg[len(args.html_dir):] + elif path.isfile(mod): + return mod[len(args.html_dir):] + return None + + parts = import_path.split('.') + for i in xrange(len(parts), 0, -1): + p = path.join(*parts[0:i]) + realp = exists(p) + if realp is not None: + return '/%s#%s' % (realp.lstrip('/'), import_path) + return None + + @property + def file_path(self): + fp = path.join(args.html_dir, *self.import_path.split('.')) + pkgp = path.join(fp, pdoc.html_package_name) + if path.isdir(fp) and path.isfile(pkgp): + fp = pkgp + else: + fp += pdoc.html_module_suffix + return fp + + @property + def import_path(self): + pieces = self.clean_path.split('/') + if pieces[-1].startswith(pdoc.html_package_name): + pieces = pieces[:-1] + if pieces[-1].endswith(pdoc.html_module_suffix): + pieces[-1] = pieces[-1][:-len(pdoc.html_module_suffix)] + return '.'.join(pieces) + + @property + def clean_path(self): + new, _ = re.subn('//+', '/', self.path) + if '#' in new: + new = new[0:new.index('#')] + return new.strip('/') + + def address_string(self): + return '%s:%s' % (self.client_address[0], self.client_address[1]) + + +def quick_desc(imp, name, ispkg): + if not hasattr(imp, 'path'): + # See issue #7. + return '' + + if ispkg: + fp = path.join(imp.path, name, '__init__.py') + else: + fp = path.join(imp.path, '%s.py' % name) + if os.path.isfile(fp): + with codecs.open(fp, 'r', 'utf-8') as f: + quotes = None + doco = [] + for i, line in enumerate(f): + if i == 0: + if len(line) >= 3 and line[0:3] in ("'''", '"""'): + quotes = line[0:3] + line = line[3:] + else: + break + line = line.rstrip() + if line.endswith(quotes): + doco.append(line[0:-3]) + break + else: + doco.append(line) + desc = '\n'.join(doco) + if len(desc) > 200: + desc = desc[0:200] + '...' + return desc + return '' + + +def _eprint(*args, **kwargs): + kwargs['file'] = sys.stderr + print(*args, **kwargs) + + +def last_modified(fp): + try: + return datetime.datetime.fromtimestamp(os.stat(fp).st_mtime) + except: + return datetime.datetime.min + + +def module_file(m): + mbase = path.join(args.html_dir, *m.name.split('.')) + if m.is_package(): + return path.join(mbase, pdoc.html_package_name) + else: + return '%s%s' % (mbase, pdoc.html_module_suffix) + + +def quit_if_exists(m): + def check_file(f): + if os.access(f, os.R_OK): + _eprint('%s already exists. Delete it or run with --overwrite' % f) + sys.exit(1) + + if args.overwrite: + return + f = module_file(m) + check_file(f) + + # If this is a package, make sure the package directory doesn't exist + # either. + if m.is_package(): + check_file(path.dirname(f)) + + +def html_out(m, html=True): + f = module_file(m) + if not html: + f = module_file(m).replace(".html", ".md") + dirpath = path.dirname(f) + if not os.access(dirpath, os.R_OK): + os.makedirs(dirpath) + try: + with codecs.open(f, 'w+', 'utf-8') as w: + if not html: + out = m.text() + else: + out = m.html(external_links=args.external_links, + link_prefix=args.link_prefix, + http_server=args.http_html, + source=not args.html_no_source) + print(out, file=w) + except Exception: + try: + os.unlink(f) + except: + pass + raise + for submodule in m.submodules(): + html_out(submodule, html) + + +def process_html_out(impath): + # This unfortunate kludge is the only reasonable way I could think of + # to support reloading of modules. It's just too difficult to get + # modules to reload in the same process. + + cmd = [sys.executable, + path.realpath(__file__), + '--html', + '--html-dir', args.html_dir, + '--http-html', + '--overwrite', + '--link-prefix', args.link_prefix] + if args.external_links: + cmd.append('--external-links') + if args.all_submodules: + cmd.append('--all-submodules') + if args.only_pypath: + cmd.append('--only-pypath') + if args.html_no_source: + cmd.append('--html-no-source') + if args.template_dir: + cmd.append('--template-dir') + cmd.append(args.template_dir) + cmd.append(impath) + + # Can we make a good faith attempt to support 2.6? + # YES WE CAN! + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out = p.communicate()[0].strip().decode('utf-8') + if p.returncode > 0: + err = subprocess.CalledProcessError(p.returncode, cmd) + err.output = out + raise err + if len(out) > 0: + print(out) + + +if __name__ == '__main__': + if args.version: + print(pdoc.__version__) + sys.exit(0) + + # We close stdin because some modules, upon import, are not very polite + # and block on stdin. + try: + sys.stdin.close() + except: + pass + + if not args.http and args.module_name is None: + _eprint('No module name specified.') + sys.exit(1) + if args.template_dir is not None: + pdoc.tpl_lookup.directories.insert(0, args.template_dir) + if args.http: + args.html = True + args.external_links = True + args.html_dir = args.http_dir + args.overwrite = True + args.link_prefix = '/' + + # If PYTHONPATH is set, let it override everything if we want it to. + pypath = os.getenv('PYTHONPATH') + if args.only_pypath and pypath is not None and len(pypath) > 0: + pdoc.import_path = pypath.split(path.pathsep) + + if args.http: + if args.module_name is not None: + _eprint('Module names cannot be given with --http set.') + sys.exit(1) + + # Run the HTTP server. + httpd = HTTPServer((args.http_host, args.http_port), WebDoc) + print('pdoc server ready at http://%s:%d' + % (args.http_host, args.http_port), file=sys.stderr) + + httpd.serve_forever() + httpd.server_close() + sys.exit(0) + + docfilter = None + if args.ident_name and len(args.ident_name.strip()) > 0: + search = args.ident_name.strip() + + def docfilter(o): + rname = o.refname + if rname.find(search) > -1 or search.find(o.name) > -1: + return True + if isinstance(o, pdoc.Class): + return search in o.doc or search in o.doc_init + return False + + # Try to do a real import first. I think it's better to prefer + # import paths over files. If a file is really necessary, then + # specify the absolute path, which is guaranteed not to be a + # Python import path. + try: + module = pdoc.import_module(args.module_name) + except Exception as e: + module = None + + # Get the module that we're documenting. Accommodate for import paths, + # files and directories. + if module is None: + isdir = path.isdir(args.module_name) + isfile = path.isfile(args.module_name) + if isdir or isfile: + fp = path.realpath(args.module_name) + module_name = path.basename(fp) + if isdir: + fp = path.join(fp, '__init__.py') + else: + module_name, _ = path.splitext(module_name) + + # Use a special module name to avoid import conflicts. + # It is hidden from view via the `Module` class. + with open(fp) as f: + module = imp.load_source('__pdoc_file_module__', fp, f) + if isdir: + module.__path__ = [path.realpath(args.module_name)] + module.__pdoc_module_name = module_name + else: + module = pdoc.import_module(args.module_name) + module = pdoc.Module(module, docfilter=docfilter, + allsubmodules=args.all_submodules) + + # Plain text? + if not args.html and not args.all_submodules: + output = module.text() + try: + print(output) + except IOError as e: + # This seems to happen for long documentation. + # This is obviously a hack. What's the real cause? Dunno. + if e.errno == 32: + pass + else: + raise e + sys.exit(0) + + # HTML output depends on whether the module being documented is a package + # or not. If not, then output is written to {MODULE_NAME}.html in + # `html-dir`. If it is a package, then a directory called {MODULE_NAME} + # is created, and output is written to {MODULE_NAME}/index.html. + # Submodules are written to {MODULE_NAME}/{MODULE_NAME}.m.html and + # subpackages are written to {MODULE_NAME}/{MODULE_NAME}/index.html. And + # so on... The same rules apply for `http_dir` when `pdoc` is run as an + # HTTP server. + if not args.http: + quit_if_exists(module) + html_out(module, args.html) + sys.exit(0) diff --git a/ext_libs/pdoc/__init__.py b/ext_libs/pdoc/__init__.py new file mode 100644 index 000000000..7353bb7f6 --- /dev/null +++ b/ext_libs/pdoc/__init__.py @@ -0,0 +1,1206 @@ +""" +Module pdoc provides types and functions for accessing the public +documentation of a Python module. This includes modules (and +sub-modules), functions, classes and module, class and instance +variables. Docstrings are taken from modules, functions and classes +using the special `__doc__` attribute. Docstrings for variables are +extracted by examining the module's abstract syntax tree. + +The public interface of a module is determined through one of two +ways. If `__all__` is defined in the module, then all identifiers in +that list will be considered public. No other identifiers will be +considered as public. Conversely, if `__all__` is not defined, then +`pdoc` will heuristically determine the public interface. There are +three rules that are applied to each identifier in the module: + +1. If the name starts with an underscore, it is **not** public. + +2. If the name is defined in a different module, it is **not** public. + +3. If the name refers to an immediate sub-module, then it is public. + +Once documentation for a module is created with `pdoc.Module`, it +can be output as either HTML or plain text using the covenience +functions `pdoc.html` and `pdoc.text`, or the corresponding methods +`pdoc.Module.html` and `pdoc.Module.text`. + +Alternatively, you may run an HTTP server with the `pdoc` script +included with this module. + + +Compatibility +------------- +`pdoc` has been tested on Python 2.6, 2.7 and 3.3. It seems to work +on all three. + + +Contributing +------------ +`pdoc` [is on GitHub](https://github.com/BurntSushi/pdoc). Pull +requests and bug reports are welcome. + + +Linking to other identifiers +---------------------------- +In your documentation, you may link to other identifiers in +your module or submodules. Linking is automatically done for +you whenever you surround an identifier with a back quote +(grave). The identifier name must be fully qualified. For +example, \`pdoc.Doc.docstring\` is correct while +\`Doc.docstring\` is incorrect. + +If the `pdoc` script is used to run an HTTP server, then external +linking to other packages installed is possible. No extra work is +necessary; simply use the fully qualified path. For example, +\`nflvid.slice\` will create a link to the `nflvid.slice` +function, which is **not** a part of `pdoc` at all. + + +Where does pdoc get documentation from? +--------------------------------------- +Broadly speaking, `pdoc` gets everything you see from introspecting the +module. This includes words describing a particular module, class, +function or variable. While `pdoc` does some analysis on the source +code of a module, importing the module itself is necessary to use +Python's introspection features. + +In Python, objects like modules, functions, classes and methods have +a special attribute named `__doc__` which contains that object's +*docstring*. The docstring comes from a special placement of a string +in your source code. For example, the following code shows how to +define a function with a docstring and access the contents of that +docstring: + + #!python + >>> def test(): + ... '''This is a docstring.''' + ... pass + ... + >>> test.__doc__ + 'This is a docstring.' + +Something similar can be done for classes and modules too. For classes, +the docstring should come on the line immediately following `class +...`. For modules, the docstring should start on the first line of +the file. These docstrings are what you see for each module, class, +function and method listed in the documentation produced by `pdoc`. + +The above just about covers *standard* uses of docstrings in Python. +`pdoc` extends the above in a few important ways. + + +### Special docstring conventions used by `pdoc` + +**Firstly**, docstrings can be inherited. Consider the following code +sample: + + #!python + >>> class A (object): + ... def test(): + ... '''Docstring for A.''' + ... + >>> class B (A): + ... def test(): + ... pass + ... + >>> print(A.test.__doc__) + Docstring for A. + >>> print(B.test.__doc__) + None + +In Python, the docstring for `B.test` is empty, even though one was +defined in `A.test`. If `pdoc` generates documentation for the above +code, then it will automatically attach the docstring for `A.test` to +`B.test` only if `B.test` does not have a docstring. In the default +HTML output, an inherited docstring is grey. + +**Secondly**, docstrings can be attached to variables, which includes +module (or global) variables, class variables and instance variables. +Python by itself [does not allow docstrings to be attached to +variables](http://www.python.org/dev/peps/pep-0224). For example: + + #!python + variable = "SomeValue" + '''Docstring for variable.''' + +The resulting `variable` will have no `__doc__` attribute. To +compensate, `pdoc` will read the source code when it's available to +infer a connection between a variable and a docstring. The connection +is only made when an assignment statement is followed by a docstring. + +Something similar is done for instance variables as well. By +convention, instance variables are initialized in a class's `__init__` +method. Therefore, `pdoc` adheres to that convention and looks for +docstrings of variables like so: + + #!python + def __init__(self): + self.variable = "SomeValue" + '''Docstring for instance variable.''' + +Note that `pdoc` only considers attributes defined on `self` as +instance variables. + +Class and instance variables can also have inherited docstrings. + +**Thirdly and finally**, docstrings can be overridden with a special +`__pdoc__` dictionary that `pdoc` inspects if it exists. The keys of +`__pdoc__` should be identifiers within the scope of the module. (In +the case of an instance variable `self.variable` for class `A`, its +module identifier would be `A.variable`.) The values of `__pdoc__` +should be docstrings. + +This particular feature is useful when there's no feasible way of +attaching a docstring to something. A good example of this is a +[namedtuple](http://goo.gl/akfXJ9): + + #!python + __pdoc__ = {} + + Table = namedtuple('Table', ['types', 'names', 'rows']) + __pdoc__['Table.types'] = 'Types for each column in the table.' + __pdoc__['Table.names'] = 'The names of each column in the table.' + __pdoc__['Table.rows'] = 'Lists corresponding to each row in the table.' + +`pdoc` will then show `Table` as a class with documentation for the +`types`, `names` and `rows` members. + +Note that assignments to `__pdoc__` need to placed where they'll be +executed when the module is imported. For example, at the top level +of a module or in the definition of a class. + +If `__pdoc__[key] = None`, then `key` will not be included in the +public interface of the module. + + +License +------- +`pdoc` is in the public domain via the +[UNLICENSE](http://unlicense.org). +""" +from __future__ import absolute_import, division, print_function +import ast +import imp +import inspect +import os +import os.path as path +import pkgutil +import re +import sys + +from mako.lookup import TemplateLookup +from mako.exceptions import TopLevelLookupException + +__version__ = '0.3.2' +""" +The current version of pdoc. This value is read from `setup.py`. +""" + +html_module_suffix = '.m.html' +""" +The suffix to use for module HTML files. By default, this is set to +`.m.html`, where the extra `.m` is used to differentiate a package's +`index.html` from a submodule called `index`. +""" + +html_package_name = 'index.html' +""" +The file name to use for a package's `__init__.py` module. +""" + +import_path = sys.path[:] +""" +A list of paths to restrict imports to. Any module that cannot be +found in `import_path` will not be imported. By default, it is set to a +copy of `sys.path` at initialization. +""" + +_template_path = [ + path.join(path.dirname(__file__), 'templates'), +] +""" +A list of paths to search for Mako templates used to produce the +plain text and HTML output. Each path is tried until a template is +found. +""" +if os.getenv('XDG_CONFIG_HOME'): + _template_path.insert(0, path.join(os.getenv('XDG_CONFIG_HOME'), 'pdoc')) + +__pdoc__ = {} +tpl_lookup = TemplateLookup(directories=_template_path, + cache_args={'cached': True, + 'cache_type': 'memory'}) +""" +A `mako.lookup.TemplateLookup` object that knows how to load templates +from the file system. You may add additional paths by modifying the +object's `directories` attribute. +""" + + +def html(module_name, docfilter=None, allsubmodules=False, + external_links=False, link_prefix='', source=True): + """ + Returns the documentation for the module `module_name` in HTML + format. The module must be importable. + + `docfilter` is an optional predicate that controls which + documentation objects are shown in the output. It is a single + argument function that takes a documentation object and returns + `True` or `False`. If `False`, that object will not be included in + the output. + + If `allsubmodules` is `True`, then every submodule of this module + that can be found will be included in the documentation, regardless + of whether `__all__` contains it. + + If `external_links` is `True`, then identifiers to external modules + are always turned into links. + + If `link_prefix` is `True`, then all links will have that prefix. + Otherwise, links are always relative. + + If `source` is `True`, then source code will be retrieved for + every Python object whenever possible. This can dramatically + decrease performance when documenting large modules. + """ + mod = Module(import_module(module_name), + docfilter=docfilter, + allsubmodules=allsubmodules) + return mod.html(external_links=external_links, + link_prefix=link_prefix, source=source) + + +def text(module_name, docfilter=None, allsubmodules=False): + """ + Returns the documentation for the module `module_name` in plain + text format. The module must be importable. + + `docfilter` is an optional predicate that controls which + documentation objects are shown in the output. It is a single + argument function that takes a documentation object and returns + True of False. If False, that object will not be included in the + output. + + If `allsubmodules` is `True`, then every submodule of this module + that can be found will be included in the documentation, regardless + of whether `__all__` contains it. + """ + mod = Module(import_module(module_name), + docfilter=docfilter, + allsubmodules=allsubmodules) + return mod.text() + + +def import_module(module_name): + """ + Imports a module. A single point of truth for importing modules to + be documented by `pdoc`. In particular, it makes sure that the top + module in `module_name` can be imported by using only the paths in + `pdoc.import_path`. + + If a module has already been imported, then its corresponding entry + in `sys.modules` is returned. This means that modules that have + changed on disk cannot be re-imported in the same process and have + its documentation updated. + """ + if import_path != sys.path: + # Such a kludge. Only restrict imports if the `import_path` has + # been changed. We don't want to always restrict imports, since + # providing a path to `imp.find_module` stops it from searching + # in special locations for built ins or frozen modules. + # + # The problem here is that this relies on the `sys.path` not being + # independently changed since the initialization of this module. + # If it is changed, then some packages may fail. + # + # Any other options available? + + # Raises an exception if the parent module cannot be imported. + # This hopefully ensures that we only explicitly import modules + # contained in `pdoc.import_path`. + imp.find_module(module_name.split('.')[0], import_path) + + if module_name in sys.modules: + return sys.modules[module_name] + else: + __import__(module_name) + return sys.modules[module_name] + + +def _source(obj): + """ + Returns the source code of the Python object `obj` as a list of + lines. This tries to extract the source from the special + `__wrapped__` attribute if it exists. Otherwise, it falls back + to `inspect.getsourcelines`. + + If neither works, then the empty list is returned. + """ + try: + return inspect.getsourcelines(obj.__wrapped__)[0] + except: + pass + try: + return inspect.getsourcelines(obj)[0] + except: + return [] + + +def _get_tpl(name): + """ + Returns the Mako template with the given name. If the template + cannot be found, a nicer error message is displayed. + """ + try: + t = tpl_lookup.get_template(name) + except TopLevelLookupException: + locs = [path.join(p, name.lstrip('/')) for p in _template_path] + raise IOError(2, 'No template at any of: %s' % ', '.join(locs)) + return t + + +def _eprint(*args, **kwargs): + """Print to stderr.""" + kwargs['file'] = sys.stderr + print(*args, **kwargs) + + +def _safe_import(module_name): + """ + A function for safely importing `module_name`, where errors are + suppressed and `stdout` and `stderr` are redirected to a null + device. The obligation is on the caller to close `stdin` in order + to avoid impolite modules from blocking on `stdin` when imported. + """ + class _Null (object): + def write(self, *_): + pass + + sout, serr = sys.stdout, sys.stderr + sys.stdout, sys.stderr = _Null(), _Null() + try: + m = import_module(module_name) + except: + m = None + sys.stdout, sys.stderr = sout, serr + return m + + +def _var_docstrings(tree, module, cls=None, init=False): + """ + Extracts variable docstrings given `tree` as the abstract syntax, + `module` as a `pdoc.Module` containing `tree` and an option `cls` + as a `pdoc.Class` corresponding to the tree. In particular, `cls` + should be specified when extracting docstrings from a class or an + `__init__` method. Finally, `init` should be `True` when searching + the AST of an `__init__` method so that `_var_docstrings` will only + accept variables starting with `self.` as instance variables. + + A dictionary mapping variable name to a `pdoc.Variable` object is + returned. + """ + vs = {} + children = list(ast.iter_child_nodes(tree)) + for i, child in enumerate(children): + if isinstance(child, ast.Assign) and len(child.targets) == 1: + if not init and isinstance(child.targets[0], ast.Name): + name = child.targets[0].id + elif (isinstance(child.targets[0], ast.Attribute) + and isinstance(child.targets[0].value, ast.Name) + and child.targets[0].value.id == 'self'): + name = child.targets[0].attr + else: + continue + if not _is_exported(name) \ + and name not in getattr(module, '__all__', []): + continue + + docstring = '' + if (i+1 < len(children) + and isinstance(children[i+1], ast.Expr) + and isinstance(children[i+1].value, ast.Str)): + docstring = children[i+1].value.s + + vs[name] = Variable(name, module, docstring, cls=cls) + return vs + + +def _is_exported(ident_name): + """ + Returns `True` if `ident_name` matches the export criteria for an + identifier name. + + This should not be used by clients. Instead, use + `pdoc.Module.is_public`. + """ + return not ident_name.startswith('_') + + +class Doc (object): + """ + A base class for all documentation objects. + + A documentation object corresponds to *something* in a Python module + that has a docstring associated with it. Typically, this only includes + modules, classes, functions and methods. However, `pdoc` adds support + for extracting docstrings from the abstract syntax tree, which means + that variables (module, class or instance) are supported too. + + A special type of documentation object `pdoc.External` is used to + represent identifiers that are not part of the public interface of + a module. (The name "External" is a bit of a misnomer, since it can + also correspond to unexported members of the module, particularly in + a class's ancestor list.) + """ + def __init__(self, name, module, docstring): + """ + Initializes a documentation object, where `name` is the public + identifier name, `module` is a `pdoc.Module` object, and + `docstring` is a string containing the docstring for `name`. + """ + self.module = module + """ + The module documentation object that this object was defined + in. + """ + + self.name = name + """ + The identifier name for this object. + """ + + self.docstring = inspect.cleandoc(docstring or '') + """ + The docstring for this object. It has already been cleaned + by `inspect.cleandoc`. + """ + + @property + def source(self): + """ + Returns the source code of the Python object `obj` as a list of + lines. This tries to extract the source from the special + `__wrapped__` attribute if it exists. Otherwise, it falls back + to `inspect.getsourcelines`. + + If neither works, then the empty list is returned. + """ + assert False, 'subclass responsibility' + + @property + def refname(self): + """ + Returns an appropriate reference name for this documentation + object. Usually this is its fully qualified path. Every + documentation object must provide this property. + + e.g., The refname for this property is + pdoc.Doc.refname. + """ + assert False, 'subclass responsibility' + + def __lt__(self, other): + return self.name < other.name + + def is_empty(self): + """ + Returns true if the docstring for this object is empty. + """ + return len(self.docstring.strip()) == 0 + + +class Module (Doc): + """ + Representation of a module's documentation. + """ + + __pdoc__['Module.module'] = 'The Python module object.' + __pdoc__['Module.name'] = \ + """ + The name of this module with respect to the context in which + it was imported. It is always an absolute import path. + """ + + def __init__(self, module, docfilter=None, allsubmodules=False): + """ + Creates a `Module` documentation object given the actual + module Python object. + + `docfilter` is an optional predicate that controls which + documentation objects are returned in the following + methods: `pdoc.Module.classes`, `pdoc.Module.functions`, + `pdoc.Module.variables` and `pdoc.Module.submodules`. The + filter is propagated to the analogous methods on a `pdoc.Class` + object. + + If `allsubmodules` is `True`, then every submodule of this + module that can be found will be included in the + documentation, regardless of whether `__all__` contains it. + """ + name = getattr(module, '__pdoc_module_name', module.__name__) + super(Module, self).__init__(name, module, inspect.getdoc(module)) + + self._filtering = docfilter is not None + self._docfilter = (lambda _: True) if docfilter is None else docfilter + self._allsubmodules = allsubmodules + + self.doc = {} + """A mapping from identifier name to a documentation object.""" + + self.refdoc = {} + """ + The same as `pdoc.Module.doc`, but maps fully qualified + identifier names to documentation objects. + """ + + vardocs = {} + try: + tree = ast.parse(inspect.getsource(self.module)) + vardocs = _var_docstrings(tree, self, cls=None) + except: + pass + self._declared_variables = vardocs.keys() + + public = self.__public_objs() + for name, obj in public.items(): + # Skip any identifiers that already have doco. + if name in self.doc and not self.doc[name].is_empty(): + continue + + # Functions and some weird builtins?, plus methods, classes, + # modules and module level variables. + if inspect.isfunction(obj) or inspect.isbuiltin(obj): + self.doc[name] = Function(name, self, obj) + elif inspect.ismethod(obj): + self.doc[name] = Function(name, self, obj) + elif inspect.isclass(obj): + self.doc[name] = Class(name, self, obj) + elif inspect.ismodule(obj): + # Only document modules that are submodules or are forcefully + # exported by __all__. + if obj is not self.module and \ + (self.__is_exported(name, obj) + or self.is_submodule(obj.__name__)): + self.doc[name] = self.__new_submodule(name, obj) + elif name in vardocs: + self.doc[name] = vardocs[name] + else: + # Catch all for variables. + self.doc[name] = Variable(name, self, '', cls=None) + + # Now scan the directory if this is a package for all modules. + if not hasattr(self.module, '__path__') \ + and not hasattr(self.module, '__file__'): + pkgdir = [] + else: + pkgdir = getattr(self.module, '__path__', + [path.dirname(self.module.__file__)]) + if self.is_package(): + for (_, root, _) in pkgutil.iter_modules(pkgdir): + # Ignore if this module was already doc'd. + if root in self.doc: + continue + + # Ignore if it isn't exported, unless we've specifically + # requested to document all submodules. + if not self._allsubmodules \ + and not self.__is_exported(root, self.module): + continue + + fullname = '%s.%s' % (self.name, root) + m = _safe_import(fullname) + if m is None: + continue + self.doc[root] = self.__new_submodule(root, m) + + # Now see if we can grab inheritance relationships between classes. + for docobj in self.doc.values(): + if isinstance(docobj, Class): + docobj._fill_inheritance() + + # Build the reference name dictionary. + for basename, docobj in self.doc.items(): + self.refdoc[docobj.refname] = docobj + if isinstance(docobj, Class): + for v in docobj.class_variables(): + self.refdoc[v.refname] = v + for v in docobj.instance_variables(): + self.refdoc[v.refname] = v + for f in docobj.methods(): + self.refdoc[f.refname] = f + for f in docobj.functions(): + self.refdoc[f.refname] = f + + # Finally look for more docstrings in the __pdoc__ override. + for name, docstring in getattr(self.module, '__pdoc__', {}).items(): + refname = '%s.%s' % (self.refname, name) + if docstring is None: + self.doc.pop(name, None) + self.refdoc.pop(refname, None) + continue + + dobj = self.find_ident(refname) + if isinstance(dobj, External): + continue + dobj.docstring = inspect.cleandoc(docstring) + + def text(self): + """ + Returns the documentation for this module as plain text. + """ + t = _get_tpl('/text.mako') + text, _ = re.subn('\n\n\n+', '\n\n', t.render(module=self).strip()) + return text + + def html(self, external_links=False, link_prefix='', + source=True, **kwargs): + """ + Returns the documentation for this module as + self-contained HTML. + + If `external_links` is `True`, then identifiers to external + modules are always turned into links. + + If `link_prefix` is `True`, then all links will have that + prefix. Otherwise, links are always relative. + + If `source` is `True`, then source code will be retrieved for + every Python object whenever possible. This can dramatically + decrease performance when documenting large modules. + + `kwargs` is passed to the `mako` render function. + """ + t = _get_tpl('/html.mako') + t = t.render(module=self, + external_links=external_links, + link_prefix=link_prefix, + show_source_code=source, + **kwargs) + return t.strip() + + def is_package(self): + """ + Returns `True` if this module is a package. + + Works by checking if `__package__` is not `None` and whether it + has the `__path__` attribute. + """ + return hasattr(self.module, '__path__') + + @property + def source(self): + return _source(self.module) + + @property + def refname(self): + return self.name + + def mro(self, cls): + """ + Returns a method resolution list of documentation objects + for `cls`, which must be a documentation object. + + The list will contain objects belonging to `pdoc.Class` or + `pdoc.External`. Objects belonging to the former are exported + classes either in this module or in one of its sub-modules. + """ + ups = inspect.getmro(cls.cls) + return list(map(lambda c: self.find_class(c), ups)) + + def descendents(self, cls): + """ + Returns a descendent list of documentation objects for `cls`, + which must be a documentation object. + + The list will contain objects belonging to `pdoc.Class` or + `pdoc.External`. Objects belonging to the former are exported + classes either in this module or in one of its sub-modules. + """ + if cls.cls == type or not hasattr(cls.cls, '__subclasses__'): + # Is this right? + return [] + + downs = cls.cls.__subclasses__() + return list(map(lambda c: self.find_class(c), downs)) + + def is_public(self, name): + """ + Returns `True` if and only if an identifier with name `name` is + part of the public interface of this module. While the names + of sub-modules are included, identifiers only exported by + sub-modules are not checked. + + `name` should be a fully qualified name, e.g., + pdoc.Module.is_public. + """ + return name in self.refdoc + + def find_class(self, cls): + """ + Given a Python `cls` object, try to find it in this module + or in any of the exported identifiers of the submodules. + """ + for doc_cls in self.classes(): + if cls is doc_cls.cls: + return doc_cls + for module in self.submodules(): + doc_cls = module.find_class(cls) + if not isinstance(doc_cls, External): + return doc_cls + return External('%s.%s' % (cls.__module__, cls.__name__)) + + def find_ident(self, name): + """ + Searches this module and **all** of its sub-modules for an + identifier with name `name` in its list of exported + identifiers according to `pdoc`. Note that unexported + sub-modules are searched. + + A bare identifier (without `.` separators) will only be checked + for in this module. + + The documentation object corresponding to the identifier is + returned. If one cannot be found, then an instance of + `External` is returned populated with the given identifier. + """ + if name in self.refdoc: + return self.refdoc[name] + for module in self.submodules(): + o = module.find_ident(name) + if not isinstance(o, External): + return o + return External(name) + + def variables(self): + """ + Returns all documented module level variables in the module + sorted alphabetically as a list of `pdoc.Variable`. + """ + p = lambda o: isinstance(o, Variable) and self._docfilter(o) + return sorted(filter(p, self.doc.values())) + + def classes(self): + """ + Returns all documented module level classes in the module + sorted alphabetically as a list of `pdoc.Class`. + """ + p = lambda o: isinstance(o, Class) and self._docfilter(o) + return sorted(filter(p, self.doc.values())) + + def functions(self): + """ + Returns all documented module level functions in the module + sorted alphabetically as a list of `pdoc.Function`. + """ + p = lambda o: isinstance(o, Function) and self._docfilter(o) + return sorted(filter(p, self.doc.values())) + + def submodules(self): + """ + Returns all documented sub-modules in the module sorted + alphabetically as a list of `pdoc.Module`. + """ + p = lambda o: isinstance(o, Module) and self._docfilter(o) + return sorted(filter(p, self.doc.values())) + + def is_submodule(self, name): + """ + Returns `True` if and only if `name` starts with the full + import path of `self` and has length at least one greater than + `len(self.name)`. + """ + return self.name != name and name.startswith(self.name) + + def __is_exported(self, name, module): + """ + Returns `True` if and only if `pdoc` considers `name` to be + a public identifier for this module where `name` was defined + in the Python module `module`. + + If this module has an `__all__` attribute, then `name` is + considered to be exported if and only if it is a member of + this module's `__all__` list. + + If `__all__` is not set, then whether `name` is exported or + not is heuristically determined. Firstly, if `name` starts + with an underscore, it will not be considered exported. + Secondly, if `name` was defined in a module other than this + one, it will not be considered exported. In all other cases, + `name` will be considered exported. + """ + if hasattr(self.module, '__all__'): + return name in self.module.__all__ + if not _is_exported(name): + return False + if module is not None and self.module.__name__ != module.__name__: + return name in self._declared_variables + return True + + def __public_objs(self): + """ + Returns a dictionary mapping a public identifier name to a + Python object. + """ + members = dict(inspect.getmembers(self.module)) + return dict([(name, obj) + for name, obj in members.items() + if self.__is_exported(name, inspect.getmodule(obj))]) + + def __new_submodule(self, name, obj): + """ + Create a new submodule documentation object for this `obj`, + which must by a Python module object and pass along any + settings in this module. + """ + # Forcefully set the module name so that it is always the absolute + # import path. We can't rely on `obj.__name__`, since it doesn't + # necessarily correspond to the public exported name of the module. + obj.__dict__['__pdoc_module_name'] = '%s.%s' % (self.refname, name) + return Module(obj, + docfilter=self._docfilter, + allsubmodules=self._allsubmodules) + + +class Class (Doc): + """ + Representation of a class's documentation. + """ + + def __init__(self, name, module, class_obj): + """ + Same as `pdoc.Doc.__init__`, except `class_obj` must be a + Python class object. The docstring is gathered automatically. + """ + super(Class, self).__init__(name, module, inspect.getdoc(class_obj)) + + self.cls = class_obj + """The class Python object.""" + + self.doc = {} + """A mapping from identifier name to a `pdoc.Doc` objects.""" + + self.doc_init = {} + """ + A special version of `pdoc.Class.doc` that contains + documentation for instance variables found in the `__init__` + method. + """ + + public = self.__public_objs() + try: + # First try and find docstrings for class variables. + # Then move on to finding docstrings for instance variables. + # This must be optional, since not all modules have source + # code available. + cls_ast = ast.parse(inspect.getsource(self.cls)).body[0] + self.doc = _var_docstrings(cls_ast, self.module, cls=self) + + for n in (cls_ast.body if '__init__' in public else []): + if isinstance(n, ast.FunctionDef) and n.name == '__init__': + self.doc_init = _var_docstrings(n, self.module, + cls=self, init=True) + break + except: + pass + + # Convert the public Python objects to documentation objects. + for name, obj in public.items(): + # Skip any identifiers that already have doco. + if name in self.doc and not self.doc[name].is_empty(): + continue + if name in self.doc_init: + # Let instance members override class members. + continue + + if inspect.ismethod(obj): + self.doc[name] = Function(name, self.module, obj.__func__, + cls=self, method=True) + elif inspect.isfunction(obj): + self.doc[name] = Function(name, self.module, obj, + cls=self, method=False) + elif isinstance(obj, property): + docstring = getattr(obj, '__doc__', '') + self.doc_init[name] = Variable(name, self.module, docstring, + cls=self) + elif not inspect.isbuiltin(obj) \ + and not inspect.isroutine(obj): + if name in getattr(self.cls, '__slots__', []): + self.doc_init[name] = Variable(name, self.module, + '', cls=self) + else: + self.doc[name] = Variable(name, self.module, '', cls=self) + + @property + def source(self): + return _source(self.cls) + + @property + def refname(self): + return '%s.%s' % (self.module.refname, self.cls.__name__) + + def class_variables(self): + """ + Returns all documented class variables in the class, sorted + alphabetically as a list of `pdoc.Variable`. + """ + p = lambda o: isinstance(o, Variable) and self.module._docfilter(o) + return sorted(filter(p, self.doc.values())) + + def instance_variables(self): + """ + Returns all instance variables in the class, sorted + alphabetically as a list of `pdoc.Variable`. Instance variables + are attributes of `self` defined in a class's `__init__` + method. + """ + p = lambda o: isinstance(o, Variable) and self.module._docfilter(o) + return sorted(filter(p, self.doc_init.values())) + + def methods(self): + """ + Returns all documented methods as `pdoc.Function` objects in + the class, sorted alphabetically with `__init__` always coming + first. + + Unfortunately, this also includes class methods. + """ + p = lambda o: (isinstance(o, Function) + and o.method + and self.module._docfilter(o)) + return sorted(filter(p, self.doc.values())) + + def functions(self): + """ + Returns all documented static functions as `pdoc.Function` + objects in the class, sorted alphabetically. + """ + p = lambda o: (isinstance(o, Function) + and not o.method + and self.module._docfilter(o)) + return sorted(filter(p, self.doc.values())) + + def _fill_inheritance(self): + """ + Traverses this class's ancestor list and attempts to fill in + missing documentation from its ancestor's documentation. + + The first pass connects variables, methods and functions with + their inherited couterparts. (The templates will decide how to + display docstrings.) The second pass attempts to add instance + variables to this class that were only explicitly declared in + a parent class. This second pass is necessary since instance + variables are only discoverable by traversing the abstract + syntax tree. + """ + mro = filter(lambda c: c != self and isinstance(c, Class), + self.module.mro(self)) + + def search(d, fdoc): + for c in mro: + doc = fdoc(c) + if d.name in doc and isinstance(d, type(doc[d.name])): + return doc[d.name] + return None + for fdoc in (lambda c: c.doc_init, lambda c: c.doc): + for d in fdoc(self).values(): + dinherit = search(d, fdoc) + if dinherit is not None: + d.inherits = dinherit + + # Since instance variables aren't part of a class's members, + # we need to manually deduce inheritance. Oh lawdy. + for c in mro: + for name in filter(lambda n: n not in self.doc_init, c.doc_init): + d = c.doc_init[name] + self.doc_init[name] = Variable(d.name, d.module, '', cls=self) + self.doc_init[name].inherits = d + + def __public_objs(self): + """ + Returns a dictionary mapping a public identifier name to a + Python object. This counts the `__init__` method as being + public. + """ + _pdoc = getattr(self.module.module, '__pdoc__', {}) + + def forced_out(name): + return _pdoc.get('%s.%s' % (self.name, name), False) is None + + def exported(name): + exported = name == '__init__' or _is_exported(name) + return not forced_out(name) and exported + + idents = dict(inspect.getmembers(self.cls)) + return dict([(n, o) for n, o in idents.items() if exported(n)]) + + +class Function (Doc): + """ + Representation of documentation for a Python function or method. + """ + + def __init__(self, name, module, func_obj, cls=None, method=False): + """ + Same as `pdoc.Doc.__init__`, except `func_obj` must be a + Python function object. The docstring is gathered automatically. + + `cls` should be set when this is a method or a static function + beloing to a class. `cls` should be a `pdoc.Class` object. + + `method` should be `True` when the function is a method. In + all other cases, it should be `False`. + """ + super(Function, self).__init__(name, module, inspect.getdoc(func_obj)) + + self.func = func_obj + """The Python function object.""" + + self.cls = cls + """ + The `pdoc.Class` documentation object if this is a method. If + not, this is None. + """ + + self.method = method + """ + Whether this function is a method or not. + + In particular, static class methods have this set to False. + """ + + @property + def source(self): + return _source(self.func) + + @property + def refname(self): + if self.cls is None: + return '%s.%s' % (self.module.refname, self.name) + else: + return '%s.%s' % (self.cls.refname, self.name) + + def spec(self): + """ + Returns a nicely formatted spec of the function's parameter + list as a string. This includes argument lists, keyword + arguments and default values. + """ + return ', '.join(self.params()) + + def params(self): + """ + Returns a list where each element is a nicely formatted + parameter of this function. This includes argument lists, + keyword arguments and default values. + """ + def fmt_param(el): + if isinstance(el, str) or isinstance(el, unicode): + return el + else: + return '(%s)' % (', '.join(map(fmt_param, el))) + try: + getspec = getattr(inspect, 'getfullargspec', inspect.getargspec) + s = getspec(self.func) + except TypeError: + # I guess this is for C builtin functions? + return ['...'] + + params = [] + for i, param in enumerate(s.args): + if s.defaults is not None and len(s.args) - i <= len(s.defaults): + defind = len(s.defaults) - (len(s.args) - i) + params.append('%s=%s' % (param, repr(s.defaults[defind]))) + else: + params.append(fmt_param(param)) + if s.varargs is not None: + params.append('*%s' % s.varargs) + + # TODO: This needs to be adjusted in Python 3. There's more stuff + # returned from getfullargspec than what we're looking at here. + keywords = getattr(s, 'varkw', getattr(s, 'keywords', None)) + if keywords is not None: + params.append('**%s' % keywords) + return params + + def __lt__(self, other): + # Push __init__ to the top. + if '__init__' in (self.name, other.name): + return self.name != other.name and self.name == '__init__' + else: + return self.name < other.name + + +class Variable (Doc): + """ + Representation of a variable's documentation. This includes + module, class and instance variables. + """ + + def __init__(self, name, module, docstring, cls=None): + """ + Same as `pdoc.Doc.__init__`, except `cls` should be provided + as a `pdoc.Class` object when this is a class or instance + variable. + """ + super(Variable, self).__init__(name, module, docstring) + + self.cls = cls + """ + The `podc.Class` object if this is a class or instance + variable. If not, this is None. + """ + + @property + def source(self): + return [] + + @property + def refname(self): + if self.cls is None: + return '%s.%s' % (self.module.refname, self.name) + else: + return '%s.%s' % (self.cls.refname, self.name) + + +class External (Doc): + """ + A representation of an external identifier. The textual + representation is the same as an internal identifier, but without + any context. (Usually this makes linking more difficult.) + + External identifiers are also used to represent something that is + not exported but appears somewhere in the public interface (like + the ancestor list of a class). + """ + + __pdoc__['External.docstring'] = \ + """ + An empty string. External identifiers do not have + docstrings. + """ + __pdoc__['External.module'] = \ + """ + Always `None`. External identifiers have no associated + `pdoc.Module`. + """ + __pdoc__['External.name'] = \ + """ + Always equivalent to `pdoc.External.refname` since external + identifiers are always expressed in their fully qualified + form. + """ + + def __init__(self, name): + """ + Initializes an external identifier with `name`, where `name` + should be a fully qualified name. + """ + super(External, self).__init__(name, None, '') + + @property + def source(self): + return [] + + @property + def refname(self): + return self.name diff --git a/ext_libs/pdoc/templates/LICENSE b/ext_libs/pdoc/templates/LICENSE new file mode 100644 index 000000000..294e91d80 --- /dev/null +++ b/ext_libs/pdoc/templates/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) HTML5 Boilerplate + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ext_libs/pdoc/templates/README.md b/ext_libs/pdoc/templates/README.md new file mode 100644 index 000000000..a396ff5d2 --- /dev/null +++ b/ext_libs/pdoc/templates/README.md @@ -0,0 +1,3 @@ +The license included in this directory is for +[HTML5 Boiler Plate](http://html5boilerplate.com). Some of the HTML and CSS +used here is derived from that project. diff --git a/ext_libs/pdoc/templates/css.mako b/ext_libs/pdoc/templates/css.mako new file mode 100644 index 000000000..1c3ac6c87 --- /dev/null +++ b/ext_libs/pdoc/templates/css.mako @@ -0,0 +1,928 @@ +<%def name="pdoc()"> + html, body { + margin: 0; + padding: 0; + min-height: 100%; + } + body { + background: #fff; + font-family: "Source Sans Pro", "Helvetica Neueue", Helvetica, sans; + font-weight: 300; + font-size: 16px; + line-height: 1.6em; + } + #content { + width: 70%; + max-width: 850px; + float: left; + padding: 30px 60px; + border-left: 1px solid #ddd; + } + #sidebar { + width: 25%; + float: left; + padding: 30px; + overflow: hidden; + } + #nav { + font-size: 130%; + margin: 0 0 15px 0; + } + + #top { + display: block; + position: fixed; + bottom: 5px; + left: 5px; + font-size: .85em; + text-transform: uppercase; + } + + #footer { + font-size: .75em; + padding: 5px 30px; + border-top: 1px solid #ddd; + text-align: right; + } + #footer p { + margin: 0 0 0 30px; + display: inline-block; + } + + h1, h2, h3, h4, h5 { + font-weight: 300; + } + h1 { + font-size: 2.5em; + line-height: 1.1em; + margin: 0 0 .50em 0; + } + + h2 { + font-size: 1.75em; + margin: 1em 0 .50em 0; + } + + h3 { + margin: 25px 0 10px 0; + } + + h4 { + margin: 0; + font-size: 105%; + } + + a { + color: #058; + text-decoration: none; + transition: color .3s ease-in-out; + } + + a:hover { + color: #e08524; + transition: color .3s ease-in-out; + } + + pre, code, .mono, .name { + font-family: "Ubuntu Mono", "Cousine", "DejaVu Sans Mono", monospace; + } + + .title .name { + font-weight: bold; + } + .section-title { + margin-top: 2em; + } + .ident { + color: #900; + } + + code { + background: #f9f9f9; + } + + pre { + background: #fefefe; + border: 1px solid #ddd; + box-shadow: 2px 2px 0 #f3f3f3; + margin: 0 30px; + padding: 15px 30px; + } + + .codehilite { + margin: 0 30px 10px 30px; + } + + .codehilite pre { + margin: 0; + } + .codehilite .err { background: #ff3300; color: #fff !important; } + + table#module-list { + font-size: 110%; + } + + table#module-list tr td:first-child { + padding-right: 10px; + white-space: nowrap; + } + + table#module-list td { + vertical-align: top; + padding-bottom: 8px; + } + + table#module-list td p { + margin: 0 0 7px 0; + } + + .def { + display: table; + } + + .def p { + display: table-cell; + vertical-align: top; + text-align: left; + } + + .def p:first-child { + white-space: nowrap; + } + + .def p:last-child { + width: 100%; + } + + + #index { + list-style-type: none; + margin: 0; + padding: 0; + } + ul#index .class_name { + /* font-size: 110%; */ + font-weight: bold; + } + #index ul { + margin: 0; + } + + .item { + margin: 0 0 15px 0; + } + + .item .class { + margin: 0 0 25px 30px; + } + + .item .class ul.class_list { + margin: 0 0 20px 0; + } + + .item .name { + background: #fafafa; + margin: 0; + font-weight: bold; + padding: 5px 10px; + border-radius: 3px; + display: inline-block; + min-width: 40%; + } + .item .name:hover { + background: #f6f6f6; + } + + .item .empty_desc { + margin: 0 0 5px 0; + padding: 0; + } + + .item .inheritance { + margin: 3px 0 0 30px; + } + + .item .inherited { + color: #666; + } + + .item .desc { + padding: 0 8px; + margin: 0; + } + + .item .desc p { + margin: 0 0 10px 0; + } + + .source_cont { + margin: 0; + padding: 0; + } + + .source_link a { + background: #ffc300; + font-weight: 400; + font-size: .75em; + text-transform: uppercase; + color: #fff; + text-shadow: 1px 1px 0 #f4b700; + + padding: 3px 8px; + border-radius: 2px; + transition: background .3s ease-in-out; + } + .source_link a:hover { + background: #FF7200; + text-shadow: none; + transition: background .3s ease-in-out; + } + + .source { + display: none; + max-height: 600px; + overflow-y: scroll; + margin-bottom: 15px; + } + + .source .codehilite { + margin: 0; + } + + .desc h1, .desc h2, .desc h3 { + font-size: 100% !important; + } + .clear { + clear: both; + } + + @media all and (max-width: 950px) { + #sidebar { + width: 35%; + } + #content { + width: 65%; + } + } + @media all and (max-width: 650px) { + #top { + display: none; + } + #sidebar { + float: none; + width: auto; + } + #content { + float: none; + width: auto; + padding: 30px; + } + + #index ul { + padding: 0; + margin-bottom: 15px; + } + #index ul li { + display: inline-block; + margin-right: 30px; + } + #footer { + text-align: left; + } + #footer p { + display: block; + margin: inherit; + } + } + + /*****************************/ + +<%def name="pre()"> +* { + box-sizing: border-box; +} +/*! normalize.css v1.1.1 | MIT License | git.io/normalize */ + +/* ========================================================================== + HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +/** + * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. + */ + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. + * Known issue: no IE 6 support. + */ + +[hidden] { + display: none; +} + +/* ========================================================================== + Base + ========================================================================== */ + +/** + * 1. Prevent system color scheme's background color being used in Firefox, IE, + * and Opera. + * 2. Prevent system color scheme's text color being used in Firefox, IE, and + * Opera. + * 3. Correct text resizing oddly in IE 6/7 when body `font-size` is set using + * `em` units. + * 4. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + background: #fff; /* 1 */ + color: #000; /* 2 */ + font-size: 100%; /* 3 */ + -webkit-text-size-adjust: 100%; /* 4 */ + -ms-text-size-adjust: 100%; /* 4 */ +} + +/** + * Address `font-family` inconsistency between `textarea` and other form + * elements. + */ + +html, +button, +input, +select, +textarea { + font-family: sans-serif; +} + +/** + * Address margins handled incorrectly in IE 6/7. + */ + +body { + margin: 0; +} + +/* ========================================================================== + Links + ========================================================================== */ + +/** + * Address `outline` inconsistency between Chrome and other browsers. + */ + +a:focus { + outline: thin dotted; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* ========================================================================== + Typography + ========================================================================== */ + +/** + * Address font sizes and margins set differently in IE 6/7. + * Address font sizes within `section` and `article` in Firefox 4+, Safari 5, + * and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +h2 { + font-size: 1.5em; + margin: 0.83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: 0.83em; + margin: 1.67em 0; +} + +h6 { + font-size: 0.67em; + margin: 2.33em 0; +} + +/** + * Address styling not present in IE 7/8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +/** + * Address styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address differences between Firefox and other browsers. + * Known issue: no IE 6/7 normalization. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Address styling not present in IE 6/7/8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address margins set differently in IE 6/7. + */ + +p, +pre { + margin: 1em 0; +} + +/** + * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +/** + * Improve readability of pre-formatted text in all browsers. + */ + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +/** + * Address CSS quotes not supported in IE 6/7. + */ + +q { + quotes: none; +} + +/** + * Address `quotes` property not supported in Safari 4. + */ + +q:before, +q:after { + content: ''; + content: none; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ========================================================================== + Lists + ========================================================================== */ + +/** + * Address margins set differently in IE 6/7. + */ + +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +/** + * Address paddings set differently in IE 6/7. + */ + +menu, +ol, +ul { + padding: 0 0 0 40px; +} + +/** + * Correct list images handled incorrectly in IE 7. + */ + +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + +/* ========================================================================== + Embedded content + ========================================================================== */ + +/** + * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. + * 2. Improve image quality when scaled in IE 7. + */ + +img { + border: 0; /* 1 */ + -ms-interpolation-mode: bicubic; /* 2 */ +} + +/** + * Correct overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* ========================================================================== + Figures + ========================================================================== */ + +/** + * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. + */ + +figure { + margin: 0; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +/** + * Correct margin displayed oddly in IE 6/7. + */ + +form { + margin: 0; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct color not being inherited in IE 6/7/8/9. + * 2. Correct text not wrapping in Firefox 3. + * 3. Correct alignment displayed oddly in IE 6/7. + */ + +legend { + border: 0; /* 1 */ + padding: 0; + white-space: normal; /* 2 */ + *margin-left: -7px; /* 3 */ +} + +/** + * 1. Correct font size not being inherited in all browsers. + * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, + * and Chrome. + * 3. Improve appearance and consistency in all browsers. + */ + +button, +input, +select, +textarea { + font-size: 100%; /* 1 */ + margin: 0; /* 2 */ + vertical-align: baseline; /* 3 */ + *vertical-align: middle; /* 3 */ +} + +/** + * Address Firefox 3+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +button, +input { + line-height: normal; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. + * Correct `select` style inheritance in Firefox 4+ and Opera. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + * 4. Remove inner spacing in IE 7 without affecting normal text inputs. + * Known issue: inner spacing remains in IE 6. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ + *overflow: visible; /* 4 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * 1. Address box sizing set to content-box in IE 8/9. + * 2. Remove excess padding in IE 8/9. + * 3. Remove excess padding in IE 7. + * Known issue: excess padding remains in IE 6. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + *height: 13px; /* 3 */ + *width: 13px; /* 3 */ +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari 5 and Chrome + * on OS X. + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Remove inner padding and border in Firefox 3+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * 1. Remove default vertical scrollbar in IE 6/7/8/9. + * 2. Improve readability and alignment in all browsers. + */ + +textarea { + overflow: auto; /* 1 */ + vertical-align: top; /* 2 */ +} + +/* ========================================================================== + Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + + +<%def name="post()"> +/* ========================================================================== + EXAMPLE Media Queries for Responsive Design. + These examples override the primary ('mobile first') styles. + Modify as content requires. + ========================================================================== */ + +@media only screen and (min-width: 35em) { + /* Style adjustments for viewports that meet the condition */ +} + +@media print, + (-o-min-device-pixel-ratio: 5/4), + (-webkit-min-device-pixel-ratio: 1.25), + (min-resolution: 120dpi) { + /* Style adjustments for high resolution devices */ +} + +/* ========================================================================== + Print styles. + Inlined to avoid required HTTP connection: h5bp.com/r + ========================================================================== */ + +@media print { + * { + background: transparent !important; + color: #000 !important; /* Black prints faster: h5bp.com/s */ + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + /* + * Don't show links for images, or javascript/internal links + */ + + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; /* h5bp.com/t */ + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + @page { + margin: 0.5cm; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} + diff --git a/ext_libs/pdoc/templates/html.mako b/ext_libs/pdoc/templates/html.mako new file mode 100644 index 000000000..46a1726d2 --- /dev/null +++ b/ext_libs/pdoc/templates/html.mako @@ -0,0 +1,484 @@ +<% + import re + import sys + + import markdown + try: + import pygments + import pygments.formatters + import pygments.lexers + use_pygments = True + except ImportError: + use_pygments = False + + import pdoc + + # From language reference, but adds '.' to allow fully qualified names. + pyident = re.compile('^[a-zA-Z_][a-zA-Z0-9_.]+$') + indent = re.compile('^\s*') + + # Whether we're showing the module list or a single module. + module_list = 'modules' in context.keys() + + def decode(s): + if sys.version_info[0] < 3 and isinstance(s, str): + return s.decode('utf-8', 'ignore') + return s + + def ident(s): + return '%s' % s + + def sourceid(dobj): + return 'source-%s' % dobj.refname + + def clean_source_lines(lines): + """ + Cleans the source code so that pygments can render it well. + + Returns one string with all of the source code. + """ + base_indent = len(indent.match(lines[0]).group(0)) + base_indent = 0 + for line in lines: + if len(line.strip()) > 0: + base_indent = len(indent.match(lines[0]).group(0)) + break + lines = [line[base_indent:] for line in lines] + if not use_pygments: # :-( + return '
%s
' % (''.join(lines)) + + pylex = pygments.lexers.PythonLexer() + htmlform = pygments.formatters.HtmlFormatter(cssclass='codehilite') + return pygments.highlight(''.join(lines), pylex, htmlform) + + def linkify(match): + matched = match.group(0) + ident = matched[1:-1] + name, url = lookup(ident) + if name is None: + return matched + return '[`%s`](%s)' % (name, url) + + def mark(s, linky=True): + if linky: + s, _ = re.subn('\b\n\b', ' ', s) + if not module_list: + s, _ = re.subn('`[^`]+`', linkify, s) + + extensions = [] + if use_pygments: + extensions = ['markdown.extensions.codehilite(linenums=False)'] + s = markdown.markdown(s.strip(), extensions=extensions) + return s + + def glimpse(s, length=100): + if len(s) < length: + return s + return s[0:length] + '...' + + def module_url(m): + """ + Returns a URL for `m`, which must be an instance of `Module`. + Also, `m` must be a submodule of the module being documented. + + Namely, '.' import separators are replaced with '/' URL + separators. Also, packages are translated as directories + containing `index.html` corresponding to the `__init__` module, + while modules are translated as regular HTML files with an + `.m.html` suffix. (Given default values of + `pdoc.html_module_suffix` and `pdoc.html_package_name`.) + """ + if module.name == m.name: + return '' + + if len(link_prefix) > 0: + base = m.name + else: + base = m.name[len(module.name)+1:] + url = base.replace('.', '/') + if m.is_package(): + url += '/%s' % pdoc.html_package_name + else: + url += pdoc.html_module_suffix + return link_prefix + url + + def external_url(refname): + """ + Attempts to guess an absolute URL for the external identifier + given. + + Note that this just returns the refname with an ".ext" suffix. + It will be up to whatever is interpreting the URLs to map it + to an appropriate documentation page. + """ + return '/%s.ext' % refname + + def is_external_linkable(name): + return external_links and pyident.match(name) and '.' in name + + def lookup(refname): + """ + Given a fully qualified identifier name, return its refname + with respect to the current module and a value for a `href` + attribute. If `refname` is not in the public interface of + this module or its submodules, then `None` is returned for + both return values. (Unless this module has enabled external + linking.) + + In particular, this takes into account sub-modules and external + identifiers. If `refname` is in the public API of the current + module, then a local anchor link is given. If `refname` is in the + public API of a sub-module, then a link to a different page with + the appropriate anchor is given. Otherwise, `refname` is + considered external and no link is used. + """ + d = module.find_ident(refname) + if isinstance(d, pdoc.External): + if is_external_linkable(refname): + return d.refname, external_url(d.refname) + else: + return None, None + if isinstance(d, pdoc.Module): + return d.refname, module_url(d) + if module.is_public(d.refname): + return d.name, '#%s' % d.refname + return d.refname, '%s#%s' % (module_url(d.module), d.refname) + + def link(refname): + """ + A convenience wrapper around `href` to produce the full + `a` tag if `refname` is found. Otherwise, plain text of + `refname` is returned. + """ + name, url = lookup(refname) + if name is None: + return refname + return '%s' % (url, name) +%> +<%def name="show_source(d)"> + % if show_source_code and d.source is not None and len(d.source) > 0: + +
+ ${decode(clean_source_lines(d.source))} +
+ % endif + + +<%def name="show_desc(d, limit=None)"> + <% + inherits = (hasattr(d, 'inherits') + and (len(d.docstring) == 0 + or d.docstring == d.inherits.docstring)) + docstring = (d.inherits.docstring if inherits else d.docstring).strip() + if limit is not None: + docstring = glimpse(docstring, limit) + %> + % if len(docstring) > 0: + % if inherits: +
${docstring | mark}
+ % else: +
${docstring | mark}
+ % endif + % endif + % if not isinstance(d, pdoc.Module): +
${show_source(d)}
+ % endif + + +<%def name="show_inheritance(d)"> + % if hasattr(d, 'inherits'): +

+ Inheritance: + % if hasattr(d.inherits, 'cls'): + ${link(d.inherits.cls.refname)}.${link(d.inherits.refname)} + % else: + ${link(d.inherits.refname)} + % endif +

+ % endif + + +<%def name="show_module_list(modules)"> +

Python module list

+ +% if len(modules) == 0: +

No modules found.

+% else: + + % for name, desc in modules: + + + + + % endfor +
${name} + % if len(desc.strip()) > 0: +
${desc | mark}
+ % endif +
+% endif + + +<%def name="show_column_list(items, numcols=3)"> + + + +<%def name="show_module(module)"> + <% + variables = module.variables() + classes = module.classes() + functions = module.functions() + submodules = module.submodules() + %> + + <%def name="show_func(f)"> +
+
+

def ${ident(f.name)}(

${f.spec() | h})

+
+ ${show_inheritance(f)} + ${show_desc(f)} +
+ + + % if 'http_server' in context.keys() and http_server: + + % endif + +
+

${module.name} module

+ ${module.docstring | mark} + ${show_source(module)} +
+ +
+ % if len(variables) > 0: +

Module variables

+ % for v in variables: +
+

var ${ident(v.name)}

+ ${show_desc(v)} +
+ % endfor + % endif + + % if len(functions) > 0: +

Functions

+ % for f in functions: + ${show_func(f)} + % endfor + % endif + + % if len(classes) > 0: +

Classes

+ % for c in classes: + <% + class_vars = c.class_variables() + smethods = c.functions() + inst_vars = c.instance_variables() + methods = c.methods() + mro = c.module.mro(c) + %> +
+

class ${ident(c.name)}

+ ${show_desc(c)} + +
+ % if len(mro) > 0: +

Ancestors (in MRO)

+
    + % for cls in mro: +
  • ${link(cls.refname)}
  • + % endfor +
+ % endif + % if len(class_vars) > 0: +

Class variables

+ % for v in class_vars: +
+

var ${ident(v.name)}

+ ${show_inheritance(v)} + ${show_desc(v)} +
+ % endfor + % endif + % if len(smethods) > 0: +

Static methods

+ % for f in smethods: + ${show_func(f)} + % endfor + % endif + % if len(inst_vars) > 0: +

Instance variables

+ % for v in inst_vars: +
+

var ${ident(v.name)}

+ ${show_inheritance(v)} + ${show_desc(v)} +
+ % endfor + % endif + % if len(methods) > 0: +

Methods

+ % for f in methods: + ${show_func(f)} + % endfor + % endif +
+
+ % endfor + % endif + + % if len(submodules) > 0: +

Sub-modules

+ % for m in submodules: +
+

${link(m.refname)}

+ ${show_desc(m, limit=300)} +
+ % endfor + % endif +
+ + +<%def name="module_index(module)"> + <% + variables = module.variables() + classes = module.classes() + functions = module.functions() + submodules = module.submodules() + %> + + + + + + + + + % if module_list: + Python module list + + % else: + ${module.name} API documentation + + % endif + + + <%namespace name="css" file="css.mako" /> + + + + + % if use_pygments: + + % endif + + + + + + +Top + +
+ % if module_list: +
+ ${show_module_list(modules)} +
+ % else: + ${module_index(module)} +
+ ${show_module(module)} +
+ % endif +
+ +
+ + diff --git a/ext_libs/pdoc/templates/text.mako b/ext_libs/pdoc/templates/text.mako new file mode 100644 index 000000000..c5464dafd --- /dev/null +++ b/ext_libs/pdoc/templates/text.mako @@ -0,0 +1,147 @@ +## Define mini-templates for each portion of the doco. + +<%! + import re + + def indent(s, spaces=4): + """ + Inserts `spaces` after each string of new lines in `s` + and before the start of the string. + """ + new = re.sub('(\n+)', '\\1%s' % (' ' * spaces), s) + return (' ' * spaces) + new.strip() + + def docstring(d): + if len(d.docstring) == 0 and hasattr(d, 'inherits'): + return d.inherits.docstring + else: + return d.docstring +%> + +<%def name="function(func)" filter="trim"> +${func.name}(${func.spec()}) +${docstring(func) | indent} + + +<%def name="variable(var)" filter="trim"> +${var.name} +${docstring(var) | indent} + + +<%def name="class_(cls)" filter="trim"> +${cls.name} \ +% if len(cls.docstring) > 0: + +${cls.docstring | indent} +% endif +<% + class_vars = cls.class_variables() + static_methods = cls.functions() + inst_vars = cls.instance_variables() + methods = cls.methods() + mro = cls.module.mro(cls) + descendents = cls.module.descendents(cls) +%> +% if len(mro) > 0: + Ancestors (in MRO) + ------------------ + % for c in mro: + ${c.refname} + % endfor + +% endif +% if len(descendents) > 0: + Descendents + ----------- + % for c in descendents: + ${c.refname} + % endfor + +% endif +% if len(class_vars) > 0: + Class variables + --------------- + % for v in class_vars: +${capture(variable, v) | indent} + + % endfor +% endif +% if len(static_methods) > 0: + Static methods + -------------- + % for f in static_methods: +${capture(function, f) | indent} + + % endfor +% endif +% if len(inst_vars) > 0: + Instance variables + ------------------ + % for v in inst_vars: +${capture(variable, v) | indent} + + % endfor +% endif +% if len(methods) > 0: + Methods + ------- + % for m in methods: +${capture(function, m) | indent} + + % endfor +% endif + + +## Start the output logic for an entire module. + +<% + variables = module.variables() + classes = module.classes() + functions = module.functions() + submodules = module.submodules() +%> + +Module ${module.name} +-------${'-' * len(module.name)} +% if not module._filtering: +${module.docstring} +% endif + + +% if len(variables) > 0: +Variables +--------- + % for v in variables: +${variable(v)} + + % endfor +% endif + + +% if len(functions) > 0: +Functions +--------- + % for f in functions: +${function(f)} + + % endfor +% endif + + +% if len(classes) > 0: +Classes +------- + % for c in classes: +${class_(c)} + + % endfor +% endif + + +% if len(submodules) > 0: +Sub-modules +----------- + % for m in submodules: + ${m.name} + % endfor +% endif