diff options
author | David Robillard <d@drobilla.net> | 2021-03-28 13:42:35 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2023-12-02 18:49:08 -0500 |
commit | d094448c095a59117febc8bd4687df071ce9759a (patch) | |
tree | 08e81a3a9a46627dc8b545c12ebf17ae51ef76f4 | |
parent | f74a7448036d6fbe3f6562aa6e87d7e7478f0341 (diff) | |
download | serd-d094448c095a59117febc8bd4687df071ce9759a.tar.gz serd-d094448c095a59117febc8bd4687df071ce9759a.tar.bz2 serd-d094448c095a59117febc8bd4687df071ce9759a.zip |
Add high-level documentation
31 files changed, 2765 insertions, 38 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc5e5b29..12830224 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -148,9 +148,11 @@ pages: script: - mkdir public - mkdir public/doc + - mkdir public/doc/epub - mv build/meson-logs/coveragereport/ public/coverage - mv build/dest/share/doc/serd-0/html/ public/doc/html/ - mv build/dest/share/doc/serd-0/singlehtml/ public/doc/singlehtml/ + - mv build/doc/c/epub/Serd-*.epub public/c/epub/ - mv build/doc/man/ public/man/ needs: - dev diff --git a/.reuse/dep5 b/.reuse/dep5 index df791849..75b5374e 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -27,3 +27,8 @@ Files: doc/_static/serd*.svg doc/serd*.svg doc/serd*.txt Copyright: 2011-2023 David Robillard <d@drobilla.net> Comment: Documentation License: ISC + +Files: doc/*.rst doc/c/*.rst doc/_static/*.* +Copyright: 2020-2022 David Robillard <d@drobilla.net> +Comment: Documentation +License: ISC @@ -55,10 +55,14 @@ Documentation ------------- * [Installation instructions](INSTALL.md) - * [Single-page API reference](https://drobilla.gitlab.io/serd/doc/singlehtml/) - * [Paginated API reference](https://drobilla.gitlab.io/serd/doc/html/) - * [`serd-filter` man page](https://drobilla.gitlab.io/serd/man/serd-filter.html) - * [`serd-pipe` man page](https://drobilla.gitlab.io/serd/man/serd-pipe.html) + * Overview and reference documentation: + * [Single-page HTML](https://drobilla.gitlab.io/serd/doc/singlehtml/) + * [Paginated HTML](https://drobilla.gitlab.io/serd/doc/html/) + * [EPub](https://drobilla.gitlab.io/serd/c/epub/Serd-1.0.1.epub) + * Man pages: + * [`serd-filter`](https://drobilla.gitlab.io/serd/man/serd-filter.html) + * [`serd-pipe`](https://drobilla.gitlab.io/serd/man/serd-pipe.html) + * [`serd-sort`](https://drobilla.gitlab.io/serd/man/serd-sort.html) Versioning ---------- diff --git a/doc/.clang-tidy b/doc/.clang-tidy new file mode 100644 index 00000000..1a78b0f8 --- /dev/null +++ b/doc/.clang-tidy @@ -0,0 +1,9 @@ +# Copyright 2021-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: 0BSD OR ISC + +Checks: > + -*-magic-numbers, + -clang-analyzer-deadcode.DeadStores, + -clang-analyzer-nullability.NullablePassedToNonnull, + -hicpp-signed-bitwise, +InheritParentConfig: true diff --git a/doc/_static/epubstyle.css b/doc/_static/epubstyle.css new file mode 100644 index 00000000..1c74fc64 --- /dev/null +++ b/doc/_static/epubstyle.css @@ -0,0 +1,56 @@ +/* + Copyright 2020-2023 David Robillard <d@drobilla.net> + SPDX-License-Identifier: 0BSD or ISC +*/ + +pre { + padding: 0.4380em; +} + +.signame, .descname { + font-weight: bold; +} + +dl.field-list > dt::after, +dl.docinfo > dt::after { + content: ""; +} + +@media all and (color >= 4) { + a:link { + color: #546E00; + text-decoration: none; + } + + a:visited { + color: #3C4F00; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + div.highlight { + background-color: #F8F8F8; + margin: 0.6180em 0; + border-radius: 0.271em; + } +} + +@media print { + a:link { + color: #000; + text-decoration: none; + } + + a:visited { + color: #000; + text-decoration: none; + } + + div.highlight { + background-color: #FFF; + margin: 0.6180em; + } +} diff --git a/doc/_static/meson.build b/doc/_static/meson.build new file mode 100644 index 00000000..5149eb5a --- /dev/null +++ b/doc/_static/meson.build @@ -0,0 +1,18 @@ +# Copyright 2020-2023 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: 0BSD OR ISC + +doc_static_build_dir = meson.current_build_dir() + +static_files = files( + 'model_pipeline.svg', + 'serd.svg', + 'writer_pipeline.svg', +) + +foreach static_file : static_files + configure_file( + copy: true, + input: static_file, + output: '@PLAINNAME@', + ) +endforeach diff --git a/doc/_static/model_pipeline.ipe b/doc/_static/model_pipeline.ipe new file mode 100644 index 00000000..d76b83fb --- /dev/null +++ b/doc/_static/model_pipeline.ipe @@ -0,0 +1,409 @@ +<?xml version="1.0"?> +<!DOCTYPE ipe SYSTEM "ipe.dtd"> +<ipe version="70218" creator="Ipe 7.2.24"> +<info created="D:20210613154951" modified="D:20210731123443"/> +<preamble>\usepackage{helvet} +\renewcommand{\familydefault}{\sfdefault}</preamble> +<ipestyle name="basic"> +<symbol name="arrow/arc(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/farc(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/ptarc(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/fptarc(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="mark/circle(sx)" transformations="translations"> +<path fill="sym-stroke"> +0.6 0 0 0.6 0 0 e +0.4 0 0 0.4 0 0 e +</path> +</symbol> +<symbol name="mark/disk(sx)" transformations="translations"> +<path fill="sym-stroke"> +0.6 0 0 0.6 0 0 e +</path> +</symbol> +<symbol name="mark/fdisk(sfx)" transformations="translations"> +<group> +<path fill="sym-fill"> +0.5 0 0 0.5 0 0 e +</path> +<path fill="sym-stroke" fillrule="eofill"> +0.6 0 0 0.6 0 0 e +0.4 0 0 0.4 0 0 e +</path> +</group> +</symbol> +<symbol name="mark/box(sx)" transformations="translations"> +<path fill="sym-stroke" fillrule="eofill"> +-0.6 -0.6 m +0.6 -0.6 l +0.6 0.6 l +-0.6 0.6 l +h +-0.4 -0.4 m +0.4 -0.4 l +0.4 0.4 l +-0.4 0.4 l +h +</path> +</symbol> +<symbol name="mark/square(sx)" transformations="translations"> +<path fill="sym-stroke"> +-0.6 -0.6 m +0.6 -0.6 l +0.6 0.6 l +-0.6 0.6 l +h +</path> +</symbol> +<symbol name="mark/fsquare(sfx)" transformations="translations"> +<group> +<path fill="sym-fill"> +-0.5 -0.5 m +0.5 -0.5 l +0.5 0.5 l +-0.5 0.5 l +h +</path> +<path fill="sym-stroke" fillrule="eofill"> +-0.6 -0.6 m +0.6 -0.6 l +0.6 0.6 l +-0.6 0.6 l +h +-0.4 -0.4 m +0.4 -0.4 l +0.4 0.4 l +-0.4 0.4 l +h +</path> +</group> +</symbol> +<symbol name="mark/cross(sx)" transformations="translations"> +<group> +<path fill="sym-stroke"> +-0.43 -0.57 m +0.57 0.43 l +0.43 0.57 l +-0.57 -0.43 l +h +</path> +<path fill="sym-stroke"> +-0.43 0.57 m +0.57 -0.43 l +0.43 -0.57 l +-0.57 0.43 l +h +</path> +</group> +</symbol> +<symbol name="arrow/fnormal(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/pointed(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/fpointed(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/linear(spx)"> +<path stroke="sym-stroke" pen="sym-pen"> +-1 0.333 m +0 0 l +-1 -0.333 l +</path> +</symbol> +<symbol name="arrow/fdouble(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +-1 0 m +-2 0.333 l +-2 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/double(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +-1 0 m +-2 0.333 l +-2 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-normal(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-fnormal(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-pointed(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.3 0 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-fpointed(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.3 0 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-double(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +1 0 m +0 0.333 l +0 -0.333 l +h +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-fdouble(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +1 0 m +0 0.333 l +0 -0.333 l +h +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<anglesize name="22.5 deg" value="22.5"/> +<anglesize name="30 deg" value="30"/> +<anglesize name="45 deg" value="45"/> +<anglesize name="60 deg" value="60"/> +<anglesize name="90 deg" value="90"/> +<arrowsize name="large" value="10"/> +<arrowsize name="small" value="5"/> +<arrowsize name="tiny" value="3"/> +<color name="blue" value="0 0 1"/> +<color name="brown" value="0.647 0.165 0.165"/> +<color name="darkblue" value="0 0 0.545"/> +<color name="darkcyan" value="0 0.545 0.545"/> +<color name="darkgray" value="0.663"/> +<color name="darkgreen" value="0 0.392 0"/> +<color name="darkmagenta" value="0.545 0 0.545"/> +<color name="darkorange" value="1 0.549 0"/> +<color name="darkred" value="0.545 0 0"/> +<color name="gold" value="1 0.843 0"/> +<color name="gray" value="0.745"/> +<color name="green" value="0 1 0"/> +<color name="lightblue" value="0.678 0.847 0.902"/> +<color name="lightcyan" value="0.878 1 1"/> +<color name="lightgray" value="0.827"/> +<color name="lightgreen" value="0.565 0.933 0.565"/> +<color name="lightyellow" value="1 1 0.878"/> +<color name="navy" value="0 0 0.502"/> +<color name="orange" value="1 0.647 0"/> +<color name="pink" value="1 0.753 0.796"/> +<color name="purple" value="0.627 0.125 0.941"/> +<color name="red" value="1 0 0"/> +<color name="seagreen" value="0.18 0.545 0.341"/> +<color name="turquoise" value="0.251 0.878 0.816"/> +<color name="violet" value="0.933 0.51 0.933"/> +<color name="yellow" value="1 1 0"/> +<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/> +<dashstyle name="dash dotted" value="[4 2 1 2] 0"/> +<dashstyle name="dashed" value="[4] 0"/> +<dashstyle name="dotted" value="[1 3] 0"/> +<gridsize name="10 pts (~3.5 mm)" value="10"/> +<gridsize name="14 pts (~5 mm)" value="14"/> +<gridsize name="16 pts (~6 mm)" value="16"/> +<gridsize name="20 pts (~7 mm)" value="20"/> +<gridsize name="28 pts (~10 mm)" value="28"/> +<gridsize name="32 pts (~12 mm)" value="32"/> +<gridsize name="4 pts" value="4"/> +<gridsize name="56 pts (~20 mm)" value="56"/> +<gridsize name="8 pts (~3 mm)" value="8"/> +<opacity name="10%" value="0.1"/> +<opacity name="30%" value="0.3"/> +<opacity name="50%" value="0.5"/> +<opacity name="75%" value="0.75"/> +<pen name="fat" value="1.2"/> +<pen name="heavier" value="0.8"/> +<pen name="ultrafat" value="2"/> +<symbolsize name="large" value="5"/> +<symbolsize name="small" value="2"/> +<symbolsize name="tiny" value="1.1"/> +<textsize name="Huge" value="\Huge"/> +<textsize name="LARGE" value="\LARGE"/> +<textsize name="Large" value="\Large"/> +<textsize name="footnote" value="\footnotesize"/> +<textsize name="huge" value="\huge"/> +<textsize name="large" value="\large"/> +<textsize name="small" value="\small"/> +<textsize name="tiny" value="\tiny"/> +<textstyle name="center" begin="\begin{center}" end="\end{center}"/> +<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/> +<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/> +<tiling name="falling" angle="-60" step="4" width="1"/> +<tiling name="rising" angle="30" step="4" width="1"/> +</ipestyle> +<page> +<layer name="alpha"/> +<view layers="alpha" active="alpha"/> +<text layer="alpha" matrix="1 0 0 1 88 12" transformations="translations" pos="112 788" stroke="black" type="label" width="40.868" height="6.616" depth="0.14" valign="center" size="small">Statement</text> +<path matrix="1 0 0 1 8 44" stroke="black" pen="heavier"> +188 764 m +188 688 l +236 688 l +236 764 l +h +</path> +<text matrix="1 0 0 1 128 4" transformations="translations" pos="100 812" stroke="black" type="label" width="27.128" height="7.202" depth="0.16" halign="center" valign="center">Model</text> +<path matrix="1 0 0 1 8 -8" stroke="black" pen="heavier"> +184 832 m +184 736 l +252 736 l +252 832 l +h +</path> +<text matrix="1 0 0 1 128 60" transformations="translations" pos="28 716" stroke="black" type="label" width="34.171" height="7.202" depth="0.16" halign="center" valign="center">Inserter</text> +<path matrix="1 0 0 1 128 60" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<path matrix="1 0 0 1 32 60" stroke="black" pen="heavier"> +144 716 m +160 716 l +</path> +<text matrix="1 0 0 1 72 60" transformations="translations" pos="28 716" stroke="black" type="label" width="29.35" height="7.347" depth="0.16" halign="center" valign="center">Canon</text> +<path matrix="1 0 0 1 72 60" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<path matrix="1 0 0 1 -24 60" stroke="black" dash="dotted" pen="heavier" arrow="normal/small"> +144 716 m +160 716 l +</path> +<path matrix="1 0 0 1 -80 60" stroke="black" dash="dotted" pen="heavier" arrow="normal/small"> +144 716 m +160 716 l +</path> +<text matrix="1 0 0 1 16 60" transformations="translations" pos="28 716" stroke="black" type="label" width="32.667" height="7.202" depth="0.16" halign="center" valign="center">Reader</text> +<path matrix="1 0 0 1 16 60" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<text matrix="1 0 0 1 84 32" transformations="translations" pos="124 752" stroke="black" type="label" width="21.429" height="6.486" depth="0.14" valign="center" size="small">Node</text> +<path matrix="1 0 0 1 12 44" stroke="black" pen="heavier"> +192 748 m +192 732 l +224 732 l +224 748 l +h +</path> +<path matrix="1 0 0 1 12 24" stroke="black" pen="heavier"> +192 748 m +192 732 l +224 732 l +224 748 l +h +</path> +<path matrix="1 0 0 1 12 4" stroke="black" pen="heavier"> +192 748 m +192 732 l +224 732 l +224 748 l +h +</path> +<text matrix="1 0 0 1 84 12" transformations="translations" pos="124 752" stroke="black" type="label" width="21.429" height="6.486" depth="0.14" valign="center" size="small">Node</text> +<text matrix="1 0 0 1 84 -8" transformations="translations" pos="124 752" stroke="black" type="label" width="21.429" height="6.486" depth="0.14" valign="center" size="small">Node</text> +<use matrix="1 0 0 1 8 48" name="mark/disk(sx)" pos="240 724" size="small" stroke="black"/> +<use matrix="1 0 0 1 4 48" name="mark/disk(sx)" pos="248 724" size="small" stroke="black"/> +<use matrix="1 0 0 1 0 48" name="mark/disk(sx)" pos="256 724" size="small" stroke="black"/> +<text matrix="1 0 0 1 16 100" transformations="translations" pos="28 716" stroke="black" type="label" width="16.966" height="7.198" depth="0" halign="center" valign="center">Env</text> +<path matrix="1 0 0 1 16 100" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<path matrix="1 0 0 1 8 -8" stroke="black" pen="heavier" arrow="normal/small"> +36 792 m +36 816 l +</path> +</page> +</ipe> diff --git a/doc/_static/model_pipeline.svg b/doc/_static/model_pipeline.svg new file mode 100644 index 00000000..cc5f9784 --- /dev/null +++ b/doc/_static/model_pipeline.svg @@ -0,0 +1,159 @@ +<svg height="100pt" viewBox="0 0 239 100" width="424" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <style type="text/css"> + svg { background: inherit; fill: #000; } + svg > path , g { stroke: #000; } + @media (prefers-color-scheme: dark) { + svg { fill: #CCC; } + svg > path , g { stroke: #CCC; } + } + </style> + <symbol id="a" overflow="visible"> + <path d="M5.344-4.625c0-1.281-.89-2.016-2.39-2.016-1.438 0-2.329.735-2.329 1.907 0 .812.422 1.312 1.281 1.53l1.625.438c.828.204 1.203.547 1.203 1.047 0 .36-.187.719-.468.907-.25.187-.672.28-1.204.28-.703 0-1.187-.171-1.5-.546-.25-.281-.359-.594-.343-1H.437c0 .594.126 1 .376 1.36.453.609 1.203.921 2.203.921.78 0 1.421-.172 1.843-.5.438-.344.704-.937.704-1.5 0-.797-.5-1.39-1.391-1.64l-1.64-.438c-.782-.219-1.063-.469-1.063-.969 0-.656.578-1.11 1.453-1.11 1.047 0 1.625.485 1.64 1.329zm0 0"/> + </symbol> + <symbol id="b" overflow="visible"> + <path d="M2.281-4.703H1.5v-1.281H.766v1.28H.125v.61h.64V-.53c0 .469.313.734.907.734.172 0 .36-.016.61-.062v-.625c-.11.03-.22.03-.36.03-.328 0-.422-.077-.422-.421v-3.219h.781zm0 0"/> + </symbol> + <symbol id="c" overflow="visible"> + <path d="M4.797-.438a.634.634 0 01-.156.016c-.266 0-.407-.14-.407-.375v-2.75c0-.844-.609-1.281-1.765-1.281-.688 0-1.25.187-1.563.547-.219.234-.312.5-.328.968h.766c.062-.578.39-.828 1.093-.828.672 0 1.047.25 1.047.704v.187c0 .313-.187.453-.78.531-1.048.125-1.22.156-1.5.281-.548.22-.829.641-.829 1.25 0 .86.594 1.391 1.547 1.391.594 0 1.062-.203 1.594-.687.046.468.28.687.765.687.157 0 .282-.016.516-.078zM3.484-1.484c0 .25-.062.406-.296.609-.297.281-.672.422-1.11.422-.578 0-.922-.266-.922-.75s.328-.75 1.125-.86.953-.14 1.203-.265zm0 0"/> + </symbol> + <symbol id="d" overflow="visible"> + <path d="M4.594-2.094c0-.719-.047-1.156-.188-1.5-.297-.781-1.015-1.234-1.89-1.234-1.313 0-2.157 1-2.157 2.547C.36-.75 1.172.203 2.5.203c1.063 0 1.813-.61 2-1.625h-.75c-.203.61-.625.938-1.234.938-.47 0-.875-.22-1.125-.61-.188-.265-.25-.531-.25-1zm-3.438-.61C1.22-3.577 1.75-4.14 2.5-4.14c.734 0 1.297.61 1.297 1.375v.063zm0 0"/> + </symbol> + <symbol id="e" overflow="visible"> + <path d="M.625-4.703V0h.75v-2.953c0-.672.5-1.219 1.11-1.219.562 0 .874.328.874.938V0h.75v-2.953c0-.672.485-1.219 1.094-1.219.563 0 .875.344.875.938V0h.75v-3.531c0-.844-.484-1.297-1.36-1.297-.624 0-1 .187-1.437.719-.281-.516-.656-.72-1.265-.72-.625 0-1.047.235-1.454.798v-.672zm0 0"/> + </symbol> + <symbol id="f" overflow="visible"> + <path d="M.625-4.703V0h.75v-2.594c0-.953.516-1.578 1.281-1.578.594 0 .969.344.969.922V0h.75v-3.547c0-.781-.594-1.281-1.5-1.281-.703 0-1.14.266-1.563.922v-.797zm0 0"/> + </symbol> + <symbol id="g" overflow="visible"> + <path d="M4.672 0l2.031-6.094V0h.89v-7.266H6.298L4.187-.937 2.031-7.266H.75V0h.875v-6.094L3.688 0zm0 0"/> + </symbol> + <symbol id="h" overflow="visible"> + <path d="M2.719-5.375c-1.469 0-2.36 1.047-2.36 2.797C.36-.813 1.234.234 2.72.234c1.469 0 2.36-1.046 2.36-2.765 0-1.813-.86-2.844-2.36-2.844zm0 .766c.937 0 1.5.765 1.5 2.062 0 1.235-.578 2.016-1.5 2.016s-1.5-.781-1.5-2.047c0-1.25.578-2.031 1.5-2.031zm0 0"/> + </symbol> + <symbol id="i" overflow="visible"> + <path d="M4.938-7.266h-.829v2.704c-.343-.532-.906-.813-1.609-.813-1.36 0-2.234 1.094-2.234 2.75 0 1.766.859 2.86 2.265 2.86.719 0 1.219-.282 1.672-.922V0h.734zM2.64-4.594c.89 0 1.468.797 1.468 2.047 0 1.203-.578 2-1.453 2-.922 0-1.531-.812-1.531-2.031 0-1.203.61-2.016 1.516-2.016zm0 0"/> + </symbol> + <symbol id="j" overflow="visible"> + <path d="M5.11-2.328c0-.797-.063-1.281-.204-1.672-.343-.86-1.14-1.375-2.11-1.375-1.468 0-2.39 1.125-2.39 2.828 0 1.719.906 2.781 2.36 2.781C3.969.234 4.796-.453 5-1.578h-.828c-.234.687-.703 1.047-1.375 1.047a1.44 1.44 0 01-1.25-.688c-.203-.297-.266-.593-.281-1.11zm-3.83-.688c.078-.968.657-1.593 1.5-1.593.813 0 1.453.687 1.453 1.53 0 .032 0 .048-.015.063zm0 0"/> + </symbol> + <symbol id="k" overflow="visible"> + <path d="M1.516-7.266H.672V0h.844zm0 0"/> + </symbol> + <symbol id="l" overflow="visible"> + <path d="M1.938-7.266H1V0h.938zm0 0"/> + </symbol> + <symbol id="m" overflow="visible"> + <path d="M.703-5.219V0h.828v-2.875c0-1.078.563-1.766 1.422-1.766.656 0 1.078.391 1.078 1.016V0h.828v-3.953c0-.86-.656-1.422-1.656-1.422-.781 0-1.281.297-1.734 1.031v-.875zm0 0"/> + </symbol> + <symbol id="n" overflow="visible"> + <path d="M4.36-3.766c0-1.03-.688-1.609-1.891-1.609-1.219 0-2 .625-2 1.594 0 .828.422 1.203 1.656 1.515l.781.188c.578.14.797.344.797.719 0 .484-.484.828-1.203.828-.453 0-.828-.14-1.047-.36-.125-.14-.187-.296-.234-.671H.344C.375-.345 1.063.234 2.422.234c1.312 0 2.156-.656 2.156-1.656 0-.781-.437-1.203-1.484-1.453l-.797-.203c-.672-.156-.969-.375-.969-.735 0-.484.438-.796 1.11-.796s1.03.296 1.046.843zm0 0"/> + </symbol> + <symbol id="o" overflow="visible"> + <path d="M.688-5.219V0h.843v-2.719c0-.734.188-1.234.578-1.515.266-.188.516-.25 1.094-.266v-.844c-.14-.015-.219-.031-.328-.031-.531 0-.938.328-1.422 1.094v-.938zm0 0"/> + </symbol> + <symbol id="p" overflow="visible"> + <path d="M2.531-5.219h-.86v-1.437H.845v1.437H.14v.672h.703v3.953c0 .531.36.828 1.015.828.188 0 .391-.03.672-.078V-.53c-.11.015-.234.031-.39.031-.36 0-.47-.094-.47-.469v-3.578h.86zm0 0"/> + </symbol> + <symbol id="u" overflow="visible"> + <path d="M1.86-3.125h2.39c.828 0 1.188.39 1.188 1.297v.64c0 .454.078.891.203 1.188h1.125v-.234c-.344-.235-.422-.5-.438-1.454-.016-1.203-.203-1.562-.984-1.906.812-.39 1.14-.906 1.14-1.734 0-1.25-.78-1.938-2.203-1.938H.921V0h.938zm0-.828v-2.5h2.234c.515 0 .828.078 1.047.281.25.203.375.547.375.984 0 .844-.438 1.235-1.422 1.235zm0 0"/> + </symbol> + <symbol id="v" overflow="visible"> + <path d="M5.328-.484c-.078.015-.125.015-.172.015-.297 0-.453-.156-.453-.406v-3.078c0-.922-.672-1.422-1.969-1.422-.75 0-1.375.219-1.734.61-.234.265-.328.562-.36 1.093h.844c.079-.64.454-.937 1.235-.937.734 0 1.156.28 1.156.78v.22c0 .343-.203.5-.86.578-1.187.156-1.359.187-1.687.312-.594.25-.906.719-.906 1.406 0 .938.656 1.547 1.719 1.547.656 0 1.171-.234 1.765-.765.063.515.328.765.86.765.171 0 .296-.03.562-.093zM3.875-1.641c0 .282-.078.438-.328.672-.344.313-.75.469-1.235.469-.64 0-1.03-.313-1.03-.828 0-.563.374-.828 1.265-.969.875-.11 1.047-.156 1.328-.281zm0 0"/> + </symbol> + <symbol id="A" overflow="visible"> + <path d="M1.828-3.313h3.953v-.812H1.828v-2.328h4.11v-.813H.89V0h5.218v-.813H1.83zm0 0"/> + </symbol> + <symbol id="B" overflow="visible"> + <path d="M2.844 0l2-5.219h-.938L2.437-.984 1.032-5.22H.094L1.937 0zm0 0"/> + </symbol> + <symbol id="q" overflow="visible"> + <path d="M6.594-5.016C6.312-6.609 5.39-7.39 3.797-7.39c-.969 0-1.766.313-2.297.907C.844-5.766.484-4.72.484-3.547c0 1.188.36 2.219 1.047 2.922.563.578 1.282.86 2.235.86 1.765 0 2.765-.97 2.984-2.891h-.953c-.078.5-.188.844-.328 1.125-.313.61-.922.937-1.703.937-1.438 0-2.36-1.156-2.36-2.969 0-1.859.875-3 2.282-3 .593 0 1.14.172 1.437.454.266.25.422.562.531 1.093zm0 0"/> + </symbol> + <symbol id="r" overflow="visible"> + <path d="M5.328-.484c-.078.015-.125.015-.172.015-.297 0-.453-.156-.453-.406v-3.078c0-.922-.672-1.422-1.969-1.422-.75 0-1.375.219-1.734.61-.234.265-.328.562-.36 1.093h.844c.079-.64.454-.937 1.235-.937.734 0 1.156.28 1.156.78v.22c0 .343-.203.5-.86.578-1.187.156-1.359.187-1.687.312-.594.25-.906.719-.906 1.406 0 .938.656 1.547 1.719 1.547.656 0 1.171-.234 1.765-.765.063.515.328.765.86.765.171 0 .296-.03.562-.093zM3.875-1.641c0 .282-.078.438-.328.672-.344.313-.75.469-1.235.469-.64 0-1.03-.313-1.03-.828 0-.563.374-.828 1.265-.969.875-.11 1.047-.156 1.328-.281zm0 0"/> + </symbol> + <symbol id="s" overflow="visible"> + <path d="M.703-5.219V0h.828v-2.875c0-1.078.563-1.766 1.422-1.766.656 0 1.078.391 1.078 1.016V0h.828v-3.953c0-.86-.656-1.422-1.656-1.422-.781 0-1.281.297-1.734 1.031v-.875zm0 0"/> + </symbol> + <symbol id="t" overflow="visible"> + <path d="M2.719-5.375c-1.469 0-2.36 1.047-2.36 2.797C.36-.813 1.234.234 2.72.234c1.469 0 2.36-1.046 2.36-2.765 0-1.813-.86-2.844-2.36-2.844zm0 .766c.937 0 1.5.765 1.5 2.062 0 1.235-.578 2.016-1.5 2.016s-1.5-.781-1.5-2.047c0-1.25.578-2.031 1.5-2.031zm0 0"/> + </symbol> + <symbol id="w" overflow="visible"> + <path d="M5.797-6.531H5v5.343L1.594-6.53H.687V0h.782v-5.297L4.859 0h.938zm0 0"/> + </symbol> + <symbol id="x" overflow="visible"> + <path d="M2.438-4.828c-1.313 0-2.11.937-2.11 2.516S1.11.203 2.453.203c1.313 0 2.125-.937 2.125-2.484 0-1.625-.781-2.547-2.14-2.547zm.015.687c.844 0 1.344.688 1.344 1.86 0 1.094-.516 1.797-1.344 1.797-.844 0-1.344-.688-1.344-1.829 0-1.124.5-1.828 1.344-1.828zm0 0"/> + </symbol> + <symbol id="y" overflow="visible"> + <path d="M4.438-6.531h-.75v2.422c-.313-.47-.813-.72-1.438-.72-1.219 0-2.016.97-2.016 2.47 0 1.593.782 2.562 2.047 2.562.64 0 1.094-.234 1.5-.828V0h.656zm-2.063 2.39c.813 0 1.313.72 1.313 1.844C3.688-1.203 3.171-.5 2.39-.5c-.829 0-1.375-.719-1.375-1.813s.547-1.827 1.36-1.827zm0 0"/> + </symbol> + <symbol id="z" overflow="visible"> + <path d="M4.594-2.094c0-.719-.047-1.156-.188-1.5-.297-.781-1.015-1.234-1.89-1.234-1.313 0-2.157 1-2.157 2.547C.36-.75 1.172.203 2.5.203c1.063 0 1.813-.61 2-1.625h-.75c-.203.61-.625.938-1.234.938-.47 0-.875-.22-1.125-.61-.188-.265-.25-.531-.25-1zm-3.438-.61C1.22-3.577 1.75-4.14 2.5-4.14c.734 0 1.297.61 1.297 1.375v.063zm0 0"/> + </symbol> + <use x="177" xlink:href="#a" y="28.23"/> + <use x="182.981" xlink:href="#b" y="28.23"/> + <use x="185.473" xlink:href="#c" y="28.23"/> + <use x="190.459" xlink:href="#b" y="28.23"/> + <use x="192.951" xlink:href="#d" y="28.23"/> + <use x="197.937" xlink:href="#e" y="28.23"/> + <use x="205.406" xlink:href="#d" y="28.23"/> + <use x="210.391" xlink:href="#f" y="28.23"/> + <use x="215.376" xlink:href="#b" y="28.23"/> + <path d="M173 17v76h48V17zm0 0" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="191.436" xlink:href="#g" y="12.517"/> + <use x="199.735" xlink:href="#h" y="12.517"/> + <use x="205.274" xlink:href="#i" y="12.517"/> + <use x="210.813" xlink:href="#j" y="12.517"/> + <use x="216.352" xlink:href="#k" y="12.517"/> + <path d="M169 1v96h68V1zm0 0" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="115.915" xlink:href="#l" y="52.517"/> + <use x="118.684" xlink:href="#m" y="52.517"/> + <use x="124.223" xlink:href="#n" y="52.517"/> + <use x="129.205" xlink:href="#j" y="52.517"/> + <use x="134.744" xlink:href="#o" y="52.517"/> + <use x="138.46" xlink:href="#p" y="52.517"/> + <use x="141.229" xlink:href="#j" y="52.517"/> + <use x="146.769" xlink:href="#o" y="52.517"/> + <path d="M113 41v16h40V41zm40 8h16" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="62.325" xlink:href="#q" y="52.59"/> + <use x="69.518" xlink:href="#r" y="52.59"/> + <use x="75.057" xlink:href="#s" y="52.59"/> + <use x="80.596" xlink:href="#t" y="52.59"/> + <use x="86.136" xlink:href="#s" y="52.59"/> + <g stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"> + <path d="M57 41v16h40V41zm0 0" fill="none"/> + <path d="M97 49h16" fill="none" stroke-dasharray="1 1"/> + <path d="M113 49l-5-1.664v3.328zm0 0" fill-rule="evenodd"/> + <path d="M41 49h16" fill="none" stroke-dasharray="1 1"/> + <path d="M57 49l-5-1.664v3.328zm0 0" fill-rule="evenodd"/> + </g> + <use x="4.667" xlink:href="#u" y="52.517"/> + <use x="11.859" xlink:href="#j" y="52.517"/> + <use x="17.399" xlink:href="#v" y="52.517"/> + <use x="22.938" xlink:href="#i" y="52.517"/> + <use x="28.477" xlink:href="#j" y="52.517"/> + <use x="34.016" xlink:href="#o" y="52.517"/> + <path d="M1 41v16h40V41zm0 0" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="185" xlink:href="#w" y="44.165"/> + <use x="191.474" xlink:href="#x" y="44.165"/> + <use x="196.459" xlink:href="#y" y="44.165"/> + <use x="201.444" xlink:href="#z" y="44.165"/> + <path d="M181 33v16h32V33zm0 20v16h32V53zm0 20v16h32V73zm0 0" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="185" xlink:href="#w" y="64.165"/> + <use x="191.474" xlink:href="#x" y="64.165"/> + <use x="196.459" xlink:href="#y" y="64.165"/> + <use x="201.444" xlink:href="#z" y="64.165"/> + <use x="185" xlink:href="#w" y="84.165"/> + <use x="191.474" xlink:href="#x" y="84.165"/> + <use x="196.459" xlink:href="#y" y="84.165"/> + <use x="201.444" xlink:href="#z" y="84.165"/> + <path d="M226.2 53c0-1.602-2.4-1.602-2.4 0s2.4 1.602 2.4 0zm4 0c0-1.602-2.4-1.602-2.4 0s2.4 1.602 2.4 0zm4 0c0-1.602-2.4-1.602-2.4 0s2.4 1.602 2.4 0zm0 0" fill-rule="evenodd"/> + <use x="12.517" xlink:href="#A" y="12.599"/> + <use x="19.162" xlink:href="#m" y="12.599"/> + <use x="24.502" xlink:href="#B" y="12.599"/> + <g stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"> + <path d="M1 1v16h40V1zm20 40V17" fill="none"/> + <path d="M21 17l-1.664 5h3.328zm0 0" fill-rule="evenodd"/> + </g> +</svg> diff --git a/doc/_static/serd.svg b/doc/_static/serd.svg index 855b2874..3901c98e 100644 --- a/doc/_static/serd.svg +++ b/doc/_static/serd.svg @@ -1,11 +1,19 @@ <svg height="128" viewBox="0 0 33.867 33.867" width="128" xmlns="http://www.w3.org/2000/svg"> + <style type="text/css"> + svg { background: inherit; fill: #000; } + svg > path , g { stroke: #000; } + @media (prefers-color-scheme: dark) { + svg { fill: #CCC; } + svg > path , g { stroke: #CCC; } + } + </style> <g fill="none" stroke="#444" stroke-linejoin="round" stroke-width="1.058"> <path d="M26.726 7.14h6.529V.613h-6.529z"/> - <path d="M26.726 7.14h6.529V.613h-6.529zM13.67 7.14h6.528V.613h-6.529z"/> - <path d="M13.67 7.14h6.528V.613h-6.529zM.612 7.14h6.529V.613H.612z"/> - <path d="M.612 7.14h6.529V.613H.612zM26.726 33.255h6.529v-6.529h-6.529z"/> - <path d="M26.726 33.255h6.529v-6.529h-6.529zM13.67 33.255h6.528v-6.529h-6.529z"/> - <path d="M13.67 33.255h6.528v-6.529h-6.529zM.612 33.255h6.529v-6.529H.612z"/> + <path d="M26.726 7.14h6.529V.613h-6.529zm-13.056 0h6.528V.613h-6.529z"/> + <path d="M13.67 7.14h6.528V.613h-6.529zm-13.058 0h6.529V.613H.612z"/> + <path d="M.612 7.14h6.529V.613H.612zm26.114 26.115h6.529v-6.529h-6.529z"/> + <path d="M26.726 33.255h6.529v-6.529h-6.529zm-13.056 0h6.528v-6.529h-6.529z"/> + <path d="M13.67 33.255h6.528v-6.529h-6.529zm-13.058 0h6.529v-6.529H.612z"/> <path d="M.612 33.255h6.529v-6.529H.612zM13.67 20.198h6.528v-6.529h-6.529z"/> <path d="M13.67 20.198h6.528v-6.529h-6.529zm6.528-16.322h6.528m-19.586 0h6.53M7.14 7.14l6.53 6.53m6.528 6.528l6.528 6.528M7.14 29.99h6.53m6.528 0h6.528"/> </g> diff --git a/doc/_static/writer_pipeline.ipe b/doc/_static/writer_pipeline.ipe new file mode 100644 index 00000000..2a8c6b5c --- /dev/null +++ b/doc/_static/writer_pipeline.ipe @@ -0,0 +1,368 @@ +<?xml version="1.0"?> +<!DOCTYPE ipe SYSTEM "ipe.dtd"> +<ipe version="70218" creator="Ipe 7.2.24"> +<info created="D:20210613154951" modified="D:20210731123501"/> +<preamble>\usepackage{helvet} +\renewcommand{\familydefault}{\sfdefault}</preamble> +<ipestyle name="basic"> +<symbol name="arrow/arc(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/farc(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/ptarc(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/fptarc(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="mark/circle(sx)" transformations="translations"> +<path fill="sym-stroke"> +0.6 0 0 0.6 0 0 e +0.4 0 0 0.4 0 0 e +</path> +</symbol> +<symbol name="mark/disk(sx)" transformations="translations"> +<path fill="sym-stroke"> +0.6 0 0 0.6 0 0 e +</path> +</symbol> +<symbol name="mark/fdisk(sfx)" transformations="translations"> +<group> +<path fill="sym-fill"> +0.5 0 0 0.5 0 0 e +</path> +<path fill="sym-stroke" fillrule="eofill"> +0.6 0 0 0.6 0 0 e +0.4 0 0 0.4 0 0 e +</path> +</group> +</symbol> +<symbol name="mark/box(sx)" transformations="translations"> +<path fill="sym-stroke" fillrule="eofill"> +-0.6 -0.6 m +0.6 -0.6 l +0.6 0.6 l +-0.6 0.6 l +h +-0.4 -0.4 m +0.4 -0.4 l +0.4 0.4 l +-0.4 0.4 l +h +</path> +</symbol> +<symbol name="mark/square(sx)" transformations="translations"> +<path fill="sym-stroke"> +-0.6 -0.6 m +0.6 -0.6 l +0.6 0.6 l +-0.6 0.6 l +h +</path> +</symbol> +<symbol name="mark/fsquare(sfx)" transformations="translations"> +<group> +<path fill="sym-fill"> +-0.5 -0.5 m +0.5 -0.5 l +0.5 0.5 l +-0.5 0.5 l +h +</path> +<path fill="sym-stroke" fillrule="eofill"> +-0.6 -0.6 m +0.6 -0.6 l +0.6 0.6 l +-0.6 0.6 l +h +-0.4 -0.4 m +0.4 -0.4 l +0.4 0.4 l +-0.4 0.4 l +h +</path> +</group> +</symbol> +<symbol name="mark/cross(sx)" transformations="translations"> +<group> +<path fill="sym-stroke"> +-0.43 -0.57 m +0.57 0.43 l +0.43 0.57 l +-0.57 -0.43 l +h +</path> +<path fill="sym-stroke"> +-0.43 0.57 m +0.57 -0.43 l +0.43 -0.57 l +-0.57 0.43 l +h +</path> +</group> +</symbol> +<symbol name="arrow/fnormal(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/pointed(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/fpointed(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-0.8 0 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/linear(spx)"> +<path stroke="sym-stroke" pen="sym-pen"> +-1 0.333 m +0 0 l +-1 -0.333 l +</path> +</symbol> +<symbol name="arrow/fdouble(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +-1 0 m +-2 0.333 l +-2 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/double(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0 0 m +-1 0.333 l +-1 -0.333 l +h +-1 0 m +-2 0.333 l +-2 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-normal(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-fnormal(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-pointed(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.3 0 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-fpointed(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +0.5 0 m +-0.5 0.333 l +-0.3 0 l +-0.5 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-double(spx)"> +<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen"> +1 0 m +0 0.333 l +0 -0.333 l +h +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<symbol name="arrow/mid-fdouble(spx)"> +<path stroke="sym-stroke" fill="white" pen="sym-pen"> +1 0 m +0 0.333 l +0 -0.333 l +h +0 0 m +-1 0.333 l +-1 -0.333 l +h +</path> +</symbol> +<anglesize name="22.5 deg" value="22.5"/> +<anglesize name="30 deg" value="30"/> +<anglesize name="45 deg" value="45"/> +<anglesize name="60 deg" value="60"/> +<anglesize name="90 deg" value="90"/> +<arrowsize name="large" value="10"/> +<arrowsize name="small" value="5"/> +<arrowsize name="tiny" value="3"/> +<color name="blue" value="0 0 1"/> +<color name="brown" value="0.647 0.165 0.165"/> +<color name="darkblue" value="0 0 0.545"/> +<color name="darkcyan" value="0 0.545 0.545"/> +<color name="darkgray" value="0.663"/> +<color name="darkgreen" value="0 0.392 0"/> +<color name="darkmagenta" value="0.545 0 0.545"/> +<color name="darkorange" value="1 0.549 0"/> +<color name="darkred" value="0.545 0 0"/> +<color name="gold" value="1 0.843 0"/> +<color name="gray" value="0.745"/> +<color name="green" value="0 1 0"/> +<color name="lightblue" value="0.678 0.847 0.902"/> +<color name="lightcyan" value="0.878 1 1"/> +<color name="lightgray" value="0.827"/> +<color name="lightgreen" value="0.565 0.933 0.565"/> +<color name="lightyellow" value="1 1 0.878"/> +<color name="navy" value="0 0 0.502"/> +<color name="orange" value="1 0.647 0"/> +<color name="pink" value="1 0.753 0.796"/> +<color name="purple" value="0.627 0.125 0.941"/> +<color name="red" value="1 0 0"/> +<color name="seagreen" value="0.18 0.545 0.341"/> +<color name="turquoise" value="0.251 0.878 0.816"/> +<color name="violet" value="0.933 0.51 0.933"/> +<color name="yellow" value="1 1 0"/> +<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/> +<dashstyle name="dash dotted" value="[4 2 1 2] 0"/> +<dashstyle name="dashed" value="[4] 0"/> +<dashstyle name="dotted" value="[1 3] 0"/> +<gridsize name="10 pts (~3.5 mm)" value="10"/> +<gridsize name="14 pts (~5 mm)" value="14"/> +<gridsize name="16 pts (~6 mm)" value="16"/> +<gridsize name="20 pts (~7 mm)" value="20"/> +<gridsize name="28 pts (~10 mm)" value="28"/> +<gridsize name="32 pts (~12 mm)" value="32"/> +<gridsize name="4 pts" value="4"/> +<gridsize name="56 pts (~20 mm)" value="56"/> +<gridsize name="8 pts (~3 mm)" value="8"/> +<opacity name="10%" value="0.1"/> +<opacity name="30%" value="0.3"/> +<opacity name="50%" value="0.5"/> +<opacity name="75%" value="0.75"/> +<pen name="fat" value="1.2"/> +<pen name="heavier" value="0.8"/> +<pen name="ultrafat" value="2"/> +<symbolsize name="large" value="5"/> +<symbolsize name="small" value="2"/> +<symbolsize name="tiny" value="1.1"/> +<textsize name="Huge" value="\Huge"/> +<textsize name="LARGE" value="\LARGE"/> +<textsize name="Large" value="\Large"/> +<textsize name="footnote" value="\footnotesize"/> +<textsize name="huge" value="\huge"/> +<textsize name="large" value="\large"/> +<textsize name="small" value="\small"/> +<textsize name="tiny" value="\tiny"/> +<textstyle name="center" begin="\begin{center}" end="\end{center}"/> +<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/> +<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/> +<tiling name="falling" angle="-60" step="4" width="1"/> +<tiling name="rising" angle="30" step="4" width="1"/> +</ipestyle> +<page> +<layer name="alpha"/> +<view layers="alpha" active="alpha"/> +<text layer="alpha" matrix="1 0 0 1 128 68" transformations="translations" pos="28 716" stroke="black" type="label" width="26.709" height="7.202" depth="0.16" halign="center" valign="center">Writer</text> +<path matrix="1 0 0 1 128 68" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<text matrix="1 0 0 1 72 68" transformations="translations" pos="28 716" stroke="black" type="label" width="22.137" height="7.202" depth="0.16" halign="center" valign="center">Filter</text> +<path matrix="1 0 0 1 72 68" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<path matrix="1 0 0 1 -24 68" stroke="black" dash="dotted" pen="heavier" arrow="normal/small"> +144 716 m +160 716 l +</path> +<path matrix="1 0 0 1 -80 68" stroke="black" dash="dotted" pen="heavier" arrow="normal/small"> +144 716 m +160 716 l +</path> +<text matrix="1 0 0 1 16 68" transformations="translations" pos="28 716" stroke="black" type="label" width="32.667" height="7.202" depth="0.16" halign="center" valign="center">Reader</text> +<path matrix="1 0 0 1 16 68" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<text matrix="1 0 0 1 72 100" transformations="translations" pos="28 716" stroke="black" type="label" width="16.966" height="7.198" depth="0" halign="center" valign="center">Env</text> +<path matrix="1 0 0 1 72 100" stroke="black" pen="heavier"> +8 724 m +8 708 l +48 708 l +48 724 l +h +</path> +<path matrix="1 0 0 1 8 8" stroke="black" pen="heavier" arrow="normal/normal"> +36 784 m +36 808 l +72 808 l +</path> +<path matrix="1 0 0 1 8 8" stroke="black" pen="heavier" arrow="normal/normal"> +148 784 m +148 808 l +112 808 l +</path> +</page> +</ipe> diff --git a/doc/_static/writer_pipeline.svg b/doc/_static/writer_pipeline.svg new file mode 100644 index 00000000..93ec359c --- /dev/null +++ b/doc/_static/writer_pipeline.svg @@ -0,0 +1,85 @@ +<svg height="52pt" viewBox="0 0 239 52" width="424" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <style type="text/css"> + svg { background: inherit; fill: #000; } + svg > path , g { stroke: #000; } + @media (prefers-color-scheme: dark) { + svg { fill: #CCC; } + svg > path , g { stroke: #CCC; } + } + </style> + <symbol id="a" overflow="visible"> + <path d="M7.422 0l1.844-7.266H8.219L6.89-1.359 5.234-7.266h-1L2.625-1.359 1.25-7.266H.219L2.079 0h1.015l1.625-5.969L6.406 0zm0 0"/> + </symbol> + <symbol id="b" overflow="visible"> + <path d="M.688-5.219V0h.843v-2.719c0-.734.188-1.234.578-1.515.266-.188.516-.25 1.094-.266v-.844c-.14-.015-.219-.031-.328-.031-.531 0-.938.328-1.422 1.094v-.938zm0 0"/> + </symbol> + <symbol id="c" overflow="visible"> + <path d="M1.5-5.219H.672V0H1.5zm0-2.047H.656v1.047H1.5zm0 0"/> + </symbol> + <symbol id="d" overflow="visible"> + <path d="M2.531-5.219h-.86v-1.437H.845v1.437H.14v.672h.703v3.953c0 .531.36.828 1.015.828.188 0 .391-.03.672-.078V-.53c-.11.015-.234.031-.39.031-.36 0-.47-.094-.47-.469v-3.578h.86zm0 0"/> + </symbol> + <symbol id="e" overflow="visible"> + <path d="M5.11-2.328c0-.797-.063-1.281-.204-1.672-.343-.86-1.14-1.375-2.11-1.375-1.468 0-2.39 1.125-2.39 2.828 0 1.719.906 2.781 2.36 2.781C3.969.234 4.796-.453 5-1.578h-.828c-.234.687-.703 1.047-1.375 1.047a1.44 1.44 0 01-1.25-.688c-.203-.297-.266-.593-.281-1.11zm-3.83-.688c.078-.968.657-1.593 1.5-1.593.813 0 1.453.687 1.453 1.53 0 .032 0 .048-.015.063zm0 0"/> + </symbol> + <symbol id="f" overflow="visible"> + <path d="M1.828-3.313h3.469v-.812H1.828v-2.328h3.938v-.813H.89V0h.937zm0 0"/> + </symbol> + <symbol id="g" overflow="visible"> + <path d="M1.516-7.266H.672V0h.844zm0 0"/> + </symbol> + <symbol id="h" overflow="visible"> + <path d="M1.86-3.125h2.39c.828 0 1.188.39 1.188 1.297v.64c0 .454.078.891.203 1.188h1.125v-.234c-.344-.235-.422-.5-.438-1.454-.016-1.203-.203-1.562-.984-1.906.812-.39 1.14-.906 1.14-1.734 0-1.25-.78-1.938-2.203-1.938H.921V0h.938zm0-.828v-2.5h2.234c.515 0 .828.078 1.047.281.25.203.375.547.375.984 0 .844-.438 1.235-1.422 1.235zm0 0"/> + </symbol> + <symbol id="i" overflow="visible"> + <path d="M5.328-.484c-.078.015-.125.015-.172.015-.297 0-.453-.156-.453-.406v-3.078c0-.922-.672-1.422-1.969-1.422-.75 0-1.375.219-1.734.61-.234.265-.328.562-.36 1.093h.844c.079-.64.454-.937 1.235-.937.734 0 1.156.28 1.156.78v.22c0 .343-.203.5-.86.578-1.187.156-1.359.187-1.687.312-.594.25-.906.719-.906 1.406 0 .938.656 1.547 1.719 1.547.656 0 1.171-.234 1.765-.765.063.515.328.765.86.765.171 0 .296-.03.562-.093zM3.875-1.641c0 .282-.078.438-.328.672-.344.313-.75.469-1.235.469-.64 0-1.03-.313-1.03-.828 0-.563.374-.828 1.265-.969.875-.11 1.047-.156 1.328-.281zm0 0"/> + </symbol> + <symbol id="j" overflow="visible"> + <path d="M4.938-7.266h-.829v2.704c-.343-.532-.906-.813-1.609-.813-1.36 0-2.234 1.094-2.234 2.75 0 1.766.859 2.86 2.265 2.86.719 0 1.219-.282 1.672-.922V0h.734zM2.64-4.594c.89 0 1.468.797 1.468 2.047 0 1.203-.578 2-1.453 2-.922 0-1.531-.812-1.531-2.031 0-1.203.61-2.016 1.516-2.016zm0 0"/> + </symbol> + <symbol id="k" overflow="visible"> + <path d="M1.828-3.313h3.953v-.812H1.828v-2.328h4.11v-.813H.89V0h5.218v-.813H1.83zm0 0"/> + </symbol> + <symbol id="l" overflow="visible"> + <path d="M.703-5.219V0h.828v-2.875c0-1.078.563-1.766 1.422-1.766.656 0 1.078.391 1.078 1.016V0h.828v-3.953c0-.86-.656-1.422-1.656-1.422-.781 0-1.281.297-1.734 1.031v-.875zm0 0"/> + </symbol> + <symbol id="m" overflow="visible"> + <path d="M2.844 0l2-5.219h-.938L2.437-.984 1.032-5.22H.094L1.937 0zm0 0"/> + </symbol> + <use x="119.645" xlink:href="#a" y="44.517"/> + <use x="129.05" xlink:href="#b" y="44.517"/> + <use x="132.517" xlink:href="#c" y="44.517"/> + <use x="134.729" xlink:href="#d" y="44.517"/> + <use x="137.498" xlink:href="#e" y="44.517"/> + <use x="143.038" xlink:href="#b" y="44.517"/> + <path d="M113 33v16h40V33zm0 0" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="65.931" xlink:href="#f" y="44.517"/> + <use x="72.019" xlink:href="#c" y="44.517"/> + <use x="74.23" xlink:href="#g" y="44.517"/> + <use x="76.442" xlink:href="#d" y="44.517"/> + <use x="79.212" xlink:href="#e" y="44.517"/> + <use x="84.751" xlink:href="#b" y="44.517"/> + <g stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"> + <path d="M57 33v16h40V33zm0 0" fill="none"/> + <path d="M97 41h16" fill="none" stroke-dasharray="1 1"/> + <path d="M113 41l-5-1.664v3.328zm0 0" fill-rule="evenodd"/> + <path d="M41 41h16" fill="none" stroke-dasharray="1 1"/> + <path d="M57 41l-5-1.664v3.328zm0 0" fill-rule="evenodd"/> + </g> + <use x="4.667" xlink:href="#h" y="44.517"/> + <use x="11.859" xlink:href="#e" y="44.517"/> + <use x="17.399" xlink:href="#i" y="44.517"/> + <use x="22.938" xlink:href="#j" y="44.517"/> + <use x="28.477" xlink:href="#e" y="44.517"/> + <use x="34.016" xlink:href="#b" y="44.517"/> + <path d="M1 33v16h40V33zm0 0" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"/> + <use x="68.517" xlink:href="#k" y="12.599"/> + <use x="75.162" xlink:href="#l" y="12.599"/> + <use x="80.502" xlink:href="#m" y="12.599"/> + <g stroke="#000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width=".8"> + <path d="M57 1v16h40V1zM21 33V9h36" fill="none"/> + <path d="M57 9l-7-2.332v4.664zm0 0" fill-rule="evenodd"/> + <path d="M133 33V9H97" fill="none"/> + <path d="M97 9l7 2.332V6.668zm0 0" fill-rule="evenodd"/> + </g> +</svg> diff --git a/doc/command_line_tools.rst.in b/doc/command_line_tools.rst.in new file mode 100644 index 00000000..927159c1 --- /dev/null +++ b/doc/command_line_tools.rst.in @@ -0,0 +1,12 @@ +################## +Command-Line Tools +################## + +Serd includes several tools that can be used to process data on the command-line. +Each is documented by their own man page: + + * @SERD_PIPE_LINK@ is a streaming tool for reading and writing documents. + + * @SERD_SORT_LINK@ is similar to serd-pipe, but loads data into an in-memory model instead of streaming. + + * @SERD_FILTER_LINK@ is a ``grep``-like statement filtering tool. diff --git a/doc/conf.py.in b/doc/conf.py.in index 24dede6f..3eb8a1d8 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -59,7 +59,7 @@ nitpick_ignore = list(map(lambda x: ("c:identifier", x), _opaque)) html_copy_source = False html_secnumber_suffix = " " html_short_title = "@SERD_TITLE@" -html_static_path = ["@SERD_SRCDIR@/doc/_static"] +html_static_path = ["@SERD_STATIC_PATH@"] html_theme_options = { "description": desc, @@ -133,3 +133,12 @@ else: "nosidebar": True, "show_relbars": True, } + +# EPub output + +epub_show_urls = "no" +epub_cover = ("../_static/serd.svg", "") +epub_description = "Serd, a lightweight library for working with RDF" +epub_title = "Serd @SERD_VERSION@ Documentation" +epub_basename = "Serd-@SERD_VERSION@" +epub_css_files = ["epubstyle.css"] diff --git a/doc/data_model.rst b/doc/data_model.rst new file mode 100644 index 00000000..a0ee2e46 --- /dev/null +++ b/doc/data_model.rst @@ -0,0 +1,107 @@ +########## +Data Model +########## + +********* +Structure +********* + +Serd is based on RDF, a model for Linked Data. +A deep understanding of what this means isn't necessary, +but it is important to have a basic understanding of how this data is structured. + +The basic building block of data is the *node*, +which is essentially a string with some extra type information. +A *statement* is a tuple of 3 or 4 nodes. +All information is represented by a set of statements, +which makes this model structurally very simple: +any document or database is essentially a single table with 3 or 4 columns. +This is easiest to see in NTriples or NQuads documents, +which are simple flat files with a single statement per line. + +There are, however, some restrictions. +Each node in a statement has a specific role: +subject, predicate, object, and (optionally) graph, in that order. +A statement declares that a subject has some property. +The predicate identifies the property, +and the object is its value. + +A statement is a bit like a very simple machine-readable sentence. +The "subject" and "object" are as in natural language, +and the predicate is something like a verb (but much more general). +For example, we could make a statement in English +about your intrepid author: + + drobilla has the first name David + +We can break this statement into 3 pieces like so: + +.. list-table:: + :header-rows: 1 + + * - Subject + - Predicate + - Object + * - drobilla + - has the first name + - David + +The subject and predicate must be *resources* with an identifier, +so we will need to define some URIs to represent this statement. +Conventionally, predicate names do not start with "has" or similar words, +since that would be redundant in this context. +So, +we assume that ``http://example.org/drobilla`` is the URI for drobilla, +and that ``http://example.org/firstName`` has been defined as the appropriate property ("has the first name"), +and can represent the statement in a machine-readable way: + +.. list-table:: + :header-rows: 1 + + * - Subject + - Predicate + - Object + * - ``http://example.org/drobilla`` + - ``http://example.org/firstName`` + - David + +Which can be written in NTriples like so:: + + <http://example.org/drobilla> <http://example.org/firstName> "David" . + +***************** +Working with Data +***************** + +The power of this data model lies in its uniform "physical" structure, +and the use of URIs as a decentralized namespace mechanism. +In particular, it makes filtering, merging, and otherwise "mixing" data from various sources easy. + +For example, we could add some statements to the above example to better describe the same subject:: + + <http://example.org/drobilla> <http://example.org/firstName> "David" . + <http://example.org/drobilla> <http://example.org/lastName> "Robillard" . + +We could also add information about other subjects:: + + <http://drobilla.net/sw/serd> <http://example.org/programmingLanguage> "C" . + +Including statements that relate them to each other:: + + <http://example.org/drobilla> <http://example.org/wrote> <http://drobilla.net/sw/serd> . + +Note that there is no "physical" tree structure here, +which is an important distinction from structured document formats like XML or JSON. +Since all information is just a set of statements, +the information in two documents, +for example, +can be combined by simply concatenating the documents. +Similarly, +any arbitrary subset of statements in a document can be separated into a new document. +The use of URIs enables such things even with data from many independent sources, +without any need to agree on a common schema. + +In practice, sharing URI "vocabulary" is encouraged since this is how different parties can have a shared understanding of what data *means*. +That, however, is a higher-level application concern. +Only the "physical" structure of data described here is important for understanding how Serd works, +and what its tools and APIs can do. diff --git a/doc/epub/meson.build b/doc/epub/meson.build new file mode 100644 index 00000000..c88091c0 --- /dev/null +++ b/doc/epub/meson.build @@ -0,0 +1,26 @@ +# Copyright 2021-2023 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: 0BSD OR ISC + +epub_dir = docdir / versioned_name / 'epub' + +# TODO: Add install_tag: 'doc' after requiring meson 0.60.0 + +custom_target( + 'epub', + build_by_default: true, + command: [ + sphinx_build, + '-M', 'epub', + '@OUTDIR@' / '..', + '@OUTDIR@' / '..', + '-t', 'epub', + ] + sphinx_flags, + input: [c_rst_files, c_serd_rst, c_index_xml, conf_py], + install: true, + install_dir: epub_dir, + output: ['Serd-1.1.1.epub'], +) + +if not meson.is_subproject() + summary('EPub', get_option('prefix') / epub_dir, section: 'Directories') +endif diff --git a/doc/getting_started.rst b/doc/getting_started.rst new file mode 100644 index 00000000..00bd97a9 --- /dev/null +++ b/doc/getting_started.rst @@ -0,0 +1,88 @@ +############### +Getting Started +############### + +*********** +Downloading +*********** + +Serd is distributed in several ways. +There are no "official" binaries, only source code releases which must be compiled. +However, many operating system distributions do package binaries. +Check if your package manager has a reasonably recent package, +if so, +that is the easiest and most reliable installation method. + +Release announcements with links to source code archives can be found at `<https://drobilla.net/category/serd/>`_. +All release archives and their signatures are available in the directory `<http://download.drobilla.net/>`_. + +The code can also be checked out of `git <https://gitlab.com/drobilla/serd>`_:: + + git clone https://gitlab.com/drobilla/serd.git + +********* +Compiling +********* + +Serd uses the `meson <https://mesonbuild.com/>`_ build system. +From within an extracted release archive or repository checkout, +the library can be built and tested with default options like so:: + + meson setup build + cd build + ninja test + +There are many configuration options, +which can be displayed by running ``meson configure``. + +See the `meson documentation <https://mesonbuild.com/Quick-guide.html>`_ for more details on using meson. + +********** +Installing +********** + +If the library compiled successfully, +then ``meson install`` can be used to install it. +Note that you may need superuser privileges:: + + meson install + +The installation prefix can be changed by setting the ``prefix`` option, for example:: + + meson configure -Dprefix=/opt/serd + +If you do not want to install anything, +you can also "vendor" the code in your project +(provided, of course, that you adhere to the terms of the license). +If you are using meson, +then it should simply work as a subproject without modification. +Otherwise, +you will need to set up the build yourself. + +********* +Including +********* + +Serd installs a `pkg-config <https://www.freedesktop.org/wiki/Software/pkg-config/>`_ file, +which can be used to set the appropriate compiler and linker flags for projects to use it. +If installed to a standard prefix, +then it should show up in ``pkg-config`` automatically:: + + pkg-config --list-all | grep serd + +If not, you may need to adjust the ``PKG_CONFIG_PATH`` environment variable to include the installation prefix, for example:: + + export PKG_CONFIG_PATH=/opt/serd/lib/pkgconfig + pkg-config --list-all | grep serd + +Most popular build systems natively support pkg-config. +For example, in meson:: + + serd_dep = dependency('serd-1') + +On systems where pkg-config is not available, +you will need to set up compiler and linker flags manually, +by adding something like ``-I/opt/serd/include/serd-1``, +and ``-lserd-1``, respectively. + +Once things are set up, you should be able to include the API header and start using Serd in your code. diff --git a/doc/html/meson.build b/doc/html/meson.build index 3e91bcaa..1ea034c7 100644 --- a/doc/html/meson.build +++ b/doc/html/meson.build @@ -22,8 +22,20 @@ custom_target( '_static', 'api', + 'command_line_tools.html', + 'data_model.html', 'genindex.html', + 'getting_started.html', + 'model.html', + 'nodes.html', 'overview.html', + 'reading_and_writing.html', + 'search.html', + 'statements.html', + 'stream_processing.html', + 'string_views.html', + 'using_serd.html', + 'world.html', ], ) diff --git a/doc/index.rst b/doc/index.rst index 73e1a5fb..f41e82df 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,12 +6,15 @@ Serd #### -Serd is a lightweight C library for reading and writing RDF in Turtle_, NTriples_, NQuads_, and TriG_. +Serd is a lightweight C library and set of command-line utilities for working with RDF data in Turtle_, NTriples_, NQuads_, and TriG_ formats. .. toctree:: :numbered: - overview + getting_started + data_model + command_line_tools + using_serd api/serd .. _Turtle: http://www.w3.org/TR/turtle/ diff --git a/doc/meson.build b/doc/meson.build index 1528a4b7..0caa349e 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -13,6 +13,61 @@ subdir('man') # Reference # ############# +doc_config = configuration_data() +if is_variable('mandoc') and mandoc.found() + doc_config.set('SERD_COMMAND_LINE_INDEX_ENTRY', '\n command_line_tools\n') + doc_config.set('SERD_FILTER_LINK', '`serd-filter <../man/serd-filter.html>`_') + doc_config.set('SERD_PIPE_LINK', '`serd-pipe <../man/serd-pipe.html>`_') + doc_config.set('SERD_SORT_LINK', '`serd-sort <../man/serd-sort.html>`_') +else + doc_config.set('SERD_COMMAND_LINE_INDEX_ENTRY', '') + doc_config.set('SERD_FILTER_LINK', '``serd-filter``') + doc_config.set('SERD_PIPE_LINK', '``serd-pipe``') + doc_config.set('SERD_SORT_LINK', '``serd-sort``') +endif + +command_line_tools_rst = configure_file( + configuration: doc_config, + input: files('command_line_tools.rst.in'), + output: 'command_line_tools.rst', +) + +c_rst_files = files( + 'data_model.rst', + 'getting_started.rst', + 'index.rst', + 'model.rst', + 'nodes.rst', + 'overview.rst', + 'reading_and_writing.rst', + 'statements.rst', + 'stream_processing.rst', + 'string_views.rst', + 'using_serd.rst', + 'world.rst', +) + +# Copy hand-written documentation files +sphinx_input = [] +foreach f : c_rst_files + sphinx_input += [ + configure_file(copy: true, input: f, output: '@PLAINNAME@'), + ] +endforeach + +configure_file( + copy: true, + input: files('overview_code.c'), + output: 'overview_code.c', +) + +executable( + 'overview_code', + files('overview_code.c'), + dependencies: [serd_dep], + c_args: c_suppressions, +) + # Find required programs doxygen = find_program('doxygen', required: get_option('docs')) sphinx_build = find_program('sphinx-build', required: get_option('docs')) @@ -30,6 +85,8 @@ endif # Build documentation if all required tools are found build_docs = doxygen.found() and sphinxygen.found() and sphinx_build.found() if build_docs + subdir('_static') + # Warn if the "official" theme isn't present pymod = import('python') doc_modules = ['sphinx_lv2_theme'] @@ -41,6 +98,7 @@ if build_docs # Configure conf.py for Sphinx conf_config = configuration_data() conf_config.set('SERD_SRCDIR', serd_src_root) + conf_config.set('SERD_STATIC_PATH', doc_static_build_dir) conf_config.set('SERD_TITLE', get_option('title')) conf_config.set('SERD_VERSION', meson.project_version()) conf_py = configure_file( @@ -49,16 +107,6 @@ if build_docs output: 'conf.py', ) - # Copy hand-written documentation files - rst_sources = files('index.rst', 'overview.rst') - sphinx_input = [] - foreach f : rst_sources - sphinx_input += [ - configure_file(copy: true, input: f, output: '@PLAINNAME@'), - ] - endforeach - - # Generate reference documentation input with Doxygen and Sphinxygen subdir('xml') subdir('api') @@ -71,9 +119,18 @@ if build_docs # Run Sphinx to generate final documentation for each format sphinx_build_command = [sphinx_build] + sphinx_flags - foreach format : ['html', 'singlehtml'] + foreach format : ['epub', 'html', 'singlehtml'] if not get_option(format).disabled() subdir(format) endif endforeach endif + +if not meson.is_subproject() + summary( + 'Reference', + build_docs, + bool_yn: true, + section: 'Components', + ) +endif diff --git a/doc/model.rst b/doc/model.rst new file mode 100644 index 00000000..399f370b --- /dev/null +++ b/doc/model.rst @@ -0,0 +1,237 @@ +Model +===== + +.. default-domain:: c +.. highlight:: c + +A :struct:`SerdModel` is an indexed set of statements. +A model can be used to store any data set, +from a few statements (for example, a protocol message), +to an entire document, +to a database with millions of statements. + +A new model can be created with :func:`serd_model_new`: + +.. literalinclude:: overview_code.c + :start-after: begin model-new + :end-before: end model-new + :dedent: 2 + +The information to store for each statement can be controlled by passing flags. +Additional indices can also be enabled with :func:`serd_model_add_index`. +For example, to be able to quickly search by predicate, +and store a cursor for each statement, +the model can be constructed with the :enumerator:`SERD_STORE_CARETS` flag, +and an additional :enumerator:`SERD_ORDER_PSO` index can be added like so: + +.. literalinclude:: overview_code.c + :start-after: begin fancy-model-new + :end-before: end fancy-model-new + :dedent: 2 + +Accessors +--------- + +The flags set for a model can be accessed with :func:`serd_model_flags`. + +The number of statements can be accessed with :func:`serd_model_size` and :func:`serd_model_empty`: + +.. literalinclude:: overview_code.c + :start-after: begin model-size + :end-before: end model-size + :dedent: 2 + +Adding Statements +----------------- + +Statements can be added to a model with :func:`serd_model_add`: + +.. literalinclude:: overview_code.c + :start-after: begin model-add + :end-before: end model-add + :dedent: 2 + +Alternatively, :func:`serd_model_insert` can be used if you already have a statement. +For example, the first statement in one model could be added to another like so: + +.. literalinclude:: overview_code.c + :start-after: begin model-insert + :end-before: end model-insert + :dedent: 2 + +An entire range of statements can be inserted at once with :func:`serd_model_insert_statements`. +For example, all statements in one model could be copied into another like so: + +.. literalinclude:: overview_code.c + :start-after: begin model-add-range + :end-before: end model-add-range + :dedent: 2 + +Iteration +--------- + +An iterator is a reference to a particular statement in a model. +:func:`serd_model_begin` returns an iterator to the first statement in the model, +and :func:`serd_model_end` returns a sentinel that is one past the last statement in the model: + +.. literalinclude:: overview_code.c + :start-after: begin model-begin-end + :end-before: end model-begin-end + :dedent: 2 + +A cursor can be advanced to the next statement with :func:`serd_cursor_advance`, +which returns :enumerator:`SERD_FAILURE` if the iterator reached the end: + +.. literalinclude:: overview_code.c + :start-after: begin iter-next + :end-before: end iter-next + :dedent: 2 + +Iterators are dynamically allocated, +and must eventually be destroyed with :func:`serd_cursor_free`: + +.. literalinclude:: overview_code.c + :start-after: begin iter-free + :end-before: end iter-free + :dedent: 2 + +Pattern Matching +---------------- + +There are several functions that can be used to quickly find statements in the model that match a pattern. +The simplest is :func:`serd_model_ask` which checks if there is any matching statement: + +.. literalinclude:: overview_code.c + :start-after: begin model-ask + :end-before: end model-ask + :dedent: 2 + +To access the unknown fields, +an iterator to the matching statement can be found with :func:`serd_model_find` instead: + +.. literalinclude:: overview_code.c + :start-after: begin model-find + :end-before: end model-find + :dedent: 2 + +To iterate over the matching statements, +the iterator returned by :func:`serd_model_find` can be advanced. +It will reach its end when it reaches the last matching statement: + +.. literalinclude:: overview_code.c + :start-after: begin model-range + :end-before: end model-range + :dedent: 2 + + +Similar to :func:`serd_model_ask`, +:func:`serd_model_count` can be used to count the number of matching statements: + +.. literalinclude:: overview_code.c + :start-after: begin model-count + :end-before: end model-count + :dedent: 2 + +Indexing +-------- + +A model can contain several indices that use different orderings to support different kinds of queries. +For good performance, +there should be an index where the least significant fields in the ordering correspond to wildcards in the pattern +(or, in other words, one where the most significant fields in the ordering correspond to nodes given in the pattern). +The table below lists the indices that best support a kind of pattern, +where a "?" represents a wildcard in the pattern. + ++---------+--------------+ +| Pattern | Good Indices | ++=========+==============+ +| s p o | Any | ++---------+--------------+ +| s p ? | SPO, PSO | ++---------+--------------+ +| s ? o | SOP, OSP | ++---------+--------------+ +| s ? ? | SPO, SOP | ++---------+--------------+ +| ? p o | POS, OPS | ++---------+--------------+ +| ? p ? | POS, PSO | ++---------+--------------+ +| ? ? o | OSP, OPS | ++---------+--------------+ +| ? ? ? | Any | ++---------+--------------+ + +If graphs are enabled, +then statements are indexed both with and without the graph fields, +so queries with and without a graph wildcard will have similar performance. + +Since indices take up space and slow down insertion, +it is best to enable the fewest indices possible that cover the queries that will be performed. +For example, +an applications might enable just SPO and OPS order, +because they always search for specific subjects or objects, +but never for just a predicate without specifying any other field. + +Getting Values +-------------- + +Sometimes you are only interested in a single node, +and it is cumbersome to first search for a statement and then get the node from it. +A more convenient way is to use :func:`serd_model_get`. +To get a value, specify a triple pattern where exactly one of the subject, predicate, and object is a wildcard. +If a statement matches, then the node that "fills" the wildcard will be returned: + +.. literalinclude:: overview_code.c + :start-after: begin model-get + :end-before: end model-get + :dedent: 2 + +If multiple statements match the pattern, +then the matching node from an arbitrary statement is returned. +It is an error to specify more than one wildcard, excluding the graph. + +The similar :func:`serd_model_get_statement` instead returns the matching statement: + +.. literalinclude:: overview_code.c + :start-after: begin model-get-statement + :end-before: end model-get-statement + :dedent: 2 + +Erasing Statements +------------------ + +Individual statements can be erased with :func:`serd_model_erase`, +which takes a cursor: + +.. literalinclude:: overview_code.c + :start-after: begin model-erase + :end-before: end model-erase + :dedent: 2 + +The similar :func:`serd_model_erase_statements` will erase all statements in the cursor's range: + +.. literalinclude:: overview_code.c + :start-after: begin model-erase-range + :end-before: end model-erase-range + :dedent: 2 + +Lifetime +-------- + +Models are value-like and can be copied with :func:`serd_model_copy` and compared with :func:`serd_model_equals`: + +.. literalinclude:: overview_code.c + :start-after: begin model-copy + :end-before: end model-copy + :dedent: 2 + +When a model is no longer needed, it can be destroyed with :func:`serd_model_free`: + +.. literalinclude:: overview_code.c + :start-after: begin model-free + :end-before: end model-free + :dedent: 2 + +Destroying a model invalidates all nodes and statements within that model, +so care should be taken to ensure that no dangling pointers are created. diff --git a/doc/nodes.rst b/doc/nodes.rst new file mode 100644 index 00000000..cbf16cb2 --- /dev/null +++ b/doc/nodes.rst @@ -0,0 +1,81 @@ +Nodes +===== + +.. default-domain:: c +.. highlight:: c + +Nodes are the basic building blocks of data. +Nodes are essentially strings, +but also have a :enum:`type <SerdNodeType>`, +and optionally either a datatype or a language. + +In the abstract, a node is either a literal, a URI, or blank. +Literals are essentially strings, +but may have a datatype or a language tag. +URIs are used to identify resources, +as are blank nodes, +except blank nodes only have labels with a limited scope and may be written anonymously. + +Serd also has a type for variable nodes, +which are used for some features but not present in RDF data. + +Construction +------------ + +Nodes can be allocated and constructed in several ways. +To accommodate many functions that access or create arbitrary nodes, +the arguments to specify a node are passed in a :c:struct:`SerdNodeArgs`. +This is a tagged union of values and/or views that define a node. + +Convenience constructors are provided, +which can be used to specify arguments for any node: + +- :func:`serd_a_token` + +- :func:`serd_a_file_uri` +- :func:`serd_a_parsed_uri` +- :func:`serd_a_uri_string` +- :func:`serd_a_uri` + +- :func:`serd_a_blank` + +- :func:`serd_a_base64` +- :func:`serd_a_decimal` +- :func:`serd_a_hex` +- :func:`serd_a_integer` +- :func:`serd_a_literal` +- :func:`serd_a_plain_literal` +- :func:`serd_a_primitive` +- :func:`serd_a_string_view` +- :func:`serd_a_string` +- :func:`serd_a_typed_literal` + +Note that most of these are simple wrappers for more fundamental constructors; +there are only three "kinds" of RDF nodes: URIs, blank nodes, and literals. + +Nodes can be constructed in a user-provided buffer with :func:`serd_node_construct`. +This is useful for applications with custom memory management schemes, +such as allocating memory in a preexisting buffer. + +Typical higher-level applications without such needs can use :func:`serd_node_new`, +which dynamically allocates a new node using the given allocator (the system's, by default). +The application must eventually call :func:`serd_node_free` to free the node. + +The memory management hassle can be avoided by using :c:struct:`SerdNodes`. +A node can be created or retrieved using :func:`serd_nodes_get`, +and it will be freed when the whole set of nodes is destroyed with :func:`serd_nodes_free`. + +Accessors +--------- + +The basic attributes of a node can be accessed with :func:`serd_node_type`, +:func:`serd_node_string`, +and :func:`serd_node_length`. + +A measured view of the string can be accessed with :func:`serd_node_string_view`. +This can be passed to functions that take a string view, +to avoid redundant measurement of the node string. + +The datatype or language of a literal can be retrieved with :func:`serd_node_datatype` or :func:`serd_node_language`, respectively. +Note that literals may have a datatype or a language, +but never both at once. diff --git a/doc/overview.rst b/doc/overview.rst index b03615b9..620d9225 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -2,25 +2,87 @@ Copyright 2020-2021 David Robillard <d@drobilla.net> SPDX-License-Identifier: ISC -######## +======== Overview -######## +======== .. default-domain:: c .. highlight:: c -The API revolves around two main types: the :doc:`api/serd_reader`, -which reads text and fires callbacks, -and the :doc:`api/serd_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/serd_env`, -which stores the current base URI and set of namespace prefixes. - -The complete API is declared in ``serd.h``: +The serd API is declared in ``serd.h``: .. code-block:: c #include <serd/serd.h> + +An instance of serd is represented by a :doc:`api/serd_world`, +which manages "global" facilities like memory allocation and logging. +The rest of the API can be broadly grouped into four categories: + +Data + A :doc:`api/serd_node` is the basic building block of data, + 3 or 4 nodes together make a :doc:`api/serd_statement`. + All data is expressed in statements. + +Streams + Components communicate by sending and receiving streams of data. + Data is streamed via :doc:`api/serd_sink`, + which is an abstract interface that receives :doc:`api/serd_event`. + The fundamental event is a statement event, + but there are a few additional event types that describe context which is useful for things like pretty-printing. + + Some components both send and receive data, + which allow them to be inserted in a `pipeline` to process the data as it streams through. + For example, + a :doc:`api/serd_canon` converts literals to canonical form, + and a :doc:`api/serd_filter` filters statements that match (or do not match) some pattern. + + An event stream describes changes to data and its context, + but does not store the context. + For that, an associated :doc:`api/serd_env` is maintained. + This stores the active base URI and namespace prefixes which can, + for example, + be used to write output with the same abbreviations used in the source. + +Reading and Writing + Reading and writing data is performed using a :doc:`api/serd_reader`, + which reads text and emits data to a sink, + and a :doc:`api/serd_writer`, + which is a sink that writes the incoming data as text. + Both work in a streaming fashion so that large documents can be pretty-printed, + translated, + or otherwise processed quickly using only a small amount of memory. + +Storage + A set of statements can be stored in memory as a :doc:`api/serd_model`. + This supports quickly searching and scanning statements, + provided an appropriate index is enabled. + + Data can be loaded into a model via an :doc:`api/serd_inserter`, + which is a sink that inserts incoming statements into a model. + Data in a model can be written out by calling :func:`serd_describe_range` on the desired range of statements. + +The sink interface acts as a generic connection which can be used to build custom data processing pipelines. +For example, +a simple pipeline to read a document, filter out some statements, and write the result to a new file, +would look something like: + +.. image:: _static/writer_pipeline.svg + +Here, dotted arrows represent event streams, +and solid arrows represent explicit use of a component. +In other words, dotted arrows represent connections via the abstract :doc:`api/serd_sink` interface. +In this case both reader and writer are using the same environment, +so the output document will have the same abbreviations as the input. +It is also possible to use different environments, +for example to set additional namespace prefixes to further abbreviate the document. + +Similarly, a document could be loaded into a model with canonical literals using a pipeline like: + +.. image:: _static/model_pipeline.svg + +Many other useful pipelines can be built using the components in serd, +and applications can implement custom ones to add additional functionality. + +The following documentation gives a more detailed bottom-up introduction to the API, +with links to the complete reference where further detail can be found. diff --git a/doc/overview_code.c b/doc/overview_code.c new file mode 100644 index 00000000..37f983e8 --- /dev/null +++ b/doc/overview_code.c @@ -0,0 +1,461 @@ +// Copyright 2021-2022 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +/* + Example code that is included in the documentation. Code in the + documentation is included from here rather than written inline so that it can + be tested and avoid rotting. The code here doesn't make much sense, but is + written such that it at least compiles and will run without crashing. +*/ + +#include "serd/serd.h" +#include "zix/string_view.h" + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +static void +string_views(void) +{ + const char* const string_pointer = "some string"; + + // begin make-empty-string + ZixStringView empty = zix_empty_string(); + // end make-empty-string + + // begin make-static-string + ZixStringView hello = zix_string("hello"); + // end make-static-string + + // begin measure-string + ZixStringView view = zix_string(string_pointer); + // end measure-string + + // begin make-string-view + ZixStringView slice = zix_substring(string_pointer, 4); + // end make-string-view +} + +static void +statements(void) +{ + SerdNodes* nodes = serd_nodes_new(NULL); + + // begin statement-new + SerdStatement* statement = serd_statement_new( + NULL, + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/drobilla")), + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/firstName")), + serd_nodes_get(nodes, serd_a_string("David")), + NULL, + NULL); + // end statement-new + + serd_statement_free(NULL, statement); + serd_nodes_free(nodes); +} + +static void +statements_accessing_fields(void) +{ + SerdNode* ss = + serd_node_new(NULL, serd_a_uri_string(("http://example.org/s"))); + + SerdNode* sp = + serd_node_new(NULL, serd_a_uri_string(("http://example.org/p"))); + + SerdNode* so = + serd_node_new(NULL, serd_a_uri_string(("http://example.org/o"))); + + SerdStatement* statement = serd_statement_new(NULL, ss, sp, so, NULL, NULL); + + // begin get-subject + const SerdNode* s = serd_statement_node(statement, SERD_SUBJECT); + // end get-subject + + // begin get-pog + const SerdNode* p = serd_statement_predicate(statement); + const SerdNode* o = serd_statement_object(statement); + const SerdNode* g = serd_statement_graph(statement); + // end get-pog + + // begin get-caret + const SerdCaret* c = serd_statement_caret(statement); + // end get-caret +} + +static void +statements_comparison(void) +{ + SerdNode* ss = serd_node_new(NULL, serd_a_uri_string("http://example.org/s")); + + SerdNode* sp = serd_node_new(NULL, serd_a_uri_string("http://example.org/p")); + + SerdNode* so = serd_node_new(NULL, serd_a_uri_string("http://example.org/o")); + + SerdStatement* statement1 = serd_statement_new(NULL, ss, sp, so, NULL, NULL); + SerdStatement* statement2 = serd_statement_new(NULL, ss, sp, so, NULL, NULL); + + // begin statement-equals + if (serd_statement_equals(statement1, statement2)) { + printf("Match\n"); + } + // end statement-equals + + SerdStatement* statement = statement1; + + // begin statement-matches + SerdNode* eg_name = + serd_node_new(NULL, serd_a_uri_string("http://example.org/name")); + + if (serd_statement_matches(statement, NULL, eg_name, NULL, NULL)) { + printf("%s has name %s\n", + serd_node_string(serd_statement_subject(statement)), + serd_node_string(serd_statement_object(statement))); + } + // end statement-matches +} + +static void +statements_lifetime(void) +{ + SerdStatement* statement = NULL; + + // begin statement-copy + SerdStatement* copy = serd_statement_copy(NULL, statement); + // end statement-copy + + // begin statement-free + serd_statement_free(NULL, copy); + // end statement-free +} + +static void +world(void) +{ + // begin world-new + SerdWorld* world = serd_world_new(NULL); + // end world-new + + // begin get-blank + const SerdNode* world_blank = serd_world_get_blank(world); + SerdNode* my_blank = serd_node_copy(NULL, world_blank); + // end get-blank +} + +static void +model(void) +{ + SerdWorld* world = serd_world_new(NULL); + + // begin model-new + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + // end model-new + + // begin fancy-model-new + SerdModel* fancy_model = + serd_model_new(world, SERD_ORDER_SPO, SERD_STORE_CARETS); + + serd_model_add_index(fancy_model, SERD_ORDER_PSO); + // end fancy-model-new + + // begin model-copy + SerdModel* copy = serd_model_copy(NULL, model); + + assert(serd_model_equals(copy, model)); + // end model-copy + + // begin model-size + if (serd_model_empty(model)) { + printf("Model is empty\n"); + } else if (serd_model_size(model) > 1000) { + printf("Model has over 1000 statements\n"); + } + // end model-size + + // begin model-free + serd_model_free(copy); + // end model-free + + // begin model-add + SerdNodes* nodes = serd_nodes_new(NULL); + + serd_model_add( + model, + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/thing")), // S + serd_nodes_get(nodes, serd_a_uri_string("http://example.org/name")), // P + serd_nodes_get(nodes, serd_a_string("Thing")), // O + NULL); // G + // end model-add + + SerdModel* other_model = model; + + // begin model-insert + const SerdCursor* cursor = serd_model_begin(NULL, other_model); + + serd_model_insert(model, serd_cursor_get(cursor)); + // end model-insert + + // begin model-add-range + SerdCursor* other_range = serd_model_begin(NULL, other_model); + + serd_model_insert_statements(model, other_range); + + serd_cursor_free(NULL, other_range); + // end model-add-range + + // begin model-begin-end + SerdCursor* i = serd_model_begin(NULL, model); + if (serd_cursor_equals(i, serd_model_end(model))) { + printf("Model is empty\n"); + } else { + const SerdStatement* s = serd_cursor_get(i); + + printf("First statement subject: %s\n", + serd_node_string(serd_statement_subject(s))); + } + // end model-begin-end + + // begin iter-next + if (!serd_cursor_advance(i)) { + const SerdStatement* s = serd_cursor_get(i); + + printf("Second statement subject: %s\n", + serd_node_string(serd_statement_subject(s))); + } + // end iter-next + + // begin iter-free + serd_cursor_free(NULL, i); + // end iter-free + + // begin model-all + SerdCursor* all = serd_model_begin(NULL, model); + // end model-all + + // begin range-next + if (serd_cursor_is_end(all)) { + printf("Model is empty\n"); + } else { + const SerdStatement* s = serd_cursor_get(all); + + printf("First statement subject: %s\n", + serd_node_string(serd_statement_subject(s))); + } + + if (!serd_cursor_advance(all)) { + const SerdStatement* s = serd_cursor_get(all); + + printf("Second statement subject: %s\n", + serd_node_string(serd_statement_subject(s))); + } + // end range-next + + // begin model-ask + const SerdNode* rdf_type = serd_nodes_get( + nodes, + serd_a_uri_string("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")); + + if (serd_model_ask(model, NULL, rdf_type, NULL, NULL)) { + printf("Model contains a type statement\n"); + } + // end model-ask + + // Add a statement so that the searching examples below work + SerdNode* inst = + serd_node_new(NULL, serd_a_uri_string("http://example.org/i")); + SerdNode* type = + serd_node_new(NULL, serd_a_uri_string("http://example.org/T")); + serd_model_add(model, inst, rdf_type, type, NULL); + + // begin model-find + SerdCursor* it = serd_model_find(NULL, model, NULL, rdf_type, NULL, NULL); + + const SerdStatement* statement = serd_cursor_get(it); + const SerdNode* instance = + statement ? serd_statement_subject(statement) : NULL; + // end model-find + + // begin model-count + size_t n = serd_model_count(model, instance, rdf_type, NULL, NULL); + printf("Instance has %zu types\n", n); + // end model-count + + // begin model-range + SerdCursor* range = serd_model_find(NULL, + model, + instance, // Subject = instance + rdf_type, // Predicate = rdf:type + NULL, // Object = anything + NULL); // Graph = anything + + for (; !serd_cursor_is_end(range); serd_cursor_advance(range)) { + const SerdStatement* s = serd_cursor_get(range); + + printf("Instance has type %s\n", + serd_node_string(serd_statement_object(s))); + } + + serd_cursor_free(NULL, range); + // end model-range + + // begin model-get + const SerdNode* t = serd_model_get(model, + instance, // Subject + rdf_type, // Predicate + NULL, // Object + NULL); // Graph + if (t) { + printf("Instance has type %s\n", serd_node_string(t)); + } + // end model-get + + // begin model-get-statement + const SerdStatement* ts = + serd_model_get_statement(model, instance, rdf_type, NULL, NULL); + + if (ts) { + printf("Instance %s has type %s\n", + serd_node_string(serd_statement_subject(ts)), + serd_node_string(serd_statement_object(ts))); + } + // end model-get-statement + + // begin model-erase + SerdCursor* some_type = + serd_model_find(NULL, model, NULL, rdf_type, NULL, NULL); + serd_model_erase(model, some_type); + serd_cursor_free(NULL, some_type); + // end model-erase + + // begin model-erase-range + SerdCursor* all_types = + serd_model_find(NULL, model, NULL, rdf_type, NULL, NULL); + serd_model_erase_statements(model, all_types); + serd_cursor_free(NULL, all_types); + // end model-erase-range +} + +static void +reading_writing(void) +{ + SerdWorld* world = serd_world_new(NULL); + + // begin env-new + ZixStringView host = zix_empty_string(); + ZixStringView path = zix_string("/some/file.ttl"); + SerdNode* base = serd_node_new(NULL, serd_a_file_uri(path, host)); + SerdEnv* env = serd_env_new(NULL, serd_node_string_view(base)); + // end env-new + + // begin env-set-prefix + serd_env_set_prefix( + env, + zix_string("rdf"), + zix_string("http://www.w3.org/1999/02/22-rdf-syntax-ns#")); + // end env-set-prefix + + // begin byte-sink-new + SerdOutputStream out = serd_open_output_file("/tmp/eg.ttl"); + // end byte-sink-new + + // clang-format off + // begin writer-new + SerdWriter* writer = serd_writer_new( + world, // World + SERD_TURTLE, // Syntax + 0, // Writer flags + env, // Environment + &out, // Output stream + 4096); // Block size + // end writer-new + + // begin reader-new + SerdReader* reader = serd_reader_new( + world, // World + SERD_TURTLE, // Syntax + 0, // Reader flags + env, // Environment + serd_writer_sink(writer), // Target sink + 4096); // Block size + // end reader-new + + // clang-format on + + // begin read-document + SerdStatus st = serd_reader_read_document(reader); + if (st) { + printf("Error reading document: %s\n", serd_strerror(st)); + } + // end read-document + + // begin reader-writer-free + serd_reader_free(reader); + serd_writer_free(writer); + // end reader-writer-free + + // begin byte-sink-free + serd_close_output(&out); + // end byte-sink-free + + // begin inserter-new + SerdModel* model = serd_model_new(world, SERD_ORDER_SPO, 0U); + SerdSink* inserter = serd_inserter_new(model, NULL); + // end inserter-new + + // begin model-reader-new + SerdReader* const model_reader = + serd_reader_new(world, SERD_TURTLE, 0, env, inserter, 4096); + + st = serd_reader_read_document(model_reader); + if (st) { + printf("Error loading model: %s\n", serd_strerror(st)); + } + // end model-reader-new + + // begin write-range + serd_describe_range( + NULL, serd_model_begin(NULL, model), serd_writer_sink(writer), 0); + // end write-range + + // begin canon-new + SerdSink* canon = serd_canon_new(world, inserter, 0); + // end canon-new + + SerdNode* rdf_type = NULL; + + // begin filter-new + SerdSink* filter = serd_filter_new(world, // World + inserter, // Target + NULL, // Subject + rdf_type, // Predicate + NULL, // Object + NULL, // Graph + true); // Inclusive + // end filter-new +} + +int +main(void) +{ + string_views(); + statements(); + statements_accessing_fields(); + statements_comparison(); + statements_lifetime(); + world(); + model(); + reading_writing(); + + return 0; +} + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif diff --git a/doc/reading_and_writing.rst b/doc/reading_and_writing.rst new file mode 100644 index 00000000..1180d03d --- /dev/null +++ b/doc/reading_and_writing.rst @@ -0,0 +1,149 @@ +Reading and Writing +=================== + +.. default-domain:: c +.. highlight:: c + +Reading and writing documents in a textual syntax is handled by the :struct:`SerdReader` and :struct:`SerdWriter`, respectively. +Serd is designed around a concept of event streams, +so the reader or writer can be at the beginning or end of a "pipeline" of stream processors. +This allows large documents to be processed quickly in an "online" fashion, +while requiring only a small constant amount of memory. +If you are familiar with XML, +this is roughly analogous to SAX. + +A common simple setup is to simply connect a reader directly to a writer. +This can be used for things like pretty-printing, +or converting a document from one syntax to another. +This can be done by passing the sink returned by :func:`serd_writer_sink` to the reader constructor, :func:`serd_reader_new`. + +First, +in order to write a document, +an environment needs to be created. +This defines the base URI and any namespace prefixes, +which is used to resolve any relative URIs or prefixed names, +and may be used to abbreviate the output. +In most cases, the base URI should simply be the URI of the file being written. +For example: + +.. literalinclude:: overview_code.c + :start-after: begin env-new + :end-before: end env-new + :dedent: 2 + +Namespace prefixes can also be defined for any vocabularies used: + +.. literalinclude:: overview_code.c + :start-after: begin env-set-prefix + :end-before: end env-set-prefix + :dedent: 2 + +We now have an environment set up for our document, +but still need to specify where to write it. +This is done by creating a :struct:`SerdOutputStream`, +which is a generic interface that can be set up to write to a file, +a buffer in memory, +or a custom function that can be used to write output anywhere. +In this case, we will write to the file we set up as the base URI: + +.. literalinclude:: overview_code.c + :start-after: begin byte-sink-new + :end-before: end byte-sink-new + :dedent: 2 + +The second argument is the page size in bytes, +so I/O will be performed in chunks for better performance. +The value used here, 4096, is a typical filesystem block size that should perform well on most machines. + +With an environment and byte sink ready, +the writer can now be created: + +.. literalinclude:: overview_code.c + :start-after: begin writer-new + :end-before: end writer-new + :dedent: 2 + +Output is written by feeding statements and other events to the sink returned by :func:`serd_writer_sink`. +:struct:`SerdSink` is the generic interface for anything that can consume data streams. +Many objects provide the same interface to do various things with the data, +but in this case we will send data directly to the writer: + +.. literalinclude:: overview_code.c + :start-after: begin reader-new + :end-before: end reader-new + :dedent: 2 + +The third argument of :func:`serd_reader_new` takes a bitwise ``OR`` of :enum:`SerdReaderFlag` flags that can be used to configure the reader. +In this case only :enumerator:`SERD_READ_LAX` is given, +which tolerates some invalid input without halting on an error, +but others can be included. +For example, passing ``SERD_READ_LAX | SERD_READ_RELATIVE`` would enable lax mode and preserve relative URIs in the input. + +Now that we have a reader that is set up to directly push its output to a writer, +we can finally process the document: + +.. literalinclude:: overview_code.c + :start-after: begin read-document + :end-before: end read-document + :dedent: 2 + +Alternatively, one "chunk" of input can be read at a time with :func:`serd_reader_read_chunk`. +A "chunk" is generally one top-level description of a resource, +including any anonymous blank nodes in its description, +but this depends on the syntax and the structure of the document being read. + +The reader pushes events to its sink as input is read, +so in this scenario the data should now have been re-written by the writer +(assuming no error occurred). +To finish and ensure that a complete document has been read and written, +:func:`serd_reader_finish` can be called followed by :func:`serd_writer_finish`. +However these will be automatically called on destruction if necessary, +so if the reader and writer are no longer required they can simply be destroyed: + +.. literalinclude:: overview_code.c + :start-after: begin reader-writer-free + :end-before: end reader-writer-free + :dedent: 2 + +Note that it is important to free the reader first in this case, +since finishing the read may push events to the writer. +Finally, closing the output with :func:`serd_close_output` will flush and close the output file, +so it is ready to be read again later. + +.. literalinclude:: overview_code.c + :start-after: begin byte-sink-free + :end-before: end byte-sink-free + :dedent: 2 + +Reading into a Model +-------------------- + +A document can be loaded into a model by setting up a reader that pushes data to a model "inserter" rather than a writer: + +.. literalinclude:: overview_code.c + :start-after: begin inserter-new + :end-before: end inserter-new + :dedent: 2 + +The process of reading the document is the same as above, +only the sink is different: + +.. literalinclude:: overview_code.c + :start-after: begin model-reader-new + :end-before: end model-reader-new + :dedent: 2 + +Writing a Model +--------------- + +A model, or parts of a model, can be written by writing the desired range with :func:`serd_describe_range`: + +.. literalinclude:: overview_code.c + :start-after: begin write-range + :end-before: end write-range + :dedent: 2 + +By default, +this writes the range in chunks suited to pretty-printing with anonymous blank nodes (like "[ ... ]" in Turtle or TriG). +Any rdf:type properties (written "a" in Turtle or TriG) will be written before any other properties of their subject. +This can be disabled by passing the flag :enumerator:`SERD_NO_TYPE_FIRST`. diff --git a/doc/statements.rst b/doc/statements.rst new file mode 100644 index 00000000..da7b8a03 --- /dev/null +++ b/doc/statements.rst @@ -0,0 +1,123 @@ +Statements +========== + +.. default-domain:: c +.. highlight:: c + +A :struct:`SerdStatement` is a tuple of either 3 or 4 nodes: +the `subject`, `predicate`, `object`, and optional `graph`. +Statements declare that a subject has some property. +The predicate identifies the property, +and the object is its value. + +A statement can be thought of as a very simple machine-readable sentence. +The subject and object are as in natural language, +and the predicate is something like a verb, but more general. +For example, we could make a statement in English about your intrepid author: + + drobilla has the first name "David" + +We can break this statement into 3 pieces like so: + +.. list-table:: + :header-rows: 1 + + * - Subject + - Predicate + - Object + * - drobilla + - has the first name + - "David" + +To make a :struct:`SerdStatement` out of this, we need to define some URIs. +In RDF, the subject and predicate must be *resources* with an identifier +(for example, neither can be a string). +Conventionally, predicate names do not start with "has" or similar words, +since that would be redundant in this context. +So, we assume that ``http://example.org/drobilla`` is the URI for drobilla, +and that ``http://example.org/firstName`` has been defined somewhere to be +a property with the appropriate meaning, +and can make an equivalent :struct:`SerdStatement`: + +.. literalinclude:: overview_code.c + :start-after: begin statement-new + :end-before: end statement-new + :dedent: 2 + +The last two fields are the graph and the cursor. +The graph is another node that can be used to group statements, +for example by the URI of the document they were loaded from. +The cursor represents the location in a document where the statement was loaded from, if applicable. + +Accessing Fields +---------------- + +Statement fields can be accessed with +:func:`serd_statement_node`, for example: + +.. literalinclude:: overview_code.c + :start-after: begin get-subject + :end-before: end get-subject + :dedent: 2 + +Alternatively, an accessor function is provided for each field: + +.. literalinclude:: overview_code.c + :start-after: begin get-pog + :end-before: end get-pog + :dedent: 2 + +Every statement has a subject, predicate, and object, +but the graph may be null. +The cursor may also be null (as it would be in this case), +but if available it can be accessed with :func:`serd_statement_caret`: + +.. literalinclude:: overview_code.c + :start-after: begin get-caret + :end-before: end get-caret + :dedent: 2 + +Comparison +---------- + +Two statements can be compared with :func:`serd_statement_equals`: + +.. literalinclude:: overview_code.c + :start-after: begin statement-equals + :end-before: end statement-equals + :dedent: 2 + +Statements are equal if all four corresponding pairs of nodes are equal. +The cursor is considered metadata, and is ignored for comparison. + +It is also possible to match statements against a pattern using ``NULL`` as a wildcard, +with :func:`serd_statement_matches`: + +.. literalinclude:: overview_code.c + :start-after: begin statement-matches + :end-before: end statement-matches + :dedent: 2 + +Lifetime +-------- + +A statement only contains const references to nodes, +it does not own nodes or manage their lifetimes internally. +The cursor, however, is owned by the statement. +A statement can be copied with :func:`serd_statement_copy`: + +.. literalinclude:: overview_code.c + :start-after: begin statement-copy + :end-before: end statement-copy + :dedent: 2 + +The copied statement will refer to exactly the same nodes, +though the cursor will be deep copied. + +In most cases, statements come from a reader or model which manages them internally, +but a statement owned by the application must be freed with :func:`serd_statement_free`: + +.. literalinclude:: overview_code.c + :start-after: begin statement-free + :end-before: end statement-free + :dedent: 2 diff --git a/doc/stream_processing.rst b/doc/stream_processing.rst new file mode 100644 index 00000000..0b3f126f --- /dev/null +++ b/doc/stream_processing.rst @@ -0,0 +1,47 @@ +Stream Processing +================= + +.. default-domain:: c +.. highlight:: c + +The above examples show how a document can be either written to a file or loaded into a model, +simply by changing the sink that the data is written to. +There are also sinks that filter or transform the data before passing it on to another sink, +which can be used to build more advanced pipelines with several processing stages. + +Canonical Literals +------------------ + +A `canon` is a stream processor that converts literals with supported XSD datatypes into canonical form. +For example, this will rewrite an xsd:decimal literal like ".10" as "0.1". +A canon is created with :func:`serd_canon_new`, +which needs to be passed the "target" sink that the transformed statements should be written to, +for example: + +.. literalinclude:: overview_code.c + :start-after: begin canon-new + :end-before: end canon-new + :dedent: 2 + +The last argument is a bitwise ``OR`` of :enum:`SerdCanonFlag` flags. +For example, :enumerator:`SERD_CANON_LAX` will tolerate and pass through invalid literals, +which can be useful for cleaning up questionabe data as much as possible without losing any information. + +Filtering Statements +-------------------- + +A `filter` is a stream processor that filters statements based on a pattern. +It can be configured in either inclusive or exclusive mode, +which passes through only statements that match or don't match the pattern, +respectively. +A filter is created with :func:`serd_filter_new`, +which takes a target, pattern, and inclusive flag. +For example, all statements with predicate ``rdf:type`` could be filtered out when loading a model: + +.. literalinclude:: overview_code.c + :start-after: begin filter-new + :end-before: end filter-new + :dedent: 2 + +If ``false`` is passed for the last parameter instead, +then the filter operates in exclusive mode and will instead insert only statements with predicate ``rdf:type``. diff --git a/doc/string_views.rst b/doc/string_views.rst new file mode 100644 index 00000000..c3fc4a63 --- /dev/null +++ b/doc/string_views.rst @@ -0,0 +1,56 @@ +String Views +============ + +.. default-domain:: c +.. highlight:: c + +For performance reasons, +many functions in serd that take a string take a ``ZixStringView``, +rather than a bare pointer. +This forces code to be explicit about string measurement, +which discourages common patterns of repeated measurement of the same string. + +Since a string view interface is a useful abstraction to share across several libraries, +Serd uses the string view interface of its dependency, +`Zix <https://gitlab.com/drobilla/zix>`_. +For convenience, several constructors are provided: + +``zix_empty_string`` + + Constructs a view of an empty string, for example: + + .. literalinclude:: overview_code.c + :start-after: begin make-empty-string + :end-before: end make-empty-string + :dedent: 2 + +``zix_string`` + + Constructs a view of an arbitrary string, for example: + + .. literalinclude:: overview_code.c + :start-after: begin measure-string + :end-before: end measure-string + :dedent: 2 + + This calls ``strlen`` to measure the string. + Modern compilers should optimize this away if the parameter is a literal. + +``zix_substring`` + + Constructs a view of a slice of a string with an explicit length, + for example: + + .. literalinclude:: overview_code.c + :start-after: begin make-string-view + :end-before: end make-string-view + :dedent: 2 + + This can also be used to create a view of a pre-measured string. + If the length a dynamic string is already known, + this is faster than ``serd_string``, + since it avoids redundant measurement. + +These constructors can be used inline when passing parameters, +but if the same dynamic string is used several times, +it is better to make a string view variable to avoid redundant measurement. diff --git a/doc/using_serd.rst b/doc/using_serd.rst new file mode 100644 index 00000000..cfe57c4c --- /dev/null +++ b/doc/using_serd.rst @@ -0,0 +1,15 @@ +########## +Using Serd +########## + +.. toctree:: + + overview + string_views + nodes + statements + world + model + reading_and_writing + stream_processing + diff --git a/doc/world.rst b/doc/world.rst new file mode 100644 index 00000000..31cbe16b --- /dev/null +++ b/doc/world.rst @@ -0,0 +1,48 @@ +World +===== + +.. default-domain:: c +.. highlight:: c + +So far, we have only used nodes and statements, +which are simple independent objects. +Higher-level facilities in Serd require a :struct:`SerdWorld`, +which represents the global library state. + +A program typically uses just one world, +which can be constructed using :func:`serd_world_new`: + +.. literalinclude:: overview_code.c + :start-after: begin world-new + :end-before: end world-new + :dedent: 2 + +All "global" library state is handled explicitly via the world. +Serd does not contain any static mutable data, +allowing it to be used concurrently in several parts of a program, +for example in plugins. + +If multiple worlds *are* used in a single program, +they must never be mixed: +objects "inside" one world can not be used with objects inside another. + +Note that the world is not a database, +it only manages a small amount of library state for things like configuration and logging. + +Generating Blanks +----------------- + +Blank nodes, or simply "blanks", +are used for resources that do not have URIs. +Unlike URIs, they are not global identifiers, +and only have meaning within their local context (for example, a document). +The world provides a method for automatically generating unique blank identifiers: + +.. literalinclude:: overview_code.c + :start-after: begin get-blank + :end-before: end get-blank + :dedent: 2 + +Note that the returned pointer is to a node that will be updated on the next call to :func:`serd_world_get_blank`, +so it is usually best to copy the node, +like in the example above. diff --git a/meson.build b/meson.build index e58436a2..8cd410dd 100644 --- a/meson.build +++ b/meson.build @@ -280,6 +280,14 @@ if not meson.is_subproject() if not get_option('tools').disabled() summary( { + 'Man pages': not get_option('man').disabled(), + }, + bool_yn: true, + section: 'Components', + ) + + summary( + { 'Executables': get_option('prefix') / get_option('bindir'), }, section: 'Directories', diff --git a/meson_options.txt b/meson_options.txt index 09fc2334..c46a6d73 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,8 +7,11 @@ option('checks', type: 'feature', value: 'enabled', yield: true, option('docs', type: 'feature', value: 'auto', yield: true, description: 'Build documentation') +option('epub', type: 'feature', value: 'disabled', yield: true, + description: 'Build EPub documentation') + option('html', type: 'feature', value: 'auto', yield: true, - description: 'Build paginated HTML documentation') + description: 'Build HTML documentation') option('lint', type: 'boolean', value: false, yield: true, description: 'Run code quality checks') @@ -19,7 +22,7 @@ option('man', type: 'feature', value: 'enabled', yield: true, option('man_html', type: 'feature', value: 'auto', yield: true, description: 'Build HTML man pages') -option('singlehtml', type: 'feature', value: 'auto', yield: true, +option('singlehtml', type: 'feature', value: 'disabled', yield: true, description: 'Build single-page HTML documentation') option('static', type: 'boolean', value: false, yield: true, |