예제 #1
0
파일: build.py 프로젝트: scanlime/picogui
def publish(writer_name):

    reader=None
    reader_name='standalone'
    parser=None
    parser_name='restructuredtext'
    writer=None
    settings=None
    settings_spec=None
    settings_overrides=options[writer_name]
    config_section=None
    enable_exit=1
    argv=[]
    usage=default_usage

    pub = Publisher(reader, parser, writer, settings=settings)
    pub.set_components(reader_name, parser_name, writer_name)
    settings = pub.get_settings(settings_spec=settings_spec,
                                config_section=config_section)
    if settings_overrides:
        settings._update(settings_overrides, 'loose')
    source = file(source_path)
    pub.set_source(source, source_path)
    destination_path = 'pg1.' + extensions[writer_name]
    destination = file(destination_path, 'w')
    pub.set_destination(destination, destination_path)
    pub.publish(argv, usage, description, settings_spec, settings_overrides,
                config_section=config_section, enable_exit=enable_exit)
예제 #2
0
def main(args):
    argv = None
    reader = standalone.Reader()
    reader_name = 'standalone'
    writer = EpubWriter()
    writer_name = 'epub2'
    parser = Parser()
    parser_name = 'restructuredtext'
    settings = None
    settings_spec = None
    settings_overrides = None
    config_section = None
    enable_exit_status = 1
    usage = default_usage
    publisher = Publisher(reader,
                          parser,
                          writer,
                          settings,
                          destination_class=EpubFileOutput)
    publisher.set_components(reader_name, parser_name, writer_name)
    description = ('Generates epub books from reStructuredText sources.  ' +
                   default_description)

    output = publisher.publish(argv,
                               usage,
                               description,
                               settings_spec,
                               settings_overrides,
                               config_section=config_section,
                               enable_exit_status=enable_exit_status)
예제 #3
0
def publish_cmdline_to_binary(reader=None, reader_name='standalone',
                    parser=None, parser_name='restructuredtext',
                    writer=None, writer_name='pseudoxml',
                    settings=None, settings_spec=None,
                    settings_overrides=None, config_section=None,
                    enable_exit_status=1, argv=None,
                    usage=default_usage, description=default_description,
                    destination=None, destination_class=BinaryFileOutput
                    ):
    """
    Set up & run a `Publisher` for command-line-based file I/O (input and
    output file paths taken automatically from the command line).  Return the
    encoded string output also.

    This is just like publish_cmdline, except that it uses
    io.BinaryFileOutput instead of io.FileOutput.

    Parameters: see `publish_programmatically` for the remainder.

    - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``.
    - `usage`: Usage string, output if there's a problem parsing the command
      line.
    - `description`: Program description, output for the "--help" option
      (along with command-line option descriptions).
    """
    pub = Publisher(reader, parser, writer, settings=settings,
        destination_class=destination_class)
    pub.set_components(reader_name, parser_name, writer_name)
    output = pub.publish(
        argv, usage, description, settings_spec, settings_overrides,
        config_section=config_section, enable_exit_status=enable_exit_status)
    return output
예제 #4
0
def publish_cmdline(reader=None,
                    reader_name='standalone',
                    parser=None,
                    parser_name='restructuredtext',
                    writer=None,
                    writer_name='pseudoxml',
                    settings=None,
                    settings_spec=None,
                    settings_overrides=None,
                    config_section=None,
                    enable_exit_status=1,
                    argv=None,
                    usage=default_usage,
                    description=default_description):
    """
    See docutils.core.publish_cmdline.

    We just modified this function to return the parsed destination file.
    """
    pub = Publisher(reader, parser, writer, settings=settings)
    pub.set_components(reader_name, parser_name, writer_name)
    output = pub.publish(argv,
                         usage,
                         description,
                         settings_spec,
                         settings_overrides,
                         config_section=config_section,
                         enable_exit_status=enable_exit_status)
    return output, pub.settings._source, pub.settings._destination
예제 #5
0
def main(prog_args):
    argv = None
    reader = standalone.Reader()
    reader_name = 'standalone'
    writer = Writer()
    writer_name = 'pseudoxml'
    parser = None
    parser_name = 'restructuredtext'
    settings = None
    settings_spec = None
    settings_overrides = None
    config_section = None
    enable_exit_status = 1
    usage = default_usage
    publisher = Publisher(reader,
                          parser,
                          writer,
                          settings,
                          destination_class=BinaryFileOutput)
    publisher.set_components(reader_name, parser_name, writer_name)
    description = ('Generates OpenDocument/OpenOffice/ODF slides from '
                   'standalone reStructuredText sources.  ' +
                   default_description)

    output = publisher.publish(argv,
                               usage,
                               description,
                               settings_spec,
                               settings_overrides,
                               config_section=config_section,
                               enable_exit_status=enable_exit_status)
예제 #6
0
def publish_cmdline_to_binary(reader=None, reader_name='standalone',
                    parser=None, parser_name='restructuredtext',
                    writer=None, writer_name='pseudoxml',
                    settings=None, settings_spec=None,
                    settings_overrides=None, config_section=None,
                    enable_exit_status=1, argv=None,
                    usage=default_usage, description=default_description,
                    destination=None, destination_class=BinaryFileOutput
                    ):
    """
    Set up & run a `Publisher` for command-line-based file I/O (input and
    output file paths taken automatically from the command line).  Return the
    encoded string output also.

    This is just like publish_cmdline, except that it uses
    io.BinaryFileOutput instead of io.FileOutput.

    Parameters: see `publish_programmatically` for the remainder.

    - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``.
    - `usage`: Usage string, output if there's a problem parsing the command
      line.
    - `description`: Program description, output for the "--help" option
      (along with command-line option descriptions).
    """
    pub = Publisher(reader, parser, writer, settings=settings,
        destination_class=destination_class)
    pub.set_components(reader_name, parser_name, writer_name)
    output = pub.publish(
        argv, usage, description, settings_spec, settings_overrides,
        config_section=config_section, enable_exit_status=enable_exit_status)
    return output
예제 #7
0
def main(args=sys.argv):
    argv = None
    reader = standalone.Reader()
    reader_name = "standalone"
    writer = EpubWriter()
    writer_name = "epub2"
    parser = Parser()
    parser_name = "restructuredtext"
    settings = None
    settings_spec = None
    settings_overrides = None
    config_section = None
    enable_exit_status = 1
    usage = default_usage
    publisher = Publisher(
        reader, parser, writer, settings, destination_class=EpubFileOutput
    )
    publisher.set_components(reader_name, parser_name, writer_name)
    description = (
        "Generates epub books from reStructuredText sources.  " + default_description
    )

    publisher.publish(
        argv,
        usage,
        description,
        settings_spec,
        settings_overrides,
        config_section=config_section,
        enable_exit_status=enable_exit_status,
    )
예제 #8
0
def read_doc(app, env, filename):
    # type: (Sphinx, BuildEnvironment, unicode) -> nodes.document
    """Parse a document and convert to doctree."""
    filetype = get_filetype(app.config.source_suffix, filename)
    input_class = app.registry.get_source_input(filetype)
    reader = SphinxStandaloneReader(app)
    source = input_class(app, env, source=None, source_path=filename,
                         encoding=env.config.source_encoding)
    parser = app.registry.create_source_parser(app, filetype)
    if parser.__class__.__name__ == 'CommonMarkParser' and parser.settings_spec == ():
        # a workaround for recommonmark
        #   If recommonmark.AutoStrictify is enabled, the parser invokes reST parser
        #   internally.  But recommonmark-0.4.0 does not provide settings_spec for reST
        #   parser.  As a workaround, this copies settings_spec for RSTParser to the
        #   CommonMarkParser.
        parser.settings_spec = RSTParser.settings_spec

    pub = Publisher(reader=reader,
                    parser=parser,
                    writer=SphinxDummyWriter(),
                    source_class=SphinxDummySourceClass,
                    destination=NullOutput())
    pub.set_components(None, 'restructuredtext', None)
    pub.process_programmatic_settings(None, env.settings, None)
    pub.set_source(source, filename)
    pub.publish()
    return pub.document
예제 #9
0
def get_sphinx():
    sphinx = getattr(local_data, 'sphinx', None)
    if sphinx is None:
        sphinx = Sphinx(tempdir,
                        tempdir,
                        tempdir,
                        tempdir,
                        'json',
                        status=None,
                        warning=None)
        sphinx.builder.translator_class = CustomHTMLTranslator

        sphinx.env.patch_lookup_functions()
        sphinx.env.temp_data['docname'] = 'text'
        sphinx.env.temp_data['default_domain'] = 'py'

        pub = Publisher(reader=None,
                        parser=None,
                        writer=HTMLWriter(sphinx.builder),
                        source_class=io.StringInput,
                        destination_class=io.NullOutput)
        pub.set_components('standalone', 'restructuredtext', None)
        pub.process_programmatic_settings(None, sphinx.env.settings, None)
        pub.set_destination(None, None)

        sphinx.publisher = pub

        local_data.sphinx = sphinx

    return sphinx, sphinx.publisher
예제 #10
0
파일: io.py 프로젝트: mgeier/sphinx
def read_doc(app, env, filename):
    # type: (Sphinx, BuildEnvironment, unicode) -> nodes.document
    """Parse a document and convert to doctree."""
    filetype = get_filetype(app.config.source_suffix, filename)
    input_class = app.registry.get_source_input(filetype)
    reader = SphinxStandaloneReader(app)
    source = input_class(app, env, source=None, source_path=filename,
                         encoding=env.config.source_encoding)
    parser = app.registry.create_source_parser(app, filetype)
    if parser.__class__.__name__ == 'CommonMarkParser' and parser.settings_spec == ():
        # a workaround for recommonmark
        #   If recommonmark.AutoStrictify is enabled, the parser invokes reST parser
        #   internally.  But recommonmark-0.4.0 does not provide settings_spec for reST
        #   parser.  As a workaround, this copies settings_spec for RSTParser to the
        #   CommonMarkParser.
        parser.settings_spec = RSTParser.settings_spec

    pub = Publisher(reader=reader,
                    parser=parser,
                    writer=SphinxDummyWriter(),
                    source_class=SphinxDummySourceClass,
                    destination=NullOutput())
    pub.set_components(None, 'restructuredtext', None)
    pub.process_programmatic_settings(None, env.settings, None)
    pub.set_source(source, filename)
    pub.publish()
    return pub.document
예제 #11
0
    def _to_odp_content(self, rst, xml_filename, odp_name='/tmp/out'):
        reader = standalone.Reader()
        reader_name = 'standalone'
        writer = rst2odp.Writer()
        writer_name = 'pseudoxml'
        parser = None
        parser_name = 'restructuredtext'
        settings = None
        settings_spec = None
        settings_overrides = None
        config_section = None
        enable_exit_status = 1
        usage = default_usage
        publisher = Publisher(reader, parser, writer,# source=StringIO(rst),
                              settings=settings,
                              destination_class=rst2odp.BinaryFileOutput)
        publisher.set_components(reader_name, parser_name, writer_name)
        description = ('Generates OpenDocument/OpenOffice/ODF slides from '
                       'standalone reStructuredText sources.  ' + default_description)

        fin = open('/tmp/in.rst', 'w')
        fin.write(rst)
        fin.close()
        argv = ['--traceback', '/tmp/in.rst', odp_name]
        output = publisher.publish(argv, usage, description, settings_spec, settings_overrides, config_section=config_section, enable_exit_status=enable_exit_status)
        # pull content.xml out of /tmp/out
        z = zipwrap.Zippier(odp_name)
        fout = open(xml_filename, 'w')
        content = preso.pretty_xml(z.cat('content.xml'))
        fout.write(content)
        fout.close()
        return content
예제 #12
0
파일: rst.py 프로젝트: runyaga/ptah
def get_sphinx():
    sphinx = getattr(local_data, 'sphinx', None)
    if sphinx is None:
        sphinx = Sphinx(tempdir, tempdir, tempdir,
                        tempdir, 'json', status=None, warning=None)
        sphinx.builder.translator_class = CustomHTMLTranslator

        sphinx.env.patch_lookup_functions()
        sphinx.env.temp_data['docname'] = 'text'
        sphinx.env.temp_data['default_domain'] = 'py'

        pub = Publisher(reader=None,
                        parser=None,
                        writer=HTMLWriter(sphinx.builder),
                        source_class=io.StringInput,
                        destination_class=io.NullOutput)
        pub.set_components('standalone', 'restructuredtext', None)
        pub.process_programmatic_settings(None, sphinx.env.settings, None)
        pub.set_destination(None, None)

        sphinx.publisher = pub

        local_data.sphinx = sphinx

    return sphinx, sphinx.publisher
예제 #13
0
def init_publisher():
    from docutils.core import Publisher
    from docutils.io import StringOutput
    p = Publisher(destination_class=StringOutput,writer=g_writer)
    p.get_settings()
    p.set_components('standalone', 'restructuredtext', 'html')
    p.set_destination(None, None)
    return p
예제 #14
0
파일: app.py 프로젝트: petrushev/qtrst
    def __init__(self):
        # publisher is used for html generation
        pub = Publisher(None, None, None, settings=None,
                             source_class=StringInput,
                             destination_class=StringOutput)
        pub.set_components('standalone', 'restructuredtext', 'html')
        pub.process_programmatic_settings(None, None, None)
        pub.set_destination(None, None)

        self.pub = pub
예제 #15
0
파일: build.py 프로젝트: wyanez/tribus
 def run(self):
     log.debug("[%s.%s] Compiling manual from RST sources." %
               (__name__, self.__class__.__name__))
     pub = Publisher(writer=manpage.Writer())
     pub.set_components(reader_name='standalone',
                        parser_name='restructuredtext',
                        writer_name='pseudoxml')
     pub.publish(argv=[
         u'%s' % get_path([DOCDIR, 'man', 'tribus.rst']),
         u'%s' % get_path([DOCDIR, 'man', 'tribus.1'])
     ])
예제 #16
0
    def get_html(self, body_only=True, content_only=False, noclasses=False):
        import sys
        import pygments_rest
        from docutils.core import Publisher
        from docutils.io import StringInput, StringOutput
        from cStringIO import StringIO

        settings = {
            'doctitle_xform': 1,
            'pep_references': 1,
            'rfc_references': 1,
            'footnote_references': 'superscript',
            'output_encoding': 'unicode',
            'report_level':
            2,  # 2=show warnings, 3=show only errors, 5=off (docutils.utils
        }

        if content_only:
            post_rst = self.get_rst(noclasses=noclasses)
        else:
            post_rst = render_to('post_single.rst',
                                 post=self,
                                 noclasses=noclasses)

        pub = Publisher(reader=None,
                        parser=None,
                        writer=None,
                        settings=None,
                        source_class=StringInput,
                        destination_class=StringOutput)

        pub.set_components(reader_name='standalone',
                           parser_name='restructuredtext',
                           writer_name='html')
        pub.process_programmatic_settings(settings_spec=None,
                                          settings_overrides=settings,
                                          config_section=None)
        pub.set_source(post_rst, source_path=self.module_path)
        pub.set_destination(None, None)

        errors_io = StringIO()
        real_stderr = sys.stderr
        sys.stderr = errors_io
        try:
            html_full = pub.publish(enable_exit_status=False)
            html_body = ''.join(pub.writer.html_body)
        finally:
            sys.stderr = real_stderr
        errors = errors_io.getvalue()
        self._process_rest_errors(errors)

        errors_io.close()

        return html_body if body_only else html_full
예제 #17
0
def convert(infilename, outfilename):
    print "converting %s to %s" % (infilename, outfilename)
    pub = Publisher()
    pub.set_components('standalone',        # reader
                       'restructuredtext',  # parser
                       'latex')             # writer (arg, will be discarded)
    pub.reader = StandaloneReader()
    pub.writer = VerseLaTeXWriter()
    pub.process_programmatic_settings(None, None, None)
    pub.set_source(source_path=infilename)
    pub.set_destination(destination_path=outfilename)
    pub.publish()
예제 #18
0
    def get_html(self, body_only=True, content_only=False, noclasses=False):
        import sys
        import pygments_rest
        from docutils.core import Publisher
        from docutils.io import StringInput, StringOutput
        from cStringIO import StringIO
        
        settings = {'doctitle_xform'     : 1,
                    'pep_references'     : 1,
                    'rfc_references'     : 1,
                    'footnote_references': 'superscript',
                    'output_encoding'    : 'unicode',
                    'report_level'       : 2, # 2=show warnings, 3=show only errors, 5=off (docutils.utils
                    }

        if content_only:
            post_rst = self.get_rst(noclasses=noclasses)
        else:
            post_rst = render_to('post_single.rst', 
                                 post=self, 
                                 noclasses=noclasses)
                             
        pub = Publisher(reader=None, 
                        parser=None, 
                        writer=None, 
                        settings=None,
                        source_class=StringInput,
                        destination_class=StringOutput)

        pub.set_components(reader_name='standalone',
                           parser_name='restructuredtext',
                           writer_name='html')
        pub.process_programmatic_settings(settings_spec=None,
                                          settings_overrides=settings,
                                          config_section=None)
        pub.set_source(post_rst,source_path=self.module_path)
        pub.set_destination(None, None)

        errors_io = StringIO()
        real_stderr = sys.stderr
        sys.stderr = errors_io
        try:
            html_full = pub.publish(enable_exit_status=False)
            html_body = ''.join(pub.writer.html_body)
        finally:
            sys.stderr = real_stderr
        errors = errors_io.getvalue()
        self._process_rest_errors(errors)

        errors_io.close()

        return html_body if body_only else html_full
예제 #19
0
파일: mkpydoc.py 프로젝트: viest/ctypes
def convert(infilename, outfilename):
    print "converting %s to %s" % (infilename, outfilename)
    pub = Publisher()
    pub.set_components(
        'standalone',  # reader
        'restructuredtext',  # parser
        'latex')  # writer (arg, will be discarded)
    pub.reader = OptikReader()
    pub.writer = PyLaTeXWriter()
    pub.process_programmatic_settings(None, None, None)
    pub.set_source(source_path=infilename)
    pub.set_destination(destination_path=outfilename)
    pub.publish()
예제 #20
0
def process_labels(site, logger, source, post):
    site.processing_labels = True
    pub = Publisher(reader=Reader(), parser=None, writer=None)
    pub.set_components(None, 'restructuredtext', 'html')
    # Reading the file will generate output/errors that we don't care about
    # at this stage. The report_level = 5 means no output
    pub.process_programmatic_settings(
        settings_spec=None,
        settings_overrides={'report_level': 5},
        config_section=None,
    )
    pub.set_source(None, source)
    pub.publish()
    document = pub.document
    site.processing_labels = False

    # Code based on Sphinx std domain
    for name, is_explicit in document.nametypes.items():
        if not is_explicit:
            continue
        labelid = document.nameids[name]
        if labelid is None:
            continue
        node = document.ids[labelid]
        if node.tagname == 'target' and 'refid' in node:
            node = document.ids.get(node['refid'])
            labelid = node['names'][0]
        if node.tagname == 'footnote' or 'refuri' in node or node.tagname.startswith(
                'desc_'):
            continue
        if name in site.ref_labels:
            logger.warn(
                'Duplicate label {dup}, other instance in {other}'.format(
                    dup=name, other=site.ref_labels[name][0]))
        site.anon_ref_labels[name] = post.permalink(), labelid

        def clean_astext(node):
            """Like node.astext(), but ignore images.
            Taken from sphinx.util.nodes"""
            node = node.deepcopy()
            for img in node.traverse(nodes.image):
                img['alt'] = ''
            for raw in node.traverse(nodes.raw):
                raw.parent.remove(raw)
            return node.astext()

        if node.tagname in ('section', 'rubric'):
            sectname = clean_astext(node[0])
        else:
            continue
        site.ref_labels[name] = post.permalink(), labelid, sectname
예제 #21
0
    class HTMLGenerator:
        """
        Really simple HTMLGenerator starting from publish_parts

        It reuses the docutils.core.Publisher class, which means it is *not*
        threadsafe.
        """
        def __init__(self,
                     settings_spec=None,
                     settings_overrides=None,
                     config_section='general'):
            if settings_overrides is None:
                settings_overrides = {'report_level': 5, 'halt_level': 5}
            self.pub = Publisher(reader=None,
                                 parser=None,
                                 writer=None,
                                 settings=None,
                                 source_class=io.StringInput,
                                 destination_class=io.StringOutput)
            self.pub.set_components(reader_name='standalone',
                                    parser_name='restructuredtext',
                                    writer_name='html')
            # hack: JEP-0071 does not allow HTML char entities, so we hack our way
            # out of it.
            # — == u"\u2014"
            # a setting to only emit charater entities in the writer would be nice
            # FIXME: several   are emitted, and they are explicitly forbidden
            # in the JEP
            #   ==  u"\u00a0"
            self.pub.writer.translator_class.attribution_formats['dash'] = (
                '\u2014', '')
            self.pub.process_programmatic_settings(settings_spec,
                                                   settings_overrides,
                                                   config_section)

        def create_xhtml(self,
                         text,
                         destination=None,
                         destination_path=None,
                         enable_exit_status=None):
            """
            Create xhtml for a fragment of IM dialog. We can use the source_name
            to store info about the message
            """
            self.pub.set_source(text, None)
            self.pub.set_destination(destination, destination_path)
            output = self.pub.publish(enable_exit_status=enable_exit_status)
            # kludge until we can get docutils to stop generating (rare)  
            # entities
            return '\u00a0'.join(
                self.pub.writer.parts['fragment'].strip().split(' '))
예제 #22
0
    def _get_publisher(self, source_path):
        extra_params = {'initial_header_level': '2',
                        'syntax_highlight': 'short',
                        'input_encoding': 'utf-8'}
        user_params = self.settings.get('DOCUTILS_SETTINGS')
        if user_params:
            extra_params.update(user_params)

        pub = Publisher(destination_class=StringOutput)
        pub.set_components('standalone', 'restructuredtext', 'html')
        pub.writer.translator_class = HTMLTranslator
        pub.process_programmatic_settings(None, extra_params, None)
        pub.set_source(source_path=source_path)
        pub.publish()
        return pub
예제 #23
0
def get_doctree(path):
    """
    Obtain a Sphinx doctree from the RST file at ``path``.

    Performs no Releases-specific processing; this code would, ideally, be in
    Sphinx itself, but things there are pretty tightly coupled. So we wrote
    this.

    :param str path: A relative or absolute file path string.

    :returns:
        A two-tuple of the generated ``sphinx.application.Sphinx`` app and the
        doctree (a ``docutils.document`` object).
    """
    root, filename = os.path.split(path)
    docname, _ = os.path.splitext(filename)
    # TODO: this only works for top level changelog files (i.e. ones where
    # their dirname is the project/doc root)
    app = make_app(srcdir=root)
    # Create & init a BuildEnvironment. Mm, tasty side effects.
    app._init_env(freshenv=True)
    env = app.env
    env.update(config=app.config, srcdir=root, doctreedir=app.doctreedir, app=app)
    # Code taken from sphinx.environment.read_doc; easier to manually call
    # it with a working Environment object, instead of doing more random crap
    # to trick the higher up build system into thinking our single changelog
    # document was "updated".
    env.temp_data['docname'] = docname
    env.app = app
    # NOTE: SphinxStandaloneReader API changed in 1.4 :(
    reader_kwargs = {'app': env.app, 'parsers': env.config.source_parsers}
    if sphinx.version_info[:2] < (1, 4):
        del reader_kwargs['app']
    reader = SphinxStandaloneReader(**reader_kwargs)
    pub = Publisher(reader=reader,
                    writer=SphinxDummyWriter(),
                    destination_class=NullOutput)
    pub.set_components(None, 'restructuredtext', None)
    pub.process_programmatic_settings(None, env.settings, None)
    # NOTE: docname derived higher up, from our given path
    src_path = env.doc2path(docname)
    source = SphinxFileInput(app, env, source=None, source_path=src_path,
                             encoding=env.config.source_encoding)
    pub.source = source
    pub.settings._source = src_path
    pub.set_destination(None, None)
    pub.publish()
    return app, pub.document
