# -*- coding: utf-8 -*-
"""
    sphinx.builders.qthelp
    ~~~~~~~~~~~~~~~~~~~~~~

    Build input files for the Qt collection generator.

    :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import os
import re
import codecs
import posixpath
from os import path

from six import text_type
from docutils import nodes

from sphinx import addnodes
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.util import force_decode
from sphinx.util.pycompat import htmlescape


_idpattern = re.compile(
    r'(?P<title>.+) (\((class in )?(?P<id>[\w\.]+)( (?P<descr>\w+))?\))$')


# Qt Help Collection Project (.qhcp).
# Is the input file for the help collection generator.
# It contains references to compressed help files which should be
# included in the collection.
# It may contain various other information for customizing Qt Assistant.
collection_template = u'''\
<?xml version="1.0" encoding="utf-8" ?>
<QHelpCollectionProject version="1.0">
    <assistant>
        <title>%(title)s</title>
        <homePage>%(homepage)s</homePage>
        <startPage>%(startpage)s</startPage>
    </assistant>
    <docFiles>
        <generate>
            <file>
                <input>%(outname)s.qhp</input>
                <output>%(outname)s.qch</output>
            </file>
        </generate>
        <register>
            <file>%(outname)s.qch</file>
        </register>
    </docFiles>
</QHelpCollectionProject>
'''

# Qt Help Project (.qhp)
# This is the input file for the help generator.
# It contains the table of contents, indices and references to the
# actual documentation files (*.html).
# In addition it defines a unique namespace for the documentation.
project_template = u'''\
<?xml version="1.0" encoding="utf-8" ?>
<QtHelpProject version="1.0">
    <namespace>%(namespace)s</namespace>
    <virtualFolder>doc</virtualFolder>
    <customFilter name="%(project)s %(version)s">
        <filterAttribute>%(outname)s</filterAttribute>
        <filterAttribute>%(version)s</filterAttribute>
    </customFilter>
    <filterSection>
        <filterAttribute>%(outname)s</filterAttribute>
        <filterAttribute>%(version)s</filterAttribute>
        <toc>
            <section title="%(title)s" ref="%(masterdoc)s.html">
%(sections)s
            </section>
        </toc>
        <keywords>
%(keywords)s
        </keywords>
        <files>
%(files)s
        </files>
    </filterSection>
</QtHelpProject>
'''

section_template = '<section title="%(title)s" ref="%(ref)s"/>'
file_template = ' '*12 + '<file>%(filename)s</file>'


class QtHelpBuilder(StandaloneHTMLBuilder):
    """
    Builder that also outputs Qt help project, contents and index files.
    """
    name = 'qthelp'

    # don't copy the reST source
    copysource = False
    supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
                             'image/jpeg']

    # don't add links
    add_permalinks = False
    # don't add sidebar etc.
    embedded = True

    def init(self):
        StandaloneHTMLBuilder.init(self)
        # the output files for HTML help must be .html only
        self.out_suffix = '.html'
        # self.config.html_style = 'traditional.css'

    def handle_finish(self):
        self.build_qhp(self.outdir, self.config.qthelp_basename)

    def build_qhp(self, outdir, outname):
        self.info('writing project file...')

        # sections
        tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self,
                                                  prune_toctrees=False)

        def istoctree(node):
            return isinstance(node, addnodes.compact_paragraph) and \
                'toctree' in node
        sections = []
        for node in tocdoc.traverse(istoctree):
            sections.extend(self.write_toc(node))

        for indexname, indexcls, content, collapse in self.domain_indices:
            item = section_template % {'title': indexcls.localname,
                                       'ref': '%s.html' % indexname}
            sections.append(' ' * 4 * 4 + item)
        # sections may be unicode strings or byte strings, we have to make sure
        # they are all unicode strings before joining them
        new_sections = []
        for section in sections:
            if not isinstance(section, text_type):
                new_sections.append(force_decode(section, None))
            else:
                new_sections.append(section)
        sections = u'\n'.join(new_sections)

        # keywords
        keywords = []
        index = self.env.create_index(self, group_entries=False)
        for (key, group) in index:
            for title, (refs, subitems, key_) in group:
                keywords.extend(self.build_keywords(title, refs, subitems))
        keywords = u'\n'.join(keywords)

        # files
        if not outdir.endswith(os.sep):
            outdir += os.sep
        olen = len(outdir)
        projectfiles = []
        staticdir = path.join(outdir, '_static')
        imagesdir = path.join(outdir, self.imagedir)
        for root, dirs, files in os.walk(outdir):
            resourcedir = root.startswith(staticdir) or \
                root.startswith(imagesdir)
            for fn in files:
                if (resourcedir and not fn.endswith('.js')) or \
                   fn.endswith('.html'):
                    filename = path.join(root, fn)[olen:]
                    projectfiles.append(file_template %
                                        {'filename': htmlescape(filename)})
        projectfiles = '\n'.join(projectfiles)

        # it seems that the "namespace" may not contain non-alphanumeric
        # characters, and more than one successive dot, or leading/trailing
        # dots, are also forbidden
        nspace = 'org.sphinx.%s.%s' % (outname, self.config.version)
        nspace = re.sub('[^a-zA-Z0-9.]', '', nspace)
        nspace = re.sub(r'\.+', '.', nspace).strip('.')
        nspace = nspace.lower()

        # write the project file
        f = codecs.open(path.join(outdir, outname+'.qhp'), 'w', 'utf-8')
        try:
            f.write(project_template % {
                'outname': htmlescape(outname),
                'title': htmlescape(self.config.html_title),
                'version': htmlescape(self.config.version),
                'project': htmlescape(self.config.project),
                'namespace': htmlescape(nspace),
                'masterdoc': htmlescape(self.config.master_doc),
                'sections': sections,
                'keywords': keywords,
                'files': projectfiles})
        finally:
            f.close()

        homepage = 'qthelp://' + posixpath.join(
            nspace, 'doc', self.get_target_uri(self.config.master_doc))
        startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index.html')

        self.info('writing collection project file...')
        f = codecs.open(path.join(outdir, outname+'.qhcp'), 'w', 'utf-8')
        try:
            f.write(collection_template % {
                'outname': htmlescape(outname),
                'title': htmlescape(self.config.html_short_title),
                'homepage': htmlescape(homepage),
                'startpage': htmlescape(startpage)})
        finally:
            f.close()

    def isdocnode(self, node):
        if not isinstance(node, nodes.list_item):
            return False
        if len(node.children) != 2:
            return False
        if not isinstance(node.children[0], addnodes.compact_paragraph):
            return False
        if not isinstance(node.children[0][0], nodes.reference):
            return False
        if not isinstance(node.children[1], nodes.bullet_list):
            return False
        return True

    def write_toc(self, node, indentlevel=4):
        # XXX this should return a Unicode string, not a bytestring
        parts = []
        if self.isdocnode(node):
            refnode = node.children[0][0]
            link = refnode['refuri']
            title = htmlescape(refnode.astext()).replace('"', '&quot;')
            item = '<section title="%(title)s" ref="%(ref)s">' % \
                {'title': title, 'ref': link}
            parts.append(' '*4*indentlevel + item)
            for subnode in node.children[1]:
                parts.extend(self.write_toc(subnode, indentlevel+1))
            parts.append(' '*4*indentlevel + '</section>')
        elif isinstance(node, nodes.list_item):
            for subnode in node:
                parts.extend(self.write_toc(subnode, indentlevel))
        elif isinstance(node, nodes.reference):
            link = node['refuri']
            title = htmlescape(node.astext()).replace('"', '&quot;')
            item = section_template % {'title': title, 'ref': link}
            item = u' ' * 4 * indentlevel + item
            parts.append(item.encode('ascii', 'xmlcharrefreplace'))
        elif isinstance(node, nodes.bullet_list):
            for subnode in node:
                parts.extend(self.write_toc(subnode, indentlevel))
        elif isinstance(node, addnodes.compact_paragraph):
            for subnode in node:
                parts.extend(self.write_toc(subnode, indentlevel))

        return parts

    def keyword_item(self, name, ref):
        matchobj = _idpattern.match(name)
        if matchobj:
            groupdict = matchobj.groupdict()
            shortname = groupdict['title']
            id = groupdict.get('id')
            # descr = groupdict.get('descr')
            if shortname.endswith('()'):
                shortname = shortname[:-2]
            id = '%s.%s' % (id, shortname)
        else:
            id = None

        if id:
            item = ' '*12 + '<keyword name="%s" id="%s" ref="%s"/>' % (
                name, id, ref[1])
        else:
            item = ' '*12 + '<keyword name="%s" ref="%s"/>' % (name, ref[1])
        item.encode('ascii', 'xmlcharrefreplace')
        return item

    def build_keywords(self, title, refs, subitems):
        keywords = []

        title = htmlescape(title)
        # if len(refs) == 0: # XXX
        #     write_param('See Also', title)
        if len(refs) == 1:
            keywords.append(self.keyword_item(title, refs[0]))
        elif len(refs) > 1:
            for i, ref in enumerate(refs):  # XXX
                # item = (' '*12 +
                #         '<keyword name="%s [%d]" ref="%s"/>' % (
                #          title, i, ref))
                # item.encode('ascii', 'xmlcharrefreplace')
                # keywords.append(item)
                keywords.append(self.keyword_item(title, ref))

        if subitems:
            for subitem in subitems:
                keywords.extend(self.build_keywords(subitem[0], subitem[1], []))

        return keywords

[((27, 13, 28, 74), 're.compile', 're.compile', ({(28, 4, 28, 73): '"""(?P<title>.+) (\\\\((class in )?(?P<id>[\\\\w\\\\.]+)( (?P<descr>\\\\w+))?\\\\))$"""'}, {}), "(\n    '(?P<title>.+) (\\\\((class in )?(?P<id>[\\\\w\\\\.]+)( (?P<descr>\\\\w+))?\\\\))$')", False, 'import re\n'), ((111, 8, 111, 40), 'sphinx.builders.html.StandaloneHTMLBuilder.init', 'StandaloneHTMLBuilder.init', ({(111, 35, 111, 39): 'self'}, {}), '(self)', False, 'from sphinx.builders.html import StandaloneHTMLBuilder\n'), ((160, 20, 160, 48), 'os.path.join', 'path.join', ({(160, 30, 160, 36): 'outdir', (160, 38, 160, 47): '"""_static"""'}, {}), "(outdir, '_static')", False, 'from os import path\n'), ((161, 20, 161, 52), 'os.path.join', 'path.join', ({(161, 30, 161, 36): 'outdir', (161, 38, 161, 51): 'self.imagedir'}, {}), '(outdir, self.imagedir)', False, 'from os import path\n'), ((162, 33, 162, 48), 'os.walk', 'os.walk', ({(162, 41, 162, 47): 'outdir'}, {}), '(outdir)', False, 'import os\n'), ((177, 17, 177, 52), 're.sub', 're.sub', ({(177, 24, 177, 39): '"""[^a-zA-Z0-9.]"""', (177, 41, 177, 43): '""""""', (177, 45, 177, 51): 'nspace'}, {}), "('[^a-zA-Z0-9.]', '', nspace)", False, 'import re\n'), ((280, 16, 280, 33), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(280, 27, 280, 32): 'title'}, {}), '(title)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((182, 24, 182, 57), 'os.path.join', 'path.join', ({(182, 34, 182, 40): 'outdir', (182, 42, 182, 56): "outname + '.qhp'"}, {}), "(outdir, outname + '.qhp')", False, 'from os import path\n'), ((199, 34, 199, 77), 'posixpath.join', 'posixpath.join', ({(199, 49, 199, 55): 'nspace', (199, 57, 199, 62): '"""doc"""', (199, 64, 199, 76): '"""index.html"""'}, {}), "(nspace, 'doc', 'index.html')", False, 'import posixpath\n'), ((202, 24, 202, 58), 'os.path.join', 'path.join', ({(202, 34, 202, 40): 'outdir', (202, 42, 202, 57): "outname + '.qhcp'"}, {}), "(outdir, outname + '.qhcp')", False, 'from os import path\n'), ((178, 17, 178, 44), 're.sub', 're.sub', ({(178, 24, 178, 30): '"""\\\\.+"""', (178, 32, 178, 35): '"""."""', (178, 37, 178, 43): 'nspace'}, {}), "('\\\\.+', '.', nspace)", False, 'import re\n'), ((142, 36, 142, 63), 'sphinx.util.force_decode', 'force_decode', ({(142, 49, 142, 56): 'section', (142, 58, 142, 62): 'None'}, {}), '(section, None)', False, 'from sphinx.util import force_decode\n'), ((168, 31, 168, 50), 'os.path.join', 'path.join', ({(168, 41, 168, 45): 'root', (168, 47, 168, 49): 'fn'}, {}), '(root, fn)', False, 'from os import path\n'), ((185, 27, 185, 46), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(185, 38, 185, 45): 'outname'}, {}), '(outname)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((186, 25, 186, 59), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(186, 36, 186, 58): 'self.config.html_title'}, {}), '(self.config.html_title)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((187, 27, 187, 58), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(187, 38, 187, 57): 'self.config.version'}, {}), '(self.config.version)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((188, 27, 188, 58), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(188, 38, 188, 57): 'self.config.project'}, {}), '(self.config.project)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((189, 29, 189, 47), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(189, 40, 189, 46): 'nspace'}, {}), '(nspace)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((190, 29, 190, 63), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(190, 40, 190, 62): 'self.config.master_doc'}, {}), '(self.config.master_doc)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((205, 27, 205, 46), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(205, 38, 205, 45): 'outname'}, {}), '(outname)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((206, 25, 206, 65), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(206, 36, 206, 64): 'self.config.html_short_title'}, {}), '(self.config.html_short_title)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((207, 28, 207, 48), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(207, 39, 207, 47): 'homepage'}, {}), '(homepage)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((208, 29, 208, 50), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(208, 40, 208, 49): 'startpage'}, {}), '(startpage)', False, 'from sphinx.util.pycompat import htmlescape\n'), ((170, 53, 170, 73), 'sphinx.util.pycompat.htmlescape', 'htmlescape', ({(170, 64, 170, 72): 'filename'}, {}), '(filename)', False, 'from sphinx.util.pycompat import htmlescape\n')]