DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (d38398e5144e)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.

from __future__ import absolute_import, print_function, unicode_literals

import os
import sys

from mach.decorators import (
    Command,
    CommandArgument,
    CommandProvider,
)

import mozhttpd

from mozbuild.base import MachCommandBase


@CommandProvider
class Documentation(MachCommandBase):
    """Helps manage in-tree documentation."""

    @Command('doc', category='devenv',
        description='Generate and display documentation from the tree.')
    @CommandArgument('what', nargs='*', metavar='DIRECTORY [, DIRECTORY]',
        help='Path(s) to documentation to build and display.')
    @CommandArgument('--format', default='html',
        help='Documentation format to write.')
    @CommandArgument('--outdir', default=None, metavar='DESTINATION',
        help='Where to write output.')
    @CommandArgument('--no-open', dest='auto_open', default=True, action='store_false',
        help="Don't automatically open HTML docs in a browser.")
    @CommandArgument('--http', const=':6666', metavar='ADDRESS', nargs='?',
        help='Serve documentation on an HTTP server, e.g. ":6666".')
    def build_docs(self, what=None, format=None, outdir=None, auto_open=True, http=None):
        self._activate_virtualenv()
        self.virtualenv_manager.install_pip_package('sphinx_rtd_theme==0.1.6')

        import sphinx
        import webbrowser

        if not outdir:
            outdir = os.path.join(self.topobjdir, 'docs')
        if not what:
            what = [os.path.join(self.topsrcdir, 'tools')]
        outdir = os.path.join(outdir, format)

        generated = []
        failed = []
        for path in what:
            path = os.path.normpath(os.path.abspath(path))
            docdir = self._find_doc_dir(path)

            if not docdir:
                failed.append((path, 'could not find docs at this location'))
                continue

            # find project name to use as a namespace within `outdir`
            project = self._find_project_name(docdir)
            savedir = os.path.join(outdir, project)

            args = [
                'sphinx',
                '-b', format,
                docdir,
                savedir,
            ]
            result = sphinx.build_main(args)
            if result != 0:
                failed.append((path, 'sphinx return code %d' % result))
            else:
                generated.append(savedir)

            index_path = os.path.join(savedir, 'index.html')
            if not http and auto_open and os.path.isfile(index_path):
                webbrowser.open(index_path)

        if generated:
            print('\nGenerated documentation:\n%s\n' % '\n'.join(generated))

        if failed:
            failed = ['%s: %s' % (f[0], f[1]) for f in failed]
            return die('failed to generate documentation:\n%s' % '\n'.join(failed))

        if http is not None:
            host, port = http.split(':', 1)
            addr = (host, int(port))
            if len(addr) != 2:
                return die('invalid address: %s' % http)

            httpd = mozhttpd.MozHttpd(host=addr[0], port=addr[1], docroot=outdir)
            print('listening on %s:%d' % addr)
            httpd.start(block=True)

    def _find_project_name(self, path):
        import imp
        path = os.path.join(path, 'conf.py')
        with open(path, 'r') as fh:
            conf = imp.load_module('doc_conf', fh, path,
                                   ('.py', 'r', imp.PY_SOURCE))

        return conf.project.replace(' ', '_')

    def _find_doc_dir(self, path):
        search_dirs = ('doc', 'docs')
        for d in search_dirs:
            p = os.path.join(path, d)
            if os.path.isfile(os.path.join(p, 'conf.py')):
                return p


def die(msg, exit_code=1):
    msg = '%s: %s' % (sys.argv[0], msg)
    print(msg, file=sys.stderr)
    return exit_code