예제 #24
0
파일: io.py 프로젝트: LFYG/sphinx
def read_doc(app, env, filename):
    # type: (Sphinx, BuildEnvironment, unicode) -> nodes.document
    """Parse a document and convert to doctree."""
    reader = SphinxStandaloneReader(app, parsers=app.registry.get_source_parsers())
    source = SphinxFileInput(app, env, source=None, source_path=filename,
                             encoding=env.config.source_encoding)

    pub = Publisher(reader=reader,
                    writer=SphinxDummyWriter(),
                    source_class=SphinxDummySourceClass,
                    destination=NullOutput())
    pub.set_components(None, 'restructuredtext', None)
    pub.process_programmatic_settings(None, env.settings, None)
    pub.set_source(source, filename)
    pub.publish()
    return pub.document
예제 #25
0
 def check_rst(self, rst):
     pub = Publisher(reader=None, parser=None, writer=None, settings=None,
                     source_class=io.StringInput,
                     destination_class=io.StringOutput)
     pub.set_components(reader_name='standalone',
                        parser_name='restructuredtext',
                        writer_name='pseudoxml')
     pub.process_programmatic_settings(
         settings_spec=None,
         settings_overrides={'output_encoding': 'unicode'},
         config_section=None,
     )
     pub.set_source(rst, source_path=None)
     pub.set_destination(destination=None, destination_path=None)
     output = pub.publish(enable_exit_status=False)
     self.assertLess(pub.document.reporter.max_level, 0)
     return output, pub
예제 #26
0
    def _get_publisher(self, source_path):
        extra_params = {
            'initial_header_level': '2',
            'syntax_highlight': 'short',
            'input_encoding': 'utf-8'
        }
        user_params = self.settings.get('DOCUTILS_SETTINGS')
        if user_params:
            extra_params.update(user_params)

        pub = Publisher(destination_class=StringOutput)
        pub.set_components('standalone', 'restructuredtext', 'html')
        pub.writer.translator_class = HTMLTranslator
        pub.process_programmatic_settings(None, extra_params, None)
        pub.set_source(source_path=source_path)
        pub.publish()
        return pub
예제 #27
0
def render_map(builder, node):
    if node:
        doc = new_document(b('<partial node>'))
        doc.append(node)

        publisher = Publisher( source_class = DocTreeInput, destination_class=StringOutput)
        publisher.set_components('standalone', 'restructuredtext', 'pseudoxml')
        publisher.reader = DoctreeReader()
        publisher.writer = DitaMapWriter(builder)
        publisher.process_programmatic_settings(None, {'output_encoding': 'utf-8'}, None)
        publisher.set_source(doc, None)
        publisher.set_destination(None, None)
        publisher.publish()
        return publisher.writer.output
    output = XML_HEAD
    output += u"<map></map>"
    return output
예제 #28
0
 def check_rst(self, rst):
     pub = Publisher(reader=None, parser=None, writer=None, settings=None,
                     source_class=io.StringInput,
                     destination_class=io.StringOutput)
     pub.set_components(reader_name='standalone',
                        parser_name='restructuredtext',
                        writer_name='pseudoxml')
     pub.process_programmatic_settings(
         settings_spec=None,
         settings_overrides={'output_encoding': 'unicode'},
         config_section=None,
     )
     pub.set_source(rst, source_path=None)
     pub.set_destination(destination=None, destination_path=None)
     output = pub.publish(enable_exit_status=False)
     self.assertLess(pub.document.reporter.max_level, 0)
     return output, pub
예제 #29
0
    def _get_publisher(self, source_path):
        extra_params = {
            "initial_header_level": "2",
            "syntax_highlight": "short",
            "input_encoding": "utf-8",
        }
        user_params = self.settings.get("DOCUTILS_SETTINGS")
        if user_params:
            extra_params.update(user_params)

        pub = Publisher(destination_class=StringOutput)
        pub.set_components("standalone", "restructuredtext", "html")
        pub.writer.translator_class = HTMLTranslator
        pub.process_programmatic_settings(None, extra_params, None)
        pub.set_source(source_path=source_path)
        pub.publish()
        return pub
예제 #30
0
	class HTMLGenerator:
		'''Really simple HTMLGenerator starting from publish_parts.

		It reuses the docutils.core.Publisher class, which means it is *not*
		threadsafe.
		'''
		def __init__(self,
			settings_spec=None,
			settings_overrides=dict(report_level=5, halt_level=5),
			config_section='general'):
			self.pub = Publisher(reader=None, parser=None, writer=None,
				settings=None,
				source_class=io.StringInput,
				destination_class=io.StringOutput)
			self.pub.set_components(reader_name='standalone',
				parser_name='restructuredtext',
				writer_name='html')
			# hack: JEP-0071 does not allow HTML char entities, so we hack our way
			# out of it.
			# &mdash; == u"\u2014"
			# a setting to only emit charater entities in the writer would be nice
			# FIXME: several &nbsp; are emitted, and they are explicitly forbidden
			# in the JEP
			# &nbsp; ==  u"\u00a0"
			self.pub.writer.translator_class.attribution_formats['dash'] = (
				u'\u2014', '')
			self.pub.process_programmatic_settings(settings_spec,
				settings_overrides,
				config_section)


		def create_xhtml(self, text,
			destination=None,
			destination_path=None,
			enable_exit_status=None):
			''' Create xhtml for a fragment of IM dialog.
			We can use the source_name to store info about
			the message.'''
			self.pub.set_source(text, None)
			self.pub.set_destination(destination, destination_path)
			output = self.pub.publish(enable_exit_status=enable_exit_status)
			# kludge until we can get docutils to stop generating (rare) &nbsp;
			# entities
			return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
				'&nbsp;'))
예제 #31
0
def publish_string_with_traceback(reader=None,reader_name=None,
                                  parser_name=None,writer_name=None,
                                  source=None,source_path=None):
    """A modified version of publish_string, so I can request traceback.
    """
    from docutils.core import Publisher
    from docutils import io
    pub = Publisher(reader=reader,
                    source_class=io.StringInput,
                    destination_class=io.StringOutput)
    pub.set_components(reader_name="python",
                       parser_name="restructuredtext",
                       writer_name="pseudoxml")

    pub.process_command_line(argv=["--traceback"])

    pub.set_source(source=source, source_path=source_path)
    return pub.publish(enable_exit=False)
예제 #32
0
def publish_cmdline(reader=None, reader_name='standalone',
                    parser=None, parser_name='restructuredtext',
                    writer=None, writer_name='pseudoxml',
                    settings=None, settings_spec=None,
                    settings_overrides=None, config_section=None,
                    enable_exit_status=1, argv=None,
                    usage=default_usage, description=default_description):
    """
    See docutils.core.publish_cmdline.

    We just modified this function to return the parsed destination file.
    """
    pub = Publisher(reader, parser, writer, settings=settings)
    pub.set_components(reader_name, parser_name, writer_name)
    output = pub.publish(
        argv, usage, description, settings_spec, settings_overrides,
        config_section=config_section, enable_exit_status=enable_exit_status)
    return output, pub.settings._source, pub.settings._destination
예제 #33
0
def publish_string_with_traceback(reader=None,reader_name=None,
                                  parser_name=None,writer_name=None,
                                  source=None,source_path=None):
    """A modified version of publish_string, so I can request traceback.
    """
    from docutils.core import Publisher
    from docutils import io
    pub = Publisher(reader=reader,
                    source_class=io.StringInput,
                    destination_class=io.StringOutput)
    pub.set_components(reader_name="python",
                       parser_name="restructuredtext",
                       writer_name="pseudoxml")

    pub.process_command_line(argv=["--traceback"])

    pub.set_source(source=source, source_path=source_path)
    return pub.publish(enable_exit=False)
예제 #34
0
파일: junk.py 프로젝트: pombredanne/horetu
def aoeuaoeuaoeu_docs(f):
    '''
    I couldn't figure out how to use the docutils docstring parser, so I wrote
    my own. Can somebody show me the right way to do this?
    '''
    raise NotImplementedError
    from docutils.core import Publisher
    from docutils.io import StringInput
    pub = Publisher(None, None, None, settings = settings,
                    source_class = StringInput,
                    destination_class = destination_class)
    pub.set_components('standalone', 'restructuredtext', 'pseudoxml')
    pub.process_programmatic_settings(
        settings_spec, settings_overrides, config_section)
    pub.set_source(f.__doc__, f.__name__)
    pub.set_destination(None, f.__name__)
    output = pub.publish(enable_exit_status = False)
    return output, pub
    return publish_parts(f.__doc__, source_class = StringInput, source_path = f.__name__)
예제 #35
0
def read_doc(app, env, filename):
    # type: (Sphinx, BuildEnvironment, unicode) -> nodes.document
    """Parse a document and convert to doctree."""
    input_class = app.registry.get_source_input(filename)
    reader = SphinxStandaloneReader(app)
    source = input_class(app, env, source=None, source_path=filename,
                         encoding=env.config.source_encoding)
    parser = app.registry.create_source_parser(app, filename)

    pub = Publisher(reader=reader,
                    parser=parser,
                    writer=SphinxDummyWriter(),
                    source_class=SphinxDummySourceClass,
                    destination=NullOutput())
    pub.set_components(None, 'restructuredtext', None)
    pub.process_programmatic_settings(None, env.settings, None)
    pub.set_source(source, filename)
    pub.publish()
    return pub.document
예제 #36
0
    def _to_odp_content(self, rst, xml_filename, odp_name='/tmp/out'):
        reader = standalone.Reader()
        reader_name = 'standalone'
        writer = rst2odp.Writer()
        writer_name = 'pseudoxml'
        parser = None
        parser_name = 'restructuredtext'
        settings = None
        settings_spec = None
        settings_overrides = None
        config_section = None
        enable_exit_status = 1
        usage = default_usage
        publisher = Publisher(
            reader,
            parser,
            writer,  # source=StringIO(rst),
            settings=settings,
            destination_class=rst2odp.BinaryFileOutput)
        publisher.set_components(reader_name, parser_name, writer_name)
        description = ('Generates OpenDocument/OpenOffice/ODF slides from '
                       'standalone reStructuredText sources.  ' +
                       default_description)

        fin = open('/tmp/in.rst', 'w')
        fin.write(rst)
        fin.close()
        argv = ['--traceback', '/tmp/in.rst', odp_name]
        output = publisher.publish(argv,
                                   usage,
                                   description,
                                   settings_spec,
                                   settings_overrides,
                                   config_section=config_section,
                                   enable_exit_status=enable_exit_status)
        # pull content.xml out of /tmp/out
        z = zipwrap.Zippier(odp_name)
        fout = open(xml_filename, 'w')
        content = preso.pretty_xml(z.cat('content.xml'))
        fout.write(content)
        fout.close()
        return content
예제 #37
0
def sandbox_partial_builder(doctree, env):
    env.process_downloads(env.docname, doctree)
    for domain in env.domains.values():
        domain.process_doc(env, env.docname, doctree)
    env.resolve_references(doctree, env.docname, env.app.builder)
    if not hasattr(env.app.builder, 'dlpath'):
        # FIXME: builders.html.StandaloneHTMLBuilder.write_doc で設定される属性のため、この時点では存在しない
        env.app.builder.dlpath = '_downloads'
        env.app.builder.fignumbers = env.toc_secnumbers.get(env.docname, {})
        env.app.builder.secnumbers = env.toc_secnumbers.get(env.docname, {})
    pub = Publisher(
        reader=SandboxDoctreeReader(),
        writer=Writer(env.app.builder),
        source=DocTreeInput(doctree),
        destination_class=StringOutput,
    )
    pub.set_components(None, 'restructuredtext', None)
    defaults = env.settings.copy()
    defaults['output_encoding'] = 'unicode'
    pub.process_programmatic_settings(None, defaults, None)
    pub.set_destination(None, None)
    out = pub.publish(enable_exit_status=False)
    return pub.writer.parts['fragment']
예제 #38
0
def main(prog_args):
    argv = None
    reader = standalone.Reader()
    reader_name = 'standalone'
    writer = Writer()
    writer_name = 'pseudoxml'
    parser = None
    parser_name = 'restructuredtext'
    settings = None
    settings_spec = None
    settings_overrides = None
    config_section = None
    enable_exit_status = 1
    usage = default_usage
    publisher = Publisher(reader, parser, writer, settings,
                          destination_class=BinaryFileOutput)
    publisher.set_components(reader_name, parser_name, writer_name)
    description = ('Generates OpenDocument/OpenOffice/ODF slides from '
                   'standalone reStructuredText sources.  ' + default_description)

    output = publisher.publish(argv, usage, description,
                               settings_spec, settings_overrides,
                               config_section=config_section,
                               enable_exit_status=enable_exit_status)
예제 #39
0
파일: doc.py 프로젝트: stbe/django-screener
class _PydocParser:
    def __init__(self):
        # Set up the instance we'll be using to render docstrings.
        self.errors = []
        self.writer = _DocumentPseudoWriter()
        self.publisher = Publisher(_EpydocReader(self.errors),
            writer=self.writer,
            source_class=io.StringInput)
        self.publisher.set_components('standalone', 'restructuredtext',
            'pseudoxml')
        settings_overrides={
            'report_level':10000,
            'halt_level':10000,
            'warning_stream':None,
            }
        self.publisher.process_programmatic_settings(None,
            settings_overrides, None)
        self.publisher.set_destination()


    def parse_docstring(self, docstring, errors):
        """Parse a docstring for eventual transformation into HTML

        This function is a replacement for parse_docstring from
        epydoc.markup.restructuredtext.parse_docstring.  This function reuses
        the Publisher instance while the original did not.  Using This
        function yields significantly faster WADL generation for complex
        systems.
        """
        # Clear any errors from previous calls.
        del self.errors[:]
        self.publisher.set_source(docstring, None)
        self.publisher.publish()
        # Move any errors into the caller-provided list.
        errors[:] = self.errors[:]
        return ParsedRstDocstring(self.writer.document)
예제 #40
0
    def _get_publisher(self, source_path, translator_class=BlogHTMLTranslator):
        extra_params = {
            "initial_header_level": "2",
            "syntax_highlight": "short",
            "input_encoding": "utf-8",
            "exit_status_level": 2,
            "language_code": self._language_code,
            "halt_level": 2,
            "traceback": True,
            "warning_stream": StringIO(),
            "embed_stylesheet": False,
        }
        user_params = self.settings.get("DOCUTILS_SETTINGS")
        if user_params:
            extra_params.update(user_params)

        pub = Publisher(writer=self.writer_class(),
                        destination_class=StringOutput)
        pub.set_components("standalone", "restructuredtext", "html")
        pub.writer.translator_class = translator_class
        pub.process_programmatic_settings(None, extra_params, None)
        pub.set_source(source_path=source_path)
        pub.publish()
        return pub
예제 #41
0
def main(args):
    print "ARGS", args
    argv = None
    reader = standalone.Reader()
    reader_name = 'standalone'
    writer = EpubWriter()
    writer_name = 'epub2'
    parser = Parser()
    parser_name = 'restructuredtext'
    settings = None
    settings_spec = None
    settings_overrides = None
    config_section = None
    enable_exit_status = 1
    usage = default_usage
    publisher = Publisher(reader, parser, writer, settings,
                          destination_class=EpubFileOutput)
    publisher.set_components(reader_name, parser_name, writer_name)
    description = ('Generates epub books from reStructuredText sources.  ' + default_description)

    output = publisher.publish(argv, usage, description,
                               settings_spec, settings_overrides,
                               config_section=config_section,
                               enable_exit_status=enable_exit_status)
def check_for_errors(content, filepath=None):
    """Lint reStructuredText and return errors

    :param string content: reStructuredText to be linted
    :param string filepath: Optional path to file, this will be returned as the source
    :rtype list: List of errors. Each error will contain a line, source (filepath),
        message (error message), and full message (error message + source lines)
    """
    # Generate a new parser (copying `rst2html.py` flow)
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/tools/rst2html.py
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l348
    pub = Publisher(None, None, None, settings=None)
    pub.set_components('standalone', 'restructuredtext', 'pseudoxml')

    # Configure publisher
    # DEV: We cannot use `process_command_line` since it processes `sys.argv` which is for `rst-lint`, not `docutils`
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l201
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l143
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l118
    settings = pub.get_settings(halt_level=5)
    pub.set_io()

    # Prepare a document to parse on
    # DEV: We avoid the `read` method because when `source` is `None`, it attempts to read from `stdin`.
    #      However, we already know our content.
    # DEV: We create our document without `parse` because we need to attach observer's before parsing
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/readers/__init__.py#l66
    reader = pub.reader
    document = utils.new_document(filepath, settings)

    # Disable stdout
    # TODO: Find a more proper way to do this
    # TODO: We might exit the program if a certain error level is reached
    document.reporter.stream = None

    # Collect errors via an observer
    errors = []

    def error_collector(data):
        # Mutate the data since it was just generated
        data.line = data.get('line')
        data.source = data['source']
        data.level = data['level']
        data.type = data['type']
        data.message = Element.astext(data.children[0])
        data.full_message = Element.astext(data)

        # Save the error
        errors.append(data)
    document.reporter.attach_observer(error_collector)

    # Parse the content (and collect errors)
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/readers/__init__.py#l75
    reader.parser.parse(content, document)

    # Apply transforms (and more collect errors)
    # DEV: We cannot use `apply_transforms` since it has `attach_observer` baked in. We want only our listener.
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l195
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/transforms/__init__.py#l159
    document.transformer.populate_from_components(
        (pub.source, pub.reader, pub.reader.parser, pub.writer, pub.destination)
    )
    transformer = document.transformer
    while transformer.transforms:
        if not transformer.sorted:
            # Unsorted initially, and whenever a transform is added.
            transformer.transforms.sort()
            transformer.transforms.reverse()
            transformer.sorted = 1
        priority, transform_class, pending, kwargs = transformer.transforms.pop()
        transform = transform_class(transformer.document, startnode=pending)
        transform.apply(**kwargs)
        transformer.applied.append((priority, transform_class, pending, kwargs))
    return errors
