From c941061aa989bba945e296aad47df22f9c8ddd5f Mon Sep 17 00:00:00 2001 From: Alex Merry Date: Sun, 15 Nov 2015 15:09:48 +0000 Subject: Overhaul the ECM build system. It should now be easier to read, and more featureful. Among other tweaks, we now print a summary of dependencies and build options, and the documentation is generated with more sensible breadcrumbs and builds properly with Sphinx 1.3. REVIEW: 126075 --- docs/CMakeLists.txt | 107 ++++++++-------- docs/sphinx/conf.py.in | 18 ++- docs/sphinx/ecm.css.in | 8 ++ docs/sphinx/ecm.py | 307 --------------------------------------------- docs/sphinx/ext/ecm.py | 307 +++++++++++++++++++++++++++++++++++++++++++++ docs/sphinx/static/ecm.css | 8 -- 6 files changed, 381 insertions(+), 374 deletions(-) create mode 100644 docs/sphinx/ecm.css.in delete mode 100644 docs/sphinx/ecm.py create mode 100644 docs/sphinx/ext/ecm.py delete mode 100644 docs/sphinx/static/ecm.css (limited to 'docs') diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index f17400f8..1f307855 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,51 +1,55 @@ #============================================================================= -# CMake - Cross Platform Makefile Generator -# Copyright 2000-2013 Kitware, Inc., Insight Software Consortium -# Copyright 2014 Alex Merry +# Copyright 2000-2013 Kitware, Inc. +# Copyright 2014-2015 Alex Merry # # Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. +# see accompanying file COPYING-CMAKE-SCRIPTS for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= -# Distros sometimes rename Python executables to allow for parallel -# installation of Python2 and Python3 versions -message(STATUS "Looking for Sphinx Documentation Builder...") -find_program(SPHINX_EXECUTABLE - NAMES - sphinx-build - sphinx-build2 - sphinx-build3 - DOC "Sphinx Documentation Builder (http://sphinx-doc.org/)" +include(CMakeDependentOption) + +find_package(Sphinx 1.2 MODULE) +set_package_properties( + Sphinx + PROPERTIES + URL "http://sphinx-doc.org/" + DESCRIPTION "Tool to generate documentation." + TYPE OPTIONAL + PURPOSE "Required to build documentation for Extra CMake Modules." ) -if(SPHINX_EXECUTABLE) - message(STATUS "Sphinx Documentation Builder found at ${SPHINX_EXECUTABLE} - building documentation") - set(build_docs_default ON) -else() - message(STATUS "Sphinx Documentation Builder not found - documentation will not be built (see http://sphinx-doc.org/)") - set(build_docs_default OFF) -endif() -option(BUILD_HTML_DOCS "Build html help with Sphinx" ${build_docs_default}) -option(BUILD_MAN_DOCS "Build man pages with Sphinx" ${build_docs_default}) -option(BUILD_QTHELP_DOCS "Build Qt help with Sphinx" OFF) +find_package(QCollectionGenerator MODULE) +set_package_properties( + QCollectionGenerator + PROPERTIES + URL "http://www.qt.io/" + DESCRIPTION "Qt help collection generator." + TYPE OPTIONAL + PURPOSE "Required to build Extra CMake Modules documentation in Qt Help format." +) -if(NOT BUILD_HTML_DOCS AND NOT BUILD_MAN_DOCS AND NOT BUILD_QTHELP_DOCS) - return() -elseif(NOT SPHINX_EXECUTABLE) - message(FATAL_ERROR "SPHINX_EXECUTABLE (sphinx-build) was not found!") -endif() +cmake_dependent_option( + BUILD_HTML_DOCS "Build html help with Sphinx" ON + "Sphinx_FOUND" OFF +) +add_feature_info(BUILD_HTML_DOCS BUILD_HTML_DOCS "Generate HTML documentation for installed modules.") + +cmake_dependent_option( + BUILD_MAN_DOCS "Build man pages with Sphinx" ON + "Sphinx_FOUND" OFF +) +add_feature_info(BUILD_MAN_DOCS BUILD_MAN_DOCS "Generate man page documentation for installed modules.") + +cmake_dependent_option( + BUILD_QTHELP_DOCS "Build Qt help with Sphinx" OFF + "Sphinx_FOUND;QCollectionGenerator_FOUND" OFF +) +add_feature_info(BUILD_QTHELP_DOCS BUILD_QTHELP_DOCS "Generate QtHelp documentation for installed modules.") -# the docs/ directory -set(conf_docs "${CMAKE_CURRENT_SOURCE_DIR}") -# where cmake.py and other sphinx files are -set(conf_path "${CMAKE_CURRENT_SOURCE_DIR}/sphinx") -set(conf_version "${extra-cmake-modules_VERSION_MAJOR}.${extra-cmake-modules_VERSION_MINOR}.${extra-cmake-modules_VERSION_PATCH}") -set(conf_release "${extra-cmake-modules_VERSION}") -configure_file(sphinx/conf.py.in conf.py @ONLY) set(doc_formats "") if(BUILD_HTML_DOCS) @@ -55,22 +59,26 @@ if(BUILD_MAN_DOCS) list(APPEND doc_formats man) endif() if(BUILD_QTHELP_DOCS) - find_program(QCOLLECTIONGENERATOR_EXECUTABLE - NAMES qcollectiongenerator - DOC "qcollectiongenerator tool" - ) - if (NOT QCOLLECTIONGENERATOR_EXECUTABLE) - message(FATAL_ERROR "QCOLLECTIONGENERATOR_EXECUTABLE (qcollectiongenerator) not found!") - endif() list(APPEND doc_formats qthelp) - set(qthelp_extra_commands COMMAND - qcollectiongenerator - ${CMAKE_CURRENT_BINARY_DIR}/qthelp/extra-cmake-modules.qhcp + QCollectionGenerator::Generator + ${CMAKE_CURRENT_BINARY_DIR}/qthelp/ExtraCMakeModules.qhcp ) endif() +if(NOT doc_formats) + return() +endif() + +if (Sphinx_VERSION VERSION_LESS 1.3) + set(sphinx_theme default) +else() + set(sphinx_theme classic) +endif() +configure_file(sphinx/conf.py.in conf.py @ONLY) +configure_file(sphinx/ecm.css.in static/ecm.css) + set(doc_format_outputs "") set(doc_format_last "") @@ -80,13 +88,14 @@ foreach(format ${doc_formats}) add_custom_command( OUTPUT ${doc_format_output} COMMAND - ${SPHINX_EXECUTABLE} + Sphinx::Build -c ${CMAKE_CURRENT_BINARY_DIR} -d ${CMAKE_CURRENT_BINARY_DIR}/doctrees -b ${format} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${format} > ${doc_format_log} # log stdout, pass stderr + ${${format}_extra_commands} DEPENDS ${doc_format_last} COMMENT "sphinx-build ${format}: see ${CMAKE_CURRENT_BINARY_DIR}/${doc_format_log}" VERBATIM @@ -99,8 +108,8 @@ endforeach() add_custom_target(documentation ALL DEPENDS ${doc_format_outputs}) if(BUILD_MAN_DOCS) - file(GLOB man_rst RELATIVE ${extra-cmake-modules_SOURCE_DIR}/docs/manual - ${extra-cmake-modules_SOURCE_DIR}/docs/manual/*.[1-9].rst) + file(GLOB man_rst RELATIVE ${ECM_SOURCE_DIR}/docs/manual + ${ECM_SOURCE_DIR}/docs/manual/*.[1-9].rst) foreach(m ${man_rst}) if("x${m}" MATCHES "^x(.+)\\.([1-9])\\.rst$") set(name "${CMAKE_MATCH_1}") @@ -122,7 +131,7 @@ if(BUILD_HTML_DOCS) endif() if(BUILD_QTHELP_DOCS) install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/qthelp/extra-cmake-modules.qch + FILES ${CMAKE_CURRENT_BINARY_DIR}/qthelp/ExtraCMakeModules.qch DESTINATION ${DOC_INSTALL_DIR} ) endif() diff --git a/docs/sphinx/conf.py.in b/docs/sphinx/conf.py.in index 792c87ca..d32b53f4 100644 --- a/docs/sphinx/conf.py.in +++ b/docs/sphinx/conf.py.in @@ -14,24 +14,23 @@ import os import re import glob -sys.path.insert(0, r'@conf_path@') +sys.path.insert(0, r'@CMAKE_CURRENT_SOURCE_DIR@/sphinx/ext') source_suffix = '.rst' master_doc = 'index' project = 'Extra CMake Modules' copyright = 'KDE Developers' -version = '@conf_version@' # feature version -release = '@conf_release@' # full version string +version = '@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@' # feature version +release = '@PROJECT_VERSION@' # full version string primary_domain = 'ecm' exclude_patterns = [] extensions = ['ecm'] -templates_path = ['@conf_path@/templates'] -ecm_manuals = sorted(glob.glob(r'@conf_docs@/manual/*.rst')) +ecm_manuals = sorted(glob.glob(r'@CMAKE_CURRENT_SOURCE_DIR@/manual/*.rst')) ecm_manual_description = re.compile('^\.\. ecm-manual-description:(.*)$') man_pages = [] for fpath in ecm_manuals: @@ -55,9 +54,8 @@ for fpath in ecm_manuals: man_show_urls = False html_show_sourcelink = True -html_static_path = ['@conf_path@/static'] +html_static_path = ['@CMAKE_CURRENT_BINARY_DIR@/static'] html_style = 'ecm.css' -html_theme = 'default' -html_title = 'Extra CMake Modules %s Documentation' % release -html_short_title = '%s Documentation' % release -html_favicon = '@conf_path@/kde-favicon.ico' +html_theme = '@sphinx_theme@' +html_short_title = 'ECM %s documentation' % version +html_favicon = '@CMAKE_CURRENT_SOURCE_DIR@/sphinx/kde-favicon.ico' diff --git a/docs/sphinx/ecm.css.in b/docs/sphinx/ecm.css.in new file mode 100644 index 00000000..a05e4527 --- /dev/null +++ b/docs/sphinx/ecm.css.in @@ -0,0 +1,8 @@ +/* Import the Sphinx theme style. */ +@import url("${sphinx_theme}.css"); + +/* Wrap sidebar content even within words so that long + document names do not escape sidebar borders. */ +div.sphinxsidebarwrapper { + word-wrap: break-word; +} diff --git a/docs/sphinx/ecm.py b/docs/sphinx/ecm.py deleted file mode 100644 index ed966bfb..00000000 --- a/docs/sphinx/ecm.py +++ /dev/null @@ -1,307 +0,0 @@ -# Copyright 2014 Alex Merry -# Based on cmake.py from CMake: -# Copyright 2000-2013 Kitware, Inc., Insight Software Consortium -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import re - -# Monkey patch for pygments reporting an error when generator expressions are -# used. -# https://bitbucket.org/birkenfeld/pygments-main/issue/942/cmake-generator-expressions-not-handled -from pygments.lexers import CMakeLexer -from pygments.token import Name, Operator -from pygments.lexer import bygroups -CMakeLexer.tokens["args"].append(('(\\$<)(.+?)(>)', - bygroups(Operator, Name.Variable, Operator))) - -# Monkey patch for sphinx generating invalid content for qcollectiongenerator -# https://bitbucket.org/birkenfeld/sphinx/issue/1435/qthelp-builder-should-htmlescape-keywords -from sphinx.util.pycompat import htmlescape -from sphinx.builders.qthelp import QtHelpBuilder -old_build_keywords = QtHelpBuilder.build_keywords -def new_build_keywords(self, title, refs, subitems): - old_items = old_build_keywords(self, title, refs, subitems) - new_items = [] - for item in old_items: - before, rest = item.split("ref=\"", 1) - ref, after = rest.split("\"") - if ("<" in ref and ">" in ref): - new_items.append(before + "ref=\"" + htmlescape(ref) + "\"" + after) - else: - new_items.append(item) - return new_items -QtHelpBuilder.build_keywords = new_build_keywords - - -from docutils.parsers.rst import Directive, directives -from docutils.transforms import Transform -try: - from docutils.utils.error_reporting import SafeString, ErrorString -except ImportError: - # error_reporting was not in utils before version 0.11: - from docutils.error_reporting import SafeString, ErrorString - -from docutils import io, nodes - -from sphinx.directives import ObjectDescription -from sphinx.domains import Domain, ObjType -from sphinx.roles import XRefRole -from sphinx.util.nodes import make_refnode -from sphinx import addnodes - -class ECMModule(Directive): - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = {'encoding': directives.encoding} - - def __init__(self, *args, **keys): - self.re_start = re.compile(r'^#\[(?P=*)\[\.rst:$') - Directive.__init__(self, *args, **keys) - - def run(self): - settings = self.state.document.settings - if not settings.file_insertion_enabled: - raise self.warning('"%s" directive disabled.' % self.name) - - env = self.state.document.settings.env - rel_path, path = env.relfn2path(self.arguments[0]) - path = os.path.normpath(path) - encoding = self.options.get('encoding', settings.input_encoding) - e_handler = settings.input_encoding_error_handler - try: - settings.record_dependencies.add(path) - f = io.FileInput(source_path=path, encoding=encoding, - error_handler=e_handler) - except UnicodeEncodeError as error: - raise self.severe('Problems with "%s" directive path:\n' - 'Cannot encode input file path "%s" ' - '(wrong locale?).' % - (self.name, SafeString(path))) - except IOError as error: - raise self.severe('Problems with "%s" directive path:\n%s.' % - (self.name, ErrorString(error))) - raw_lines = f.read().splitlines() - f.close() - rst = None - lines = [] - for line in raw_lines: - if rst is not None and rst != '#': - # Bracket mode: check for end bracket - pos = line.find(rst) - if pos >= 0: - if line[0] == '#': - line = '' - else: - line = line[0:pos] - rst = None - else: - # Line mode: check for .rst start (bracket or line) - m = self.re_start.match(line) - if m: - rst = ']%s]' % m.group('eq') - line = '' - elif line == '#.rst:': - rst = '#' - line = '' - elif rst == '#': - if line == '#' or line[:2] == '# ': - line = line[2:] - else: - rst = None - line = '' - elif rst is None: - line = '' - lines.append(line) - if rst is not None and rst != '#': - raise self.warning('"%s" found unclosed bracket "#[%s[.rst:" in %s' % - (self.name, rst[1:-1], path)) - self.state_machine.insert_input(lines, path) - return [] - -class _ecm_index_entry: - def __init__(self, desc): - self.desc = desc - - def __call__(self, title, targetid): - return ('pair', u'%s ; %s' % (self.desc, title), targetid, 'main') - -_ecm_index_objs = { - 'manual': _ecm_index_entry('manual'), - 'module': _ecm_index_entry('module'), - 'find-module': _ecm_index_entry('find-module'), - 'kde-module': _ecm_index_entry('kde-module'), - 'toolchain': _ecm_index_entry('toolchain'), - } - -def _ecm_object_inventory(env, document, line, objtype, targetid): - inv = env.domaindata['ecm']['objects'] - if targetid in inv: - document.reporter.warning( - 'ECM object "%s" also described in "%s".' % - (targetid, env.doc2path(inv[targetid][0])), line=line) - inv[targetid] = (env.docname, objtype) - -class ECMTransform(Transform): - - # Run this transform early since we insert nodes we want - # treated as if they were written in the documents. - default_priority = 210 - - def __init__(self, document, startnode): - Transform.__init__(self, document, startnode) - self.titles = {} - - def parse_title(self, docname): - """Parse a document title as the first line starting in [A-Za-z0-9<] - or fall back to the document basename if no such line exists. - Return the title or False if the document file does not exist. - """ - env = self.document.settings.env - title = self.titles.get(docname) - if title is None: - fname = os.path.join(env.srcdir, docname+'.rst') - try: - f = open(fname, 'r') - except IOError: - title = False - else: - for line in f: - if len(line) > 0 and (line[0].isalnum() or line[0] == '<'): - title = line.rstrip() - break - f.close() - if title is None: - title = os.path.basename(docname) - self.titles[docname] = title - return title - - def apply(self): - env = self.document.settings.env - - # Treat some documents as ecm domain objects. - objtype, sep, tail = env.docname.rpartition('/') - make_index_entry = _ecm_index_objs.get(objtype) - if make_index_entry: - title = self.parse_title(env.docname) - # Insert the object link target. - targetid = '%s:%s' % (objtype, title) - targetnode = nodes.target('', '', ids=[targetid]) - self.document.insert(0, targetnode) - # Insert the object index entry. - indexnode = addnodes.index() - indexnode['entries'] = [make_index_entry(title, targetid)] - self.document.insert(0, indexnode) - # Add to ecm domain object inventory - _ecm_object_inventory(env, self.document, 1, objtype, targetid) - -class ECMObject(ObjectDescription): - - def handle_signature(self, sig, signode): - # called from sphinx.directives.ObjectDescription.run() - signode += addnodes.desc_name(sig, sig) - return sig - - def add_target_and_index(self, name, sig, signode): - targetid = '%s:%s' % (self.objtype, name) - if targetid not in self.state.document.ids: - signode['names'].append(targetid) - signode['ids'].append(targetid) - signode['first'] = (not self.names) - self.state.document.note_explicit_target(signode) - _ecm_object_inventory(self.env, self.state.document, - self.lineno, self.objtype, targetid) - - make_index_entry = _ecm_index_objs.get(self.objtype) - if make_index_entry: - self.indexnode['entries'].append(make_index_entry(name, targetid)) - -class ECMXRefRole(XRefRole): - - # See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'. - _re = re.compile(r'^(.+?)(\s*)(?$', re.DOTALL) - _re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL) - - def __call__(self, typ, rawtext, text, *args, **keys): - # CMake cross-reference targets may contain '<' so escape - # any explicit `` with '<' not preceded by whitespace. - while True: - m = ECMXRefRole._re.match(text) - if m and len(m.group(2)) == 0: - text = '%s\x00<%s>' % (m.group(1), m.group(3)) - else: - break - return XRefRole.__call__(self, typ, rawtext, text, *args, **keys) - -class ECMDomain(Domain): - """ECM domain.""" - name = 'ecm' - label = 'ECM' - object_types = { - 'module': ObjType('module', 'module'), - 'kde-module': ObjType('kde-module', 'kde-module'), - 'find-module': ObjType('find-module', 'find-module'), - 'manual': ObjType('manual', 'manual'), - 'toolchain': ObjType('toolchain', 'toolchain'), - } - directives = {} - roles = { - 'module': XRefRole(), - 'kde-module': XRefRole(), - 'find-module': XRefRole(), - 'manual': XRefRole(), - 'toolchain': XRefRole(), - } - initial_data = { - 'objects': {}, # fullname -> docname, objtype - } - - def clear_doc(self, docname): - to_clear = [] - for fullname, (fn, _) in self.data['objects'].items(): - if fn == docname: - to_clear.append(fullname) - for fullname in to_clear: - del self.data['objects'][fullname] - - def resolve_xref(self, env, fromdocname, builder, - typ, target, node, contnode): - targetid = '%s:%s' % (typ, target) - obj = self.data['objects'].get(targetid) - if obj is None: - # TODO: warn somehow? - return None - return make_refnode(builder, fromdocname, obj[0], targetid, - contnode, target) - - def get_objects(self): - for refname, (docname, type) in self.data['objects'].items(): - yield (refname, refname, type, docname, refname, 1) - -def setup(app): - app.add_directive('ecm-module', ECMModule) - app.add_transform(ECMTransform) - app.add_domain(ECMDomain) diff --git a/docs/sphinx/ext/ecm.py b/docs/sphinx/ext/ecm.py new file mode 100644 index 00000000..ed966bfb --- /dev/null +++ b/docs/sphinx/ext/ecm.py @@ -0,0 +1,307 @@ +# Copyright 2014 Alex Merry +# Based on cmake.py from CMake: +# Copyright 2000-2013 Kitware, Inc., Insight Software Consortium +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import re + +# Monkey patch for pygments reporting an error when generator expressions are +# used. +# https://bitbucket.org/birkenfeld/pygments-main/issue/942/cmake-generator-expressions-not-handled +from pygments.lexers import CMakeLexer +from pygments.token import Name, Operator +from pygments.lexer import bygroups +CMakeLexer.tokens["args"].append(('(\\$<)(.+?)(>)', + bygroups(Operator, Name.Variable, Operator))) + +# Monkey patch for sphinx generating invalid content for qcollectiongenerator +# https://bitbucket.org/birkenfeld/sphinx/issue/1435/qthelp-builder-should-htmlescape-keywords +from sphinx.util.pycompat import htmlescape +from sphinx.builders.qthelp import QtHelpBuilder +old_build_keywords = QtHelpBuilder.build_keywords +def new_build_keywords(self, title, refs, subitems): + old_items = old_build_keywords(self, title, refs, subitems) + new_items = [] + for item in old_items: + before, rest = item.split("ref=\"", 1) + ref, after = rest.split("\"") + if ("<" in ref and ">" in ref): + new_items.append(before + "ref=\"" + htmlescape(ref) + "\"" + after) + else: + new_items.append(item) + return new_items +QtHelpBuilder.build_keywords = new_build_keywords + + +from docutils.parsers.rst import Directive, directives +from docutils.transforms import Transform +try: + from docutils.utils.error_reporting import SafeString, ErrorString +except ImportError: + # error_reporting was not in utils before version 0.11: + from docutils.error_reporting import SafeString, ErrorString + +from docutils import io, nodes + +from sphinx.directives import ObjectDescription +from sphinx.domains import Domain, ObjType +from sphinx.roles import XRefRole +from sphinx.util.nodes import make_refnode +from sphinx import addnodes + +class ECMModule(Directive): + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'encoding': directives.encoding} + + def __init__(self, *args, **keys): + self.re_start = re.compile(r'^#\[(?P=*)\[\.rst:$') + Directive.__init__(self, *args, **keys) + + def run(self): + settings = self.state.document.settings + if not settings.file_insertion_enabled: + raise self.warning('"%s" directive disabled.' % self.name) + + env = self.state.document.settings.env + rel_path, path = env.relfn2path(self.arguments[0]) + path = os.path.normpath(path) + encoding = self.options.get('encoding', settings.input_encoding) + e_handler = settings.input_encoding_error_handler + try: + settings.record_dependencies.add(path) + f = io.FileInput(source_path=path, encoding=encoding, + error_handler=e_handler) + except UnicodeEncodeError as error: + raise self.severe('Problems with "%s" directive path:\n' + 'Cannot encode input file path "%s" ' + '(wrong locale?).' % + (self.name, SafeString(path))) + except IOError as error: + raise self.severe('Problems with "%s" directive path:\n%s.' % + (self.name, ErrorString(error))) + raw_lines = f.read().splitlines() + f.close() + rst = None + lines = [] + for line in raw_lines: + if rst is not None and rst != '#': + # Bracket mode: check for end bracket + pos = line.find(rst) + if pos >= 0: + if line[0] == '#': + line = '' + else: + line = line[0:pos] + rst = None + else: + # Line mode: check for .rst start (bracket or line) + m = self.re_start.match(line) + if m: + rst = ']%s]' % m.group('eq') + line = '' + elif line == '#.rst:': + rst = '#' + line = '' + elif rst == '#': + if line == '#' or line[:2] == '# ': + line = line[2:] + else: + rst = None + line = '' + elif rst is None: + line = '' + lines.append(line) + if rst is not None and rst != '#': + raise self.warning('"%s" found unclosed bracket "#[%s[.rst:" in %s' % + (self.name, rst[1:-1], path)) + self.state_machine.insert_input(lines, path) + return [] + +class _ecm_index_entry: + def __init__(self, desc): + self.desc = desc + + def __call__(self, title, targetid): + return ('pair', u'%s ; %s' % (self.desc, title), targetid, 'main') + +_ecm_index_objs = { + 'manual': _ecm_index_entry('manual'), + 'module': _ecm_index_entry('module'), + 'find-module': _ecm_index_entry('find-module'), + 'kde-module': _ecm_index_entry('kde-module'), + 'toolchain': _ecm_index_entry('toolchain'), + } + +def _ecm_object_inventory(env, document, line, objtype, targetid): + inv = env.domaindata['ecm']['objects'] + if targetid in inv: + document.reporter.warning( + 'ECM object "%s" also described in "%s".' % + (targetid, env.doc2path(inv[targetid][0])), line=line) + inv[targetid] = (env.docname, objtype) + +class ECMTransform(Transform): + + # Run this transform early since we insert nodes we want + # treated as if they were written in the documents. + default_priority = 210 + + def __init__(self, document, startnode): + Transform.__init__(self, document, startnode) + self.titles = {} + + def parse_title(self, docname): + """Parse a document title as the first line starting in [A-Za-z0-9<] + or fall back to the document basename if no such line exists. + Return the title or False if the document file does not exist. + """ + env = self.document.settings.env + title = self.titles.get(docname) + if title is None: + fname = os.path.join(env.srcdir, docname+'.rst') + try: + f = open(fname, 'r') + except IOError: + title = False + else: + for line in f: + if len(line) > 0 and (line[0].isalnum() or line[0] == '<'): + title = line.rstrip() + break + f.close() + if title is None: + title = os.path.basename(docname) + self.titles[docname] = title + return title + + def apply(self): + env = self.document.settings.env + + # Treat some documents as ecm domain objects. + objtype, sep, tail = env.docname.rpartition('/') + make_index_entry = _ecm_index_objs.get(objtype) + if make_index_entry: + title = self.parse_title(env.docname) + # Insert the object link target. + targetid = '%s:%s' % (objtype, title) + targetnode = nodes.target('', '', ids=[targetid]) + self.document.insert(0, targetnode) + # Insert the object index entry. + indexnode = addnodes.index() + indexnode['entries'] = [make_index_entry(title, targetid)] + self.document.insert(0, indexnode) + # Add to ecm domain object inventory + _ecm_object_inventory(env, self.document, 1, objtype, targetid) + +class ECMObject(ObjectDescription): + + def handle_signature(self, sig, signode): + # called from sphinx.directives.ObjectDescription.run() + signode += addnodes.desc_name(sig, sig) + return sig + + def add_target_and_index(self, name, sig, signode): + targetid = '%s:%s' % (self.objtype, name) + if targetid not in self.state.document.ids: + signode['names'].append(targetid) + signode['ids'].append(targetid) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + _ecm_object_inventory(self.env, self.state.document, + self.lineno, self.objtype, targetid) + + make_index_entry = _ecm_index_objs.get(self.objtype) + if make_index_entry: + self.indexnode['entries'].append(make_index_entry(name, targetid)) + +class ECMXRefRole(XRefRole): + + # See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'. + _re = re.compile(r'^(.+?)(\s*)(?$', re.DOTALL) + _re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL) + + def __call__(self, typ, rawtext, text, *args, **keys): + # CMake cross-reference targets may contain '<' so escape + # any explicit `` with '<' not preceded by whitespace. + while True: + m = ECMXRefRole._re.match(text) + if m and len(m.group(2)) == 0: + text = '%s\x00<%s>' % (m.group(1), m.group(3)) + else: + break + return XRefRole.__call__(self, typ, rawtext, text, *args, **keys) + +class ECMDomain(Domain): + """ECM domain.""" + name = 'ecm' + label = 'ECM' + object_types = { + 'module': ObjType('module', 'module'), + 'kde-module': ObjType('kde-module', 'kde-module'), + 'find-module': ObjType('find-module', 'find-module'), + 'manual': ObjType('manual', 'manual'), + 'toolchain': ObjType('toolchain', 'toolchain'), + } + directives = {} + roles = { + 'module': XRefRole(), + 'kde-module': XRefRole(), + 'find-module': XRefRole(), + 'manual': XRefRole(), + 'toolchain': XRefRole(), + } + initial_data = { + 'objects': {}, # fullname -> docname, objtype + } + + def clear_doc(self, docname): + to_clear = [] + for fullname, (fn, _) in self.data['objects'].items(): + if fn == docname: + to_clear.append(fullname) + for fullname in to_clear: + del self.data['objects'][fullname] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + targetid = '%s:%s' % (typ, target) + obj = self.data['objects'].get(targetid) + if obj is None: + # TODO: warn somehow? + return None + return make_refnode(builder, fromdocname, obj[0], targetid, + contnode, target) + + def get_objects(self): + for refname, (docname, type) in self.data['objects'].items(): + yield (refname, refname, type, docname, refname, 1) + +def setup(app): + app.add_directive('ecm-module', ECMModule) + app.add_transform(ECMTransform) + app.add_domain(ECMDomain) diff --git a/docs/sphinx/static/ecm.css b/docs/sphinx/static/ecm.css deleted file mode 100644 index 2a326d47..00000000 --- a/docs/sphinx/static/ecm.css +++ /dev/null @@ -1,8 +0,0 @@ -/* Import the Sphinx theme style. */ -@import url("default.css"); - -/* Wrap sidebar content even within words so that long - document names do not escape sidebar borders. */ -div.sphinxsidebarwrapper { - word-wrap: break-word; -} -- cgit v1.2.1