diff options
author | David Robillard <d@drobilla.net> | 2020-12-20 20:20:07 +0100 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2020-12-21 09:48:06 +0100 |
commit | f95f22013d51133ec1a7b1554878ff354b9f0f21 (patch) | |
tree | 2f65c78412fa9a083bc82e49480ba206fa14a689 | |
parent | fabf7113483ffd70024df989de3aa9361146d60c (diff) | |
download | serd-f95f22013d51133ec1a7b1554878ff354b9f0f21.tar.gz serd-f95f22013d51133ec1a7b1554878ff354b9f0f21.tar.bz2 serd-f95f22013d51133ec1a7b1554878ff354b9f0f21.zip |
Generate documentation with Sphinx
-rw-r--r-- | .gitlab-ci.yml | 15 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | doc/_static/custom.css | 95 | ||||
-rw-r--r-- | doc/_templates/about.html | 57 | ||||
-rw-r--r-- | doc/c/Doxyfile | 33 | ||||
-rw-r--r-- | doc/c/index.rst | 5 | ||||
-rw-r--r-- | doc/c/overview.rst | 22 | ||||
-rw-r--r-- | doc/c/reference.rst | 11 | ||||
-rw-r--r-- | doc/c/wscript | 42 | ||||
-rw-r--r-- | doc/conf.py.in | 86 | ||||
-rw-r--r-- | doc/footer.html | 20 | ||||
-rw-r--r-- | doc/header.html | 46 | ||||
-rw-r--r-- | doc/index.html.in | 35 | ||||
-rw-r--r-- | doc/layout.xml | 194 | ||||
-rw-r--r-- | doc/mainpage.md | 15 | ||||
-rw-r--r-- | doc/serd.rst | 11 | ||||
-rw-r--r-- | doc/style.css | 808 | ||||
-rw-r--r-- | include/serd/serd.h | 16 | ||||
-rw-r--r-- | resources/serd.svg | 20 | ||||
-rwxr-xr-x | scripts/dox_to_sphinx.py | 674 | ||||
-rw-r--r-- | wscript | 11 |
21 files changed, 1076 insertions, 1142 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 16992228..3f50493d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -88,7 +88,9 @@ test:x64_dbg: - groff -Thtml -P -l -P -r -man -wall doc/serdi.1 > build/doc/serdi.html needs: ["x64_dbg"] artifacts: - paths: ["build/index.html", "build/coverage", "build/doc"] + paths: + - build/doc + - build/coverage x64_rel: @@ -216,15 +218,12 @@ pages: script: - mkdir -p .public/doc - mkdir -p .public/man - - mkdir -p .public/images - - mv build/doc/*.svg .public/images - - mv build/doc/html/ .public/doc/html - - mv build/coverage/ .public/coverage - - mv build/index.html .public/index.html + - mkdir -p .public/c + - mv build/doc/c/singlehtml .public/c/singlehtml - mv build/doc/serdi.html .public/man/serdi.html - mv .public public artifacts: paths: - public - only: - - master + # only: + # - master @@ -48,7 +48,7 @@ constant amount of memory (a single page) for all input sizes. Documentation ------------- - * [API reference](https://drobilla.gitlab.io/serd/doc/html/index.html) + * [API reference](https://drobilla.gitlab.io/serd/c/singlehtml) * [`serdi` man page](https://drobilla.gitlab.io/serd/man/serdi.html) -- David Robillard <d@drobilla.net> diff --git a/doc/_static/custom.css b/doc/_static/custom.css new file mode 100644 index 00000000..60aa759e --- /dev/null +++ b/doc/_static/custom.css @@ -0,0 +1,95 @@ +div.document { + margin: 0; +} + +div.body { + margin-top: 2em; +} + +div.sphinxsidebarwrapper { + background: #EEE; +} + +div.sphinxsidebarwrapper p.blurb { + text-align: center; +} + +div.sphinxsidebarwrapper span.logo { + display: block; + text-align: center; + font-family: Georgia, serif; + padding: 0; + font-size: 180%; +} + +div.sphinxsidebar a { + border-width: 0; +} + +div.sphinxsidebar li { + color: #444; +} + +div.section { + margin-top: 2.5em; +} + +a.reference { + border-bottom: none; +} + +code.xref { + font-weight: normal; + background-color: #F8F8F8; + padding: 0.1em 0 0.1em 0; +} + +div.section > dl.c > dt:first-child, +div.section > dl.cpp > dt:first-child { + background-color: #F8F8F8; + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; + font-weight: normal; + margin-bottom: 0.5em; + padding: 0.1em 0 0.1em 0; +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.9em; +} + +dl.member { + margin-top: 0.5em; +} + +dl.enumerator { + margin-top: 0.5em; +} + +dl.field-list > dt { + padding-left: 0; +} + +pre, tt, code { + background-color: #F8F8F8; +} + +.toctree-l1 { + margin-top: 1.0em; +} + +img.logo { + width: 6em; +} + +.class { + padding-top: 1.5em; +} + +.exception { + padding-top: 1.5em; +} + +.class > dd > dl.function { + padding-top: 1.0em; +} diff --git a/doc/_templates/about.html b/doc/_templates/about.html new file mode 100644 index 00000000..5bbadbe9 --- /dev/null +++ b/doc/_templates/about.html @@ -0,0 +1,57 @@ +{% if theme_logo %} +<p class="logo"> + <a href="{{ pathto(master_doc) }}"> + <img class="logo" src="{{ pathto('_static/' ~ theme_logo, 1) }}" alt="Logo"/> + {% if theme_logo_name|lower == 'true' %} + <span class="logo logo-name">{{ project }}</span> + {% endif %} + </a> +</p> +{% else %} +<h1 class="logo"><a href="{{ pathto(master_doc) }}">{{ project }}</a></h1> +{% endif %} + +{% if theme_description %} +<p class="blurb">{{ theme_description }}</p> +{% endif %} + +{% if theme_github_user and theme_github_repo %} +{% if theme_github_button|lower == 'true' %} +<p> +<iframe src="https://ghbtns.com/github-btn.html?user={{ theme_github_user }}&repo={{ theme_github_repo }}&type={{ theme_github_type }}&count={{ theme_github_count }}&size=large&v=2" + allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe> +</p> +{% endif %} +{% endif %} + +{% if theme_travis_button|lower != 'false' %} +{% if theme_travis_button|lower == 'true' %} + {% set path = theme_github_user + '/' + theme_github_repo %} +{% else %} + {% set path = theme_travis_button %} +{% endif %} +<p> +<a class="badge" href="https://travis-ci.org/{{ path }}"> + <img + alt="https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }}" + src="https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }}" + /> +</a> +</p> +{% endif %} + +{% if theme_codecov_button|lower != 'false' %} +{% if theme_codecov_button|lower == 'true' %} + {% set path = theme_github_user + '/' + theme_github_repo %} +{% else %} + {% set path = theme_codecov_button %} +{% endif %} +<p> +<a class="badge" href="https://codecov.io/github/{{ path }}"> + <img + alt="https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }}" + src="https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }}" + /> +</a> +</p> +{% endif %} diff --git a/doc/c/Doxyfile b/doc/c/Doxyfile new file mode 100644 index 00000000..a88f8ce8 --- /dev/null +++ b/doc/c/Doxyfile @@ -0,0 +1,33 @@ +PROJECT_NAME = Serd +PROJECT_BRIEF = "A lightweight library for RDF storage and serialisation" + +QUIET = YES +WARN_AS_ERROR = NO +WARN_IF_UNDOCUMENTED = NO +WARN_NO_PARAMDOC = NO + +JAVADOC_AUTOBRIEF = YES + +CASE_SENSE_NAMES = YES +HIDE_IN_BODY_DOCS = YES +REFERENCES_LINK_SOURCE = NO + +GENERATE_HTML = NO +GENERATE_LATEX = NO +GENERATE_XML = YES +XML_PROGRAMLISTING = NO +SHOW_FILES = NO + +MACRO_EXPANSION = YES +PREDEFINED = SERD_ALLOCATED \ + SERD_API \ + SERD_CONST_FUNC= \ + SERD_DEPRECATED_BY(x)= \ + SERD_DISABLE_DEPRECATED \ + SERD_NONNULL= \ + SERD_NULLABLE= \ + SERD_PURE_FUNC= + +INPUT = ../../include/serd/serd.h + +OUTPUT_DIRECTORY = . diff --git a/doc/c/index.rst b/doc/c/index.rst new file mode 100644 index 00000000..3fe14ca6 --- /dev/null +++ b/doc/c/index.rst @@ -0,0 +1,5 @@ +.. toctree:: + + serd + overview + reference diff --git a/doc/c/overview.rst b/doc/c/overview.rst new file mode 100644 index 00000000..95862368 --- /dev/null +++ b/doc/c/overview.rst @@ -0,0 +1,22 @@ +######## +Overview +######## + +.. default-domain:: c +.. highlight:: c + +The API revolves around two main types: the :doc:`api/reader`, +which reads text and fires callbacks, +and the :doc:`api/writer`, +which writes text when driven by corresponding functions. +Both work in a streaming fashion but still support pretty-printing, +so the pair can be used to pretty-print, translate, +or otherwise process arbitrarily large documents very quickly. +The context of a stream is tracked by the :doc:`api/env`, +which stores the current base URI and set of namespace prefixes. + +The complete API is declared in ``serd.h``: + +.. code-block:: c + + #include <serd/serd.h> diff --git a/doc/c/reference.rst b/doc/c/reference.rst new file mode 100644 index 00000000..70de0603 --- /dev/null +++ b/doc/c/reference.rst @@ -0,0 +1,11 @@ +############# +API Reference +############# + +This section contains the generated documentation for all symbols in the public +API. + +.. toctree:: + + api/serd + diff --git a/doc/c/wscript b/doc/c/wscript new file mode 100644 index 00000000..1132f197 --- /dev/null +++ b/doc/c/wscript @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +def build(bld): + dox_to_sphinx = bld.path.find_node("../../scripts/dox_to_sphinx.py") + index_xml = bld.path.get_bld().make_node("xml/index.xml") + + files = [ + ("../../resources/serd.svg", "sphinx/_static/serd.svg"), + ("../_static/custom.css", "sphinx/_static/custom.css"), + ("../_templates/about.html", "sphinx/_templates/about.html"), + ("../serd.rst", "sphinx/serd.rst"), + ("index.rst", "sphinx/index.rst"), + ("overview.rst", "sphinx/overview.rst"), + ("reference.rst", "sphinx/reference.rst"), + ] + + # Run Doxygen to generate XML documentation + bld(features="doxygen", doxyfile="Doxyfile") + + # Substitute variables to make Sphinx configuration file + bld(features="subst", + source="../conf.py.in", + target="sphinx/conf.py", + SERD_VERSION=bld.env.SERD_VERSION) + + # Copy static documentation files to Sphinx build directory + for f in files: + bld(features="subst", is_copy=True, source=f[0], target=f[1]) + + # Generate Sphinx markup from Doxygen XML + bld.add_group() + bld(rule="${PYTHON} " + dox_to_sphinx.abspath() + " -f ${SRC} ${TGT}", + source=index_xml, + target="sphinx/api/") + + # Run Sphinx to generate HTML documentation + doc_dir = bld.env.DOCDIR + "/serd-%s/" % bld.env.SERD_MAJOR_VERSION + bld(features="sphinx", + sphinx_source=bld.path.get_bld().make_node("sphinx"), + sphinx_output_format="singlehtml", + sphinx_options=["-E", "-q"], + install_path=doc_dir + "c/singlehtml/") diff --git a/doc/conf.py.in b/doc/conf.py.in new file mode 100644 index 00000000..224b6e77 --- /dev/null +++ b/doc/conf.py.in @@ -0,0 +1,86 @@ +# Project information + +project = "Serd" +copyright = "2020, David Robillard" +author = "David Robillard" +release = "@SERD_VERSION@" + +# General configuration + +language = "en" + +extensions = [ + # 'sphinx_rtd_theme', +] + +# Enable nitpicky mode to get warnings about broken links +# Unfortunately this means we need to explicitly ignore everything external +nitpicky = True +_opaque = [ + "FILE", + "SerdByteSinkImpl", + "SerdByteSourceImpl", + "SerdCursorImpl", + "SerdEnvImpl", + "SerdIterImpl", + "SerdModelImpl", + "SerdNodeImpl", + "SerdNodesImpl", + "SerdRangeImpl", + "SerdReaderImpl", + "SerdSinkImpl", + "SerdStatementImpl", + "SerdWorldImpl", + "SerdWriterImpl", + "int64_t", + "size_t", + "uint32_t", + "uint8_t", + "uintptr_t", + "va_list", +] + +nitpick_ignore = list(map(lambda x: ("c:identifier", x), _opaque)) + +templates_path = ["_templates"] + +pygments_style = "friendly" + +# HTML output + +exclude_patterns = ["xml"] +html_static_path = ["_static"] + +html_theme = "alabaster" +# html_theme = "sphinx_rtd_theme" + +if html_theme == "alabaster": + + html_theme_options = { + "description": "A lightweight library for RDF storage and serialisation", + "donate_url": "http://drobilla.net/pages/donate.html", + # "github_repo": "serd", + # "github_user": "lv2", + "logo": "serd.svg", + "logo_name": True, + "logo_text_align": "center", + "page_width": "80em - 20em", + "sidebar_width": "24em", + } + + html_sidebars = { + "**": [ + "about.html", + "localtoc.html", + "donate.html", + ] + } + +elif html_theme == "sphinx_rtd_theme": + + html_theme_options = { + "sticky_navigation": False, + "collapse_navigation": False, + "navigation_depth": 4, + "display_version": True, + } diff --git a/doc/footer.html b/doc/footer.html deleted file mode 100644 index 0dc69197..00000000 --- a/doc/footer.html +++ /dev/null @@ -1,20 +0,0 @@ -<!-- HTML footer for doxygen 1.8.15--> -<!-- start footer part --> -<!--BEGIN GENERATE_TREEVIEW--> -<div id="nav-path" class="navpath"><!-- id is needed for treeview function! --> - <ul> - $navpath - <li class="footer">$generatedby - <a href="http://www.doxygen.org/index.html">Doxygen $doxygenversion</li> - </ul> -</div> -<!--END GENERATE_TREEVIEW--> -<!--BEGIN !GENERATE_TREEVIEW--> -<div id="footer"> - <address class="footer">$generatedby - <a href="http://www.doxygen.org/">Doxygen</a> $doxygenversion - </address> -</div> -<!--END !GENERATE_TREEVIEW--> -</body> -</html> diff --git a/doc/header.html b/doc/header.html deleted file mode 100644 index b907bc8b..00000000 --- a/doc/header.html +++ /dev/null @@ -1,46 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> - <!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME--> - <!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME--> - <link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> - $extrastylesheet - </head> - <body> - <div id="top"><!-- do not remove this div, it is closed by doxygen! --> - - <!--BEGIN TITLEAREA--> - <div id="titlearea"> - <div id="header"> - <div id="titlebox"> - <!--BEGIN PROJECT_LOGO--> - <div id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></div> - <!--END PROJECT_LOGO--> - <!--BEGIN PROJECT_NAME--> - <h1 id="title">$projectname</h1> - <!--END PROJECT_NAME--> - <!--BEGIN PROJECT_BRIEF--> - <div id="shortdesc">$projectbrief</div> - <!--END PROJECT_BRIEF--> - </div> - <div id="metabox"> - <table id="meta"> - <!--BEGIN PROJECT_NUMBER--> - <tr><th>Version</th><td>$projectnumber</td></tr> - <!--END PROJECT_NUMBER--> - </table> - </div> - </div> - </div> - <!--END TITLEAREA--> - <!-- end header part --> - - <!-- Fake static menu from Doxygen 1.8.15 --> - <div id="staticnavrow" class="tabs"> - <ul class="tablist"> - <li><a href="index.html"><span>Main Page</span></a></li> - <li><a href="modules.html"><span>Modules</span></a></li> - <li><a href="annotated.html"><span>Data Structures</span></a></li> - </ul> - </div> diff --git a/doc/index.html.in b/doc/index.html.in deleted file mode 100644 index 55d2d1e7..00000000 --- a/doc/index.html.in +++ /dev/null @@ -1,35 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> - <title>Serd</title> - <link href="doc/html/style.css" rel="stylesheet" type="text/css" /> - </head> - <body> - <div id="top"> - <div id="titlearea"> - <div id="header"> - <div id="titlebox"> - <h1 id="title">Serd</h1> - <div id="shortdesc">A lightweight library for RDF storage and serialisation</div> - </div> - <div id="metabox"> - <table id="meta"> - <tr><th>Version</th><td>@SERD_VERSION@</td></tr> - </table> - </div> - </div> - </div> - - <div id="navrow1" class="tabs" /> - <ul class="tablist" /> - </div> - - <ul> - <li><a href="doc/html/index.html">API Documentation</a></li> - <li><a href="coverage/index.html">Test Coverage</a></li> - </ul> - - <div id="footer" /> - </body> -</html> diff --git a/doc/layout.xml b/doc/layout.xml deleted file mode 100644 index 4744245e..00000000 --- a/doc/layout.xml +++ /dev/null @@ -1,194 +0,0 @@ -<doxygenlayout version="1.0"> - <!-- Generated by doxygen 1.8.13 --> - <!-- Navigation index tabs for HTML output --> - <navindex> - <tab type="mainpage" visible="yes" title=""/> - <tab type="pages" visible="yes" title="" intro=""/> - <tab type="modules" visible="yes" title="" intro=""/> - <tab type="namespaces" visible="yes" title=""> - <tab type="namespacelist" visible="yes" title="" intro=""/> - <tab type="namespacemembers" visible="yes" title="" intro=""/> - </tab> - <tab type="classes" visible="yes" title=""> - <tab type="classlist" visible="yes" title="" intro=""/> - <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> - <tab type="hierarchy" visible="yes" title="" intro=""/> - <tab type="classmembers" visible="yes" title="" intro=""/> - </tab> - <tab type="files" visible="yes" title=""> - <tab type="filelist" visible="yes" title="" intro=""/> - <tab type="globals" visible="yes" title="" intro=""/> - </tab> - <tab type="examples" visible="yes" title="" intro=""/> - </navindex> - - <!-- Layout definition for a class page --> - <class> - <briefdescription visible="yes"/> - <detaileddescription title=""/> - <includes visible="$SHOW_INCLUDE_FILES"/> - <inheritancegraph visible="$CLASS_GRAPH"/> - <collaborationgraph visible="$COLLABORATION_GRAPH"/> - <memberdecl> - <nestedclasses visible="yes" title=""/> - <publictypes title=""/> - <services title=""/> - <interfaces title=""/> - <publicslots title=""/> - <signals title=""/> - <publicmethods title=""/> - <publicstaticmethods title=""/> - <publicattributes title=""/> - <publicstaticattributes title=""/> - <protectedtypes title=""/> - <protectedslots title=""/> - <protectedmethods title=""/> - <protectedstaticmethods title=""/> - <protectedattributes title=""/> - <protectedstaticattributes title=""/> - <packagetypes title=""/> - <packagemethods title=""/> - <packagestaticmethods title=""/> - <packageattributes title=""/> - <packagestaticattributes title=""/> - <properties title=""/> - <events title=""/> - <privatetypes title=""/> - <privateslots title=""/> - <privatemethods title=""/> - <privatestaticmethods title=""/> - <privateattributes title=""/> - <privatestaticattributes title=""/> - <friends title=""/> - <related title="" subtitle=""/> - <membergroups visible="yes"/> - </memberdecl> - <memberdef> - <inlineclasses title=""/> - <typedefs title=""/> - <enums title=""/> - <services title=""/> - <interfaces title=""/> - <constructors title=""/> - <functions title=""/> - <related title=""/> - <variables title=""/> - <properties title=""/> - <events title=""/> - </memberdef> - <allmemberslink visible="yes"/> - <usedfiles visible="$SHOW_USED_FILES"/> - <authorsection visible="yes"/> - </class> - - <!-- Layout definition for a namespace page --> - <namespace> - <briefdescription visible="yes"/> - <detaileddescription title=""/> - <memberdecl> - <nestednamespaces visible="yes" title=""/> - <constantgroups visible="yes" title=""/> - <classes visible="yes" title=""/> - <typedefs title=""/> - <enums title=""/> - <functions title=""/> - <variables title=""/> - <membergroups visible="yes"/> - </memberdecl> - <memberdef> - <inlineclasses title=""/> - <typedefs title=""/> - <enums title=""/> - <functions title=""/> - <variables title=""/> - </memberdef> - <authorsection visible="yes"/> - </namespace> - - <!-- Layout definition for a file page --> - <file> - <briefdescription visible="yes"/> - <includes visible="$SHOW_INCLUDE_FILES"/> - <includegraph visible="$INCLUDE_GRAPH"/> - <includedbygraph visible="$INCLUDED_BY_GRAPH"/> - <sourcelink visible="yes"/> - <memberdecl> - <classes visible="yes" title=""/> - <namespaces visible="yes" title=""/> - <constantgroups visible="yes" title=""/> - <defines title=""/> - <typedefs title=""/> - <enums title=""/> - <functions title=""/> - <variables title=""/> - <membergroups visible="yes"/> - </memberdecl> - <detaileddescription title=""/> - <memberdef> - <inlineclasses title=""/> - <defines title=""/> - <typedefs title=""/> - <enums title=""/> - <functions title=""/> - <variables title=""/> - </memberdef> - <authorsection/> - </file> - - <!-- Layout definition for a group page --> - <group> - <briefdescription visible="yes"/> - <detaileddescription title=""/> - <groupgraph visible="$GROUP_GRAPHS"/> - <memberdecl> - <nestedgroups visible="yes" title=""/> - <dirs visible="yes" title=""/> - <files visible="yes" title=""/> - <namespaces visible="yes" title=""/> - <classes visible="yes" title=""/> - <defines title=""/> - <typedefs title=""/> - <enums title=""/> - <enumvalues title=""/> - <functions title=""/> - <variables title=""/> - <signals title=""/> - <publicslots title=""/> - <protectedslots title=""/> - <privateslots title=""/> - <events title=""/> - <properties title=""/> - <friends title=""/> - <membergroups visible="yes"/> - </memberdecl> - <memberdef> - <pagedocs/> - <inlineclasses title=""/> - <defines title=""/> - <typedefs title=""/> - <enums title=""/> - <enumvalues title=""/> - <functions title=""/> - <variables title=""/> - <signals title=""/> - <publicslots title=""/> - <protectedslots title=""/> - <privateslots title=""/> - <events title=""/> - <properties title=""/> - <friends title=""/> - </memberdef> - <authorsection visible="yes"/> - </group> - - <!-- Layout definition for a directory page --> - <directory> - <briefdescription visible="yes"/> - <directorygraph visible="yes"/> - <memberdecl> - <dirs visible="yes"/> - <files visible="yes"/> - </memberdecl> - <detaileddescription title=""/> - </directory> -</doxygenlayout> diff --git a/doc/mainpage.md b/doc/mainpage.md deleted file mode 100644 index 33ba7ec0..00000000 --- a/doc/mainpage.md +++ /dev/null @@ -1,15 +0,0 @@ -Serd is a lightweight and dependency-free C library for RDF serialisation which -supports [Turtle], [NTriples], [NQuads], and [TriG]. - -The complete API is documented in the [serd](@ref serd) group. It revolves -around two main types: @ref SerdReader, which reads text and fires callbacks, -and @ref SerdWriter, which writes text when driven by corresponding functions. -Both work in a streaming fashion but still support pretty-printing, so the pair -can be used to pretty-print, translate, or otherwise process arbitrarily large -documents very quickly. The stream context is maintained by @ref SerdEnv, -which stores the current base URI and set of namespace prefixes. - -[Turtle]: http://www.w3.org/TR/turtle/ -[NTriples]: http://www.w3.org/TR/n-triples/ -[NQuads]: http://www.w3.org/TR/n-quads/ -[TriG]: http://www.w3.org/TR/trig/ diff --git a/doc/serd.rst b/doc/serd.rst new file mode 100644 index 00000000..e90a8368 --- /dev/null +++ b/doc/serd.rst @@ -0,0 +1,11 @@ +#### +Serd +#### + +Serd is a lightweight and dependency-free C library for RDF serialisation which +supports Turtle_, NTriples_, NQuads_, and TriG_. + +.. _Turtle: http://www.w3.org/TR/turtle/ +.. _NTriples: http://www.w3.org/TR/n-triples/ +.. _NQuads: http://www.w3.org/TR/n-quads/ +.. _TriG: http://www.w3.org/TR/trig/ diff --git a/doc/style.css b/doc/style.css deleted file mode 100644 index 2092dab0..00000000 --- a/doc/style.css +++ /dev/null @@ -1,808 +0,0 @@ -body { - background: #FFF; - color: #222; - font-style: normal; - line-height: 1.6em; - margin-left: auto; - margin-right: auto; - padding: 1em; - max-width: 60em; - font-family: "SF Pro Text", Verdana, "DejaVu Sans", sans-serif; - text-rendering: optimizeLegibility; -} - -h1 { - font-size: 1.68em; - font-weight: 500; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - line-height: 2em; - margin: 0 0 0.25em 0; -} - -h2 { - line-height: 1.68em; - font-size: 1.41em; - font-weight: 600; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - margin: 1.25em 0 0.5em 0; -} - -h3 { - line-height: 1.41em; - font-size: 1.18em; - font-weight: 600; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - margin: 1.25em 0 0.5em 0; -} - -h4 { - line-height: 1.18em; - font-size: 1em; - font-weight: 600; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - margin: 1.25em 0 0.5em 0; -} - -h5, h6 { - font-size: 0.7em; - font-weight: 600; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - margin: 1.25em 0 0.5em 0; -} - -a { - color: #546E00; - text-decoration: none; -} - -h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { - color: #444; -} - -a:hover { - text-decoration: underline; -} - -h1 a:link, h2 a:link, h3 a:link, h4 a:link, h5 a:link, h6 a:link { - color: #444; -} - -h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited { - color: #444; -} - -p { - margin: 0.5em 0 0.5em 0; -} - -dt { - font-weight: 600; -} - -dd { - margin-left: 2em; -} - -caption { - font-weight: 700; -} - -.title, #projectname { - line-height: 1.0125em; - margin: 0.75em 0 0 0; -} - -.titlearea .header .titlebox, #projectname { - font-size: 1.68em; - font-weight: 400; - margin-bottom: 0.25em; - margin-top: 0; -} - -#header { - padding: 0 0 0.5em 0; - border-bottom: 1px solid #EEE; -} - -.header .headertitle .title { - line-height: 1.68em; - font-size: 1.68em; - font-weight: 600; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; -} - -.ingroups { - display: none; -} - -.title .ingroups a { - font-size: small; - margin-left: 1em; -} - -#titlebox, #metabox { - display: inline-block; -} - -#titlebox { - display: inline-block; - width: 75%; - left: 0; - top: 0; -} - -#title { - margin-bottom: 0.25em; - line-height: 1.25em; - font-size: 2.5em; - color: #333; - font-weight: 600; -} - -.PageDoc { - margin-top: 1.5em; -} - -.PageDoc .header .headertitle .title { - display: none; -} - -#shortdesc { - margin: 0; - color: #666; - display: inline-block; - font-style: italic; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - padding: 0; -} - -#titlearea { - margin: 0.25em auto 0 auto; - padding: 0; - position: relative; - clear: both; - line-height: 1em; -} - -.legend { - font-size: small; - text-align: center; -} - -.version { - font-size: small; - text-align: center; -} - -div.qindex,div.navtab { - background-color: #EBEFF6; - border: 1px solid #A3B4D7; - text-align: center; - margin: 2px; - padding: 2px; -} - -div.navtab { - margin-right: 15px; -} - -.contents a:visited { - color: #344E00; -} - -a.qindexHL { - background-color: #9CAFD4; - color: #FFF; - border: 1px double #869DCA; -} - -code { - color: #444; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; -} - -dl.el { - margin-left: -1cm; -} - -.fragment { - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; -} - -pre.fragment { - border: 1px solid #C4C4C4; - background-color: #F9F9F9; - padding: 0.5em; - overflow: auto; -} - -div.ah { - background-color: #000; - font-weight: 700; - color: #FFF; - margin-bottom: 3px; - margin-top: 3px; - padding: 0.2em; - border: thin solid #333; -} - -div.groupHeader { - margin-left: 16px; - margin-top: 12px; - margin-bottom: 6px; - font-weight: 700; -} - -a + h2.groupheader { - display: none; -} - -div.groupText { - margin-left: 16px; - font-style: italic; -} - -div.contents, #content { - max-width: 60em; - margin-left: auto; - margin-right: auto; -} - -.groupheader + p { - font-style: italic; - color: #666; - margin: 0 0 1em 0; -} - -td.indexkey { - background-color: #EBEFF6; - font-weight: 700; - border: 1px solid #C4CFE5; - margin: 2px 0; - padding: 2px 10px; -} - -td.indexvalue { - background-color: #EBEFF6; - border: 1px solid #C4CFE5; - padding: 2px 10px; - margin: 2px 0; -} - -table.memname { - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; - border-spacing: 0; -} - -table.memname tbody tr:last-child { - display: none; -} - -table.memname tbody tr:only-child { - display: table-cell; -} - -table.memname tbody tr:nth-last-child(2)::after { - content: ")"; -} - -tr.memlist { - background-color: #EEF1F7; -} - -p.formulaDsp { - text-align: center; -} - -img.formulaInl { - vertical-align: middle; -} - -div.center { - text-align: center; - margin-top: 0; - margin-bottom: 0; - padding: 0; -} - -div.center img { - border: 0; -} - -address.footer { - text-align: right; -} - -img.footer { - border: 0; - vertical-align: middle; -} - -span.keyword { - color: #586E75; -} - -span.keywordtype { - color: #546E00; -} - -span.keywordflow { - color: #586E75; -} - -span.comment { - color: #6C71C4; -} - -span.preprocessor { - color: #D33682; -} - -span.stringliteral { - color: #CB4B16; -} - -span.charliteral { - color: #CB4B16; -} - -td.tiny { - font-size: x-small; -} - -.dirtab { - padding: 4px; - border-collapse: collapse; - border: 1px solid #A3B4D7; -} - -th.dirtab { - background: #EBEFF6; - font-weight: 700; -} - -hr { - height: 0; - border: none; - border-top: 1px solid #DDD; - margin: 2em 0; -} - -#footer { - bottom: 0; - clear: both; - font-size: x-small; - margin: 2em 0 0; - padding: 0 1em 1em 1em; - vertical-align: top; - color: #888; -} - -td.ititle { - padding-bottom: 0.75em; -} - -table.memberdecls { - border-spacing: 0.125em; - line-height: 1.3em; -} - -.mdescLeft,.mdescRight,.memItemLeft,.memItemRight,.memTemplItemLeft,.memTemplItemRight,.memTemplParams { - margin: 0; - padding: 0; -} - -.mdescLeft,.mdescRight { - color: #555; -} - -.memItemLeft,.memItemRight,.memTemplParams { - border: 0; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; -} - -.memItemLeft,.memTemplItemLeft { - white-space: nowrap; - padding-left: 2em; -} - -.memItemLeft a.el { - font-weight: bold; -} - -.memTemplParams { - color: #464646; - white-space: nowrap; -} - -td.memSeparator { - display: none; -} - -td.mlabels-right { - color: #B4C342; - font-weight: normal; - margin-left: 1em; -} - -.memtitle { - display: none; -} - -.memtemplate { - color: #888; - font-style: italic; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; - font-size: small; -} - -.memnav { - background-color: #EEE; - border: 1px solid #B4C342; - text-align: center; - margin: 2px; - margin-right: 15px; - padding: 2px; -} - -.memitem { - padding: 0.5em 0.5em 0.25em 0.5em; - margin: 1em 0 2em 0; -} - -.memproto { - border-bottom: 1px solid #EEE; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; - font-size: 1.09em; - font-weight: 600; - line-height: 1.41em; - margin-bottom: 0.25em; - padding-bottom: 0.125em; -} - -.memproto .paramname { - font-style: normal; - padding-right: 0.25em; -} - -.memdoc { - padding: 0; -} - -.memdoc > p:first-child, .memdoc .textblock > p:first-child { - font-style: italic; - color: #444; - margin-bottom: 0.75em; -} - -.paramkey { - text-align: right; -} - -.paramtype { - color: #666; - padding: 0 0.25em 0 0.25em; - white-space: nowrap; -} - -.params .paramname { - color: #111; - white-space: nowrap; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; - font-style: italic; - padding-right: 0.5em; - vertical-align: top; -} - -.fieldname { - color: #000; -} - -.fieldtable { - margin-top: 1.0em; - border-collapse: collapse; -} - -.fieldtable tbody tr:first-child { - display: none; -} - -td.fieldname { - vertical-align: top; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; -} - -td.fielddoc { - padding: 0.125em 0.5em 0 0.25em; - vertical-align: top; -} - -.fieldtable tbody tr td { - border-top: 1px dashed #DDD; - border-bottom: 1px dashed #DDD; -} - -td.fieldtype { - color: #666; - padding: 0 0.5em 0 0; - vertical-align: top; - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; -} - -td.fielddoc p { - margin: 0; - padding: 0 0.5em 0 0; -} - -p.reference { - font-size: x-small; - font-style: italic; -} - -.ftvtree { - font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif; - margin: 0; -} - -.directory { - margin: 0.5em; -} - -.directory h3 { - margin: 0; - margin-top: 1em; - font-size: 11pt; -} - -.directory > h3 { - margin-top: 0; -} - -.directory p { - margin: 0; - white-space: nowrap; -} - -.directory div { - display: none; - margin: 0; -} - -.directory img { - vertical-align: -30%; -} - -td.entry { - font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif; - font-weight: 400; - padding-right: 1em; -} - -.arrow { - color: #CCC; - user-select: none; - font-size: 80%; - display: inline-block; - width: 16px; - height: 22px; - vertical-align: top; -} - -td.entry b { - font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif; - font-weight: 400; - font-size: 130%; -} - -.directory-alt { - font-size: 100%; - font-weight: bold; -} - -.directory-alt h3 { - margin: 0; - margin-top: 1em; - font-size: 11pt; -} - -.directory-alt > h3 { - margin-top: 0; -} - -.directory-alt p { - margin: 0; - white-space: nowrap; -} - -.directory-alt div { - display: none; - margin: 0; -} - -.directory-alt img { - vertical-align: -30%; -} - -div.dynheader { - margin-top: 8px; -} - -address { - font-style: normal; - color: #444; -} - -table.doxtable { - border-collapse: collapse; - margin: 0.5em; -} - -table.doxtable td,table.doxtable th { - border: 1px solid #DDD; - padding: 3px 7px 2px; -} - -table.doxtable th { - background-color: #F3F3F3; - color: #000; - padding-bottom: 4px; - padding-top: 5px; - text-align: left; - font-weight: bold; -} - -.tabsearch { - top: 0; - left: 10px; - height: 36px; - z-index: 101; - overflow: hidden; - font-size: 13px; -} - -div.navpath { - color: #DDD; -} - -.navpath ul { - overflow: hidden; - margin: 0; - padding: 0; -} - -.navpath li { - float: left; - padding-left: 0; - margin-left: 0.5em; - padding-right: 1em; -} - -.navpath a { - display: block; - text-decoration: none; - outline: none; -} - -div.summary { - font-size: small; - font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif; - margin: 0; - padding: 0.25em 0; - display: none; -} - -div.summary a { - white-space: nowrap; -} - -#metabox { - display: inline-block; - font-size: x-small; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - position: absolute; - right: 0; - bottom: 0.25em; - color: #666; - font-style: italic; -} - -#meta { - border-style: hidden; - margin-right: 0.25em; -} - -#meta tr, #meta th, #meta td { - background-color: transparent; - border: 0; - margin: 0; - font-weight: normal; -} - -#meta th { - text-align: right; -} - -#meta th::after { - content: ":"; -} - -div.line { - font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed; - line-height: 1.4em; - white-space: pre-wrap; -} - -.glow { - background-color: #2AA198; - box-shadow: 0 0 10px #2AA198; -} - -span.lineno { - padding-right: 4px; - text-align: right; - border-right: 2px solid #546E00; - background-color: #E8E8E8; - white-space: pre; -} - -span.lineno a { - background-color: #D8D8D8; -} - -span.lineno a:hover { - background-color: #C8C8C8; -} - -.tabs, .tabs2, .navpath { - padding: 0.25em 0; - font-size: small; - font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif; - margin: 0; -} - -th { - text-align: left; - font-size: 110%; - font-weight: 500; -} - -.mlabel { - padding: 0.125em; -} - -#navrow1, #navrow2 { - /* Disable menu from Doxygen 1.8.15, it is faked in the template */ - display: none; -} - -.tablist { - margin: 0; - padding: 0; - display: table; -} - -.tablist li { - display: table-cell; - line-height: 2em; - list-style: none; - border-bottom: 0; -} - -.tablist a { - display: block; - padding: 0 1em 0 0; - text-decoration: none; - outline: none; -} - -.tabs3 .tablist a { - padding: 0 10px; -} - -.tablist li.current a { - color: #222; -} - -span.icon { - display: none; -} diff --git a/include/serd/serd.h b/include/serd/serd.h index 12b9f44e..6dc33441 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -258,7 +258,7 @@ void serd_free(void* SERD_NULLABLE ptr); /** - @name String Utilities + @defgroup string String Utilities @{ */ @@ -312,7 +312,7 @@ serd_base64_decode(const uint8_t* SERD_NONNULL str, /** @} - @name Byte Streams + @defgroup streams Byte Streams @{ */ @@ -349,7 +349,7 @@ typedef size_t (*SerdSink)(const void* SERD_NONNULL buf, /** @} - @name URI + @defgroup uri URI @{ */ @@ -427,7 +427,7 @@ serd_uri_serialise_relative(const SerdURI* SERD_NONNULL uri, /** @} - @name Node + @defgroup node Node @{ */ @@ -585,7 +585,7 @@ serd_node_free(SerdNode* SERD_NULLABLE node); /** @} - @name Event Handlers + @defgroup event Event Handlers @{ */ @@ -642,7 +642,7 @@ typedef SerdStatus (*SerdEndSink)(void* SERD_NULLABLE handle, /** @} - @name Environment + @defgroup env Environment @{ */ @@ -728,7 +728,7 @@ serd_env_foreach(const SerdEnv* SERD_NONNULL env, /** @} - @name Reader + @defgroup reader Reader @{ */ @@ -880,7 +880,7 @@ serd_reader_free(SerdReader* SERD_NULLABLE reader); /** @} - @name Writer + @defgroup writer Writer @{ */ diff --git a/resources/serd.svg b/resources/serd.svg new file mode 100644 index 00000000..570a4f98 --- /dev/null +++ b/resources/serd.svg @@ -0,0 +1,20 @@ +<svg height="128" viewBox="0 0 33.867 33.867" width="128" xmlns="http://www.w3.org/2000/svg"> + <g stroke="#444" stroke-linejoin="round" stroke-width="1.058"> + <path d="M26.726 7.14h6.529V.613h-6.529z" fill="#fff" fill-rule="evenodd"/> + <path d="M26.726 7.14h6.529V.613h-6.529z" fill="none"/> + <path d="M13.67 7.14h6.528V.613h-6.529z" fill="#fff" fill-rule="evenodd"/> + <path d="M13.67 7.14h6.528V.613h-6.529z" fill="none"/> + <path d="M.612 7.14h6.529V.613H.612z" fill="#fff" fill-rule="evenodd"/> + <path d="M.612 7.14h6.529V.613H.612z" fill="none"/> + <path d="M26.726 33.255h6.529v-6.529h-6.529z" fill="#fff" fill-rule="evenodd"/> + <path d="M26.726 33.255h6.529v-6.529h-6.529z" fill="none"/> + <path d="M13.67 33.255h6.528v-6.529h-6.529z" fill="#fff" fill-rule="evenodd"/> + <path d="M13.67 33.255h6.528v-6.529h-6.529z" fill="none"/> + <path d="M.612 33.255h6.529v-6.529H.612z" fill="#fff" fill-rule="evenodd"/> + <path d="M.612 33.255h6.529v-6.529H.612z" fill="none"/> + <path d="M13.67 20.198h6.528v-6.529h-6.529z" fill="#fff" fill-rule="evenodd"/> + <g fill="none"> + <path d="M13.67 20.198h6.528v-6.529h-6.529zM20.198 3.876h6.528M7.14 3.876h6.53M7.14 7.14l6.53 6.53M20.198 20.198l6.528 6.528M7.14 29.99h6.53M20.198 29.99h6.528"/> + </g> + </g> +</svg> diff --git a/scripts/dox_to_sphinx.py b/scripts/dox_to_sphinx.py new file mode 100755 index 00000000..c9d401cc --- /dev/null +++ b/scripts/dox_to_sphinx.py @@ -0,0 +1,674 @@ +#!/usr/bin/env python3 + +# Copyright 2020 David Robillard <d@drobilla.net> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +""" +Write Sphinx markup from Doxygen XML. + +Takes a path to a directory of XML generated by Doxygen, and emits a directory +with a reStructuredText file for every documented symbol. +""" + +import argparse +import os +import sys +import textwrap +import xml.etree.ElementTree + +__author__ = "David Robillard" +__date__ = "2020-11-18" +__email__ = "d@drobilla.net" +__license__ = "ISC" +__version__ = __date__.replace("-", ".") + + +def load_index(index_path): + """ + Load the index from XML. + + :returns: A dictionary from ID to skeleton records with basic information + for every documented entity. Some records have an ``xml_filename`` key + with the filename of a definition file. These files will be loaded later + to flesh out the records in the index. + """ + + root = xml.etree.ElementTree.parse(index_path).getroot() + index = {} + + for compound in root: + compound_id = compound.get("refid") + compound_kind = compound.get("kind") + compound_name = compound.find("name").text + if compound_kind in ["dir", "file", "page"]: + continue + + # Add record for compound (compounds appear only once in the index) + assert compound_id not in index + index[compound_id] = { + "kind": compound_kind, + "name": compound_name, + "xml_filename": compound_id + ".xml", + "children": [], + } + + name_prefix = ( + ("%s::" % compound_name) if compound_kind == "namespace" else "" + ) + + for child in compound.findall("member"): + if child.get("refid") in index: + assert compound_kind == "group" + continue + + # Everything has a kind and a name + child_record = { + "kind": child.get("kind"), + "name": name_prefix + child.find("name").text, + } + + if child.get("kind") == "enum": + # Enums are not compounds, but we want to resolve the parent of + # their values so they are not written as top level documents + child_record["children"] = [] + + if child.get("kind") == "enumvalue": + # Remove namespace prefix + child_record["name"] = child.find("name").text + + index[child.get("refid")] = child_record + + return index + + +def resolve_index(index, root): + """ + Walk a definition document and extend the index for linking. + + This does two things: sets the "parent" and "children" fields of all + applicable records, and sets the "strong" field of enums so that the + correct Sphinx role can be used when referring to them. + """ + + def add_child(index, parent_id, child_id): + parent = index[parent_id] + child = index[child_id] + + if child["kind"] == "enumvalue": + assert parent["kind"] == "enum" + assert "parent" not in child or child["parent"] == parent_id + child["parent"] = parent_id + + else: + if parent["kind"] in ["class", "struct", "union"]: + assert "parent" not in child or child["parent"] == parent_id + child["parent"] = parent_id + + if child_id not in parent["children"]: + parent["children"] += [child_id] + + compound = root.find("compounddef") + compound_kind = compound.get("kind") + + if compound_kind == "group": + for subgroup in compound.findall("innergroup"): + add_child(index, compound.get("id"), subgroup.get("refid")) + + for klass in compound.findall("innerclass"): + add_child(index, compound.get("id"), klass.get("refid")) + + for section in compound.findall("sectiondef"): + if section.get("kind").startswith("private"): + for member in section.findall("memberdef"): + if member.get("id") in index: + del index[member.get("id")] + else: + for member in section.findall("memberdef"): + member_id = member.get("id") + add_child(index, compound.get("id"), member_id) + + if member.get("kind") == "enum": + index[member_id]["strong"] = member.get("strong") == "yes" + for value in member.findall("enumvalue"): + add_child(index, member_id, value.get("id")) + + +def sphinx_role(record, lang): + """ + Return the Sphinx role used for a record. + + This is used for the description directive like ".. c:function::", and + links like ":c:func:`foo`. + """ + + kind = record["kind"] + + if kind in ["class", "function", "namespace", "struct", "union"]: + return lang + ":" + kind + + if kind == "define": + return "c:macro" + + if kind == "enum": + return lang + (":enum-class" if record["strong"] else ":enum") + + if kind == "typedef": + return lang + ":type" + + if kind == "enumvalue": + return lang + ":enumerator" + + if kind == "variable": + return lang + (":member" if "parent" in record else ":var") + + raise RuntimeError("No known role for kind '%s'" % kind) + + +def child_identifier(lang, parent_name, child_name): + """ + Return the identifier for an enum value or struct member. + + Sphinx, for some reason, uses a different syntax for this in C and C++. + """ + + separator = "::" if lang == "cpp" else "." + + return "%s%s%s" % (parent_name, separator, child_name) + + +def link_markup(index, lang, refid): + """Return a Sphinx link for a Doxygen reference.""" + + record = index[refid] + kind, name = record["kind"], record["name"] + role = sphinx_role(record, lang) + + if kind in ["class", "enum", "struct", "typedef", "union"]: + return ":%s:`%s`" % (role, name) + + if kind == "function": + return ":%s:func:`%s`" % (lang, name) + + if kind == "enumvalue": + parent_name = index[record["parent"]]["name"] + return ":%s:`%s`" % (role, child_identifier(lang, parent_name, name)) + + if kind == "variable": + if "parent" not in record: + return ":%s:var:`%s`" % (lang, name) + + parent_name = index[record["parent"]]["name"] + return ":%s:`%s`" % (role, child_identifier(lang, parent_name, name)) + + raise RuntimeError("Unknown link target kind: %s" % kind) + + +def indent(markup, depth): + """ + Indent markup to a depth level. + + Like textwrap.indent() but takes an integer and works in reST indentation + levels for clarity." + """ + + return textwrap.indent(markup, " " * depth) + + +def heading(text, level): + """ + Return a ReST heading at a given level. + + Follows the style in the Python documentation guide, see + <https://devguide.python.org/documenting/#sections>. + """ + + assert 1 <= level <= 6 + + chars = ("#", "*", "=", "-", "^", '"') + line = chars[level] * len(text) + + return "%s\n%s\n%s\n\n" % (line if level < 3 else "", text, line) + + +def dox_to_rst(index, lang, node): + """ + Convert documentation commands (docCmdGroup) to Sphinx markup. + + This is used to convert the content of descriptions in the documentation. + It recursively parses all children tags and raises a RuntimeError if any + unknown tag is encountered. + """ + + def field_value(markup): + """Return a value for a field as a single line or indented block.""" + if "\n" in markup.strip(): + return "\n" + indent(markup, 1) + + return " " + markup.strip() + + if node.tag == "computeroutput": + # assert len(node) == 0 FIXME + return "``%s``" % node.text + + if node.tag == "itemizedlist": + markup = "" + for item in node.findall("listitem"): + assert len(item) == 1 + markup += "\n- %s" % dox_to_rst(index, lang, item[0]) + + return markup + + if node.tag == "para": + markup = node.text if node.text is not None else "" + for child in node: + markup += dox_to_rst(index, lang, child) + markup += child.tail if child.tail is not None else "" + + return markup.strip() + "\n\n" + + if node.tag == "parameterlist": + markup = "" + for item in node.findall("parameteritem"): + name = item.find("parameternamelist/parametername") + description = item.find("parameterdescription") + assert len(description) == 1 + markup += "\n\n:param %s:%s" % ( + name.text, + field_value(dox_to_rst(index, lang, description[0])), + ) + + return markup + "\n" + + if node.tag == "programlisting": + return "\n.. code-block:: %s\n\n%s" % ( + lang, + indent(plain_text(node), 1), + ) + + if node.tag == "ref": + refid = node.get("refid") + if refid not in index: + sys.stderr.write("warning: Unresolved link: %s\n" % refid) + return node.text + + assert len(node) == 0 + assert len(link_markup(index, lang, refid)) > 0 + return link_markup(index, lang, refid) + + if node.tag == "simplesect": + assert len(node) == 1 + + if node.get("kind") == "return": + return "\n:returns:" + field_value( + dox_to_rst(index, lang, node[0]) + ) + + if node.get("kind") == "see": + return dox_to_rst(index, lang, node[0]) + + raise RuntimeError("Unknown simplesect kind: %s" % node.get("kind")) + + if node.tag == "ulink": + return "`%s <%s>`_" % (node.text, node.get("url")) + + raise RuntimeError("Unknown documentation command: %s" % node.tag) + + +def description_markup(index, lang, node): + """Return the markup for a brief or detailed description.""" + + assert node.tag == "briefdescription" or node.tag == "detaileddescription" + assert not (node.tag == "briefdescription" and len(node) > 1) + assert len(node.text.strip()) == 0 + + return "".join([dox_to_rst(index, lang, child) for child in node]) + + +def set_descriptions(index, lang, definition, record): + """Set a record's brief/detailed descriptions from the XML definition.""" + + for tag in ["briefdescription", "detaileddescription"]: + node = definition.find(tag) + if node is not None: + record[tag] = description_markup(index, lang, node) + + +def set_template_params(node, record): + """Set a record's template_params from the XML definition.""" + + template_param_list = node.find("templateparamlist") + if template_param_list is not None: + params = [] + for param in template_param_list.findall("param"): + if param.find("declname") is not None: + # Value parameter + type_text = plain_text(param.find("type")) + name_text = plain_text(param.find("declname")) + + params += ["%s %s" % (type_text, name_text)] + else: + # Type parameter + params += ["%s" % (plain_text(param.find("type")))] + + record["template_params"] = "%s" % ", ".join(params) + + +def plain_text(node): + """ + Return the plain text of a node with all tags ignored. + + This is needed where Doxygen may include refs but Sphinx needs plain text + because it parses things itself to generate links. + """ + + if node.tag == "sp": + markup = " " + elif node.text is not None: + markup = node.text + else: + markup = "" + + for child in node: + markup += plain_text(child) + markup += child.tail if child.tail is not None else "" + + return markup + + +def local_name(name): + """Return a name with all namespace prefixes stripped.""" + + return name[name.rindex("::") + 2 :] if "::" in name else name + + +def read_definition_doc(index, lang, root): + """Walk a definition document and update described records in the index.""" + + # Set descriptions for the compound itself + compound = root.find("compounddef") + compound_record = index[compound.get("id")] + set_descriptions(index, lang, compound, compound_record) + set_template_params(compound, compound_record) + + if compound.find("title") is not None: + compound_record["title"] = compound.find("title").text.strip() + + # Set documentation for all children + for section in compound.findall("sectiondef"): + if section.get("kind").startswith("private"): + continue + + for member in section.findall("memberdef"): + kind = member.get("kind") + record = index[member.get("id")] + set_descriptions(index, lang, member, record) + set_template_params(member, record) + + if compound.get("kind") in ["class", "struct", "union"]: + assert kind in ["function", "typedef", "variable"] + record["type"] = plain_text(member.find("type")) + + if kind == "enum": + for value in member.findall("enumvalue"): + set_descriptions( + index, lang, value, index[value.get("id")] + ) + + elif kind == "function": + record["prototype"] = "%s %s%s" % ( + plain_text(member.find("type")), + member.find("name").text, + member.find("argsstring").text, + ) + + elif kind == "typedef": + name = local_name(record["name"]) + args_text = member.find("argsstring").text + target_text = plain_text(member.find("type")) + if args_text is not None: # Function pointer + assert target_text[-2:] == "(*" and args_text[0] == ")" + record["type"] = target_text + args_text + record["definition"] = target_text + name + args_text + else: # Normal named typedef + assert target_text is not None + record["type"] = target_text + if member.find("definition").text.startswith("using"): + record["definition"] = "%s = %s" % ( + name, + target_text, + ) + else: + record["definition"] = "%s %s" % ( + target_text, + name, + ) + + elif kind == "variable": + record["definition"] = member.find("definition").text + + +def declaration_string(record): + """ + Return the string that describes a declaration. + + This is what follows the directive, and is in C/C++ syntax, except without + keywords like "typedef" and "using" as expected by Sphinx. For example, + "struct ThingImpl Thing" or "void run(int value)". + """ + + kind = record["kind"] + result = "" + + if "template_params" in record: + result = "template <%s> " % record["template_params"] + + if kind == "function": + result += record["prototype"] + elif kind == "typedef": + result += record["definition"] + elif kind == "variable": + if "parent" in record: + result += "%s %s" % (record["type"], local_name(record["name"])) + else: + result += record["definition"] + elif "type" in record: + result += "%s %s" % (record["type"], local_name(record["name"])) + else: + result += local_name(record["name"]) + + return result + + +def document_markup(index, lang, record): + """Return the complete document that describes some documented entity.""" + + kind = record["kind"] + role = sphinx_role(record, lang) + name = record["name"] + markup = "" + + if name != local_name(name): + markup += ".. cpp:namespace:: %s\n\n" % name[0 : name.rindex("::")] + + # Write top-level directive + markup += ".. %s:: %s\n" % (role, declaration_string(record)) + + # Write main description blurb + markup += "\n" + markup += indent(record["briefdescription"], 1) + markup += indent(record["detaileddescription"], 1) + + assert ( + kind in ["class", "enum", "namespace", "struct", "union"] + or "children" not in record + ) + + # Sphinx C++ namespaces work by setting a scope, they have no content + child_indent = 0 if kind == "namespace" else 1 + + # Write inline children if applicable + markup += "\n" + for child_id in record.get("children", []): + child_record = index[child_id] + child_role = sphinx_role(child_record, lang) + + child_header = ".. %s:: %s\n\n" % ( + child_role, + declaration_string(child_record), + ) + + markup += "\n" + markup += indent(child_header, child_indent) + markup += indent(child_record["briefdescription"], child_indent + 1) + markup += indent(child_record["detaileddescription"], child_indent + 1) + markup += "\n" + + return markup + + +def symbol_filename(name): + """Adapt the name of a symbol to be suitable for use as a filename.""" + + return name.replace("::", "__") + + +def emit_symbols(index, lang, symbol_dir, force): + """Write a description file for every symbol documented in the index.""" + + for record in index.values(): + if ( + record["kind"] in ["group", "namespace"] + or "parent" in record + and index[record["parent"]]["kind"] != "group" + ): + continue + + name = record["name"] + filename = os.path.join(symbol_dir, symbol_filename("%s.rst" % name)) + if not force and os.path.exists(filename): + raise FileExistsError("File already exists: '%s'" % filename) + + with open(filename, "w") as rst: + rst.write(heading(local_name(name), 3)) + rst.write(document_markup(index, lang, record)) + + +def emit_groups(index, output_dir, symbol_dir_name, force): + """Write a description file for every group documented in the index.""" + + for record in index.values(): + if record["kind"] != "group": + continue + + name = record["name"] + filename = os.path.join(output_dir, "%s.rst" % name) + if not force and os.path.exists(filename): + raise FileExistsError("File already exists: '%s'" % filename) + + with open(filename, "w") as rst: + rst.write(heading(record["title"], 2)) + + # Get all child group and symbol names + group_names = [] + symbol_names = [] + for child_id in record["children"]: + child = index[child_id] + if child["kind"] == "group": + group_names += [child["name"]] + else: + symbol_names += [child["name"]] + + # Emit description (document body) + rst.write(record["briefdescription"] + "\n\n") + rst.write(record["detaileddescription"] + "\n\n") + + # Emit TOC + rst.write(".. toctree::\n") + + # Emit groups at the top of the TOC + for group_name in group_names: + rst.write("\n" + indent(group_name, 1)) + + # Emit symbols in sorted order + for symbol_name in sorted(symbol_names): + path = "/".join( + [symbol_dir_name, symbol_filename(symbol_name)] + ) + rst.write("\n" + indent(path, 1)) + + rst.write("\n") + + +def run(index_xml_path, output_dir, symbol_dir_name, language, force): + """Write a directory of Sphinx files from a Doxygen XML directory.""" + + # Build skeleton index from index.xml + xml_dir = os.path.dirname(index_xml_path) + index = load_index(index_xml_path) + + # Load all definition documents + definition_docs = [] + for record in index.values(): + if "xml_filename" in record: + xml_path = os.path.join(xml_dir, record["xml_filename"]) + definition_docs += [xml.etree.ElementTree.parse(xml_path)] + + # Do an initial pass of the definition documents to resolve the index + for root in definition_docs: + resolve_index(index, root) + + # Finally read the documentation from definition documents + for root in definition_docs: + read_definition_doc(index, language, root) + + # Emit output files + symbol_dir = os.path.join(output_dir, symbol_dir_name) + os.makedirs(symbol_dir, exist_ok=True) + emit_symbols(index, language, symbol_dir, force) + emit_groups(index, output_dir, symbol_dir_name, force) + + +if __name__ == "__main__": + ap = argparse.ArgumentParser( + usage="%(prog)s [OPTION]... XML_DIR OUTPUT_DIR", + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + ap.add_argument( + "-f", + "--force", + action="store_true", + help="overwrite files", + ) + + ap.add_argument( + "-l", + "--language", + default="c", + choices=["c", "cpp"], + help="language domain for output", + ) + + ap.add_argument( + "-s", + "--symbol-dir-name", + default="symbols", + help="name for subdirectory of symbol documentation files", + ) + + ap.add_argument("index_xml_path", help="path index.xml from Doxygen") + ap.add_argument("output_dir", help="output directory") + + run(**vars(ap.parse_args(sys.argv[1:]))) @@ -43,6 +43,9 @@ def configure(conf): conf.load('compiler_c', cache=True) conf.load('autowaf', cache=True) + if conf.env.DOCS: + conf.load('sphinx') + if not autowaf.set_c_lang(conf, 'c11', mandatory=False): autowaf.set_c_lang(conf, 'c99') @@ -264,13 +267,7 @@ def build(bld): # Documentation if bld.env.DOCS: - autowaf.build_dox(bld, 'SERD', SERD_VERSION, top, out) - bld(features='subst', - source='doc/index.html.in', - target='index.html', - install_path='', - name='index', - SERD_VERSION=SERD_VERSION) + bld.recurse('doc/c') # Man page bld.install_files('${MANDIR}/man1', 'doc/serdi.1') |