예제 #43
0
class StandaloneHTMLBuilder(Builder):
    """
    Builds standalone HTML docs.
    """

    name = "html"
    format = "html"
    copysource = True
    out_suffix = ".html"
    link_suffix = ".html"  # defaults to matching out_suffix
    indexer_format = js_index
    supported_image_types = ["image/svg+xml", "image/png", "image/gif", "image/jpeg"]
    searchindex_filename = "searchindex.js"
    add_permalinks = True
    embedded = False  # for things like HTML help or Qt help: suppresses sidebar

    # This is a class attribute because it is mutated by Sphinx.add_javascript.
    script_files = ["_static/jquery.js", "_static/underscore.js", "_static/doctools.js"]
    # Dito for this one.
    css_files = []

    default_sidebars = ["localtoc.html", "relations.html", "sourcelink.html", "searchbox.html"]

    # cached publisher object for snippets
    _publisher = None

    def init(self):
        # a hash of all config values that, if changed, cause a full rebuild
        self.config_hash = ""
        self.tags_hash = ""
        # section numbers for headings in the currently visited document
        self.secnumbers = {}

        self.init_templates()
        self.init_highlighter()
        self.init_translator_class()
        if self.config.html_file_suffix is not None:
            self.out_suffix = self.config.html_file_suffix

        if self.config.html_link_suffix is not None:
            self.link_suffix = self.config.html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        if self.config.language is not None:
            jsfile_list = [
                path.join(package_dir, "locale", self.config.language, "LC_MESSAGES", "sphinx.js"),
                path.join(sys.prefix, "share/sphinx/locale", self.config.language, "sphinx.js"),
            ]

            for jsfile in jsfile_list:
                if path.isfile(jsfile):
                    self.script_files.append("_static/translations.js")
                    break

    def get_theme_config(self):
        return self.config.html_theme, self.config.html_theme_options

    def init_templates(self):
        Theme.init_themes(self)
        themename, themeoptions = self.get_theme_config()
        self.theme = Theme(themename)
        self.theme_options = themeoptions.copy()
        self.create_template_bridge()
        self.templates.init(self, self.theme)

    def init_highlighter(self):
        # determine Pygments style and create the highlighter
        if self.config.pygments_style is not None:
            style = self.config.pygments_style
        elif self.theme:
            style = self.theme.get_confstr("theme", "pygments_style", "none")
        else:
            style = "sphinx"
        self.highlighter = PygmentsBridge("html", style, self.config.trim_doctest_flags)

    def init_translator_class(self):
        if self.config.html_translator_class:
            self.translator_class = self.app.import_object(
                self.config.html_translator_class, "html_translator_class setting"
            )
        elif self.config.html_use_smartypants:
            self.translator_class = SmartyPantsHTMLTranslator
        else:
            self.translator_class = HTMLTranslator

    def get_outdated_docs(self):
        cfgdict = dict(
            (name, self.config[name]) for (name, desc) in self.config.values.iteritems() if desc[1] == "html"
        )
        self.config_hash = md5(str(cfgdict)).hexdigest()
        self.tags_hash = md5(str(sorted(self.tags))).hexdigest()
        old_config_hash = old_tags_hash = ""
        try:
            fp = open(path.join(self.outdir, ".buildinfo"))
            version = fp.readline()
            if version.rstrip() != "# Sphinx build info version 1":
                raise ValueError
            fp.readline()  # skip commentary
            cfg, old_config_hash = fp.readline().strip().split(": ")
            if cfg != "config":
                raise ValueError
            tag, old_tags_hash = fp.readline().strip().split(": ")
            if tag != "tags":
                raise ValueError
            fp.close()
        except ValueError:
            self.warn("unsupported build info format in %r, building all" % path.join(self.outdir, ".buildinfo"))
        except Exception:
            pass
        if old_config_hash != self.config_hash or old_tags_hash != self.tags_hash:
            for docname in self.env.found_docs:
                yield docname
            return

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

    def render_partial(self, node):
        """Utility: Render a lone doctree node."""
        if node is None:
            return {"fragment": ""}
        doc = new_document("<partial node>")
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(source_class=DocTreeInput, destination_class=StringOutput)
            self._publisher.set_components("standalone", "restructuredtext", "pseudoxml")

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = HTMLWriter(self)
        pub.process_programmatic_settings(None, {"output_encoding": "unicode"}, None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        from sphinx.search import IndexBuilder

        self.indexer = IndexBuilder(self.env)
        self.load_indexer(docnames)
        self.docwriter = HTMLWriter(self)
        self.docsettings = OptionParser(defaults=self.env.settings, components=(self.docwriter,)).get_default_values()
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain in self.env.domains.itervalues():
                for indexcls in domain.indices:
                    indexname = "%s-%s" % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == "py-modindex" and not self.config.html_use_modindex:
                        continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append((indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = ustrftime(lufmt or _("%b %d, %Y"))
        else:
            self.last_updated = None

        logo = self.config.html_logo and path.basename(self.config.html_logo) or ""

        favicon = self.config.html_favicon and path.basename(self.config.html_favicon) or ""
        if favicon and os.path.splitext(favicon)[1] != ".ico":
            self.warn("html_favicon is not an .ico file")

        if not isinstance(self.config.html_use_opensearch, basestring):
            self.warn("html_use_opensearch config value must now be a string")

        self.relations = self.env.collect_relations()

        rellinks = []
        if self.config.html_use_index:
            rellinks.append(("genindex", _("General Index"), "I", _("index")))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append((indexname, indexcls.localname, "", indexcls.shortname))

        if self.config.html_style is not None:
            stylename = self.config.html_style
        elif self.theme:
            stylename = self.theme.get_confstr("theme", "stylesheet")
        else:
            stylename = "default.css"

        self.globalcontext = dict(
            embedded=self.embedded,
            project=self.config.project,
            release=self.config.release,
            version=self.config.version,
            last_updated=self.last_updated,
            copyright=self.config.copyright,
            master_doc=self.config.master_doc,
            use_opensearch=self.config.html_use_opensearch,
            docstitle=self.config.html_title,
            shorttitle=self.config.html_short_title,
            show_copyright=self.config.html_show_copyright,
            show_sphinx=self.config.html_show_sphinx,
            has_source=self.config.html_copy_source,
            show_source=self.config.html_show_sourcelink,
            file_suffix=self.out_suffix,
            script_files=self.script_files,
            css_files=self.css_files,
            sphinx_version=__version__,
            style=stylename,
            rellinks=rellinks,
            builder=self.name,
            parents=[],
            logo=logo,
            favicon=favicon,
        )
        if self.theme:
            self.globalcontext.update(
                ("theme_" + key, val) for (key, val) in self.theme.get_options(self.theme_options).iteritems()
            )
        self.globalcontext.update(self.config.html_context)

    def get_doc_context(self, docname, body, metatags):
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext["rellinks"][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    "link": self.get_relative_uri(docname, related[2]),
                    "title": self.render_partial(titles[related[2]])["title"],
                }
                rellinks.append((related[2], next["title"], "N", _("next")))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    "link": self.get_relative_uri(docname, related[1]),
                    "title": self.render_partial(titles[related[1]])["title"],
                }
                rellinks.append((related[1], prev["title"], "P", _("previous")))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append(
                    {
                        "link": self.get_relative_uri(docname, related[0]),
                        "title": self.render_partial(titles[related[0]])["title"],
                    }
                )
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            parents.pop()  # remove link to the master file; we have a generic
            # "back to index" link already
        parents.reverse()

        # title rendered as HTML
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)["title"] or ""
        # the name for the copied source
        sourcename = self.config.html_copy_source and docname + ".txt" or ""

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # local TOC and global TOC tree
        toc = self.render_partial(self.env.get_toc_for(docname))["fragment"]

        return dict(
            parents=parents,
            prev=prev,
            next=next,
            title=title,
            meta=meta,
            body=body,
            metatags=metatags,
            rellinks=rellinks,
            sourcename=sourcename,
            toc=toc,
            # only display a TOC if there's more than one item to show
            display_toc=(self.env.toc_num_entries[docname] > 1),
        )

    def write_doc(self, docname, doctree):
        destination = StringOutput(encoding="utf-8")
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), "_images")
        self.post_process_images(doctree)
        self.dlpath = relative_uri(self.get_target_uri(docname), "_downloads")
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts["fragment"]
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.index_page(docname, doctree, ctx.get("title", ""))
        self.handle_page(docname, ctx, event_arg=doctree)

    def finish(self):
        self.info(bold("writing additional files..."), nonl=1)

        # pages from extensions
        for pagelist in self.app.emit("html-collect-pages"):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        # the global general index
        if self.config.html_use_index:
            self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        # the search page
        if self.name != "htmlhelp":
            self.info(" search", nonl=1)
            self.handle_page("search", {}, "search.html")

        # additional pages from conf.py
        for pagename, template in self.config.html_additional_pages.items():
            self.info(" " + pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        if self.config.html_use_opensearch and self.name != "htmlhelp":
            self.info(" opensearch", nonl=1)
            fn = path.join(self.outdir, "_static", "opensearch.xml")
            self.handle_page("opensearch", {}, "opensearch.xml", outfilename=fn)

        self.info()

        self.copy_image_files()
        self.copy_download_files()
        self.copy_static_files()
        self.write_buildinfo()

        # dump the search index
        self.handle_finish()

    def write_genindex(self):
        # the total count of lines for each index letter, used to distribute
        # the entries into two columns
        genindex = self.env.create_index(self)
        indexcounts = []
        for _, entries in genindex:
            indexcounts.append(sum(1 + len(subitems) for _, (_, subitems) in entries))

        genindexcontext = dict(
            genindexentries=genindex, genindexcounts=indexcounts, split_index=self.config.html_split_index
        )
        self.info(" genindex", nonl=1)

        if self.config.html_split_index:
            self.handle_page("genindex", genindexcontext, "genindex-split.html")
            self.handle_page("genindex-all", genindexcontext, "genindex.html")
            for (key, entries), count in zip(genindex, indexcounts):
                ctx = {"key": key, "entries": entries, "count": count, "genindexentries": genindex}
                self.handle_page("genindex-" + key, ctx, "genindex-single.html")
        else:
            self.handle_page("genindex", genindexcontext, "genindex.html")

    def write_domain_indices(self):
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = dict(indextitle=indexcls.localname, content=content, collapse_index=collapse)
            self.info(" " + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, "domainindex.html")

    def copy_image_files(self):
        # copy image files
        if self.images:
            ensuredir(path.join(self.outdir, "_images"))
            for src in self.status_iterator(self.images, "copying images... ", brown, len(self.images)):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src), path.join(self.outdir, "_images", dest))
                except Exception, err:
                    self.warn("cannot copy image file %r: %s" % (path.join(self.srcdir, src), err))
