1/* Generate JSON, XML and DocBook documentation for given NixOS options.
2
3 Minimal example:
4
5 { pkgs, }:
6
7 let
8 eval = import (pkgs.path + "/nixos/lib/eval-config.nix") {
9 baseModules = [
10 ../module.nix
11 ];
12 modules = [];
13 };
14 in pkgs.nixosOptionsDoc {
15 options = eval.options;
16 }
17
18*/
19{ pkgs
20, lib
21, options
22, transformOptions ? lib.id # function for additional tranformations of the options
23, revision ? "" # Specify revision for the options
24}:
25
26let
27 # Replace functions by the string <function>
28 substFunction = x:
29 if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x
30 else if builtins.isList x then map substFunction x
31 else if lib.isFunction x then "<function>"
32 else x;
33
34 optionsListDesc = lib.flip map optionsListVisible
35 (opt: transformOptions opt
36 // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; }
37 // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; }
38 // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; }
39 // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages opt.name; }
40 );
41
42 # Generate DocBook documentation for a list of packages. This is
43 # what `relatedPackages` option of `mkOption` from
44 # ../../../lib/options.nix influences.
45 #
46 # Each element of `relatedPackages` can be either
47 # - a string: that will be interpreted as an attribute name from `pkgs`,
48 # - a list: that will be interpreted as an attribute path from `pkgs`,
49 # - an attrset: that can specify `name`, `path`, `package`, `comment`
50 # (either of `name`, `path` is required, the rest are optional).
51 genRelatedPackages = packages: optName:
52 let
53 unpack = p: if lib.isString p then { name = p; }
54 else if lib.isList p then { path = p; }
55 else p;
56 describe = args:
57 let
58 title = args.title or null;
59 name = args.name or (lib.concatStringsSep "." args.path);
60 path = args.path or [ args.name ];
61 package = args.package or (lib.attrByPath path (throw "Invalid package attribute path `${toString path}' found while evaluating `relatedPackages' of option `${optName}'") pkgs);
62 in "<listitem>"
63 + "<para><literal>${lib.optionalString (title != null) "${title} aka "}pkgs.${name} (${package.meta.name})</literal>"
64 + lib.optionalString (!package.meta.available) " <emphasis>[UNAVAILABLE]</emphasis>"
65 + ": ${package.meta.description or "???"}.</para>"
66 + lib.optionalString (args ? comment) "\n<para>${args.comment}</para>"
67 # Lots of `longDescription's break DocBook, so we just wrap them into <programlisting>
68 + lib.optionalString (package.meta ? longDescription) "\n<programlisting>${package.meta.longDescription}</programlisting>"
69 + "</listitem>";
70 in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>";
71
72 # Custom "less" that pushes up all the things ending in ".enable*"
73 # and ".package*"
74 optionLess = a: b:
75 let
76 ise = lib.hasPrefix "enable";
77 isp = lib.hasPrefix "package";
78 cmp = lib.splitByAndCompare ise lib.compare
79 (lib.splitByAndCompare isp lib.compare lib.compare);
80 in lib.compareLists cmp a.loc b.loc < 0;
81
82 # Remove invisible and internal options.
83 optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options);
84
85 # Customly sort option list for the man page.
86 # Always ensure that the sort order matches sortXML.py!
87 optionsList = lib.sort optionLess optionsListDesc;
88
89 # Convert the list of options into an XML file.
90 # This file is *not* sorted sorted to save on eval time, since the docbook XML
91 # and the manpage depend on it and thus we evaluate this on every system rebuild.
92 optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsListDesc);
93
94 optionsNix = builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList);
95
96 # TODO: declarations: link to github
97 singleAsciiDoc = name: value: ''
98 == ${name}
99
100 ${value.description}
101
102 [discrete]
103 === details
104
105 Type:: ${value.type}
106 ${ if lib.hasAttr "default" value
107 then ''
108 Default::
109 +
110 ----
111 ${builtins.toJSON value.default}
112 ----
113 ''
114 else "No Default:: {blank}"
115 }
116 ${ if value.readOnly
117 then "Read Only:: {blank}"
118 else ""
119 }
120 ${ if lib.hasAttr "example" value
121 then ''
122 Example::
123 +
124 ----
125 ${builtins.toJSON value.example}
126 ----
127 ''
128 else "No Example:: {blank}"
129 }
130 '';
131
132 singleMDDoc = name: value: ''
133 ## ${lib.escape [ "<" ">" ] name}
134 ${value.description}
135
136 ${lib.optionalString (value ? type) ''
137 *_Type_*:
138 ${value.type}
139 ''}
140
141 ${lib.optionalString (value ? default) ''
142 *_Default_*
143 ```
144 ${builtins.toJSON value.default}
145 ```
146 ''}
147
148 ${lib.optionalString (value ? example) ''
149 *_Example_*
150 ```
151 ${builtins.toJSON value.example}
152 ```
153 ''}
154 '';
155
156in {
157 inherit optionsNix;
158
159 optionsAsciiDoc = lib.concatStringsSep "\n" (lib.mapAttrsToList singleAsciiDoc optionsNix);
160
161 optionsMDDoc = lib.concatStringsSep "\n" (lib.mapAttrsToList singleMDDoc optionsNix);
162
163 optionsJSON = pkgs.runCommand "options.json"
164 { meta.description = "List of NixOS options in JSON format";
165 buildInputs = [ pkgs.brotli ];
166 }
167 ''
168 # Export list of options in different format.
169 dst=$out/share/doc/nixos
170 mkdir -p $dst
171
172 cp ${builtins.toFile "options.json" (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix))} $dst/options.json
173
174 brotli -9 < $dst/options.json > $dst/options.json.br
175
176 mkdir -p $out/nix-support
177 echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products
178 echo "file json-br $dst/options.json.br" >> $out/nix-support/hydra-build-products
179 ''; # */
180
181 optionsDocBook = pkgs.runCommand "options-docbook.xml" {} ''
182 optionsXML=${optionsXML}
183 if grep /nixpkgs/nixos/modules $optionsXML; then
184 echo "The manual appears to depend on the location of Nixpkgs, which is bad"
185 echo "since this prevents sharing via the NixOS channel. This is typically"
186 echo "caused by an option default that refers to a relative path (see above"
187 echo "for hints about the offending path)."
188 exit 1
189 fi
190
191 ${pkgs.python3Minimal}/bin/python ${./sortXML.py} $optionsXML sorted.xml
192 ${pkgs.libxslt.bin}/bin/xsltproc \
193 --stringparam revision '${revision}' \
194 -o intermediate.xml ${./options-to-docbook.xsl} sorted.xml
195 ${pkgs.libxslt.bin}/bin/xsltproc \
196 -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml
197 '';
198}