diff options
author | Alyssa Ross <hi@alyssa.is> | 2022-05-31 09:59:33 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2022-05-31 09:59:57 +0000 |
commit | 9ff36293d1e428cd7bf03e8d4b03611b6d361c28 (patch) | |
tree | 1ab51a42b868c55b83f6ccdb80371b9888739dd9 /nixos/lib/make-options-doc | |
parent | 1c4fcd0d4b0541e674ee56ace1053e23e562cc80 (diff) | |
parent | ddc3c396a51918043bb0faa6f676abd9562be62c (diff) | |
download | nixpkgs-archive.tar nixpkgs-archive.tar.gz nixpkgs-archive.tar.bz2 nixpkgs-archive.tar.lz nixpkgs-archive.tar.xz nixpkgs-archive.tar.zst nixpkgs-archive.zip |
Last good Nixpkgs for Weston+nouveau? archive
I came this commit hash to terwiz[m] on IRC, who is trying to figure out what the last version of Spectrum that worked on their NUC with Nvidia graphics is.
Diffstat (limited to 'nixos/lib/make-options-doc')
-rw-r--r-- | nixos/lib/make-options-doc/default.nix | 169 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/generateAsciiDoc.py | 37 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/generateCommonMark.py | 27 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/mergeJSON.py | 93 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/options-to-docbook.xsl | 246 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/optionsJSONtoXML.nix | 6 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/postprocess-option-descriptions.xsl | 115 | ||||
-rw-r--r-- | nixos/lib/make-options-doc/sortXML.py | 27 |
8 files changed, 720 insertions, 0 deletions
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix new file mode 100644 index 00000000000..57652dd5db1 --- /dev/null +++ b/nixos/lib/make-options-doc/default.nix @@ -0,0 +1,169 @@ +/* Generate JSON, XML and DocBook documentation for given NixOS options. + + Minimal example: + + { pkgs, }: + + let + eval = import (pkgs.path + "/nixos/lib/eval-config.nix") { + baseModules = [ + ../module.nix + ]; + modules = []; + }; + in pkgs.nixosOptionsDoc { + options = eval.options; + } + +*/ +{ pkgs +, lib +, options +, transformOptions ? lib.id # function for additional tranformations of the options +, revision ? "" # Specify revision for the options +# a set of options the docs we are generating will be merged into, as if by recursiveUpdate. +# used to split the options doc build into a static part (nixos/modules) and a dynamic part +# (non-nixos modules imported via configuration.nix, other module sources). +, baseOptionsJSON ? null +# instead of printing warnings for eg options with missing descriptions (which may be lost +# by nix build unless -L is given), emit errors instead and fail the build +, warningsAreErrors ? true +}: + +let + # Make a value safe for JSON. Functions are replaced by the string "<function>", + # derivations are replaced with an attrset + # { _type = "derivation"; name = <name of that derivation>; }. + # We need to handle derivations specially because consumers want to know about them, + # but we can't easily use the type,name subset of keys (since type is often used as + # a module option and might cause confusion). Use _type,name instead to the same + # effect, since _type is already used by the module system. + substSpecial = x: + if lib.isDerivation x then { _type = "derivation"; name = x.name; } + else if builtins.isAttrs x then lib.mapAttrs (name: substSpecial) x + else if builtins.isList x then map substSpecial x + else if lib.isFunction x then "<function>" + else x; + + optionsList = lib.flip map optionsListVisible + (opt: transformOptions opt + // lib.optionalAttrs (opt ? example) { example = substSpecial opt.example; } + // lib.optionalAttrs (opt ? default) { default = substSpecial opt.default; } + // lib.optionalAttrs (opt ? type) { type = substSpecial opt.type; } + // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages opt.name; } + ); + + # Generate DocBook documentation for a list of packages. This is + # what `relatedPackages` option of `mkOption` from + # ../../../lib/options.nix influences. + # + # Each element of `relatedPackages` can be either + # - a string: that will be interpreted as an attribute name from `pkgs` and turned into a link + # to search.nixos.org, + # - a list: that will be interpreted as an attribute path from `pkgs` and turned into a link + # to search.nixos.org, + # - an attrset: that can specify `name`, `path`, `comment` + # (either of `name`, `path` is required, the rest are optional). + # + # NOTE: No checks against `pkgs` are made to ensure that the referenced package actually exists. + # Such checks are not compatible with option docs caching. + genRelatedPackages = packages: optName: + let + unpack = p: if lib.isString p then { name = p; } + else if lib.isList p then { path = p; } + else p; + describe = args: + let + title = args.title or null; + name = args.name or (lib.concatStringsSep "." args.path); + in '' + <listitem> + <para> + <link xlink:href="https://search.nixos.org/packages?show=${name}&sort=relevance&query=${name}"> + <literal>${lib.optionalString (title != null) "${title} aka "}pkgs.${name}</literal> + </link> + </para> + ${lib.optionalString (args ? comment) "<para>${args.comment}</para>"} + </listitem> + ''; + in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>"; + + # Remove invisible and internal options. + optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options); + + optionsNix = builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList); + +in rec { + inherit optionsNix; + + optionsAsciiDoc = pkgs.runCommand "options.adoc" {} '' + ${pkgs.python3Minimal}/bin/python ${./generateAsciiDoc.py} \ + < ${optionsJSON}/share/doc/nixos/options.json \ + > $out + ''; + + optionsCommonMark = pkgs.runCommand "options.md" {} '' + ${pkgs.python3Minimal}/bin/python ${./generateCommonMark.py} \ + < ${optionsJSON}/share/doc/nixos/options.json \ + > $out + ''; + + optionsJSON = pkgs.runCommand "options.json" + { meta.description = "List of NixOS options in JSON format"; + buildInputs = [ pkgs.brotli ]; + options = builtins.toFile "options.json" + (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix)); + } + '' + # Export list of options in different format. + dst=$out/share/doc/nixos + mkdir -p $dst + + ${ + if baseOptionsJSON == null + then "cp $options $dst/options.json" + else '' + ${pkgs.python3Minimal}/bin/python ${./mergeJSON.py} \ + ${lib.optionalString warningsAreErrors "--warnings-are-errors"} \ + ${baseOptionsJSON} $options \ + > $dst/options.json + '' + } + + brotli -9 < $dst/options.json > $dst/options.json.br + + mkdir -p $out/nix-support + echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products + echo "file json-br $dst/options.json.br" >> $out/nix-support/hydra-build-products + ''; + + # Convert options.json into an XML file. + # The actual generation of the xml file is done in nix purely for the convenience + # of not having to generate the xml some other way + optionsXML = pkgs.runCommand "options.xml" {} '' + export NIX_STORE_DIR=$TMPDIR/store + export NIX_STATE_DIR=$TMPDIR/state + ${pkgs.nix}/bin/nix-instantiate \ + --eval --xml --strict ${./optionsJSONtoXML.nix} \ + --argstr file ${optionsJSON}/share/doc/nixos/options.json \ + > "$out" + ''; + + optionsDocBook = pkgs.runCommand "options-docbook.xml" {} '' + optionsXML=${optionsXML} + if grep /nixpkgs/nixos/modules $optionsXML; then + echo "The manual appears to depend on the location of Nixpkgs, which is bad" + echo "since this prevents sharing via the NixOS channel. This is typically" + echo "caused by an option default that refers to a relative path (see above" + echo "for hints about the offending path)." + exit 1 + fi + + ${pkgs.python3Minimal}/bin/python ${./sortXML.py} $optionsXML sorted.xml + ${pkgs.libxslt.bin}/bin/xsltproc \ + --stringparam revision '${revision}' \ + -o intermediate.xml ${./options-to-docbook.xsl} sorted.xml + ${pkgs.libxslt.bin}/bin/xsltproc \ + -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml + ''; +} diff --git a/nixos/lib/make-options-doc/generateAsciiDoc.py b/nixos/lib/make-options-doc/generateAsciiDoc.py new file mode 100644 index 00000000000..48eadd248c5 --- /dev/null +++ b/nixos/lib/make-options-doc/generateAsciiDoc.py @@ -0,0 +1,37 @@ +import json +import sys + +options = json.load(sys.stdin) +# TODO: declarations: link to github +for (name, value) in options.items(): + print(f'== {name}') + print() + print(value['description']) + print() + print('[discrete]') + print('=== details') + print() + print(f'Type:: {value["type"]}') + if 'default' in value: + print('Default::') + print('+') + print('----') + print(json.dumps(value['default'], ensure_ascii=False, separators=(',', ':'))) + print('----') + print() + else: + print('No Default:: {blank}') + if value['readOnly']: + print('Read Only:: {blank}') + else: + print() + if 'example' in value: + print('Example::') + print('+') + print('----') + print(json.dumps(value['example'], ensure_ascii=False, separators=(',', ':'))) + print('----') + print() + else: + print('No Example:: {blank}') + print() diff --git a/nixos/lib/make-options-doc/generateCommonMark.py b/nixos/lib/make-options-doc/generateCommonMark.py new file mode 100644 index 00000000000..404e53b0df9 --- /dev/null +++ b/nixos/lib/make-options-doc/generateCommonMark.py @@ -0,0 +1,27 @@ +import json +import sys + +options = json.load(sys.stdin) +for (name, value) in options.items(): + print('##', name.replace('<', '\\<').replace('>', '\\>')) + print(value['description']) + print() + if 'type' in value: + print('*_Type_*:') + print(value['type']) + print() + print() + if 'default' in value: + print('*_Default_*') + print('```') + print(json.dumps(value['default'], ensure_ascii=False, separators=(',', ':'))) + print('```') + print() + print() + if 'example' in value: + print('*_Example_*') + print('```') + print(json.dumps(value['example'], ensure_ascii=False, separators=(',', ':'))) + print('```') + print() + print() diff --git a/nixos/lib/make-options-doc/mergeJSON.py b/nixos/lib/make-options-doc/mergeJSON.py new file mode 100644 index 00000000000..8e2ea322dc8 --- /dev/null +++ b/nixos/lib/make-options-doc/mergeJSON.py @@ -0,0 +1,93 @@ +import collections +import json +import sys +from typing import Any, Dict, List + +JSON = Dict[str, Any] + +class Key: + def __init__(self, path: List[str]): + self.path = path + def __hash__(self): + result = 0 + for id in self.path: + result ^= hash(id) + return result + def __eq__(self, other): + return type(self) is type(other) and self.path == other.path + +Option = collections.namedtuple('Option', ['name', 'value']) + +# pivot a dict of options keyed by their display name to a dict keyed by their path +def pivot(options: Dict[str, JSON]) -> Dict[Key, Option]: + result: Dict[Key, Option] = dict() + for (name, opt) in options.items(): + result[Key(opt['loc'])] = Option(name, opt) + return result + +# pivot back to indexed-by-full-name +# like the docbook build we'll just fail if multiple options with differing locs +# render to the same option name. +def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]: + result: Dict[str, Dict] = dict() + for (key, opt) in options.items(): + if opt.name in result: + raise RuntimeError( + 'multiple options with colliding ids found', + opt.name, + result[opt.name]['loc'], + opt.value['loc'], + ) + result[opt.name] = opt.value + return result + +warningsAreErrors = sys.argv[1] == "--warnings-are-errors" +optOffset = 1 if warningsAreErrors else 0 +options = pivot(json.load(open(sys.argv[1 + optOffset], 'r'))) +overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r'))) + +# fix up declaration paths in lazy options, since we don't eval them from a full nixpkgs dir +for (k, v) in options.items(): + v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}', v.value['declarations'])) + +# merge both descriptions +for (k, v) in overrides.items(): + cur = options.setdefault(k, v).value + for (ok, ov) in v.value.items(): + if ok == 'declarations': + decls = cur[ok] + for d in ov: + if d not in decls: + decls += [d] + elif ok == "type": + # ignore types of placeholder options + if ov != "_unspecified" or cur[ok] == "_unspecified": + cur[ok] = ov + elif ov is not None or cur.get(ok, None) is None: + cur[ok] = ov + +severity = "error" if warningsAreErrors else "warning" + +# check that every option has a description +hasWarnings = False +for (k, v) in options.items(): + if v.value.get('description', None) is None: + hasWarnings = True + print(f"\x1b[1;31m{severity}: option {v.name} has no description\x1b[0m", file=sys.stderr) + v.value['description'] = "This option has no description." + if v.value.get('type', "unspecified") == "unspecified": + hasWarnings = True + print( + f"\x1b[1;31m{severity}: option {v.name} has no type. Please specify a valid type, see " + + "https://nixos.org/manual/nixos/stable/index.html#sec-option-types\x1b[0m", file=sys.stderr) + +if hasWarnings and warningsAreErrors: + print( + "\x1b[1;31m" + + "Treating warnings as errors. Set documentation.nixos.options.warningsAreErrors " + + "to false to ignore these warnings." + + "\x1b[0m", + file=sys.stderr) + sys.exit(1) + +json.dump(unpivot(options), fp=sys.stdout) diff --git a/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixos/lib/make-options-doc/options-to-docbook.xsl new file mode 100644 index 00000000000..b286f7b5e2c --- /dev/null +++ b/nixos/lib/make-options-doc/options-to-docbook.xsl @@ -0,0 +1,246 @@ +<?xml version="1.0"?> + +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:str="http://exslt.org/strings" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:nixos="tag:nixos.org" + xmlns="http://docbook.org/ns/docbook" + extension-element-prefixes="str" + > + + <xsl:output method='xml' encoding="UTF-8" /> + + <xsl:param name="revision" /> + <xsl:param name="program" /> + + + <xsl:template match="/expr/list"> + <appendix xml:id="appendix-configuration-options"> + <title>Configuration Options</title> + <variablelist xml:id="configuration-variable-list"> + <xsl:for-each select="attrs"> + <xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '<', '_'), '>', '_'), ':', '_'))" /> + <varlistentry> + <term xlink:href="#{$id}"> + <xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute> + <option> + <xsl:value-of select="attr[@name = 'name']/string/@value" /> + </option> + </term> + + <listitem> + + <nixos:option-description> + <para> + <xsl:value-of disable-output-escaping="yes" + select="attr[@name = 'description']/string/@value" /> + </para> + </nixos:option-description> + + <xsl:if test="attr[@name = 'type']"> + <para> + <emphasis>Type:</emphasis> + <xsl:text> </xsl:text> + <xsl:value-of select="attr[@name = 'type']/string/@value"/> + <xsl:if test="attr[@name = 'readOnly']/bool/@value = 'true'"> + <xsl:text> </xsl:text> + <emphasis>(read only)</emphasis> + </xsl:if> + </para> + </xsl:if> + + <xsl:if test="attr[@name = 'default']"> + <para> + <emphasis>Default:</emphasis> + <xsl:text> </xsl:text> + <xsl:apply-templates select="attr[@name = 'default']/*" mode="top" /> + </para> + </xsl:if> + + <xsl:if test="attr[@name = 'example']"> + <para> + <emphasis>Example:</emphasis> + <xsl:text> </xsl:text> + <xsl:apply-templates select="attr[@name = 'example']/*" mode="top" /> + </para> + </xsl:if> + + <xsl:if test="attr[@name = 'relatedPackages']"> + <para> + <emphasis>Related packages:</emphasis> + <xsl:text> </xsl:text> + <xsl:value-of disable-output-escaping="yes" + select="attr[@name = 'relatedPackages']/string/@value" /> + </para> + </xsl:if> + + <xsl:if test="count(attr[@name = 'declarations']/list/*) != 0"> + <para> + <emphasis>Declared by:</emphasis> + </para> + <xsl:apply-templates select="attr[@name = 'declarations']" /> + </xsl:if> + + <xsl:if test="count(attr[@name = 'definitions']/list/*) != 0"> + <para> + <emphasis>Defined by:</emphasis> + </para> + <xsl:apply-templates select="attr[@name = 'definitions']" /> + </xsl:if> + + </listitem> + + </varlistentry> + + </xsl:for-each> + + </variablelist> + </appendix> + </xsl:template> + + + <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExpression']]]" mode = "top"> + <xsl:choose> + <xsl:when test="contains(attr[@name = 'text']/string/@value, '
')"> + <programlisting><xsl:value-of select="attr[@name = 'text']/string/@value" /></programlisting> + </xsl:when> + <xsl:otherwise> + <literal><xsl:value-of select="attr[@name = 'text']/string/@value" /></literal> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + + <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalDocBook']]]" mode = "top"> + <xsl:value-of disable-output-escaping="yes" select="attr[@name = 'text']/string/@value" /> + </xsl:template> + + + <xsl:template match="string[contains(@value, '
')]" mode="top"> + <programlisting> + <xsl:text>''
</xsl:text> + <xsl:value-of select='str:replace(str:replace(@value, "''", "'''"), "${", "''${")' /> + <xsl:text>''</xsl:text> + </programlisting> + </xsl:template> + + + <xsl:template match="*" mode="top"> + <literal><xsl:apply-templates select="." /></literal> + </xsl:template> + + + <xsl:template match="null"> + <xsl:text>null</xsl:text> + </xsl:template> + + + <xsl:template match="string"> + <xsl:choose> + <xsl:when test="(contains(@value, '"') or contains(@value, '\')) and not(contains(@value, '
'))"> + <xsl:text>''</xsl:text><xsl:value-of select='str:replace(str:replace(@value, "''", "'''"), "${", "''${")' /><xsl:text>''</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>"</xsl:text><xsl:value-of select="str:replace(str:replace(str:replace(str:replace(@value, '\', '\\'), '"', '\"'), '
', '\n'), '${', '\${')" /><xsl:text>"</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + + <xsl:template match="int"> + <xsl:value-of select="@value" /> + </xsl:template> + + + <xsl:template match="bool[@value = 'true']"> + <xsl:text>true</xsl:text> + </xsl:template> + + + <xsl:template match="bool[@value = 'false']"> + <xsl:text>false</xsl:text> + </xsl:template> + + + <xsl:template match="list"> + [ + <xsl:for-each select="*"> + <xsl:apply-templates select="." /> + <xsl:text> </xsl:text> + </xsl:for-each> + ] + </xsl:template> + + + <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExpression']]]"> + <xsl:value-of select="attr[@name = 'text']/string/@value" /> + </xsl:template> + + + <xsl:template match="attrs"> + { + <xsl:for-each select="attr"> + <xsl:value-of select="@name" /> + <xsl:text> = </xsl:text> + <xsl:apply-templates select="*" /><xsl:text>; </xsl:text> + </xsl:for-each> + } + </xsl:template> + + + <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'derivation']]]"> + <replaceable>(build of <xsl:value-of select="attr[@name = 'name']/string/@value" />)</replaceable> + </xsl:template> + + <xsl:template match="attr[@name = 'declarations' or @name = 'definitions']"> + <simplelist> + <xsl:for-each select="list/string"> + <member><filename> + <!-- Hyperlink the filename either to the NixOS Subversion + repository (if it’s a module and we have a revision number), + or to the local filesystem. --> + <xsl:choose> + <xsl:when test="not(starts-with(@value, '/'))"> + <xsl:choose> + <xsl:when test="$revision = 'local'"> + <xsl:attribute name="xlink:href">https://github.com/NixOS/nixpkgs/blob/master/<xsl:value-of select="@value"/></xsl:attribute> + </xsl:when> + <xsl:otherwise> + <xsl:attribute name="xlink:href">https://github.com/NixOS/nixpkgs/blob/<xsl:value-of select="$revision"/>/<xsl:value-of select="@value"/></xsl:attribute> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:when test="$revision != 'local' and $program = 'nixops' and contains(@value, '/nix/')"> + <xsl:attribute name="xlink:href">https://github.com/NixOS/nixops/blob/<xsl:value-of select="$revision"/>/nix/<xsl:value-of select="substring-after(@value, '/nix/')"/></xsl:attribute> + </xsl:when> + <xsl:otherwise> + <xsl:attribute name="xlink:href">file://<xsl:value-of select="@value"/></xsl:attribute> + </xsl:otherwise> + </xsl:choose> + <!-- Print the filename and make it user-friendly by replacing the + /nix/store/<hash> prefix by the default location of nixos + sources. --> + <xsl:choose> + <xsl:when test="not(starts-with(@value, '/'))"> + <nixpkgs/<xsl:value-of select="@value"/>> + </xsl:when> + <xsl:when test="contains(@value, 'nixops') and contains(@value, '/nix/')"> + <nixops/<xsl:value-of select="substring-after(@value, '/nix/')"/>> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@value" /> + </xsl:otherwise> + </xsl:choose> + </filename></member> + </xsl:for-each> + </simplelist> + </xsl:template> + + + <xsl:template match="function"> + <xsl:text>λ</xsl:text> + </xsl:template> + + +</xsl:stylesheet> diff --git a/nixos/lib/make-options-doc/optionsJSONtoXML.nix b/nixos/lib/make-options-doc/optionsJSONtoXML.nix new file mode 100644 index 00000000000..ba50c5f898b --- /dev/null +++ b/nixos/lib/make-options-doc/optionsJSONtoXML.nix @@ -0,0 +1,6 @@ +{ file }: + +builtins.attrValues + (builtins.mapAttrs + (name: def: def // { inherit name; }) + (builtins.fromJSON (builtins.readFile file))) diff --git a/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl b/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl new file mode 100644 index 00000000000..1201c7612c2 --- /dev/null +++ b/nixos/lib/make-options-doc/postprocess-option-descriptions.xsl @@ -0,0 +1,115 @@ +<?xml version="1.0"?> + +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:str="http://exslt.org/strings" + xmlns:exsl="http://exslt.org/common" + xmlns:db="http://docbook.org/ns/docbook" + xmlns:nixos="tag:nixos.org" + extension-element-prefixes="str exsl"> + <xsl:output method='xml' encoding="UTF-8" /> + + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()" /> + </xsl:copy> + </xsl:template> + + <xsl:template name="break-up-description"> + <xsl:param name="input" /> + <xsl:param name="buffer" /> + + <!-- Every time we have two newlines following each other, we want to + break it into </para><para>. --> + <xsl:variable name="parbreak" select="'

'" /> + + <!-- Similar to "(head:tail) = input" in Haskell. --> + <xsl:variable name="head" select="$input[1]" /> + <xsl:variable name="tail" select="$input[position() > 1]" /> + + <xsl:choose> + <xsl:when test="$head/self::text() and contains($head, $parbreak)"> + <!-- If the haystack provided to str:split() directly starts or + ends with $parbreak, it doesn't generate a <token/> for that, + so we are doing this here. --> + <xsl:variable name="splitted-raw"> + <xsl:if test="starts-with($head, $parbreak)"><token /></xsl:if> + <xsl:for-each select="str:split($head, $parbreak)"> + <token><xsl:value-of select="node()" /></token> + </xsl:for-each> + <!-- Something like ends-with($head, $parbreak), but there is + no ends-with() in XSLT, so we need to use substring(). --> + <xsl:if test=" + substring($head, string-length($head) - + string-length($parbreak) + 1) = $parbreak + "><token /></xsl:if> + </xsl:variable> + <xsl:variable name="splitted" + select="exsl:node-set($splitted-raw)/token" /> + <!-- The buffer we had so far didn't contain any text nodes that + contain a $parbreak, so we can put the buffer along with the + first token of $splitted into a para element. --> + <para xmlns="http://docbook.org/ns/docbook"> + <xsl:apply-templates select="exsl:node-set($buffer)" /> + <xsl:apply-templates select="$splitted[1]/node()" /> + </para> + <!-- We have already emitted the first splitted result, so the + last result is going to be set as the new $buffer later + because its contents may not be directly followed up by a + $parbreak. --> + <xsl:for-each select="$splitted[position() > 1 + and position() < last()]"> + <para xmlns="http://docbook.org/ns/docbook"> + <xsl:apply-templates select="node()" /> + </para> + </xsl:for-each> + <xsl:call-template name="break-up-description"> + <xsl:with-param name="input" select="$tail" /> + <xsl:with-param name="buffer" select="$splitted[last()]/node()" /> + </xsl:call-template> + </xsl:when> + <!-- Either non-text node or one without $parbreak, which we just + want to buffer and continue recursing. --> + <xsl:when test="$input"> + <xsl:call-template name="break-up-description"> + <xsl:with-param name="input" select="$tail" /> + <!-- This essentially appends $head to $buffer. --> + <xsl:with-param name="buffer"> + <xsl:if test="$buffer"> + <xsl:for-each select="exsl:node-set($buffer)"> + <xsl:apply-templates select="." /> + </xsl:for-each> + </xsl:if> + <xsl:apply-templates select="$head" /> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <!-- No more $input, just put the remaining $buffer in a para. --> + <xsl:otherwise> + <para xmlns="http://docbook.org/ns/docbook"> + <xsl:apply-templates select="exsl:node-set($buffer)" /> + </para> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="nixos:option-description"> + <xsl:choose> + <!-- + Only process nodes that are comprised of a single <para/> element, + because if that's not the case the description already contains + </para><para> in between and we need no further processing. + --> + <xsl:when test="count(db:para) > 1"> + <xsl:apply-templates select="node()" /> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="break-up-description"> + <xsl:with-param name="input" + select="exsl:node-set(db:para/node())" /> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/nixos/lib/make-options-doc/sortXML.py b/nixos/lib/make-options-doc/sortXML.py new file mode 100644 index 00000000000..e63ff3538b3 --- /dev/null +++ b/nixos/lib/make-options-doc/sortXML.py @@ -0,0 +1,27 @@ +import xml.etree.ElementTree as ET +import sys + +tree = ET.parse(sys.argv[1]) +# the xml tree is of the form +# <expr><list> {all options, each an attrs} </list></expr> +options = list(tree.getroot().find('list')) + +def sortKey(opt): + def order(s): + if s.startswith("enable"): + return 0 + if s.startswith("package"): + return 1 + return 2 + + return [ + (order(p.attrib['value']), p.attrib['value']) + for p in opt.findall('attr[@name="loc"]/list/string') + ] + +options.sort(key=sortKey) + +doc = ET.Element("expr") +newOptions = ET.SubElement(doc, "list") +newOptions.extend(options) +ET.ElementTree(doc).write(sys.argv[2], encoding='utf-8') |