예제 #44
0
class StandaloneHTMLBuilder(Builder):
    """
    Builds standalone HTML docs.
    """
    name = 'html'
    format = 'html'
    copysource = True
    allow_parallel = True
    out_suffix = '.html'
    link_suffix = '.html'  # defaults to matching out_suffix
    indexer_format = js_index
    indexer_dumps_unicode = True
    supported_image_types = [
        'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'
    ]
    searchindex_filename = 'searchindex.js'
    add_permalinks = True
    embedded = False  # for things like HTML help or Qt help: suppresses sidebar

    # This is a class attribute because it is mutated by Sphinx.add_javascript.
    script_files = [
        '_static/jquery.js', '_static/underscore.js', '_static/doctools.js'
    ]
    # Dito for this one.
    css_files = []

    default_sidebars = [
        'localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'
    ]

    # cached publisher object for snippets
    _publisher = None

    def init(self):
        # a hash of all config values that, if changed, cause a full rebuild
        self.config_hash = ''
        self.tags_hash = ''
        # section numbers for headings in the currently visited document
        self.secnumbers = {}
        # currently written docname
        self.current_docname = None

        self.init_templates()
        self.init_highlighter()
        self.init_translator_class()
        if self.config.html_file_suffix is not None:
            self.out_suffix = self.config.html_file_suffix

        if self.config.html_link_suffix is not None:
            self.link_suffix = self.config.html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        if self.config.language is not None:
            if self._get_translations_js():
                self.script_files.append('_static/translations.js')

    def _get_translations_js(self):
        candidates = [path.join(package_dir, 'locale', self.config.language,
                                'LC_MESSAGES', 'sphinx.js'),
                      path.join(sys.prefix, 'share/sphinx/locale',
                                self.config.language, 'sphinx.js')] + \
                     [path.join(dir, self.config.language,
                                'LC_MESSAGES', 'sphinx.js')
                      for dir in self.config.locale_dirs]
        for jsfile in candidates:
            if path.isfile(jsfile):
                return jsfile
        return None

    def get_theme_config(self):
        return self.config.html_theme, self.config.html_theme_options

    def init_templates(self):
        Theme.init_themes(self.confdir,
                          self.config.html_theme_path,
                          warn=self.warn)
        themename, themeoptions = self.get_theme_config()
        self.theme = Theme(themename)
        self.theme_options = themeoptions.copy()
        self.create_template_bridge()
        self.templates.init(self, self.theme)

    def init_highlighter(self):
        # determine Pygments style and create the highlighter
        if self.config.pygments_style is not None:
            style = self.config.pygments_style
        elif self.theme:
            style = self.theme.get_confstr('theme', 'pygments_style', 'none')
        else:
            style = 'sphinx'
        self.highlighter = PygmentsBridge('html', style,
                                          self.config.trim_doctest_flags)

    def init_translator_class(self):
        if self.config.html_translator_class:
            self.translator_class = self.app.import_object(
                self.config.html_translator_class,
                'html_translator_class setting')
        elif self.config.html_use_smartypants:
            self.translator_class = SmartyPantsHTMLTranslator
        else:
            self.translator_class = HTMLTranslator

    def get_outdated_docs(self):
        cfgdict = dict((name, self.config[name])
                       for (name, desc) in self.config.values.iteritems()
                       if desc[1] == 'html')
        self.config_hash = get_stable_hash(cfgdict)
        self.tags_hash = get_stable_hash(sorted(self.tags))
        old_config_hash = old_tags_hash = ''
        try:
            fp = open(path.join(self.outdir, '.buildinfo'))
            try:
                version = fp.readline()
                if version.rstrip() != '# Sphinx build info version 1':
                    raise ValueError
                fp.readline()  # skip commentary
                cfg, old_config_hash = fp.readline().strip().split(': ')
                if cfg != 'config':
                    raise ValueError
                tag, old_tags_hash = fp.readline().strip().split(': ')
                if tag != 'tags':
                    raise ValueError
            finally:
                fp.close()
        except ValueError:
            self.warn('unsupported build info format in %r, building all' %
                      path.join(self.outdir, '.buildinfo'))
        except Exception:
            pass
        if old_config_hash != self.config_hash or \
               old_tags_hash != self.tags_hash:
            for docname in self.env.found_docs:
                yield docname
            return

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)),
                               template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

    def render_partial(self, node):
        """Utility: Render a lone doctree node."""
        if node is None:
            return {'fragment': ''}
        doc = new_document(b('<partial node>'))
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(source_class=DocTreeInput,
                                        destination_class=StringOutput)
            self._publisher.set_components('standalone', 'restructuredtext',
                                           'pseudoxml')

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = HTMLWriter(self)
        pub.process_programmatic_settings(None, {'output_encoding': 'unicode'},
                                          None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        # create the search indexer
        from sphinx.search import IndexBuilder, languages
        lang = self.config.html_search_language or self.config.language
        if not lang or lang not in languages:
            lang = 'en'
        self.indexer = IndexBuilder(self.env, lang,
                                    self.config.html_search_options,
                                    self.config.html_search_scorer)
        self.load_indexer(docnames)

        self.docwriter = HTMLWriter(self)
        self.docsettings = OptionParser(
            defaults=self.env.settings,
            components=(self.docwriter, )).get_default_values()
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain in self.env.domains.itervalues():
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == 'py-modindex' and \
                           not self.config.html_use_modindex:
                        continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append(
                            (indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = ustrftime(lufmt or _('%b %d, %Y'))
        else:
            self.last_updated = None

        logo = self.config.html_logo and \
               path.basename(self.config.html_logo) or ''

        favicon = self.config.html_favicon and \
                  path.basename(self.config.html_favicon) or ''
        if favicon and os.path.splitext(favicon)[1] != '.ico':
            self.warn('html_favicon is not an .ico file')

        if not isinstance(self.config.html_use_opensearch, basestring):
            self.warn('html_use_opensearch config value must now be a string')

        self.relations = self.env.collect_relations()

        rellinks = []
        if self.get_builder_config('use_index', 'html'):
            rellinks.append(('genindex', _('General Index'), 'I', _('index')))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append(
                    (indexname, indexcls.localname, '', indexcls.shortname))

        if self.config.html_style is not None:
            stylename = self.config.html_style
        elif self.theme:
            stylename = self.theme.get_confstr('theme', 'stylesheet')
        else:
            stylename = 'default.css'

        self.globalcontext = dict(
            embedded=self.embedded,
            project=self.config.project,
            release=self.config.release,
            version=self.config.version,
            last_updated=self.last_updated,
            copyright=self.config.copyright,
            master_doc=self.config.master_doc,
            use_opensearch=self.config.html_use_opensearch,
            docstitle=self.config.html_title,
            shorttitle=self.config.html_short_title,
            show_copyright=self.config.html_show_copyright,
            show_sphinx=self.config.html_show_sphinx,
            has_source=self.config.html_copy_source,
            show_source=self.config.html_show_sourcelink,
            file_suffix=self.out_suffix,
            script_files=self.script_files,
            css_files=self.css_files,
            sphinx_version=__version__,
            style=stylename,
            rellinks=rellinks,
            builder=self.name,
            parents=[],
            logo=logo,
            favicon=favicon,
        )
        if self.theme:
            self.globalcontext.update(('theme_' + key, val) for (
                key,
                val) in self.theme.get_options(self.theme_options).iteritems())
        self.globalcontext.update(self.config.html_context)

    def get_doc_context(self, docname, body, metatags):
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext['rellinks'][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    'link': self.get_relative_uri(docname, related[2]),
                    'title': self.render_partial(titles[related[2]])['title']
                }
                rellinks.append((related[2], next['title'], 'N', _('next')))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    'link': self.get_relative_uri(docname, related[1]),
                    'title': self.render_partial(titles[related[1]])['title']
                }
                rellinks.append(
                    (related[1], prev['title'], 'P', _('previous')))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append({
                    'link':
                    self.get_relative_uri(docname, related[0]),
                    'title':
                    self.render_partial(titles[related[0]])['title']
                })
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            parents.pop()  # remove link to the master file; we have a generic
            # "back to index" link already
        parents.reverse()

        # title rendered as HTML
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        # the name for the copied source
        sourcename = self.config.html_copy_source and docname + '.txt' or ''

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # local TOC and global TOC tree
        self_toc = self.env.get_toc_for(docname, self)
        toc = self.render_partial(self_toc)['fragment']

        return dict(
            parents=parents,
            prev=prev,
            next=next,
            title=title,
            meta=meta,
            body=body,
            metatags=metatags,
            rellinks=rellinks,
            sourcename=sourcename,
            toc=toc,
            # only display a TOC if there's more than one item to show
            display_toc=(self.env.toc_num_entries[docname] > 1),
        )

    def write_doc(self, docname, doctree):
        destination = StringOutput(encoding='utf-8')
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts['fragment']
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.post_process_images(doctree)
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        self.index_page(docname, doctree, title)

    def finish(self):
        self.info(bold('writing additional files...'), nonl=1)

        # pages from extensions
        for pagelist in self.app.emit('html-collect-pages'):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        # the global general index
        if self.get_builder_config('use_index', 'html'):
            self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        # the search page
        if self.name != 'htmlhelp':
            self.info(' search', nonl=1)
            self.handle_page('search', {}, 'search.html')

        # additional pages from conf.py
        for pagename, template in self.config.html_additional_pages.items():
            self.info(' ' + pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        if self.config.html_use_opensearch and self.name != 'htmlhelp':
            self.info(' opensearch', nonl=1)
            fn = path.join(self.outdir, '_static', 'opensearch.xml')
            self.handle_page('opensearch', {},
                             'opensearch.xml',
                             outfilename=fn)

        self.info()

        self.copy_image_files()
        self.copy_download_files()
        self.copy_static_files()
        self.write_buildinfo()

        # dump the search index
        self.handle_finish()

    def write_genindex(self):
        # the total count of lines for each index letter, used to distribute
        # the entries into two columns
        genindex = self.env.create_index(self)
        indexcounts = []
        for _, entries in genindex:
            indexcounts.append(
                sum(1 + len(subitems) for _, (_, subitems) in entries))

        genindexcontext = dict(
            genindexentries=genindex,
            genindexcounts=indexcounts,
            split_index=self.config.html_split_index,
        )
        self.info(' genindex', nonl=1)

        if self.config.html_split_index:
            self.handle_page('genindex', genindexcontext,
                             'genindex-split.html')
            self.handle_page('genindex-all', genindexcontext, 'genindex.html')
            for (key, entries), count in zip(genindex, indexcounts):
                ctx = {
                    'key': key,
                    'entries': entries,
                    'count': count,
                    'genindexentries': genindex
                }
                self.handle_page('genindex-' + key, ctx,
                                 'genindex-single.html')
        else:
            self.handle_page('genindex', genindexcontext, 'genindex.html')

    def write_domain_indices(self):
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = dict(
                indextitle=indexcls.localname,
                content=content,
                collapse_index=collapse,
            )
            self.info(' ' + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, 'domainindex.html')

    def copy_image_files(self):
        # copy image files
        if self.images:
            ensuredir(path.join(self.outdir, '_images'))
            for src in self.status_iterator(self.images, 'copying images... ',
                                            brown, len(self.images)):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, '_images', dest))
                except Exception, err:
                    self.warn('cannot copy image file %r: %s' %
                              (path.join(self.srcdir, src), err))
예제 #45
0
class StandalonePHPBuilder(Builder):
    """
    Builds standalone PHP docs.
    """
    name = 'php'
    format = 'php'
    epilog = __('The PHP pages are in %(outdir)s.')

    copysource = True
    allow_parallel = True
    out_suffix = '.php'
    link_suffix = '.php'  # defaults to matching out_suffix
    indexer_format = js_index  # type: Any
    indexer_dumps_unicode = True
    # create links to original images from images [True/False]
    html_scaled_image_link = True
    supported_image_types = ['image/svg+xml', 'image/png',
                             'image/gif', 'image/jpeg']
    supported_remote_images = True
    supported_data_uri_images = True
    searchindex_filename = 'searchindex.js'
    add_permalinks = True
    allow_sharp_as_current_path = True
    embedded = False  # for things like PHP help or Qt help: suppresses sidebar
    search = True  # for things like PHP help and Apple help: suppress search
    use_index = False
    download_support = True  # enable download role
    # use html5 translator by default
    default_html5_translator = False

    imgpath = None          # type: str
    domain_indices = []     # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]]  # NOQA

    # cached publisher object for snippets
    _publisher = None

    def __init__(self, app):
        # type: (Sphinx) -> None
        super().__init__(app)

        # CSS files
        self.css_files = []  # type: List[Dict[str, str]]

        # JS files
        self.script_files = JSContainer()  # type: List[JavaScript]

    def init(self):
        # type: () -> None
        self.build_info = self.create_build_info()
        # basename of images directory
        self.imagedir = '_images'
        # section numbers for headings in the currently visited document
        self.secnumbers = {}  # type: Dict[str, Tuple[int, ...]]
        # currently written docname
        self.current_docname = None  # type: str

        self.init_templates()
        self.init_highlighter()
        self.init_css_files()
        self.init_js_files()

        html_file_suffix = self.get_builder_config('file_suffix', 'html')
        if html_file_suffix is not None:
            self.out_suffix = html_file_suffix

        html_link_suffix = self.get_builder_config('link_suffix', 'html')
        if html_link_suffix is not None:
            self.link_suffix = html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        self.use_index = self.get_builder_config('use_index', 'html')

    def create_build_info(self):
        # type: () -> BuildInfo
        return BuildInfo(self.config, self.tags, ['html'])

    def _get_translations_js(self):
        # type: () -> str
        candidates = [path.join(dir, self.config.language,
                                'LC_MESSAGES', 'sphinx.js')
                      for dir in self.config.locale_dirs] + \
                     [path.join(package_dir, 'locale', self.config.language,
                                'LC_MESSAGES', 'sphinx.js'),
                      path.join(sys.prefix, 'share/sphinx/locale',
                                self.config.language, 'sphinx.js')]

        for jsfile in candidates:
            if path.isfile(jsfile):
                return jsfile
        return None

    def get_theme_config(self):
        # type: () -> Tuple[str, Dict]
        return self.config.html_theme, self.config.html_theme_options

    def init_templates(self):
        # type: () -> None
        theme_factory = HTMLThemeFactory(self.app)
        themename, themeoptions = self.get_theme_config()
        self.theme = theme_factory.create(themename)
        self.theme_options = themeoptions.copy()
        self.create_template_bridge()
        self.templates.init(self, self.theme)

    def init_highlighter(self):
        # type: () -> None
        # determine Pygments style and create the highlighter
        if self.config.pygments_style is not None:
            style = self.config.pygments_style
        elif self.theme:
            style = self.theme.get_config('theme', 'pygments_style', 'none')
        else:
            style = 'sphinx'
        self.highlighter = PygmentsBridge('html', style)

    def init_css_files(self):
        # type: () -> None
        for filename, attrs in self.app.registry.css_files:
            self.add_css_file(filename, **attrs)

        for filename, attrs in self.get_builder_config('css_files', 'html'):
            self.add_css_file(filename, **attrs)

    def add_css_file(self, filename, **kwargs):
        # type: (str, **str) -> None
        if '://' not in filename:
            filename = posixpath.join('_static', filename)

        self.css_files.append(Stylesheet(filename, **kwargs))  # type: ignore

    def init_js_files(self):
        # type: () -> None
        self.add_js_file('jquery.js')
        self.add_js_file('underscore.js')
        self.add_js_file('doctools.js')
        self.add_js_file('language_data.js')

        for filename, attrs in self.app.registry.js_files:
            self.add_js_file(filename, **attrs)

        for filename, attrs in self.get_builder_config('js_files', 'html'):
            self.add_js_file(filename, **attrs)

        if self.config.language and self._get_translations_js():
            self.add_js_file('translations.js')

    def add_js_file(self, filename, **kwargs):
        # type: (str, **str) -> None
        if filename and '://' not in filename:
            filename = posixpath.join('_static', filename)

        self.script_files.append(JavaScript(filename, **kwargs))

    @property
    def default_translator_class(self):  # type: ignore
        # Use HTMLTranslator for PHP builder
        return HTMLTranslator
    
    @property
    def math_renderer_name(self):
        # type: () -> str
        name = self.get_builder_config('math_renderer', 'html')
        if name is not None:
            # use given name
            return name
        else:
            # not given: choose a math_renderer from registered ones as possible
            renderers = list(self.app.registry.html_inline_math_renderers)
            if len(renderers) == 1:
                # only default math_renderer (mathjax) is registered
                return renderers[0]
            elif len(renderers) == 2:
                # default and another math_renderer are registered; prior the another
                renderers.remove('mathjax')
                return renderers[0]
            else:
                # many math_renderers are registered. can't choose automatically!
                return None

    def get_outdated_docs(self):
        # type: () -> Iterator[str]
        try:
            with open(path.join(self.outdir, '.buildinfo')) as fp:
                buildinfo = BuildInfo.load(fp)

            if self.build_info != buildinfo:
                yield from self.env.found_docs
                return
        except ValueError as exc:
            logger.warning(__('Failed to read build info file: %r'), exc)
        except OSError:
            # ignore errors on reading
            pass

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)),
                               template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except OSError:
                # source doesn't exist anymore
                pass

    def get_asset_paths(self):
        # type: () -> List[str]
        return self.config.html_extra_path + self.config.html_static_path

    def render_partial(self, node):
        # type: (nodes.Node) -> Dict[str, str]
        """Utility: Render a lone doctree node."""
        if node is None:
            return {'fragment': ''}
        doc = new_document('<partial node>')
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(
                source_class = DocTreeInput,
                destination_class=StringOutput)
            self._publisher.set_components('standalone',
                                           'restructuredtext', 'pseudoxml')

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = HTMLWriter(self)
        pub.process_programmatic_settings(
            None, {'output_encoding': 'unicode'}, None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        # type: (Set[str]) -> None
        # create the search indexer
        self.indexer = None
        if self.search:
            from sphinx.search import IndexBuilder
            lang = self.config.html_search_language or self.config.language
            if not lang:
                lang = 'en'
            self.indexer = IndexBuilder(self.env, lang,
                                        self.config.html_search_options,
                                        self.config.html_search_scorer)
            self.load_indexer(docnames)

        self.docwriter = HTMLWriter(self)
        self.docsettings = OptionParser(
            defaults=self.env.settings,
            components=(self.docwriter,),
            read_config_files=True).get_default_values()  # type: Any
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain_name in sorted(self.env.domains):
                domain = None  # type: Domain
                domain = self.env.domains[domain_name]
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append(
                            (indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = format_date(lufmt or _('%b %d, %Y'),
                                            language=self.config.language)
        else:
            self.last_updated = None

        logo = self.config.html_logo and \
            path.basename(self.config.html_logo) or ''

        favicon = self.config.html_favicon and \
            path.basename(self.config.html_favicon) or ''

        if not isinstance(self.config.html_use_opensearch, str):
            logger.warning(__('html_use_opensearch config value must now be a string'))

        self.relations = self.env.collect_relations()

        rellinks = []  # type: List[Tuple[str, str, str, str]]
        if self.use_index:
            rellinks.append(('genindex', _('General Index'), 'I', _('index')))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append((indexname, indexcls.localname,
                                 '', indexcls.shortname))

        if self.config.html_style is not None:
            stylename = self.config.html_style
        elif self.theme:
            stylename = self.theme.get_config('theme', 'stylesheet')
        else:
            stylename = 'default.css'

        self.globalcontext = {
            'embedded': self.embedded,
            'project': self.config.project,
            'release': return_codes_re.sub('', self.config.release),
            'version': self.config.version,
            'last_updated': self.last_updated,
            'copyright': self.config.copyright,
            'master_doc': self.config.master_doc,
            'use_opensearch': self.config.html_use_opensearch,
            'docstitle': self.config.html_title,
            'shorttitle': self.config.html_short_title,
            'show_copyright': self.config.html_show_copyright,
            'show_sphinx': self.config.html_show_sphinx,
            'has_source': self.config.html_copy_source,
            'show_source': self.config.html_show_sourcelink,
            'sourcelink_suffix': self.config.html_sourcelink_suffix,
            'file_suffix': self.out_suffix,
            'script_files': self.script_files,
            'language': self.config.language,
            'css_files': self.css_files,
            'sphinx_version': __display_version__,
            'style': stylename,
            'rellinks': rellinks,
            'builder': self.name,
            'parents': [],
            'logo': logo,
            'favicon': favicon
        }
        if self.theme:
            self.globalcontext.update(
                ('theme_' + key, val) for (key, val) in
                self.theme.get_options(self.theme_options).items())
        self.globalcontext.update(self.config.html_context)

    def get_doc_context(self, docname, body, metatags):
        # type: (str, str, str) -> Dict[str, Any]
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext['rellinks'][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    'link': self.get_relative_uri(docname, related[2]),
                    'title': self.render_partial(titles[related[2]])['title']
                }
                rellinks.append((related[2], next['title'], 'N', _('next')))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    'link': self.get_relative_uri(docname, related[1]),
                    'title': self.render_partial(titles[related[1]])['title']
                }
                rellinks.append((related[1], prev['title'], 'P', _('previous')))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append(
                    {'link': self.get_relative_uri(docname, related[0]),
                     'title': self.render_partial(titles[related[0]])['title']})
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            # remove link to the master file; we have a generic
            # "back to index" link already
            parents.pop()
        parents.reverse()

        # title rendered as HTML
        title_node = self.env.longtitles.get(docname)
        title = title_node and self.render_partial(title_node)['title'] or ''

        # Suffix for the document
        source_suffix = path.splitext(self.env.doc2path(docname))[1]

        # the name for the copied source
        if self.config.html_copy_source:
            sourcename = docname + source_suffix
            if source_suffix != self.config.html_sourcelink_suffix:
                sourcename += self.config.html_sourcelink_suffix
        else:
            sourcename = ''

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # local TOC and global TOC tree
        self_toc = TocTree(self.env).get_toc_for(docname, self)
        toc = self.render_partial(self_toc)['fragment']

        return {
            'parents': parents,
            'prev': prev,
            'next': next,
            'title': title,
            'meta': meta,
            'body': body,
            'metatags': metatags,
            'rellinks': rellinks,
            'sourcename': sourcename,
            'toc': toc,
            # only display a TOC if there's more than one item to show
            'display_toc': (self.env.toc_num_entries[docname] > 1),
            'page_source_suffix': source_suffix,
        }

    def write_doc(self, docname, doctree):
        # type: (str, nodes.document) -> None
        destination = StringOutput(encoding='utf-8')
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.fignumbers = self.env.toc_fignumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts['fragment']
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        # type: (str, nodes.document) -> None
        self.imgpath = relative_uri(self.get_target_uri(docname), self.imagedir)
        self.post_process_images(doctree)
        title_node = self.env.longtitles.get(docname)
        title = title_node and self.render_partial(title_node)['title'] or ''
        self.index_page(docname, doctree, title)

    def finish(self):
        # type: () -> None
        self.finish_tasks.add_task(self.gen_indices)
        self.finish_tasks.add_task(self.gen_additional_pages)
        self.finish_tasks.add_task(self.copy_image_files)
        self.finish_tasks.add_task(self.copy_download_files)
        self.finish_tasks.add_task(self.copy_static_files)
        self.finish_tasks.add_task(self.copy_custom_pages)
        self.finish_tasks.add_task(self.copy_extra_files)
        self.finish_tasks.add_task(self.write_buildinfo)

        # dump the search index
        self.handle_finish()

    def gen_indices(self):
        # type: () -> None
        logger.info(bold(__('generating indices...')), nonl=1)

        # the global general index
        if self.use_index:
            self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        logger.info('')

    def gen_additional_pages(self):
        # type: () -> None
        # pages from extensions
        for pagelist in self.app.emit('html-collect-pages'):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        logger.info(bold(__('writing additional pages...')), nonl=1)

        # additional pages from conf.py
        for pagename, template in self.config.html_additional_pages.items():
            logger.info(' ' + pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        # the search page
        if self.search:
            logger.info(' search', nonl=1)
            self.handle_page('search', {}, 'search.html')

        # the opensearch xml file
        if self.config.html_use_opensearch and self.search:
            logger.info(' opensearch', nonl=1)
            fn = path.join(self.outdir, '_static', 'opensearch.xml')
            self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)

        logger.info('')

    def write_genindex(self):
        # type: () -> None
        # the total count of lines for each index letter, used to distribute
        # the entries into two columns
        genindex = IndexEntries(self.env).create_index(self)
        indexcounts = []
        for _k, entries in genindex:
            indexcounts.append(sum(1 + len(subitems)
                                   for _, (_, subitems, _) in entries))

        genindexcontext = {
            'genindexentries': genindex,
            'genindexcounts': indexcounts,
            'split_index': self.config.html_split_index,
        }
        logger.info(' genindex', nonl=1)

        if self.config.html_split_index:
            self.handle_page('genindex', genindexcontext,
                             'genindex-split.html')
            self.handle_page('genindex-all', genindexcontext,
                             'genindex.html')
            for (key, entries), count in zip(genindex, indexcounts):
                ctx = {'key': key, 'entries': entries, 'count': count,
                       'genindexentries': genindex}
                self.handle_page('genindex-' + key, ctx,
                                 'genindex-single.html')
        else:
            self.handle_page('genindex', genindexcontext, 'genindex.html')

    def write_domain_indices(self):
        # type: () -> None
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = {
                'indextitle': indexcls.localname,
                'content': content,
                'collapse_index': collapse,
            }
            logger.info(' ' + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, 'domainindex.html')

    def copy_image_files(self):
        # type: () -> None
        if self.images:
            stringify_func = ImageAdapter(self.app.env).get_original_image_uri
            ensuredir(path.join(self.outdir, self.imagedir))
            for src in status_iterator(self.images, __('copying images... '), "brown",
                                       len(self.images), self.app.verbosity,
                                       stringify_func=stringify_func):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, self.imagedir, dest))
                except Exception as err:
                    logger.warning(__('cannot copy image file %r: %s'),
                                   path.join(self.srcdir, src), err)

    def copy_download_files(self):
        # type: () -> None
        def to_relpath(f):
            # type: (str) -> str
            return relative_path(self.srcdir, f)
        # copy downloadable files
        if self.env.dlfiles:
            ensuredir(path.join(self.outdir, '_downloads'))
            for src in status_iterator(self.env.dlfiles, __('copying downloadable files... '),
                                       "brown", len(self.env.dlfiles), self.app.verbosity,
                                       stringify_func=to_relpath):
                try:
                    dest = path.join(self.outdir, '_downloads', self.env.dlfiles[src][1])
                    ensuredir(path.dirname(dest))
                    copyfile(path.join(self.srcdir, src), dest)
                except OSError as err:
                    logger.warning(__('cannot copy downloadable file %r: %s'),
                                   path.join(self.srcdir, src), err)

    def copy_custom_pages(self):
         # then, copy over all user-supplied static files
        excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
        for static_path in self.config.html_static_path:
            entry = path.join(self.confdir, static_path)
            if not path.exists(entry):
                logger.warning(__('html_static_path entry %r does not exist'), entry)
                continue
            copy_asset(entry, path.join(self.outdir, '_static'), excluded,
                        context=ctx, renderer=self.templates)


    def copy_static_files(self):
        # type: () -> None
        try:
            # copy static files
            logger.info(bold(__('copying static files... ')), nonl=True)
            ensuredir(path.join(self.outdir, '_static'))
            # first, create pygments style file
            with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f:
                f.write(self.highlighter.get_stylesheet())
            # then, copy translations JavaScript file
            if self.config.language is not None:
                jsfile = self._get_translations_js()
                if jsfile:
                    copyfile(jsfile, path.join(self.outdir, '_static',
                                               'translations.js'))

            # copy non-minified stemmer JavaScript file
            if self.indexer is not None:
                jsfile = self.indexer.get_js_stemmer_rawcode()
                if jsfile:
                    copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))

            ctx = self.globalcontext.copy()

            # add context items for search function used in searchtools.js_t
            if self.indexer is not None:
                ctx.update(self.indexer.context_for_searchtool())

            # then, copy over theme-supplied static files
            if self.theme:
                for theme_path in self.theme.get_theme_dirs()[::-1]:
                    entry = path.join(theme_path, 'static')
                    copy_asset(entry, path.join(self.outdir, '_static'), excluded=DOTFILES,
                               context=ctx, renderer=self.templates)
            # then, copy over all user-supplied static files
            excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
            for static_path in self.config.html_static_path:
                entry = path.join(self.confdir, static_path)
                if not path.exists(entry):
                    logger.warning(__('html_static_path entry %r does not exist'), entry)
                    continue
                copy_asset(entry, path.join(self.outdir, '_static'), excluded,
                           context=ctx, renderer=self.templates)
            # copy logo and favicon files if not already in static path
            if self.config.html_logo:
                logobase = path.basename(self.config.html_logo)
                logotarget = path.join(self.outdir, '_static', logobase)
                if not path.isfile(path.join(self.confdir, self.config.html_logo)):
                    logger.warning(__('logo file %r does not exist'), self.config.html_logo)
                elif not path.isfile(logotarget):
                    copyfile(path.join(self.confdir, self.config.html_logo),
                             logotarget)
            if self.config.html_favicon:
                iconbase = path.basename(self.config.html_favicon)
                icontarget = path.join(self.outdir, '_static', iconbase)
                if not path.isfile(path.join(self.confdir, self.config.html_favicon)):
                    logger.warning(__('favicon file %r does not exist'),
                                   self.config.html_favicon)
                elif not path.isfile(icontarget):
                    copyfile(path.join(self.confdir, self.config.html_favicon),
                             icontarget)
            logger.info(__('done'))
        except OSError as err:
            logger.warning(__('cannot copy static file %r'), err)

    def copy_extra_files(self):
        # type: () -> None
        try:
            # copy html_extra_path files
            logger.info(bold(__('copying extra files... ')), nonl=True)
            excluded = Matcher(self.config.exclude_patterns)

            for extra_path in self.config.html_extra_path:
                entry = path.join(self.confdir, extra_path)
                if not path.exists(entry):
                    logger.warning(__('html_extra_path entry %r does not exist'), entry)
                    continue

                copy_asset(entry, self.outdir, excluded)
            logger.info(__('done'))
        except OSError as err:
            logger.warning(__('cannot copy extra file %r'), err)

    def write_buildinfo(self):
        # type: () -> None
        try:
            with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
                self.build_info.dump(fp)
        except OSError as exc:
            logger.warning(__('Failed to write build info file: %r'), exc)

    def cleanup(self):
        # type: () -> None
        # clean up theme stuff
        if self.theme:
            self.theme.cleanup()

    def post_process_images(self, doctree):
        # type: (nodes.Node) -> None
        """Pick the best candidate for an image and link down-scaled images to
        their high res version.
        """
        Builder.post_process_images(self, doctree)

        if self.config.html_scaled_image_link and self.html_scaled_image_link:
            for node in doctree.traverse(nodes.image):
                scale_keys = ('scale', 'width', 'height')
                if not any((key in node) for key in scale_keys) or \
                   isinstance(node.parent, nodes.reference):
                    # docutils does unfortunately not preserve the
                    # ``target`` attribute on images, so we need to check
                    # the parent node here.
                    continue
                uri = node['uri']
                reference = nodes.reference('', '', internal=True)
                if uri in self.images:
                    reference['refuri'] = posixpath.join(self.imgpath,
                                                         self.images[uri])
                else:
                    reference['refuri'] = uri
                node.replace_self(reference)
                reference.append(node)

    def load_indexer(self, docnames):
        # type: (Iterable[str]) -> None
        keep = set(self.env.all_docs) - set(docnames)
        try:
            searchindexfn = path.join(self.outdir, self.searchindex_filename)
            if self.indexer_dumps_unicode:
                with open(searchindexfn, encoding='utf-8') as ft:
                    self.indexer.load(ft, self.indexer_format)
            else:
                with open(searchindexfn, 'rb') as fb:
                    self.indexer.load(fb, self.indexer_format)
        except (OSError, ValueError):
            if keep:
                logger.warning(__('search index couldn\'t be loaded, but not all '
                                  'documents will be built: the index will be '
                                  'incomplete.'))
        # delete all entries for files that will be rebuilt
        self.indexer.prune(keep)

    def index_page(self, pagename, doctree, title):
        # type: (str, nodes.document, str) -> None
        # only index pages with title
        if self.indexer is not None and title:
            filename = self.env.doc2path(pagename, base=None)
            try:
                self.indexer.feed(pagename, filename, title, doctree)
            except TypeError:
                # fallback for old search-adapters
                self.indexer.feed(pagename, title, doctree)  # type: ignore
                indexer_name = self.indexer.__class__.__name__
                warnings.warn(
                    'The %s.feed() method signature is deprecated. Update to '
                    '%s.feed(docname, filename, title, doctree).' % (
                        indexer_name, indexer_name),
                    RemovedInSphinx40Warning)

    def _get_local_toctree(self, docname, collapse=True, **kwds):
        # type: (str, bool, Any) -> str
        if 'includehidden' not in kwds:
            kwds['includehidden'] = False
        return self.render_partial(TocTree(self.env).get_toctree_for(
            docname, self, collapse, **kwds))['fragment']

    def get_outfilename(self, pagename):
        # type: (str) -> str
        return path.join(self.outdir, os_path(pagename) + self.out_suffix)

    def add_sidebars(self, pagename, ctx):
        # type: (str, Dict) -> None
        def has_wildcard(pattern):
            # type: (str) -> bool
            return any(char in pattern for char in '*?[')
        sidebars = None
        matched = None
        customsidebar = None

        # default sidebars settings for selected theme
        if self.theme.name == 'alabaster':
            # provide default settings for alabaster (for compatibility)
            # Note: this will be removed before Sphinx-2.0
            try:
                # get default sidebars settings from alabaster (if defined)
                theme_default_sidebars = self.theme.config.get('theme', 'sidebars')
                if theme_default_sidebars:
                    sidebars = [name.strip() for name in theme_default_sidebars.split(',')]
            except Exception:
                # fallback to better default settings
                sidebars = ['about.html', 'navigation.html', 'relations.html',
                            'searchbox.html', 'donate.html']
        else:
            theme_default_sidebars = self.theme.get_config('theme', 'sidebars', None)
            if theme_default_sidebars:
                sidebars = [name.strip() for name in theme_default_sidebars.split(',')]

        # user sidebar settings
        html_sidebars = self.get_builder_config('sidebars', 'html')
        for pattern, patsidebars in html_sidebars.items():
            if patmatch(pagename, pattern):
                if matched:
                    if has_wildcard(pattern):
                        # warn if both patterns contain wildcards
                        if has_wildcard(matched):
                            logger.warning(__('page %s matches two patterns in '
                                              'html_sidebars: %r and %r'),
                                           pagename, matched, pattern)
                        # else the already matched pattern is more specific
                        # than the present one, because it contains no wildcard
                        continue
                matched = pattern
                sidebars = patsidebars

        if sidebars is None:
            # keep defaults
            pass

        ctx['sidebars'] = sidebars
        ctx['customsidebar'] = customsidebar

    # --------- these are overwritten by the serialization builder

    def get_target_uri(self, docname, typ=None):
        # type: (str, str) -> str
        return docname + self.link_suffix

    def handle_page(self, pagename, addctx, templatename='page.html',
                    outfilename=None, event_arg=None):
        # type: (str, Dict, str, str, Any) -> None
        ctx = self.globalcontext.copy()
        # current_page_name is backwards compatibility
        ctx['pagename'] = ctx['current_page_name'] = pagename
        ctx['encoding'] = self.config.html_output_encoding
        default_baseuri = self.get_target_uri(pagename)
        # in the singlehtml builder, default_baseuri still contains an #anchor
        # part, which relative_uri doesn't really like...
        default_baseuri = default_baseuri.rsplit('#', 1)[0]

        if self.config.html_baseurl:
            ctx['pageurl'] = posixpath.join(self.config.html_baseurl,
                                            pagename + self.out_suffix)
        else:
            ctx['pageurl'] = None

        def pathto(otheruri, resource=False, baseuri=default_baseuri):
            # type: (str, bool, str) -> str
            if resource and '://' in otheruri:
                # allow non-local resources given by scheme
                return otheruri
            elif not resource:
                otheruri = self.get_target_uri(otheruri)
            uri = relative_uri(baseuri, otheruri) or '#'
            if uri == '#' and not self.allow_sharp_as_current_path:
                uri = baseuri
            return uri
        ctx['pathto'] = pathto

        def css_tag(css):
            # type: (Stylesheet) -> str
            attrs = []
            for key in sorted(css.attributes):
                value = css.attributes[key]
                if value is not None:
                    attrs.append('%s="%s"' % (key, html.escape(value, True)))
            attrs.append('href="%s"' % pathto(css.filename, resource=True))
            return '<link %s />' % ' '.join(attrs)
        ctx['css_tag'] = css_tag

        def hasdoc(name):
            # type: (str) -> bool
            if name in self.env.all_docs:
                return True
            elif name == 'search' and self.search:
                return True
            elif name == 'genindex' and self.get_builder_config('use_index', 'html'):
                return True
            return False
        ctx['hasdoc'] = hasdoc

        def warn(*args, **kwargs):
            # type: (Any, Any) -> str
            """Simple warn() wrapper for themes."""
            warnings.warn('The template function warn() was deprecated. '
                          'Use warning() instead.',
                          RemovedInSphinx30Warning, stacklevel=2)
            logger.warning(*args, **kwargs)
            return ''  # return empty string
        ctx['warn'] = warn

        ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
        self.add_sidebars(pagename, ctx)
        ctx.update(addctx)

        self.update_page_context(pagename, templatename, ctx, event_arg)
        newtmpl = self.app.emit_firstresult('html-page-context', pagename,
                                            templatename, ctx, event_arg)
        if newtmpl:
            templatename = newtmpl

        try:
            output = self.templates.render(templatename, ctx)
        except UnicodeError:
            logger.warning(__("a Unicode error occurred when rendering the page %s. "
                              "Please make sure all config values that contain "
                              "non-ASCII content are Unicode strings."), pagename)
            return
        except Exception as exc:
            raise ThemeError(__("An error happened in rendering the page %s.\nReason: %r") %
                             (pagename, exc))

        if not outfilename:
            outfilename = self.get_outfilename(pagename)
        # outfilename's path is in general different from self.outdir
        ensuredir(path.dirname(outfilename))
        try:
            with open(outfilename, 'w', encoding=ctx['encoding'],
                      errors='xmlcharrefreplace') as f:
                f.write(output)
        except OSError as err:
            logger.warning(__("error writing file %s: %s"), outfilename, err)
        if self.copysource and ctx.get('sourcename'):
            # copy the source file for the "show source" link
            source_name = path.join(self.outdir, '_sources',
                                    os_path(ctx['sourcename']))
            ensuredir(path.dirname(source_name))
            copyfile(self.env.doc2path(pagename), source_name)

    def update_page_context(self, pagename, templatename, ctx, event_arg):
        # type: (str, str, Dict, Any) -> None
        pass

    def handle_finish(self):
        # type: () -> None
        if self.indexer:
            self.finish_tasks.add_task(self.dump_search_index)
        self.finish_tasks.add_task(self.dump_inventory)

    def dump_inventory(self):
        # type: () -> None
        logger.info(bold(__('dumping object inventory... ')), nonl=True)
        InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self)
        logger.info(__('done'))

    def dump_search_index(self):
        # type: () -> None
        logger.info(
            bold(__('dumping search index in %s ... ') % self.indexer.label()),
            nonl=True)
        self.indexer.prune(self.env.all_docs)
        searchindexfn = path.join(self.outdir, self.searchindex_filename)
        # first write to a temporary file, so that if dumping fails,
        # the existing index won't be overwritten
        if self.indexer_dumps_unicode:
            with open(searchindexfn + '.tmp', 'w', encoding='utf-8') as ft:
                self.indexer.dump(ft, self.indexer_format)
        else:
            with open(searchindexfn + '.tmp', 'wb') as fb:
                self.indexer.dump(fb, self.indexer_format)
        movefile(searchindexfn + '.tmp', searchindexfn)
        logger.info(__('done'))
예제 #46
0
enable_exit_status=1
argv=None
usage=default_usage
description=default_description

# Argument values given to publish_cmdline() in rst2latex.py::

description = ('Generates PDF documents from standalone reStructuredText '
               'sources using the "latex" Writer and the "rubber" '
               'building system for LaTeX documents.  ' + default_description)
writer_name = 'latex'

# Set up the publisher::

pub = Publisher(reader, parser, writer, settings=settings)
pub.set_components(reader_name, parser_name, writer_name)

# Parse the command line args 
# (Publisher.publish does this in a try statement)::

pub.process_command_line(argv, usage, description, settings_spec, 
                         config_section, **(settings_overrides or {}))
# pprint(pub.settings.__dict__)

# Get source and destination path::

source = pub.settings._source
destination = pub.settings._destination
# print source, destination

# Generate names for the temporary files and set ``destination`` to temporary
예제 #47
0
파일: html.py 프로젝트: Titan-C/sphinx
class StandaloneHTMLBuilder(Builder):
    """
    Builds standalone HTML docs.
    """
    name = 'html'
    format = 'html'
    copysource = True
    allow_parallel = True
    out_suffix = '.html'
    link_suffix = '.html'  # defaults to matching out_suffix
    indexer_format = js_index
    indexer_dumps_unicode = True
    supported_image_types = ['image/svg+xml', 'image/png',
                             'image/gif', 'image/jpeg']
    searchindex_filename = 'searchindex.js'
    add_permalinks = True
    embedded = False  # for things like HTML help or Qt help: suppresses sidebar
    search = True  # for things like HTML help and Apple help: suppress search

    # This is a class attribute because it is mutated by Sphinx.add_javascript.
    script_files = ['_static/jquery.js', '_static/underscore.js',
                    '_static/doctools.js']
    # Dito for this one.
    css_files = []

    default_sidebars = ['localtoc.html', 'relations.html',
                        'sourcelink.html', 'searchbox.html']

    # cached publisher object for snippets
    _publisher = None

    def init(self):
        # a hash of all config values that, if changed, cause a full rebuild
        self.config_hash = ''
        self.tags_hash = ''
        # basename of images directory
        self.imagedir = '_images'
        # section numbers for headings in the currently visited document
        self.secnumbers = {}
        # currently written docname
        self.current_docname = None

        self.init_templates()
        self.init_highlighter()
        self.init_translator_class()
        if self.config.html_file_suffix is not None:
            self.out_suffix = self.config.html_file_suffix

        if self.config.html_link_suffix is not None:
            self.link_suffix = self.config.html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        if self.config.language is not None:
            if self._get_translations_js():
                self.script_files.append('_static/translations.js')

    def _get_translations_js(self):
        candidates = [path.join(package_dir, 'locale', self.config.language,
                                'LC_MESSAGES', 'sphinx.js'),
                      path.join(sys.prefix, 'share/sphinx/locale',
                                self.config.language, 'sphinx.js')] + \
                     [path.join(dir, self.config.language,
                                'LC_MESSAGES', 'sphinx.js')
                      for dir in self.config.locale_dirs]
        for jsfile in candidates:
            if path.isfile(jsfile):
                return jsfile
        return None

    def get_theme_config(self):
        return self.config.html_theme, self.config.html_theme_options

    def init_templates(self):
        Theme.init_themes(self.confdir, self.config.html_theme_path,
                          warn=self.warn)
        themename, themeoptions = self.get_theme_config()
        self.theme = Theme(themename, warn=self.warn)
        self.theme_options = themeoptions.copy()
        self.create_template_bridge()
        self.templates.init(self, self.theme)

    def init_highlighter(self):
        # determine Pygments style and create the highlighter
        if self.config.pygments_style is not None:
            style = self.config.pygments_style
        elif self.theme:
            style = self.theme.get_confstr('theme', 'pygments_style', 'none')
        else:
            style = 'sphinx'
        self.highlighter = PygmentsBridge('html', style,
                                          self.config.trim_doctest_flags)

    def init_translator_class(self):
        if self.translator_class is not None:
            pass
        elif self.config.html_translator_class:
            self.translator_class = self.app.import_object(
                self.config.html_translator_class,
                'html_translator_class setting')
        elif self.config.html_use_smartypants:
            self.translator_class = SmartyPantsHTMLTranslator
        else:
            self.translator_class = HTMLTranslator

    def get_outdated_docs(self):
        cfgdict = dict((name, self.config[name])
                       for (name, desc) in iteritems(self.config.values)
                       if desc[1] == 'html')
        self.config_hash = get_stable_hash(cfgdict)
        self.tags_hash = get_stable_hash(sorted(self.tags))
        old_config_hash = old_tags_hash = ''
        try:
            fp = open(path.join(self.outdir, '.buildinfo'))
            try:
                version = fp.readline()
                if version.rstrip() != '# Sphinx build info version 1':
                    raise ValueError
                fp.readline()  # skip commentary
                cfg, old_config_hash = fp.readline().strip().split(': ')
                if cfg != 'config':
                    raise ValueError
                tag, old_tags_hash = fp.readline().strip().split(': ')
                if tag != 'tags':
                    raise ValueError
            finally:
                fp.close()
        except ValueError:
            self.warn('unsupported build info format in %r, building all' %
                      path.join(self.outdir, '.buildinfo'))
        except Exception:
            pass
        if old_config_hash != self.config_hash or \
           old_tags_hash != self.tags_hash:
            for docname in self.env.found_docs:
                yield docname
            return

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)),
                               template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

    def render_partial(self, node):
        """Utility: Render a lone doctree node."""
        if node is None:
            return {'fragment': ''}
        doc = new_document(b'<partial node>')
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(
                source_class = DocTreeInput,
                destination_class=StringOutput)
            self._publisher.set_components('standalone',
                                           'restructuredtext', 'pseudoxml')

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = HTMLWriter(self)
        pub.process_programmatic_settings(
            None, {'output_encoding': 'unicode'}, None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        # create the search indexer
        self.indexer = None
        if self.search:
            from sphinx.search import IndexBuilder, languages
            lang = self.config.html_search_language or self.config.language
            if not lang or lang not in languages:
                lang = 'en'
            self.indexer = IndexBuilder(self.env, lang,
                                        self.config.html_search_options,
                                        self.config.html_search_scorer)
            self.load_indexer(docnames)

        self.docwriter = HTMLWriter(self)
        self.docsettings = OptionParser(
            defaults=self.env.settings,
            components=(self.docwriter,),
            read_config_files=True).get_default_values()
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain_name in sorted(self.env.domains):
                domain = self.env.domains[domain_name]
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == 'py-modindex' and \
                       not self.config.html_use_modindex:
                        continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append(
                            (indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = format_date(lufmt or _('MMM dd, YYYY'),
                                            language=self.config.language)
        else:
            self.last_updated = None

        logo = self.config.html_logo and \
            path.basename(self.config.html_logo) or ''

        favicon = self.config.html_favicon and \
            path.basename(self.config.html_favicon) or ''
        if favicon and os.path.splitext(favicon)[1] != '.ico':
            self.warn('html_favicon is not an .ico file')

        if not isinstance(self.config.html_use_opensearch, string_types):
            self.warn('html_use_opensearch config value must now be a string')

        self.relations = self.env.collect_relations()

        rellinks = []
        if self.get_builder_config('use_index', 'html'):
            rellinks.append(('genindex', _('General Index'), 'I', _('index')))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append((indexname, indexcls.localname,
                                 '', indexcls.shortname))

        if self.config.html_style is not None:
            stylename = self.config.html_style
        elif self.theme:
            stylename = self.theme.get_confstr('theme', 'stylesheet')
        else:
            stylename = 'default.css'

        self.globalcontext = dict(
            embedded = self.embedded,
            project = self.config.project,
            release = self.config.release,
            version = self.config.version,
            last_updated = self.last_updated,
            copyright = self.config.copyright,
            master_doc = self.config.master_doc,
            use_opensearch = self.config.html_use_opensearch,
            docstitle = self.config.html_title,
            shorttitle = self.config.html_short_title,
            show_copyright = self.config.html_show_copyright,
            show_sphinx = self.config.html_show_sphinx,
            has_source = self.config.html_copy_source,
            show_source = self.config.html_show_sourcelink,
            file_suffix = self.out_suffix,
            script_files = self.script_files,
            language = self.config.language,
            css_files = self.css_files,
            sphinx_version = __display_version__,
            style = stylename,
            rellinks = rellinks,
            builder = self.name,
            parents = [],
            logo = logo,
            favicon = favicon,
        )
        if self.theme:
            self.globalcontext.update(
                ('theme_' + key, val) for (key, val) in
                iteritems(self.theme.get_options(self.theme_options)))
        self.globalcontext.update(self.config.html_context)

    def get_doc_context(self, docname, body, metatags):
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext['rellinks'][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    'link': self.get_relative_uri(docname, related[2]),
                    'title': self.render_partial(titles[related[2]])['title']
                }
                rellinks.append((related[2], next['title'], 'N', _('next')))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    'link': self.get_relative_uri(docname, related[1]),
                    'title': self.render_partial(titles[related[1]])['title']
                }
                rellinks.append((related[1], prev['title'], 'P', _('previous')))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append(
                    {'link': self.get_relative_uri(docname, related[0]),
                     'title': self.render_partial(titles[related[0]])['title']})
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            # remove link to the master file; we have a generic
            # "back to index" link already
            parents.pop()
        parents.reverse()

        # title rendered as HTML
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        # the name for the copied source
        sourcename = self.config.html_copy_source and docname + '.txt' or ''

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # Suffix for the document
        source_suffix = '.' + self.env.doc2path(docname).split('.')[-1]

        # local TOC and global TOC tree
        self_toc = self.env.get_toc_for(docname, self)
        toc = self.render_partial(self_toc)['fragment']

        return dict(
            parents = parents,
            prev = prev,
            next = next,
            title = title,
            meta = meta,
            body = body,
            metatags = metatags,
            rellinks = rellinks,
            sourcename = sourcename,
            toc = toc,
            # only display a TOC if there's more than one item to show
            display_toc = (self.env.toc_num_entries[docname] > 1),
            page_source_suffix = source_suffix,
        )

    def write_doc(self, docname, doctree):
        destination = StringOutput(encoding='utf-8')
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.fignumbers = self.env.toc_fignumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts['fragment']
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        self.imgpath = relative_uri(self.get_target_uri(docname), self.imagedir)
        self.post_process_images(doctree)
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        self.index_page(docname, doctree, title)

    def finish(self):
        self.finish_tasks.add_task(self.gen_indices)
        self.finish_tasks.add_task(self.gen_additional_pages)
        self.finish_tasks.add_task(self.copy_image_files)
        self.finish_tasks.add_task(self.copy_download_files)
        self.finish_tasks.add_task(self.copy_static_files)
        self.finish_tasks.add_task(self.copy_extra_files)
        self.finish_tasks.add_task(self.write_buildinfo)

        # dump the search index
        self.handle_finish()

    def gen_indices(self):
        self.info(bold('generating indices...'), nonl=1)

        # the global general index
        if self.get_builder_config('use_index', 'html'):
            self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        self.info()

    def gen_additional_pages(self):
        # pages from extensions
        for pagelist in self.app.emit('html-collect-pages'):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        self.info(bold('writing additional pages...'), nonl=1)

        # additional pages from conf.py
        for pagename, template in self.config.html_additional_pages.items():
            self.info(' '+pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        # the search page
        if self.search:
            self.info(' search', nonl=1)
            self.handle_page('search', {}, 'search.html')

        # the opensearch xml file
        if self.config.html_use_opensearch and self.search:
            self.info(' opensearch', nonl=1)
            fn = path.join(self.outdir, '_static', 'opensearch.xml')
            self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)

        self.info()

    def write_genindex(self):
        # the total count of lines for each index letter, used to distribute
        # the entries into two columns
        genindex = self.env.create_index(self)
        indexcounts = []
        for _k, entries in genindex:
            indexcounts.append(sum(1 + len(subitems)
                                   for _, (_, subitems, _) in entries))

        genindexcontext = dict(
            genindexentries = genindex,
            genindexcounts = indexcounts,
            split_index = self.config.html_split_index,
        )
        self.info(' genindex', nonl=1)

        if self.config.html_split_index:
            self.handle_page('genindex', genindexcontext,
                             'genindex-split.html')
            self.handle_page('genindex-all', genindexcontext,
                             'genindex.html')
            for (key, entries), count in zip(genindex, indexcounts):
                ctx = {'key': key, 'entries': entries, 'count': count,
                       'genindexentries': genindex}
                self.handle_page('genindex-' + key, ctx,
                                 'genindex-single.html')
        else:
            self.handle_page('genindex', genindexcontext, 'genindex.html')

    def write_domain_indices(self):
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = dict(
                indextitle = indexcls.localname,
                content = content,
                collapse_index = collapse,
            )
            self.info(' ' + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, 'domainindex.html')

    def copy_image_files(self):
        # copy image files
        if self.images:
            ensuredir(path.join(self.outdir, self.imagedir))
            for src in self.app.status_iterator(self.images, 'copying images... ',
                                                brown, len(self.images)):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, self.imagedir, dest))
                except Exception as err:
                    self.warn('cannot copy image file %r: %s' %
                              (path.join(self.srcdir, src), err))

    def copy_download_files(self):
        def to_relpath(f):
            return relative_path(self.srcdir, f)
        # copy downloadable files
        if self.env.dlfiles:
            ensuredir(path.join(self.outdir, '_downloads'))
            for src in self.app.status_iterator(self.env.dlfiles,
                                                'copying downloadable files... ',
                                                brown, len(self.env.dlfiles),
                                                stringify_func=to_relpath):
                dest = self.env.dlfiles[src][1]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, '_downloads', dest))
                except Exception as err:
                    self.warn('cannot copy downloadable file %r: %s' %
                              (path.join(self.srcdir, src), err))

    def copy_static_files(self):
        # copy static files
        self.info(bold('copying static files... '), nonl=True)
        ensuredir(path.join(self.outdir, '_static'))
        # first, create pygments style file
        f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w')
        f.write(self.highlighter.get_stylesheet())
        f.close()
        # then, copy translations JavaScript file
        if self.config.language is not None:
            jsfile = self._get_translations_js()
            if jsfile:
                copyfile(jsfile, path.join(self.outdir, '_static',
                                           'translations.js'))

        # copy non-minified stemmer JavaScript file
        if self.indexer is not None:
            jsfile = self.indexer.get_js_stemmer_rawcode()
            if jsfile:
                copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))

        ctx = self.globalcontext.copy()

        # add context items for search function used in searchtools.js_t
        if self.indexer is not None:
            ctx.update(self.indexer.context_for_searchtool())

        # then, copy over theme-supplied static files
        if self.theme:
            themeentries = [path.join(themepath, 'static')
                            for themepath in self.theme.get_dirchain()[::-1]]
            for entry in themeentries:
                copy_static_entry(entry, path.join(self.outdir, '_static'),
                                  self, ctx)
        # then, copy over all user-supplied static files
        staticentries = [path.join(self.confdir, spath)
                         for spath in self.config.html_static_path]
        matchers = compile_matchers(self.config.exclude_patterns)
        for entry in staticentries:
            if not path.exists(entry):
                self.warn('html_static_path entry %r does not exist' % entry)
                continue
            copy_static_entry(entry, path.join(self.outdir, '_static'), self,
                              ctx, exclude_matchers=matchers)
        # copy logo and favicon files if not already in static path
        if self.config.html_logo:
            logobase = path.basename(self.config.html_logo)
            logotarget = path.join(self.outdir, '_static', logobase)
            if not path.isfile(path.join(self.confdir, self.config.html_logo)):
                self.warn('logo file %r does not exist' % self.config.html_logo)
            elif not path.isfile(logotarget):
                copyfile(path.join(self.confdir, self.config.html_logo),
                         logotarget)
        if self.config.html_favicon:
            iconbase = path.basename(self.config.html_favicon)
            icontarget = path.join(self.outdir, '_static', iconbase)
            if not path.isfile(path.join(self.confdir, self.config.html_favicon)):
                self.warn('favicon file %r does not exist' % self.config.html_favicon)
            elif not path.isfile(icontarget):
                copyfile(path.join(self.confdir, self.config.html_favicon),
                         icontarget)
        self.info('done')

    def copy_extra_files(self):
        # copy html_extra_path files
        self.info(bold('copying extra files... '), nonl=True)
        extraentries = [path.join(self.confdir, epath)
                        for epath in self.config.html_extra_path]
        matchers = compile_matchers(self.config.exclude_patterns)
        for entry in extraentries:
            if not path.exists(entry):
                self.warn('html_extra_path entry %r does not exist' % entry)
                continue
            copy_extra_entry(entry, self.outdir, matchers)
        self.info('done')

    def write_buildinfo(self):
        # write build info file
        fp = open(path.join(self.outdir, '.buildinfo'), 'w')
        try:
            fp.write('# Sphinx build info version 1\n'
                     '# This file hashes the configuration used when building'
                     ' these files. When it is not found, a full rebuild will'
                     ' be done.\nconfig: %s\ntags: %s\n' %
                     (self.config_hash, self.tags_hash))
        finally:
            fp.close()

    def cleanup(self):
        # clean up theme stuff
        if self.theme:
            self.theme.cleanup()

    def post_process_images(self, doctree):
        """Pick the best candidate for an image and link down-scaled images to
        their high res version.
        """
        Builder.post_process_images(self, doctree)

        if self.config.html_scaled_image_link:
            for node in doctree.traverse(nodes.image):
                scale_keys = ('scale', 'width', 'height')
                if not any((key in node) for key in scale_keys) or \
                   isinstance(node.parent, nodes.reference):
                    # docutils does unfortunately not preserve the
                    # ``target`` attribute on images, so we need to check
                    # the parent node here.
                    continue
                uri = node['uri']
                reference = nodes.reference('', '', internal=True)
                if uri in self.images:
                    reference['refuri'] = posixpath.join(self.imgpath,
                                                         self.images[uri])
                else:
                    reference['refuri'] = uri
                node.replace_self(reference)
                reference.append(node)

    def load_indexer(self, docnames):
        keep = set(self.env.all_docs) - set(docnames)
        try:
            searchindexfn = path.join(self.outdir, self.searchindex_filename)
            if self.indexer_dumps_unicode:
                f = codecs.open(searchindexfn, 'r', encoding='utf-8')
            else:
                f = open(searchindexfn, 'rb')
            try:
                self.indexer.load(f, self.indexer_format)
            finally:
                f.close()
        except (IOError, OSError, ValueError):
            if keep:
                self.warn('search index couldn\'t be loaded, but not all '
                          'documents will be built: the index will be '
                          'incomplete.')
        # delete all entries for files that will be rebuilt
        self.indexer.prune(keep)

    def index_page(self, pagename, doctree, title):
        # only index pages with title
        if self.indexer is not None and title:
            self.indexer.feed(pagename, title, doctree)

    def _get_local_toctree(self, docname, collapse=True, **kwds):
        if 'includehidden' not in kwds:
            kwds['includehidden'] = False
        return self.render_partial(self.env.get_toctree_for(
            docname, self, collapse, **kwds))['fragment']

    def get_outfilename(self, pagename):
        return path.join(self.outdir, os_path(pagename) + self.out_suffix)

    def add_sidebars(self, pagename, ctx):
        def has_wildcard(pattern):
            return any(char in pattern for char in '*?[')
        sidebars = None
        matched = None
        customsidebar = None
        for pattern, patsidebars in iteritems(self.config.html_sidebars):
            if patmatch(pagename, pattern):
                if matched:
                    if has_wildcard(pattern):
                        # warn if both patterns contain wildcards
                        if has_wildcard(matched):
                            self.warn('page %s matches two patterns in '
                                      'html_sidebars: %r and %r' %
                                      (pagename, matched, pattern))
                        # else the already matched pattern is more specific
                        # than the present one, because it contains no wildcard
                        continue
                matched = pattern
                sidebars = patsidebars
        if sidebars is None:
            # keep defaults
            pass
        elif isinstance(sidebars, string_types):
            # 0.x compatible mode: insert custom sidebar before searchbox
            customsidebar = sidebars
            sidebars = None
        ctx['sidebars'] = sidebars
        ctx['customsidebar'] = customsidebar

    # --------- these are overwritten by the serialization builder

    def get_target_uri(self, docname, typ=None):
        return docname + self.link_suffix

    def handle_page(self, pagename, addctx, templatename='page.html',
                    outfilename=None, event_arg=None):
        ctx = self.globalcontext.copy()
        # current_page_name is backwards compatibility
        ctx['pagename'] = ctx['current_page_name'] = pagename
        default_baseuri = self.get_target_uri(pagename)
        # in the singlehtml builder, default_baseuri still contains an #anchor
        # part, which relative_uri doesn't really like...
        default_baseuri = default_baseuri.rsplit('#', 1)[0]

        def pathto(otheruri, resource=False, baseuri=default_baseuri):
            if resource and '://' in otheruri:
                # allow non-local resources given by scheme
                return otheruri
            elif not resource:
                otheruri = self.get_target_uri(otheruri)
            uri = relative_uri(baseuri, otheruri) or '#'
            return uri
        ctx['pathto'] = pathto
        ctx['hasdoc'] = lambda name: name in self.env.all_docs
        if self.name != 'htmlhelp':
            ctx['encoding'] = encoding = self.config.html_output_encoding
        else:
            ctx['encoding'] = encoding = self.encoding
        ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
        self.add_sidebars(pagename, ctx)
        ctx.update(addctx)

        newtmpl = self.app.emit_firstresult('html-page-context', pagename,
                                            templatename, ctx, event_arg)
        if newtmpl:
            templatename = newtmpl

        try:
            output = self.templates.render(templatename, ctx)
        except UnicodeError:
            self.warn("a Unicode error occurred when rendering the page %s. "
                      "Please make sure all config values that contain "
                      "non-ASCII content are Unicode strings." % pagename)
            return

        if not outfilename:
            outfilename = self.get_outfilename(pagename)
        # outfilename's path is in general different from self.outdir
        ensuredir(path.dirname(outfilename))
        try:
            f = codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace')
            try:
                f.write(output)
            finally:
                f.close()
        except (IOError, OSError) as err:
            self.warn("error writing file %s: %s" % (outfilename, err))
        if self.copysource and ctx.get('sourcename'):
            # copy the source file for the "show source" link
            source_name = path.join(self.outdir, '_sources',
                                    os_path(ctx['sourcename']))
            ensuredir(path.dirname(source_name))
            copyfile(self.env.doc2path(pagename), source_name)

    def handle_finish(self):
        if self.indexer:
            self.finish_tasks.add_task(self.dump_search_index)
        self.finish_tasks.add_task(self.dump_inventory)

    def dump_inventory(self):
        self.info(bold('dumping object inventory... '), nonl=True)
        f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb')
        try:
            f.write((u'# Sphinx inventory version 2\n'
                     u'# Project: %s\n'
                     u'# Version: %s\n'
                     u'# The remainder of this file is compressed using zlib.\n'
                     % (self.config.project, self.config.version)).encode('utf-8'))
            compressor = zlib.compressobj(9)
            for domainname, domain in sorted(self.env.domains.items()):
                for name, dispname, type, docname, anchor, prio in \
                        sorted(domain.get_objects()):
                    if anchor.endswith(name):
                        # this can shorten the inventory by as much as 25%
                        anchor = anchor[:-len(name)] + '$'
                    uri = self.get_target_uri(docname)
                    if anchor:
                        uri += '#' + anchor
                    if dispname == name:
                        dispname = u'-'
                    f.write(compressor.compress(
                        (u'%s %s:%s %s %s %s\n' % (name, domainname, type,
                                                   prio, uri, dispname)).encode('utf-8')))
            f.write(compressor.flush())
        finally:
            f.close()
        self.info('done')

    def dump_search_index(self):
        self.info(
            bold('dumping search index in %s ... ' % self.indexer.label()),
            nonl=True)
        self.indexer.prune(self.env.all_docs)
        searchindexfn = path.join(self.outdir, self.searchindex_filename)
        # first write to a temporary file, so that if dumping fails,
        # the existing index won't be overwritten
        if self.indexer_dumps_unicode:
            f = codecs.open(searchindexfn + '.tmp', 'w', encoding='utf-8')
        else:
            f = open(searchindexfn + '.tmp', 'wb')
        try:
            self.indexer.dump(f, self.indexer_format)
        finally:
            f.close()
        movefile(searchindexfn + '.tmp', searchindexfn)
        self.info('done')
예제 #48
0
파일: __init__.py 프로젝트: nwf/sphinx
    def read_doc(self, docname, app=None):
        # type: (unicode, Sphinx) -> None
        """Parse a file and add/update inventory entries for the doctree."""

        self.temp_data['docname'] = docname
        # defaults to the global default, but can be re-set in a document
        self.temp_data['default_domain'] = \
            self.domains.get(self.config.primary_domain)

        self.settings['input_encoding'] = self.config.source_encoding
        self.settings['trim_footnote_reference_space'] = \
            self.config.trim_footnote_reference_space
        self.settings['gettext_compact'] = self.config.gettext_compact

        docutilsconf = path.join(self.srcdir, 'docutils.conf')
        # read docutils.conf from source dir, not from current dir
        OptionParser.standard_config_files[1] = docutilsconf
        if path.isfile(docutilsconf):
            self.note_dependency(docutilsconf)

        with sphinx_domains(self):
            if self.config.default_role:
                role_fn, messages = roles.role(self.config.default_role, english,
                                               0, dummy_reporter)
                if role_fn:
                    roles._roles[''] = role_fn
                else:
                    logger.warning('default role %s not found', self.config.default_role,
                                   location=docname)

            codecs.register_error('sphinx', self.warn_and_replace)  # type: ignore

            # publish manually
            reader = SphinxStandaloneReader(self.app, parsers=self.config.source_parsers)
            pub = Publisher(reader=reader,
                            writer=SphinxDummyWriter(),
                            destination_class=NullOutput)
            pub.set_components(None, 'restructuredtext', None)
            pub.process_programmatic_settings(None, self.settings, None)
            src_path = self.doc2path(docname)
            source = SphinxFileInput(app, self, source=None, source_path=src_path,
                                     encoding=self.config.source_encoding)
            pub.source = source
            pub.settings._source = src_path
            pub.set_destination(None, None)
            pub.publish()
            doctree = pub.document

        # post-processing
        for domain in itervalues(self.domains):
            domain.process_doc(self, docname, doctree)

        # allow extension-specific post-processing
        if app:
            app.emit('doctree-read', doctree)

        # store time of reading, for outdated files detection
        # (Some filesystems have coarse timestamp resolution;
        # therefore time.time() can be older than filesystem's timestamp.
        # For example, FAT32 has 2sec timestamp resolution.)
        self.all_docs[docname] = max(
            time.time(), path.getmtime(self.doc2path(docname)))

        if self.versioning_condition:
            old_doctree = None
            if self.versioning_compare:
                # get old doctree
                try:
                    with open(self.doc2path(docname,
                                            self.doctreedir, '.doctree'), 'rb') as f:
                        old_doctree = pickle.load(f)
                except EnvironmentError:
                    pass

            # add uids for versioning
            if not self.versioning_compare or old_doctree is None:
                list(add_uids(doctree, self.versioning_condition))
            else:
                list(merge_doctrees(
                    old_doctree, doctree, self.versioning_condition))

        # make it picklable
        doctree.reporter = None
        doctree.transformer = None
        doctree.settings.warning_stream = None
        doctree.settings.env = None
        doctree.settings.record_dependencies = None

        # cleanup
        self.temp_data.clear()
        self.ref_context.clear()
        roles._roles.pop('', None)  # if a document has set a local default role

        # save the parsed doctree
        doctree_filename = self.doc2path(docname, self.doctreedir,
                                         '.doctree')
        ensuredir(path.dirname(doctree_filename))
        with open(doctree_filename, 'wb') as f:
            pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL)
예제 #49
0
class StandaloneHTMLBuilder(Builder):
    """
    Builds standalone HTML docs.
    """
    name = 'html'
    format = 'html'
    copysource = True
    allow_parallel = True
    out_suffix = '.html'
    link_suffix = '.html'  # defaults to matching out_suffix
    indexer_format = js_index
    indexer_dumps_unicode = True
    supported_image_types = [
        'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'
    ]
    searchindex_filename = 'searchindex.js'
    add_permalinks = True
    embedded = False  # for things like HTML help or Qt help: suppresses sidebar

    # This is a class attribute because it is mutated by Sphinx.add_javascript.
    script_files = [
        '_static/jquery.js', '_static/underscore.js', '_static/doctools.js'
    ]
    # Dito for this one.
    css_files = []

    default_sidebars = [
        'localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'
    ]

    # cached publisher object for snippets
    _publisher = None

    def init(self):
        # a hash of all config values that, if changed, cause a full rebuild
        self.config_hash = ''
        self.tags_hash = ''
        # section numbers for headings in the currently visited document
        self.secnumbers = {}
        # currently written docname
        self.current_docname = None

        self.init_templates()
        self.init_highlighter()
        self.init_translator_class()
        if self.config.html_file_suffix is not None:
            self.out_suffix = self.config.html_file_suffix

        if self.config.html_link_suffix is not None:
            self.link_suffix = self.config.html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        if self.config.language is not None:
            if self._get_translations_js():
                self.script_files.append('_static/translations.js')

    def _get_translations_js(self):
        candidates = [path.join(package_dir, 'locale', self.config.language,
                                'LC_MESSAGES', 'sphinx.js'),
                      path.join(sys.prefix, 'share/sphinx/locale',
                                self.config.language, 'sphinx.js')] + \
                     [path.join(dir, self.config.language,
                                'LC_MESSAGES', 'sphinx.js')
                      for dir in self.config.locale_dirs]
        for jsfile in candidates:
            if path.isfile(jsfile):
                return jsfile
        return None

    def get_theme_config(self):
        return self.config.html_theme, self.config.html_theme_options

    def init_templates(self):
        Theme.init_themes(self.confdir,
                          self.config.html_theme_path,
                          warn=self.warn)
        themename, themeoptions = self.get_theme_config()
        self.theme = Theme(themename)
        self.theme_options = themeoptions.copy()
        self.create_template_bridge()
        self.templates.init(self, self.theme)

    def init_highlighter(self):
        # determine Pygments style and create the highlighter
        if self.config.pygments_style is not None:
            style = self.config.pygments_style
        elif self.theme:
            style = self.theme.get_confstr('theme', 'pygments_style', 'none')
        else:
            style = 'sphinx'
        self.highlighter = PygmentsBridge('html', style,
                                          self.config.trim_doctest_flags)

    def init_translator_class(self):
        if self.config.html_translator_class:
            self.translator_class = self.app.import_object(
                self.config.html_translator_class,
                'html_translator_class setting')
        elif self.config.html_use_smartypants:
            self.translator_class = SmartyPantsHTMLTranslator
        else:
            self.translator_class = HTMLTranslator

    def get_outdated_docs(self):
        cfgdict = dict((name, self.config[name])
                       for (name, desc) in self.config.values.items()
                       if desc[1] == 'html')
        self.config_hash = get_stable_hash(cfgdict)
        self.tags_hash = get_stable_hash(sorted(self.tags))
        old_config_hash = old_tags_hash = ''
        try:
            fp = open(path.join(self.outdir, '.buildinfo'))
            try:
                version = fp.readline()
                if version.rstrip() != '# Sphinx build info version 1':
                    raise ValueError
                fp.readline()  # skip commentary
                cfg, old_config_hash = fp.readline().strip().split(': ')
                if cfg != 'config':
                    raise ValueError
                tag, old_tags_hash = fp.readline().strip().split(': ')
                if tag != 'tags':
                    raise ValueError
            finally:
                fp.close()
        except ValueError:
            self.warn('unsupported build info format in %r, building all' %
                      path.join(self.outdir, '.buildinfo'))
        except Exception:
            pass
        if old_config_hash != self.config_hash or \
               old_tags_hash != self.tags_hash:
            for docname in self.env.found_docs:
                yield docname
            return

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)),
                               template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

    def render_partial(self, node):
        """Utility: Render a lone doctree node."""
        if node is None:
            return {'fragment': ''}
        doc = new_document(b('<partial node>'))
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(source_class=DocTreeInput,
                                        destination_class=StringOutput)
            self._publisher.set_components('standalone', 'restructuredtext',
                                           'pseudoxml')

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = HTMLWriter(self)
        pub.process_programmatic_settings(None, {'output_encoding': 'unicode'},
                                          None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        # create the search indexer
        from sphinx.search import IndexBuilder, languages
        lang = self.config.html_search_language or self.config.language
        if not lang or lang not in languages:
            lang = 'en'
        self.indexer = IndexBuilder(self.env, lang,
                                    self.config.html_search_options,
                                    self.config.html_search_scorer)
        self.load_indexer(docnames)

        self.docwriter = HTMLWriter(self)
        self.docsettings = OptionParser(
            defaults=self.env.settings,
            components=(self.docwriter, ),
            read_config_files=True).get_default_values()
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain in self.env.domains.values():
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == 'py-modindex' and \
                           not self.config.html_use_modindex:
                        continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append(
                            (indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = ustrftime(lufmt or _('%b %d, %Y'))
        else:
            self.last_updated = None

        logo = self.config.html_logo and \
               path.basename(self.config.html_logo) or ''

        favicon = self.config.html_favicon and \
                  path.basename(self.config.html_favicon) or ''
        if favicon and os.path.splitext(favicon)[1] != '.ico':
            self.warn('html_favicon is not an .ico file')

        if not isinstance(self.config.html_use_opensearch, str):
            self.warn('html_use_opensearch config value must now be a string')

        self.relations = self.env.collect_relations()

        rellinks = []
        if self.get_builder_config('use_index', 'html'):
            rellinks.append(('genindex', _('General Index'), 'I', _('index')))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append(
                    (indexname, indexcls.localname, '', indexcls.shortname))

        if self.config.html_style is not None:
            stylename = self.config.html_style
        elif self.theme:
            stylename = self.theme.get_confstr('theme', 'stylesheet')
        else:
            stylename = 'default.css'

        self.globalcontext = dict(
            embedded=self.embedded,
            project=self.config.project,
            release=self.config.release,
            version=self.config.version,
            last_updated=self.last_updated,
            copyright=self.config.copyright,
            master_doc=self.config.master_doc,
            use_opensearch=self.config.html_use_opensearch,
            docstitle=self.config.html_title,
            shorttitle=self.config.html_short_title,
            show_copyright=self.config.html_show_copyright,
            show_sphinx=self.config.html_show_sphinx,
            has_source=self.config.html_copy_source,
            show_source=self.config.html_show_sourcelink,
            file_suffix=self.out_suffix,
            script_files=self.script_files,
            css_files=self.css_files,
            sphinx_version=__version__,
            style=stylename,
            rellinks=rellinks,
            builder=self.name,
            parents=[],
            logo=logo,
            favicon=favicon,
        )
        if self.theme:
            self.globalcontext.update(('theme_' + key, val) for (
                key,
                val) in self.theme.get_options(self.theme_options).items())
        self.globalcontext.update(self.config.html_context)

    def get_doc_context(self, docname, body, metatags):
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext['rellinks'][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    'link': self.get_relative_uri(docname, related[2]),
                    'title': self.render_partial(titles[related[2]])['title']
                }
                rellinks.append((related[2], next['title'], 'N', _('next')))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    'link': self.get_relative_uri(docname, related[1]),
                    'title': self.render_partial(titles[related[1]])['title']
                }
                rellinks.append(
                    (related[1], prev['title'], 'P', _('previous')))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append({
                    'link':
                    self.get_relative_uri(docname, related[0]),
                    'title':
                    self.render_partial(titles[related[0]])['title']
                })
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            parents.pop()  # remove link to the master file; we have a generic
            # "back to index" link already
        parents.reverse()

        # title rendered as HTML
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        # the name for the copied source
        sourcename = self.config.html_copy_source and docname + '.txt' or ''

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # local TOC and global TOC tree
        self_toc = self.env.get_toc_for(docname, self)
        toc = self.render_partial(self_toc)['fragment']

        return dict(
            parents=parents,
            prev=prev,
            next=next,
            title=title,
            meta=meta,
            body=body,
            metatags=metatags,
            rellinks=rellinks,
            sourcename=sourcename,
            toc=toc,
            # only display a TOC if there's more than one item to show
            display_toc=(self.env.toc_num_entries[docname] > 1),
        )

    def write_doc(self, docname, doctree):
        destination = StringOutput(encoding='utf-8')
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts['fragment']
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.post_process_images(doctree)
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        self.index_page(docname, doctree, title)

    def finish(self):
        self.info(bold('writing additional files...'), nonl=1)

        # pages from extensions
        for pagelist in self.app.emit('html-collect-pages'):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        # the global general index
        if self.get_builder_config('use_index', 'html'):
            self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        # the search page
        if self.name != 'htmlhelp':
            self.info(' search', nonl=1)
            self.handle_page('search', {}, 'search.html')

        # additional pages from conf.py
        for pagename, template in list(
                self.config.html_additional_pages.items()):
            self.info(' ' + pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        if self.config.html_use_opensearch and self.name != 'htmlhelp':
            self.info(' opensearch', nonl=1)
            fn = path.join(self.outdir, '_static', 'opensearch.xml')
            self.handle_page('opensearch', {},
                             'opensearch.xml',
                             outfilename=fn)

        self.info()

        self.copy_image_files()
        self.copy_download_files()
        self.copy_static_files()
        self.copy_extra_files()
        self.write_buildinfo()

        # dump the search index
        self.handle_finish()

    def write_genindex(self):
        # the total count of lines for each index letter, used to distribute
        # the entries into two columns
        genindex = self.env.create_index(self)
        indexcounts = []
        for _, entries in genindex:
            indexcounts.append(
                sum(1 + len(subitems) for _, (_, subitems) in entries))

        genindexcontext = dict(
            genindexentries=genindex,
            genindexcounts=indexcounts,
            split_index=self.config.html_split_index,
        )
        self.info(' genindex', nonl=1)

        if self.config.html_split_index:
            self.handle_page('genindex', genindexcontext,
                             'genindex-split.html')
            self.handle_page('genindex-all', genindexcontext, 'genindex.html')
            for (key, entries), count in zip(genindex, indexcounts):
                ctx = {
                    'key': key,
                    'entries': entries,
                    'count': count,
                    'genindexentries': genindex
                }
                self.handle_page('genindex-' + key, ctx,
                                 'genindex-single.html')
        else:
            self.handle_page('genindex', genindexcontext, 'genindex.html')

    def write_domain_indices(self):
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = dict(
                indextitle=indexcls.localname,
                content=content,
                collapse_index=collapse,
            )
            self.info(' ' + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, 'domainindex.html')

    def copy_image_files(self):
        # copy image files
        if self.images:
            ensuredir(path.join(self.outdir, '_images'))
            for src in self.status_iterator(self.images, 'copying images... ',
                                            brown, len(self.images)):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, '_images', dest))
                except Exception as err:
                    self.warn('cannot copy image file %r: %s' %
                              (path.join(self.srcdir, src), err))

    def copy_download_files(self):
        # copy downloadable files
        if self.env.dlfiles:
            ensuredir(path.join(self.outdir, '_downloads'))
            for src in self.status_iterator(self.env.dlfiles,
                                            'copying downloadable files... ',
                                            brown, len(self.env.dlfiles)):
                dest = self.env.dlfiles[src][1]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, '_downloads', dest))
                except Exception as err:
                    self.warn('cannot copy downloadable file %r: %s' %
                              (path.join(self.srcdir, src), err))

    def copy_static_files(self):
        # copy static files
        self.info(bold('copying static files... '), nonl=True)
        ensuredir(path.join(self.outdir, '_static'))
        # first, create pygments style file
        f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w')
        f.write(self.highlighter.get_stylesheet())
        f.close()
        # then, copy translations JavaScript file
        if self.config.language is not None:
            jsfile = self._get_translations_js()
            if jsfile:
                copyfile(jsfile,
                         path.join(self.outdir, '_static', 'translations.js'))

        # add context items for search function used in searchtools.js_t
        ctx = self.globalcontext.copy()
        ctx.update(self.indexer.context_for_searchtool())

        # then, copy over theme-supplied static files
        if self.theme:
            themeentries = [
                path.join(themepath, 'static')
                for themepath in self.theme.get_dirchain()[::-1]
            ]
            for entry in themeentries:
                copy_static_entry(entry, path.join(self.outdir, '_static'),
                                  self, ctx)
        # then, copy over all user-supplied static files
        staticentries = [
            path.join(self.confdir, spath)
            for spath in self.config.html_static_path
        ]
        matchers = compile_matchers(
            self.config.exclude_patterns +
            ['**/' + d for d in self.config.exclude_dirnames])
        for entry in staticentries:
            if not path.exists(entry):
                self.warn('html_static_path entry %r does not exist' % entry)
                continue
            copy_static_entry(entry,
                              path.join(self.outdir, '_static'),
                              self,
                              ctx,
                              exclude_matchers=matchers)
        # copy logo and favicon files if not already in static path
        if self.config.html_logo:
            logobase = path.basename(self.config.html_logo)
            logotarget = path.join(self.outdir, '_static', logobase)
            if not path.isfile(logotarget):
                copyfile(path.join(self.confdir, self.config.html_logo),
                         logotarget)
        if self.config.html_favicon:
            iconbase = path.basename(self.config.html_favicon)
            icontarget = path.join(self.outdir, '_static', iconbase)
            if not path.isfile(icontarget):
                copyfile(path.join(self.confdir, self.config.html_favicon),
                         icontarget)
        self.info('done')

    def copy_extra_files(self):
        # copy html_extra_path files
        self.info(bold('copying extra files... '), nonl=True)
        extraentries = [
            path.join(self.confdir, epath)
            for epath in self.config.html_extra_path
        ]
        for entry in extraentries:
            if not path.exists(entry):
                self.warn('html_extra_path entry %r does not exist' % entry)
                continue
            copy_static_entry(entry, self.outdir, self)

    def write_buildinfo(self):
        # write build info file
        fp = open(path.join(self.outdir, '.buildinfo'), 'w')
        try:
            fp.write('# Sphinx build info version 1\n'
                     '# This file hashes the configuration used when building'
                     ' these files. When it is not found, a full rebuild will'
                     ' be done.\nconfig: %s\ntags: %s\n' %
                     (self.config_hash, self.tags_hash))
        finally:
            fp.close()

    def cleanup(self):
        # clean up theme stuff
        if self.theme:
            self.theme.cleanup()

    def post_process_images(self, doctree):
        """Pick the best candidate for an image and link down-scaled images to
        their high res version.
        """
        Builder.post_process_images(self, doctree)
        for node in doctree.traverse(nodes.image):
            scale_keys = ('scale', 'width', 'height')
            if not any((key in node) for key in scale_keys) or \
               isinstance(node.parent, nodes.reference):
                # docutils does unfortunately not preserve the
                # ``target`` attribute on images, so we need to check
                # the parent node here.
                continue
            uri = node['uri']
            reference = nodes.reference('', '', internal=True)
            if uri in self.images:
                reference['refuri'] = posixpath.join(self.imgpath,
                                                     self.images[uri])
            else:
                reference['refuri'] = uri
            node.replace_self(reference)
            reference.append(node)

    def load_indexer(self, docnames):
        keep = set(self.env.all_docs) - set(docnames)
        try:
            searchindexfn = path.join(self.outdir, self.searchindex_filename)
            if self.indexer_dumps_unicode:
                f = codecs.open(searchindexfn, 'r', encoding='utf-8')
            else:
                f = open(searchindexfn, 'rb')
            try:
                self.indexer.load(f, self.indexer_format)
            finally:
                f.close()
        except (IOError, OSError, ValueError):
            if keep:
                self.warn('search index couldn\'t be loaded, but not all '
                          'documents will be built: the index will be '
                          'incomplete.')
        # delete all entries for files that will be rebuilt
        self.indexer.prune(keep)

    def index_page(self, pagename, doctree, title):
        # only index pages with title
        if self.indexer is not None and title:
            self.indexer.feed(pagename, title, doctree)

    def _get_local_toctree(self, docname, collapse=True, **kwds):
        if 'includehidden' not in kwds:
            kwds['includehidden'] = False
        return self.render_partial(
            self.env.get_toctree_for(docname, self, collapse,
                                     **kwds))['fragment']

    def get_outfilename(self, pagename):
        return path.join(self.outdir, os_path(pagename) + self.out_suffix)

    def add_sidebars(self, pagename, ctx):
        def has_wildcard(pattern):
            return any(char in pattern for char in '*?[')

        sidebars = None
        matched = None
        customsidebar = None
        for pattern, patsidebars in self.config.html_sidebars.items():
            if patmatch(pagename, pattern):
                if matched:
                    if has_wildcard(pattern):
                        # warn if both patterns contain wildcards
                        if has_wildcard(matched):
                            self.warn('page %s matches two patterns in '
                                      'html_sidebars: %r and %r' %
                                      (pagename, matched, pattern))
                        # else the already matched pattern is more specific
                        # than the present one, because it contains no wildcard
                        continue
                matched = pattern
                sidebars = patsidebars
        if sidebars is None:
            # keep defaults
            pass
        elif isinstance(sidebars, str):
            # 0.x compatible mode: insert custom sidebar before searchbox
            customsidebar = sidebars
            sidebars = None
        ctx['sidebars'] = sidebars
        ctx['customsidebar'] = customsidebar

    # --------- these are overwritten by the serialization builder

    def get_target_uri(self, docname, typ=None):
        return docname + self.link_suffix

    def handle_page(self,
                    pagename,
                    addctx,
                    templatename='page.html',
                    outfilename=None,
                    event_arg=None):
        ctx = self.globalcontext.copy()
        # current_page_name is backwards compatibility
        ctx['pagename'] = ctx['current_page_name'] = pagename
        default_baseuri = self.get_target_uri(pagename)
        # in the singlehtml builder, default_baseuri still contains an #anchor
        # part, which relative_uri doesn't really like...
        default_baseuri = default_baseuri.rsplit('#', 1)[0]

        def pathto(otheruri, resource=False, baseuri=default_baseuri):
            if resource and '://' in otheruri:
                # allow non-local resources given by scheme
                return otheruri
            elif not resource:
                otheruri = self.get_target_uri(otheruri)
            uri = relative_uri(baseuri, otheruri) or '#'
            return uri

        ctx['pathto'] = pathto
        ctx['hasdoc'] = lambda name: name in self.env.all_docs
        if self.name != 'htmlhelp':
            ctx['encoding'] = encoding = self.config.html_output_encoding
        else:
            ctx['encoding'] = encoding = self.encoding
        ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
        self.add_sidebars(pagename, ctx)
        ctx.update(addctx)

        self.app.emit('html-page-context', pagename, templatename, ctx,
                      event_arg)

        try:
            output = self.templates.render(templatename, ctx)
        except UnicodeError:
            self.warn("a Unicode error occurred when rendering the page %s. "
                      "Please make sure all config values that contain "
                      "non-ASCII content are Unicode strings." % pagename)
            return

        if not outfilename:
            outfilename = self.get_outfilename(pagename)
        # outfilename's path is in general different from self.outdir
        ensuredir(path.dirname(outfilename))
        try:
            f = codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace')
            try:
                f.write(output)
            finally:
                f.close()
        except (IOError, OSError) as err:
            self.warn("error writing file %s: %s" % (outfilename, err))
        if self.copysource and ctx.get('sourcename'):
            # copy the source file for the "show source" link
            source_name = path.join(self.outdir, '_sources',
                                    os_path(ctx['sourcename']))
            ensuredir(path.dirname(source_name))
            copyfile(self.env.doc2path(pagename), source_name)

    def handle_finish(self):
        self.dump_search_index()
        self.dump_inventory()

    def dump_inventory(self):
        self.info(bold('dumping object inventory... '), nonl=True)
        f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb')
        try:
            f.write(
                ('# Sphinx inventory version 2\n'
                 '# Project: %s\n'
                 '# Version: %s\n'
                 '# The remainder of this file is compressed using zlib.\n' %
                 (self.config.project, self.config.version)).encode('utf-8'))
            compressor = zlib.compressobj(9)
            for domainname, domain in self.env.domains.items():
                for name, dispname, type, docname, anchor, prio in \
                        domain.get_objects():
                    if anchor.endswith(name):
                        # this can shorten the inventory by as much as 25%
                        anchor = anchor[:-len(name)] + '$'
                    uri = self.get_target_uri(docname) + '#' + anchor
                    if dispname == name:
                        dispname = '-'
                    f.write(
                        compressor.compress(('%s %s:%s %s %s %s\n' %
                                             (name, domainname, type, prio,
                                              uri, dispname)).encode('utf-8')))
            f.write(compressor.flush())
        finally:
            f.close()
        self.info('done')

    def dump_search_index(self):
        self.info(bold('dumping search index... '), nonl=True)
        self.indexer.prune(self.env.all_docs)
        searchindexfn = path.join(self.outdir, self.searchindex_filename)
        # first write to a temporary file, so that if dumping fails,
        # the existing index won't be overwritten
        if self.indexer_dumps_unicode:
            f = codecs.open(searchindexfn + '.tmp', 'w', encoding='utf-8')
        else:
            f = open(searchindexfn + '.tmp', 'wb')
        try:
            self.indexer.dump(f, self.indexer_format)
        finally:
            f.close()
        movefile(searchindexfn + '.tmp', searchindexfn)
        self.info('done')
예제 #50
0
class StandaloneHTMLBuilder(Builder):
    """
    Builds standalone HTML docs.
    """
    name = 'html'
    format = 'html'
    copysource = True
    allow_parallel = True
    out_suffix = '.html'
    link_suffix = '.html'  # defaults to matching out_suffix
    indexer_format = js_index
    indexer_dumps_unicode = True
    supported_image_types = ['image/svg+xml', 'image/png',
                             'image/gif', 'image/jpeg']
    searchindex_filename = 'searchindex.js'
    add_permalinks = True
    embedded = False  # for things like HTML help or Qt help: suppresses sidebar

    # This is a class attribute because it is mutated by Sphinx.add_javascript.
    script_files = ['_static/jquery.js', '_static/underscore.js',
                    '_static/doctools.js']
    # Dito for this one.
    css_files = []

    default_sidebars = ['localtoc.html', 'relations.html',
                        'sourcelink.html', 'searchbox.html']

    # cached publisher object for snippets
    _publisher = None

    def init(self):
        # a hash of all config values that, if changed, cause a full rebuild
        self.config_hash = ''
        self.tags_hash = ''
        # section numbers for headings in the currently visited document
        self.secnumbers = {}
        # currently written docname
        self.current_docname = None

        self.init_templates()
        self.init_highlighter()
        self.init_translator_class()
        if self.config.html_file_suffix is not None:
            self.out_suffix = self.config.html_file_suffix

        if self.config.html_link_suffix is not None:
            self.link_suffix = self.config.html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        if self.config.language is not None:
            if self._get_translations_js():
                self.script_files.append('_static/translations.js')

    def _get_translations_js(self):
        candidates = [path.join(package_dir, 'locale', self.config.language,
                                'LC_MESSAGES', 'sphinx.js'),
                      path.join(sys.prefix, 'share/sphinx/locale',
                                self.config.language, 'sphinx.js')] + \
                     [path.join(dir, self.config.language,
                                'LC_MESSAGES', 'sphinx.js')
                      for dir in self.config.locale_dirs]
        for jsfile in candidates:
            if path.isfile(jsfile):
                return jsfile
        return None

    def get_theme_config(self):
        return self.config.html_theme, self.config.html_theme_options

    def init_templates(self):
        Theme.init_themes(self.confdir, self.config.html_theme_path,
                          warn=self.warn)
        themename, themeoptions = self.get_theme_config()
        self.theme = Theme(themename)
        self.theme_options = themeoptions.copy()
        self.create_template_bridge()
        self.templates.init(self, self.theme)

    def init_highlighter(self):
        # determine Pygments style and create the highlighter
        if self.config.pygments_style is not None:
            style = self.config.pygments_style
        elif self.theme:
            style = self.theme.get_confstr('theme', 'pygments_style', 'none')
        else:
            style = 'sphinx'
        self.highlighter = PygmentsBridge('html', style,
                                          self.config.trim_doctest_flags)

    def init_translator_class(self):
        if self.config.html_translator_class:
            self.translator_class = self.app.import_object(
                self.config.html_translator_class,
                'html_translator_class setting')
        elif self.config.html_use_smartypants:
            self.translator_class = SmartyPantsHTMLTranslator
        else:
            self.translator_class = HTMLTranslator

    def get_outdated_docs(self):
        cfgdict = dict((name, self.config[name])
                       for (name, desc) in self.config.values.iteritems()
                       if desc[1] == 'html')
        self.config_hash = get_stable_hash(cfgdict)
        self.tags_hash = get_stable_hash(sorted(self.tags))
        old_config_hash = old_tags_hash = ''
        try:
            fp = open(path.join(self.outdir, '.buildinfo'))
            try:
                version = fp.readline()
                if version.rstrip() != '# Sphinx build info version 1':
                    raise ValueError
                fp.readline()  # skip commentary
                cfg, old_config_hash = fp.readline().strip().split(': ')
                if cfg != 'config':
                    raise ValueError
                tag, old_tags_hash = fp.readline().strip().split(': ')
                if tag != 'tags':
                    raise ValueError
            finally:
                fp.close()
        except ValueError:
            self.warn('unsupported build info format in %r, building all' %
                      path.join(self.outdir, '.buildinfo'))
        except Exception:
            pass
        if old_config_hash != self.config_hash or \
               old_tags_hash != self.tags_hash:
            for docname in self.env.found_docs:
                yield docname
            return

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)),
                               template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

    def render_partial(self, node):
        """Utility: Render a lone doctree node."""
        if node is None:
            return {'fragment': ''}
        doc = new_document(b('<partial node>'))
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(
                    source_class = DocTreeInput,
                    destination_class=StringOutput)
            self._publisher.set_components('standalone',
                                           'restructuredtext', 'pseudoxml')

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = HTMLWriter(self)
        pub.process_programmatic_settings(
            None, {'output_encoding': 'unicode'}, None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        # create the search indexer
        from sphinx.search import IndexBuilder, languages
        lang = self.config.html_search_language or self.config.language
        if not lang or lang not in languages:
            lang = 'en'
        self.indexer = IndexBuilder(self.env, lang,
                                    self.config.html_search_options,
                                    self.config.html_search_scorer)
        self.load_indexer(docnames)

        self.docwriter = HTMLWriter(self)
        self.docsettings = OptionParser(
            defaults=self.env.settings,
            components=(self.docwriter,),
            read_config_files=True).get_default_values()
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain in self.env.domains.itervalues():
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == 'py-modindex' and \
                           not self.config.html_use_modindex:
                        continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append(
                            (indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = ustrftime(lufmt or _('%b %d, %Y'))
        else:
            self.last_updated = None

        logo = self.config.html_logo and \
               path.basename(self.config.html_logo) or ''

        favicon = self.config.html_favicon and \
                  path.basename(self.config.html_favicon) or ''
        if favicon and os.path.splitext(favicon)[1] != '.ico':
            self.warn('html_favicon is not an .ico file')

        if not isinstance(self.config.html_use_opensearch, basestring):
            self.warn('html_use_opensearch config value must now be a string')

        self.relations = self.env.collect_relations()

        rellinks = []
        if self.get_builder_config('use_index', 'html'):
            rellinks.append(('genindex', _('General Index'), 'I', _('index')))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append((indexname, indexcls.localname,
                                 '', indexcls.shortname))

        if self.config.html_style is not None:
            stylename = self.config.html_style
        elif self.theme:
            stylename = self.theme.get_confstr('theme', 'stylesheet')
        else:
            stylename = 'default.css'

        self.globalcontext = dict(
            embedded = self.embedded,
            project = self.config.project,
            release = self.config.release,
            version = self.config.version,
            last_updated = self.last_updated,
            copyright = self.config.copyright,
            master_doc = self.config.master_doc,
            use_opensearch = self.config.html_use_opensearch,
            docstitle = self.config.html_title,
            shorttitle = self.config.html_short_title,
            show_copyright = self.config.html_show_copyright,
            show_sphinx = self.config.html_show_sphinx,
            has_source = self.config.html_copy_source,
            show_source = self.config.html_show_sourcelink,
            file_suffix = self.out_suffix,
            script_files = self.script_files,
            css_files = self.css_files,
            sphinx_version = __version__,
            style = stylename,
            rellinks = rellinks,
            builder = self.name,
            parents = [],
            logo = logo,
            favicon = favicon,
        )
        if self.theme:
            self.globalcontext.update(
                ('theme_' + key, val) for (key, val) in
                self.theme.get_options(self.theme_options).iteritems())
        self.globalcontext.update(self.config.html_context)

    def get_doc_context(self, docname, body, metatags):
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext['rellinks'][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    'link': self.get_relative_uri(docname, related[2]),
                    'title': self.render_partial(titles[related[2]])['title']
                }
                rellinks.append((related[2], next['title'], 'N', _('next')))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    'link': self.get_relative_uri(docname, related[1]),
                    'title': self.render_partial(titles[related[1]])['title']
                }
                rellinks.append((related[1], prev['title'], 'P', _('previous')))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append(
                    {'link': self.get_relative_uri(docname, related[0]),
                     'title': self.render_partial(titles[related[0]])['title']})
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            parents.pop() # remove link to the master file; we have a generic
                          # "back to index" link already
        parents.reverse()

        # title rendered as HTML
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        # the name for the copied source
        sourcename = self.config.html_copy_source and docname + '.txt' or ''

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # local TOC and global TOC tree
        self_toc = self.env.get_toc_for(docname, self)
        toc = self.render_partial(self_toc)['fragment']

        return dict(
            parents = parents,
            prev = prev,
            next = next,
            title = title,
            meta = meta,
            body = body,
            metatags = metatags,
            rellinks = rellinks,
            sourcename = sourcename,
            toc = toc,
            # only display a TOC if there's more than one item to show
            display_toc = (self.env.toc_num_entries[docname] > 1),
        )

    def write_doc(self, docname, doctree):
        destination = StringOutput(encoding='utf-8')
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts['fragment']
        metatags = self.docwriter.clean_meta

        ctx = self.get_doc_context(docname, body, metatags)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
        self.post_process_images(doctree)
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)['title'] or ''
        self.index_page(docname, doctree, title)

    def finish(self):
        self.info(bold('writing additional files...'), nonl=1)

        # pages from extensions
        for pagelist in self.app.emit('html-collect-pages'):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        # the global general index
        if self.get_builder_config('use_index', 'html'):
            self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        # the search page
        if self.name != 'htmlhelp':
            self.info(' search', nonl=1)
            self.handle_page('search', {}, 'search.html')

        # additional pages from conf.py
        for pagename, template in self.config.html_additional_pages.items():
            self.info(' '+pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        if self.config.html_use_opensearch and self.name != 'htmlhelp':
            self.info(' opensearch', nonl=1)
            fn = path.join(self.outdir, '_static', 'opensearch.xml')
            self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)

        self.info()

        self.copy_image_files()
        self.copy_download_files()
        self.copy_static_files()
        self.copy_extra_files()
        self.write_buildinfo()

        # dump the search index
        self.handle_finish()

    def write_genindex(self):
        # the total count of lines for each index letter, used to distribute
        # the entries into two columns
        genindex = self.env.create_index(self)
        indexcounts = []
        for _, entries in genindex:
            indexcounts.append(sum(1 + len(subitems)
                                   for _, (_, subitems) in entries))

        genindexcontext = dict(
            genindexentries = genindex,
            genindexcounts = indexcounts,
            split_index = self.config.html_split_index,
        )
        self.info(' genindex', nonl=1)

        if self.config.html_split_index:
            self.handle_page('genindex', genindexcontext,
                             'genindex-split.html')
            self.handle_page('genindex-all', genindexcontext,
                             'genindex.html')
            for (key, entries), count in zip(genindex, indexcounts):
                ctx = {'key': key, 'entries': entries, 'count': count,
                       'genindexentries': genindex}
                self.handle_page('genindex-' + key, ctx,
                                 'genindex-single.html')
        else:
            self.handle_page('genindex', genindexcontext, 'genindex.html')

    def write_domain_indices(self):
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = dict(
                indextitle = indexcls.localname,
                content = content,
                collapse_index = collapse,
            )
            self.info(' ' + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, 'domainindex.html')

    def copy_image_files(self):
        # copy image files
        if self.images:
            ensuredir(path.join(self.outdir, '_images'))
            for src in self.status_iterator(self.images, 'copying images... ',
                                            brown, len(self.images)):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src),
                             path.join(self.outdir, '_images', dest))
                except Exception, err:
                    self.warn('cannot copy image file %r: %s' %
                              (path.join(self.srcdir, src), err))
예제 #51
0
파일: util.py 프로젝트: tony/releases
def get_doctree(path, **kwargs):
    """
    Obtain a Sphinx doctree from the RST file at ``path``.

    Performs no Releases-specific processing; this code would, ideally, be in
    Sphinx itself, but things there are pretty tightly coupled. So we wrote
    this.

    Any additional kwargs are passed unmodified into an internal `make_app`
    call.

    :param str path: A relative or absolute file path string.

    :returns:
        A two-tuple of the generated ``sphinx.application.Sphinx`` app and the
        doctree (a ``docutils.document`` object).

    .. versionchanged:: 1.6
        Added support for passing kwargs to `make_app`.
    """
    root, filename = os.path.split(path)
    docname, _ = os.path.splitext(filename)
    # TODO: this only works for top level changelog files (i.e. ones where
    # their dirname is the project/doc root)
    app = make_app(srcdir=root, **kwargs)
    # Create & init a BuildEnvironment. Mm, tasty side effects.
    app._init_env(freshenv=True)
    env = app.env
    # More arity/API changes: Sphinx 1.3/1.4-ish require one to pass in the app
    # obj in BuildEnvironment.update(); modern Sphinx performs that inside
    # Application._init_env() (which we just called above) and so that kwarg is
    # removed from update(). EAFP.
    kwargs = dict(
        config=app.config,
        srcdir=root,
        doctreedir=app.doctreedir,
        app=app,
    )
    try:
        env.update(**kwargs)
    except TypeError:
        # Assume newer Sphinx w/o an app= kwarg
        del kwargs['app']
        env.update(**kwargs)
    # Code taken from sphinx.environment.read_doc; easier to manually call
    # it with a working Environment object, instead of doing more random crap
    # to trick the higher up build system into thinking our single changelog
    # document was "updated".
    env.temp_data['docname'] = docname
    env.app = app
    # NOTE: SphinxStandaloneReader API changed in 1.4 :(
    reader_kwargs = {
        'app': app,
        'parsers': env.config.source_parsers,
    }
    if sphinx.version_info[:2] < (1, 4):
        del reader_kwargs['app']
    # This monkeypatches (!!!) docutils to 'inject' all registered Sphinx
    # domains' roles & so forth. Without this, rendering the doctree lacks
    # almost all Sphinx magic, including things like :ref: and :doc:!
    with sphinx_domains(env):
        reader = SphinxStandaloneReader(**reader_kwargs)
        pub = Publisher(reader=reader,
                        writer=SphinxDummyWriter(),
                        destination_class=NullOutput)
        pub.set_components(None, 'restructuredtext', None)
        pub.process_programmatic_settings(None, env.settings, None)
        # NOTE: docname derived higher up, from our given path
        src_path = env.doc2path(docname)
        source = SphinxFileInput(
            app,
            env,
            source=None,
            source_path=src_path,
            encoding=env.config.source_encoding,
        )
        pub.source = source
        pub.settings._source = src_path
        pub.set_destination(None, None)
        pub.publish()
        return app, pub.document
예제 #52
0
def process_targets(site, logger, source, permalink):
    """Process the target locations in the reST files."""
    site.processing_targets = True
    reader = Reader()
    reader.l_settings = {"source": source}
    with open(source, "r", encoding="utf8") as in_file:
        data = in_file.read()
    pub = Publisher(
        reader=reader,
        parser=None,
        writer=None,
        settings=None,
        source_class=StringInput,
        destination_class=StringOutput,
    )
    pub.set_components(None, "restructuredtext", "html")
    # Reading the file will generate output/errors that we don't care about
    # at this stage. The report_level = 5 means no output
    pub.process_programmatic_settings(settings_spec=None,
                                      settings_overrides={"report_level": 5},
                                      config_section=None)
    pub.set_source(data, None)
    pub.set_destination(None, None)
    pub.publish()
    document = pub.document
    site.processing_targets = False

    # Code based on Sphinx std domain
    for name, is_explicit in document.nametypes.items():
        if not is_explicit:
            continue
        labelid = document.nameids[name]
        if labelid is None:
            continue
        node = document.ids[labelid]
        if node.tagname == "target" and "refid" in node:
            node = document.ids.get(node["refid"])
            labelid = node["names"][0]
        if (node.tagname == "footnote" or "refuri" in node
                or node.tagname.startswith("desc_")):
            continue
        if name in site.ref_targets:
            logger.warn(
                "Duplicate label {dup}, other instance in {other}".format(
                    dup=name, other=site.ref_targets[name][0]))
        site.anon_ref_targets[name] = permalink, labelid

        def clean_astext(node):
            """Like node.astext(), but ignore images.

            Taken from sphinx.util.nodes
            """
            node = node.deepcopy()
            for img in node.traverse(nodes.image):
                img["alt"] = ""
            for raw in node.traverse(nodes.raw):
                raw.parent.remove(raw)
            return node.astext()

        if node.tagname in ("section", "rubric"):
            sectname = clean_astext(node[0])
        else:
            continue
        site.ref_targets[name] = permalink, labelid, sectname
예제 #53
0
class RstPublisher:

    ##############################################

    def __init__(self):

        self._publisher = Publisher(source_class=StringInput,
                                    destination_class=StringOutput)
        self._publisher.set_components('standalone', 'restructuredtext',
                                       'html')
        self._publisher.writer.translator_class = MyHTMLTranslator
        self._publisher.process_programmatic_settings(None, extra_params, None)

    ##############################################

    def init_plugins(self, plugins):

        site.addsitedir(str(PurePath(__file__).parent / 'RstPlugins'))

        for plugin in plugins:
            print('Register {}'.format(plugin))
            plugin = __import__(plugin, globals(), locals(), str('module'))
            plugin.register(self)

    ##############################################

    def register_directive(self, directive_name, directive_class):

        directives.register_directive(directive_name, directive_class)

    ##############################################

    def register_role(self, role_name, role_class):

        roles.register_local_role(role_name, role_class)

    ##############################################

    def register_node(self, node_class, visit, depart):

        print(node_class.__name__, visit, depart)
        setattr(MyHTMLTranslator, 'visit_' + node_class.__name__, visit)
        setattr(MyHTMLTranslator, 'depart_' + node_class.__name__, depart)

    ##############################################

    def publish(self, source, header_level=None, report_level=None):

        settings_overrides = DOCUTILS_RENDERER_SETTINGS.copy()

        if header_level is not None:  # starts from 1
            settings_overrides['initial_header_level'] = header_level
        if report_level is not None:  # starts from 1 too
            settings_overrides['report_level'] = 0  # report_level

        self._publisher.set_source(source=source)
        self._publisher.publish(enable_exit_status=True)
        parts = self._publisher.writer.parts

        return parts[
            'html_body']  # parts['body_pre_docinfo'] + parts['fragment']
예제 #54
0
def lint(content, filepath=None):
    """Lint reStructuredText and return errors

    :param string content: reStructuredText to be linted
    :param string filepath: Optional path to file, this will be returned as the source
    :rtype list: List of errors. Each error will contain a line, source (filepath),
        message (error message), and full message (error message + source lines)
    """
    # Generate a new parser (copying `rst2html.py` flow)
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/tools/rst2html.py
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l348
    pub = Publisher(None, None, None, settings=None)
    pub.set_components('standalone', 'restructuredtext', 'pseudoxml')

    # Configure publisher
    # DEV: We cannot use `process_command_line` since it processes `sys.argv` which is for `rst-lint`, not `docutils`
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l201
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l143
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l118
    settings = pub.get_settings(halt_level=5)
    pub.set_io()

    # Prepare a document to parse on
    # DEV: We avoid the `read` method because when `source` is `None`, it attempts to read from `stdin`.
    #      However, we already know our content.
    # DEV: We create our document without `parse` because we need to attach observer's before parsing
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/readers/__init__.py#l66
    reader = pub.reader
    document = utils.new_document(filepath, settings)

    # Disable stdout
    # TODO: Find a more proper way to do this
    # TODO: We might exit the program if a certain error level is reached
    document.reporter.stream = None

    # Collect errors via an observer
    errors = []

    def error_collector(data):
        # Mutate the data since it was just generated
        data.line = data.get('line')
        data.source = data['source']
        data.level = data['level']
        data.type = data['type']
        data.message = Element.astext(data.children[0])
        data.full_message = Element.astext(data)

        # Save the error
        errors.append(data)

    document.reporter.attach_observer(error_collector)

    # Parse the content (and collect errors)
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/readers/__init__.py#l75
    reader.parser.parse(content, document)

    # Apply transforms (and more collect errors)
    # DEV: We cannot use `apply_transforms` since it has `attach_observer` baked in. We want only our listener.
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/core.py#l195
    # http://repo.or.cz/w/docutils.git/blob/422cede485668203abc01c76ca317578ff634b30:/docutils/docutils/transforms/__init__.py#l159
    document.transformer.populate_from_components(
        (pub.source, pub.reader, pub.reader.parser, pub.writer,
         pub.destination))
    transformer = document.transformer
    while transformer.transforms:
        if not transformer.sorted:
            # Unsorted initially, and whenever a transform is added.
            transformer.transforms.sort()
            transformer.transforms.reverse()
            transformer.sorted = 1
        priority, transform_class, pending, kwargs = transformer.transforms.pop(
        )
        transform = transform_class(transformer.document, startnode=pending)
        transform.apply(**kwargs)
        transformer.applied.append(
            (priority, transform_class, pending, kwargs))
    return errors
예제 #55
0
from docutils.core import publish_cmdline, default_description,\
    publish_doctree,\
        Publisher
from dotmpe.du.ext.writer.formresults import Writer as FormResultsWriter

#publish_cmdline(reader=FormReader(), writer_name='pseudoxml')#writer=xhtmlform.Writer())

source_path  = sys.argv[1]
source = open(source_path)
#doctree = publish_doctree(source, reader=FormReader())#, writer_name='pseudoxml')
#print doctree

pub = Publisher(FormReader(),
        writer=FormResultsWriter(),
        source_class=io.FileInput,
        destination_class=io.StringOutput)#, parser, writer, settings=settings,
#                source_class=source_class,
#                destination_class=destination_class)
pub.set_components(None, 'rst', 'pseudoxml')
pub.process_programmatic_settings(None, None, None)
#    settings_spec, settings_overrides, config_section)
pub.set_source(source, source_path)
pub.set_destination(None, None)#destination, destination_path)
output = pub.publish(enable_exit_status=False)

#print pub.document.form_processor.fields
#print pub.document.form_processor.messages
#print pub.document.form_processor.values
print output

예제 #56
0
    writer_name = 'html'
else:
    writer = myWriter
    writer_name = None

settings = None
settings_spec = settings_spec
settings_overrides = None
config_section = None
enable_exit_status = 1
argv = None
usage = default_usage
description = default_description

pub = Publisher(reader, parser, writer, settings)
pub.set_components(reader_name, parser_name, writer_name)

if pub.settings is None:
    pub.process_command_line(
        argv,
        usage,
        description,
        settings_spec,
        config_section,
        **(settings_overrides or {}))

output = pub.publish(
    argv,
    usage,
    description,
    settings_spec,
예제 #57
0
class MarkdownBuilder(Builder):
    """
    Builds standalone Markdown docs.
    """

    name = "markdown"
    format = "markdown"
    copysource = True
    allow_parallel = True
    out_suffix = ".md"
    link_suffix = ".md"  # defaults to matching out_suffix
    # indexer_format = js_index
    indexer_dumps_unicode = True
    supported_image_types = ["image/svg+xml", "image/png", "image/gif", "image/jpeg"]
    # searchindex_filename = 'searchindex.js'
    add_permalinks = True
    embedded = False  # for things like HTML help or Qt help: suppresses sidebar
    search = False  # for things like HTML help and Apple help: suppress search

    # This is a class attribute because it is mutated by Sphinx.add_javascript.
    # script_files = ['_static/jquery.js', '_static/underscore.js',
    #                '_static/doctools.js']
    # Dito for this one.
    # css_files = []

    # default_sidebars = ['localtoc.html', 'relations.html',
    #                    'sourcelink.html', 'searchbox.html']

    # cached publisher object for snippets
    _publisher = None

    def init(self):
        # a hash of all config values that, if changed, cause a full rebuild
        self.config_hash = ""
        self.tags_hash = ""
        # basename of images directory
        self.imagedir = "_images"
        # section numbers for headings in the currently visited document
        self.secnumbers = {}
        # currently written docname
        self.current_docname = None
        self.translator_class = MarkdownTranslator

        if self.config.html_file_suffix is not None:
            self.out_suffix = self.config.html_file_suffix

        if self.config.html_link_suffix is not None:
            self.link_suffix = self.config.html_link_suffix
        else:
            self.link_suffix = self.out_suffix

        self.create_template_bridge()
        self.templates.init(self, dirs="./")

    def get_target_uri(self, docname, typ=None):
        if docname == "index":
            return ""
        if docname.endswith(SEP + "index"):
            return docname[:-5]  # up to sep
        return docname + SEP

    def get_outfilename(self, pagename):
        if pagename == "index" or pagename.endswith(SEP + "index"):
            outfilename = path.join(self.outdir, os_path(pagename) + self.out_suffix)
        else:
            outfilename = path.join(self.outdir, os_path(pagename), "index" + self.out_suffix)

        return outfilename

    # def _get_translations_js(self):
    #    candidates = [path.join(package_dir, 'locale', self.config.language,
    #                            'LC_MESSAGES', 'sphinx.js'),
    #                  path.join(sys.prefix, 'share/sphinx/locale',
    #                            self.config.language, 'sphinx.js')] + \
    #                 [path.join(dir, self.config.language,
    #                            'LC_MESSAGES', 'sphinx.js')
    #                  for dir in self.config.locale_dirs]
    #    for jsfile in candidates:
    #        if path.isfile(jsfile):
    #            return jsfile
    #    return None

    # def init_translator_class(self):
    #    if self.translator_class is not None:
    #        pass
    #    elif self.config.html_translator_class:
    #        self.translator_class = self.app.import_object(
    #            self.config.html_translator_class,
    #            'html_translator_class setting')
    #    elif self.config.html_use_smartypants:
    #        self.translator_class = SmartyPantsHTMLTranslator
    #    else:
    #       self.translator_class = HTMLTranslator

    def get_outdated_docs(self):
        cfgdict = dict((name, self.config[name]) for (name, desc) in iteritems(self.config.values) if desc[1] == "html")
        self.config_hash = get_stable_hash(cfgdict)
        self.tags_hash = get_stable_hash(sorted(self.tags))
        old_config_hash = old_tags_hash = ""
        try:
            fp = open(path.join(self.outdir, ".buildinfo"))
            try:
                version = fp.readline()
                if version.rstrip() != "# Sphinx build info version 1":
                    raise ValueError
                fp.readline()  # skip commentary
                cfg, old_config_hash = fp.readline().strip().split(": ")
                if cfg != "config":
                    raise ValueError
                tag, old_tags_hash = fp.readline().strip().split(": ")
                if tag != "tags":
                    raise ValueError
            finally:
                fp.close()
        except ValueError:
            self.warn("unsupported build info format in %r, building all" % path.join(self.outdir, ".buildinfo"))
        except Exception:
            pass
        if old_config_hash != self.config_hash or old_tags_hash != self.tags_hash:

            for docname in self.env.found_docs:
                yield docname
            return

        if self.templates:
            template_mtime = self.templates.newest_template_mtime()
        else:
            template_mtime = 0

        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.get_outfilename(docname)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime)
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

    def render_partial(self, node):
        """Utility: Render a lone doctree node."""
        if node is None:
            return {"fragment": ""}
        doc = new_document(b"<partial node>")
        doc.append(node)

        if self._publisher is None:
            self._publisher = Publisher(source_class=DocTreeInput, destination_class=StringOutput)
            self._publisher.set_components("standalone", "restructuredtext", "pseudoxml")

        pub = self._publisher

        pub.reader = DoctreeReader()
        pub.writer = MarkdownWriter(self)
        pub.process_programmatic_settings(None, {}, None)
        # pub.process_programmatic_settings(
        #    None, {'output_encoding': 'unicode'}, None)
        pub.set_source(doc, None)
        pub.set_destination(None, None)
        pub.publish()
        return pub.writer.parts

    def prepare_writing(self, docnames):
        # create the search indexer
        self.indexer = None

        self.docwriter = MarkdownWriter(self)
        self.docsettings = OptionParser(
            defaults=self.env.settings, components=(self.docwriter,), read_config_files=True
        ).get_default_values()
        self.docsettings.compact_lists = bool(self.config.html_compact_lists)

        # determine the additional indices to include
        self.domain_indices = []
        # html_domain_indices can be False/True or a list of index names
        indices_config = self.config.html_domain_indices
        if indices_config:
            for domain_name in sorted(self.env.domains):
                domain = self.env.domains[domain_name]
                for indexcls in domain.indices:
                    indexname = "%s-%s" % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == "py-modindex" and not self.config.html_use_modindex:
                        continue
                    content, collapse = indexcls(domain).generate()
                    if content:
                        self.domain_indices.append((indexname, indexcls, content, collapse))

        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = format_date(lufmt or _("%b %d, %Y"), language=self.config.language, warn=self.warn)
        else:
            self.last_updated = None

        self.relations = self.env.collect_relations()

        rellinks = []
        if self.get_builder_config("use_index", "html"):
            rellinks.append(("genindex", _("General Index"), "I", _("index")))
        for indexname, indexcls, content, collapse in self.domain_indices:
            # if it has a short name
            if indexcls.shortname:
                rellinks.append((indexname, indexcls.localname, "", indexcls.shortname))

        self.globalcontext = dict(
            embedded=self.embedded,
            project=self.config.project,
            release=self.config.release,
            version=self.config.version,
            last_updated=self.last_updated,
            copyright=self.config.copyright,
            master_doc=self.config.master_doc,
            docstitle=self.config.html_title,
            shorttitle=self.config.html_short_title,
            show_copyright=self.config.html_show_copyright,
            show_sphinx=self.config.html_show_sphinx,
            has_source=self.config.html_copy_source,
            show_source=self.config.html_show_sourcelink,
            file_suffix=self.out_suffix,
            language=self.config.language,
            sphinx_version=__display_version__,
            rellinks=rellinks,
            builder=self.name,
            parents=[],
        )

    def get_doc_context(self, docname, body):
        """Collect items for the template context of a page."""
        # find out relations
        prev = next = None
        parents = []
        rellinks = self.globalcontext["rellinks"][:]
        related = self.relations.get(docname)
        titles = self.env.titles
        if related and related[2]:
            try:
                next = {
                    "link": self.get_relative_uri(docname, related[2]),
                    "title": self.render_partial(titles[related[2]])["title"],
                }
                rellinks.append((related[2], next["title"], "N", _("next")))
            except KeyError:
                next = None
        if related and related[1]:
            try:
                prev = {
                    "link": self.get_relative_uri(docname, related[1]),
                    "title": self.render_partial(titles[related[1]])["title"],
                }
                rellinks.append((related[1], prev["title"], "P", _("previous")))
            except KeyError:
                # the relation is (somehow) not in the TOC tree, handle
                # that gracefully
                prev = None
        while related and related[0]:
            try:
                parents.append(
                    {
                        "link": self.get_relative_uri(docname, related[0]),
                        "title": self.render_partial(titles[related[0]])["title"],
                    }
                )
            except KeyError:
                pass
            related = self.relations.get(related[0])
        if parents:
            # remove link to the master file; we have a generic
            # "back to index" link already
            parents.pop()
        parents.reverse()

        # title rendered as HTML
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)["title"] or ""
        # the name for the copied source
        sourcename = self.config.html_copy_source and docname + ".txt" or ""

        # metadata for the document
        meta = self.env.metadata.get(docname)

        # Suffix for the document
        source_suffix = "." + self.env.doc2path(docname).split(".")[-1]

        # local TOC and global TOC tree
        self_toc = self.env.get_toc_for(docname, self)
        toc = self.render_partial(self_toc)["fragment"]

        return dict(
            parents=parents,
            prev=prev,
            next=next,
            title=title,
            meta=meta,
            body=body,
            rellinks=rellinks,
            sourcename=sourcename,
            toc=toc,
            # only display a TOC if there's more than one item to show
            display_toc=(self.env.toc_num_entries[docname] > 1),
            page_source_suffix=source_suffix,
        )

    def write_doc(self, docname, doctree):
        destination = StringOutput(encoding="utf-8")
        doctree.settings = self.docsettings

        self.secnumbers = self.env.toc_secnumbers.get(docname, {})
        self.fignumbers = self.env.toc_fignumbers.get(docname, {})
        self.imgpath = relative_uri(self.get_target_uri(docname), "_images")
        self.dlpath = relative_uri(self.get_target_uri(docname), "_downloads")
        self.current_docname = docname
        self.docwriter.write(doctree, destination)
        self.docwriter.assemble_parts()
        body = self.docwriter.parts["fragment"]

        ctx = self.get_doc_context(docname, body)
        self.handle_page(docname, ctx, event_arg=doctree)

    def write_doc_serialized(self, docname, doctree):
        self.imgpath = relative_uri(self.get_target_uri(docname), self.imagedir)
        self.post_process_images(doctree)
        title = self.env.longtitles.get(docname)
        title = title and self.render_partial(title)["title"] or ""
        self.index_page(docname, doctree, title)

    def finish(self):
        self.finish_tasks.add_task(self.gen_indices)
        self.finish_tasks.add_task(self.gen_additional_pages)
        self.finish_tasks.add_task(self.copy_image_files)
        self.finish_tasks.add_task(self.copy_download_files)
        self.finish_tasks.add_task(self.copy_static_files)
        self.finish_tasks.add_task(self.copy_extra_files)
        self.finish_tasks.add_task(self.write_buildinfo)

        # dump the search index
        self.handle_finish()

    def gen_indices(self):
        self.info(bold("generating indices..."), nonl=1)

        # the global general index
        # if self.get_builder_config('use_index', 'html'):
        #    self.write_genindex()

        # the global domain-specific indices
        self.write_domain_indices()

        self.info()

    def gen_additional_pages(self):
        # pages from extensions
        for pagelist in self.app.emit("html-collect-pages"):
            for pagename, context, template in pagelist:
                self.handle_page(pagename, context, template)

        self.info(bold("writing additional pages..."), nonl=1)

        # additional pages from conf.py
        for pagename, template in self.config.html_additional_pages.items():
            self.info(" " + pagename, nonl=1)
            self.handle_page(pagename, {}, template)

        # the search page
        if self.search:
            self.info(" search", nonl=1)
            self.handle_page("search", {}, "search.html")

        # the opensearch xml file
        if self.config.html_use_opensearch and self.search:
            self.info(" opensearch", nonl=1)
            fn = path.join(self.outdir, "_static", "opensearch.xml")
            self.handle_page("opensearch", {}, "opensearch.xml", outfilename=fn)

        self.info()

    # def write_genindex(self):
    #    # the total count of lines for each index letter, used to distribute
    #    # the entries into two columns
    #    genindex = self.env.create_index(self)
    #    indexcounts = []
    #    for _k, entries in genindex:
    #        indexcounts.append(sum(1 + len(subitems)
    #                               for _, (_, subitems, _) in entries))

    #    genindexcontext = dict(
    #        genindexentries = genindex,
    #        genindexcounts = indexcounts,
    #        split_index = self.config.html_split_index,
    #    )
    #    self.info(' genindex', nonl=1)

    #    if self.config.html_split_index:
    #        self.handle_page('genindex', genindexcontext,
    #                         'genindex-split.html')
    #        self.handle_page('genindex-all', genindexcontext,
    #                         'genindex.html')
    #        for (key, entries), count in zip(genindex, indexcounts):
    #            ctx = {'key': key, 'entries': entries, 'count': count,
    #                   'genindexentries': genindex}
    #            self.handle_page('genindex-' + key, ctx,
    #                             'genindex-single.html')
    #    else:
    #        self.handle_page('genindex', genindexcontext, 'genindex.html')

    def write_domain_indices(self):
        for indexname, indexcls, content, collapse in self.domain_indices:
            indexcontext = dict(indextitle=indexcls.localname, content=content, collapse_index=collapse)
            self.info(" " + indexname, nonl=1)
            self.handle_page(indexname, indexcontext, "domainindex.html")

    def copy_image_files(self):
        # copy image files
        if self.images:
            ensuredir(path.join(self.outdir, self.imagedir))
            for src in self.app.status_iterator(self.images, "copying images... ", brown, len(self.images)):
                dest = self.images[src]
                try:
                    copyfile(path.join(self.srcdir, src), path.join(self.outdir, self.imagedir, dest))
                except Exception as err:
                    self.warn("cannot copy image file %r: %s" % (path.join(self.srcdir, src), err))

    def copy_download_files(self):
        def to_relpath(f):
            return relative_path(self.srcdir, f)

        # copy downloadable files
        if self.env.dlfiles:
            ensuredir(path.join(self.outdir, "_downloads"))
            for src in self.app.status_iterator(
                self.env.dlfiles,
                "copying downloadable files... ",
                brown,
                len(self.env.dlfiles),
                stringify_func=to_relpath,
            ):
                dest = self.env.dlfiles[src][1]
                try:
                    copyfile(path.join(self.srcdir, src), path.join(self.outdir, "_downloads", dest))
                except Exception as err:
                    self.warn("cannot copy downloadable file %r: %s" % (path.join(self.srcdir, src), err))

    def copy_static_files(self):
        # copy static files
        self.info(bold("copying static files... "), nonl=True)
        ensuredir(path.join(self.outdir, "_static"))

        ctx = self.globalcontext.copy()

        # copy over all user-supplied static files
        staticentries = [path.join(self.confdir, spath) for spath in self.config.html_static_path]
        matchers = compile_matchers(self.config.exclude_patterns)
        for entry in staticentries:
            if not path.exists(entry):
                self.warn("html_static_path entry %r does not exist" % entry)
                continue
            copy_static_entry(entry, path.join(self.outdir, "_static"), self, ctx, exclude_matchers=matchers)
        self.info("done")

    def copy_extra_files(self):
        # copy html_extra_path files
        self.info(bold("copying extra files... "), nonl=True)
        extraentries = [path.join(self.confdir, epath) for epath in self.config.html_extra_path]
        matchers = compile_matchers(self.config.exclude_patterns)
        for entry in extraentries:
            if not path.exists(entry):
                self.warn("html_extra_path entry %r does not exist" % entry)
                continue
            copy_extra_entry(entry, self.outdir, matchers)
        self.info("done")

    def write_buildinfo(self):
        # write build info file
        fp = open(path.join(self.outdir, ".buildinfo"), "w")
        try:
            fp.write(
                "# Sphinx build info version 1\n"
                "# This file hashes the configuration used when building"
                " these files. When it is not found, a full rebuild will"
                " be done.\nconfig: %s\ntags: %s\n" % (self.config_hash, self.tags_hash)
            )
        finally:
            fp.close()

    def cleanup(self):
        pass

    def post_process_images(self, doctree):
        """Pick the best candidate for an image and link down-scaled images to
        their high res version.
        """
        Builder.post_process_images(self, doctree)

        if self.config.html_scaled_image_link:
            for node in doctree.traverse(nodes.image):
                scale_keys = ("scale", "width", "height")
                if not any((key in node) for key in scale_keys) or isinstance(node.parent, nodes.reference):
                    # docutils does unfortunately not preserve the
                    # ``target`` attribute on images, so we need to check
                    # the parent node here.
                    continue
                uri = node["uri"]
                reference = nodes.reference("", "", internal=True)
                if uri in self.images:
                    reference["refuri"] = posixpath.join(self.imgpath, self.images[uri])
                else:
                    reference["refuri"] = uri
                node.replace_self(reference)
                reference.append(node)

    def load_indexer(self, docnames):
        keep = set(self.env.all_docs) - set(docnames)
        try:
            searchindexfn = path.join(self.outdir, self.searchindex_filename)
            if self.indexer_dumps_unicode:
                f = codecs.open(searchindexfn, "r", encoding="utf-8")
            else:
                f = open(searchindexfn, "rb")
            try:
                self.indexer.load(f, self.indexer_format)
            finally:
                f.close()
        except (IOError, OSError, ValueError):
            if keep:
                self.warn(
                    "search index couldn't be loaded, but not all "
                    "documents will be built: the index will be "
                    "incomplete."
                )
        # delete all entries for files that will be rebuilt
        self.indexer.prune(keep)

    def index_page(self, pagename, doctree, title):
        # only index pages with title
        if self.indexer is not None and title:
            self.indexer.feed(pagename, title, doctree)

    def _get_local_toctree(self, docname, collapse=True, **kwds):
        if "includehidden" not in kwds:
            kwds["includehidden"] = False
        return self.render_partial(self.env.get_toctree_for(docname, self, collapse, **kwds))["fragment"]

    # --------- these are overwritten by the serialization builder

    def handle_page(self, pagename, addctx, templatename="page.html", outfilename=None, event_arg=None):
        ctx = self.globalcontext.copy()
        # current_page_name is backwards compatibility
        ctx["pagename"] = ctx["current_page_name"] = pagename
        default_baseuri = self.get_target_uri(pagename)
        # in the singlehtml builder, default_baseuri still contains an #anchor
        # part, which relative_uri doesn't really like...
        default_baseuri = default_baseuri.rsplit("#", 1)[0]

        def pathto(otheruri, resource=False, baseuri=default_baseuri):
            if resource and "://" in otheruri:
                # allow non-local resources given by scheme
                return otheruri
            elif not resource:
                otheruri = self.get_target_uri(otheruri)
            uri = relative_uri(baseuri, otheruri) or "#"
            return uri

        ctx["pathto"] = pathto
        ctx["hasdoc"] = lambda name: name in self.env.all_docs
        if self.name != "mdhelp":
            ctx["encoding"] = encoding = self.config.html_output_encoding
        else:
            ctx["encoding"] = encoding = self.encoding
        ctx["toctree"] = lambda **kw: self._get_local_toctree(pagename, **kw)
        ctx.update(addctx)

        newtmpl = self.app.emit_firstresult("md-page-context", pagename, templatename, ctx, event_arg)
        if newtmpl:
            templatename = newtmpl

        try:
            output = self.templates.render(templatename, ctx)
        except UnicodeError:
            self.warn(
                "a Unicode error occurred when rendering the page %s. "
                "Please make sure all config values that contain "
                "non-ASCII content are Unicode strings." % pagename
            )
            return

        if not outfilename:
            outfilename = self.get_outfilename(pagename)
        # outfilename's path is in general different from self.outdir
        ensuredir(path.dirname(outfilename))
        try:
            f = codecs.open(outfilename, "w", encoding, "xmlcharrefreplace")
            try:
                f.write(output)
            finally:
                f.close()
        except (IOError, OSError) as err:
            self.warn("error writing file %s: %s" % (outfilename, err))
        if self.copysource and ctx.get("sourcename"):
            # copy the source file for the "show source" link
            source_name = path.join(self.outdir, "_sources", os_path(ctx["sourcename"]))
            ensuredir(path.dirname(source_name))
            copyfile(self.env.doc2path(pagename), source_name)

    def handle_finish(self):
        if self.indexer:
            self.finish_tasks.add_task(self.dump_search_index)
        self.finish_tasks.add_task(self.dump_inventory)

    def dump_inventory(self):
        self.info(bold("dumping object inventory... "), nonl=True)
        f = open(path.join(self.outdir, INVENTORY_FILENAME), "wb")
        try:
            f.write(
                (
                    u"# Sphinx inventory version 2\n"
                    u"# Project: %s\n"
                    u"# Version: %s\n"
                    u"# The remainder of this file is compressed using zlib.\n"
                    % (self.config.project, self.config.version)
                ).encode("utf-8")
            )
            compressor = zlib.compressobj(9)
            for domainname, domain in sorted(self.env.domains.items()):
                for name, dispname, type, docname, anchor, prio in sorted(domain.get_objects()):
                    if anchor.endswith(name):
                        # this can shorten the inventory by as much as 25%
                        anchor = anchor[: -len(name)] + "$"
                    uri = self.get_target_uri(docname)
                    if anchor:
                        uri += "#" + anchor
                    if dispname == name:
                        dispname = u"-"
                    f.write(
                        compressor.compress(
                            (u"%s %s:%s %s %s %s\n" % (name, domainname, type, prio, uri, dispname)).encode("utf-8")
                        )
                    )
            f.write(compressor.flush())
        finally:
            f.close()
        self.info("done")

    def dump_search_index(self):
        self.info(bold("dumping search index in %s ... " % self.indexer.label()), nonl=True)
        self.indexer.prune(self.env.all_docs)
        searchindexfn = path.join(self.outdir, self.searchindex_filename)
        # first write to a temporary file, so that if dumping fails,
        # the existing index won't be overwritten
        if self.indexer_dumps_unicode:
            f = codecs.open(searchindexfn + ".tmp", "w", encoding="utf-8")
        else:
            f = open(searchindexfn + ".tmp", "wb")
        try:
            self.indexer.dump(f, self.indexer_format)
        finally:
            f.close()
        movefile(searchindexfn + ".tmp", searchindexfn)
        self.info("done")