1# Nixpkgs/NixOS option handling.
2{ lib }:
3
4let
5 inherit (lib)
6 all
7 collect
8 concatLists
9 concatMap
10 elemAt
11 filter
12 foldl'
13 head
14 tail
15 isAttrs
16 isBool
17 isDerivation
18 isFunction
19 isInt
20 isList
21 isString
22 length
23 mapAttrs
24 optional
25 optionals
26 take
27 ;
28 inherit (lib.attrsets)
29 optionalAttrs
30 ;
31 inherit (lib.strings)
32 concatMapStrings
33 concatStringsSep
34 ;
35 inherit (lib.types)
36 mkOptionType
37 ;
38in
39rec {
40
41 /* Returns true when the given argument is an option
42
43 Type: isOption :: a -> bool
44
45 Example:
46 isOption 1 // => false
47 isOption (mkOption {}) // => true
48 */
49 isOption = lib.isType "option";
50
51 /* Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
52
53 All keys default to `null` when not given.
54
55 Example:
56 mkOption { } // => { _type = "option"; }
57 mkOption { default = "foo"; } // => { _type = "option"; default = "foo"; }
58 */
59 mkOption =
60 {
61 # Default value used when no definition is given in the configuration.
62 default ? null,
63 # Textual representation of the default, for the manual.
64 defaultText ? null,
65 # Example value used in the manual.
66 example ? null,
67 # String describing the option.
68 description ? null,
69 # Related packages used in the manual (see `genRelatedPackages` in ../nixos/lib/make-options-doc/default.nix).
70 relatedPackages ? null,
71 # Option type, providing type-checking and value merging.
72 type ? null,
73 # Function that converts the option value to something else.
74 apply ? null,
75 # Whether the option is for NixOS developers only.
76 internal ? null,
77 # Whether the option shows up in the manual. Default: true. Use false to hide the option and any sub-options from submodules. Use "shallow" to hide only sub-options.
78 visible ? null,
79 # Whether the option can be set only once
80 readOnly ? null,
81 # Deprecated, used by types.optionSet.
82 options ? null
83 } @ attrs:
84 attrs // { _type = "option"; };
85
86 /* Creates an Option attribute set for a boolean value option i.e an
87 option to be toggled on or off:
88
89 Example:
90 mkEnableOption "foo"
91 => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; }
92 */
93 mkEnableOption =
94 # Name for the created option
95 name: mkOption {
96 default = false;
97 example = true;
98 description = "Whether to enable ${name}.";
99 type = lib.types.bool;
100 };
101
102 /* This option accepts anything, but it does not produce any result.
103
104 This is useful for sharing a module across different module sets
105 without having to implement similar features as long as the
106 values of the options are not accessed. */
107 mkSinkUndeclaredOptions = attrs: mkOption ({
108 internal = true;
109 visible = false;
110 default = false;
111 description = "Sink for option definitions.";
112 type = mkOptionType {
113 name = "sink";
114 check = x: true;
115 merge = loc: defs: false;
116 };
117 apply = x: throw "Option value is not readable because the option is not declared.";
118 } // attrs);
119
120 mergeDefaultOption = loc: defs:
121 let list = getValues defs; in
122 if length list == 1 then head list
123 else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list)
124 else if all isList list then concatLists list
125 else if all isAttrs list then foldl' lib.mergeAttrs {} list
126 else if all isBool list then foldl' lib.or false list
127 else if all isString list then lib.concatStrings list
128 else if all isInt list && all (x: x == head list) list then head list
129 else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
130
131 mergeOneOption = loc: defs:
132 if defs == [] then abort "This case should never happen."
133 else if length defs != 1 then
134 throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}"
135 else (head defs).value;
136
137 /* "Merge" option definitions by checking that they all have the same value. */
138 mergeEqualOption = loc: defs:
139 if defs == [] then abort "This case should never happen."
140 # Return early if we only have one element
141 # This also makes it work for functions, because the foldl' below would try
142 # to compare the first element with itself, which is false for functions
143 else if length defs == 1 then (head defs).value
144 else (foldl' (first: def:
145 if def.value != first.value then
146 throw "The option `${showOption loc}' has conflicting definition values:${showDefs [ first def ]}"
147 else
148 first) (head defs) (tail defs)).value;
149
150 /* Extracts values of all "value" keys of the given list.
151
152 Type: getValues :: [ { value :: a } ] -> [a]
153
154 Example:
155 getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
156 getValues [ ] // => [ ]
157 */
158 getValues = map (x: x.value);
159
160 /* Extracts values of all "file" keys of the given list
161
162 Type: getFiles :: [ { file :: a } ] -> [a]
163
164 Example:
165 getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
166 getFiles [ ] // => [ ]
167 */
168 getFiles = map (x: x.file);
169
170 # Generate documentation template from the list of option declaration like
171 # the set generated with filterOptionSets.
172 optionAttrSetToDocList = optionAttrSetToDocList' [];
173
174 optionAttrSetToDocList' = prefix: options:
175 concatMap (opt:
176 let
177 docOption = rec {
178 loc = opt.loc;
179 name = showOption opt.loc;
180 description = opt.description or (lib.warn "Option `${name}' has no description." "This option has no description.");
181 declarations = filter (x: x != unknownModule) opt.declarations;
182 internal = opt.internal or false;
183 visible =
184 if (opt?visible && opt.visible == "shallow")
185 then true
186 else opt.visible or true;
187 readOnly = opt.readOnly or false;
188 type = opt.type.description or null;
189 }
190 // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
191 // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
192 // optionalAttrs (opt ? defaultText) { default = opt.defaultText; }
193 // optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; };
194
195 subOptions =
196 let ss = opt.type.getSubOptions opt.loc;
197 in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
198 subOptionsVisible = docOption.visible && opt.visible or null != "shallow";
199 in
200 [ docOption ] ++ optionals subOptionsVisible subOptions) (collect isOption options);
201
202
203 /* This function recursively removes all derivation attributes from
204 `x` except for the `name` attribute.
205
206 This is to make the generation of `options.xml` much more
207 efficient: the XML representation of derivations is very large
208 (on the order of megabytes) and is not actually used by the
209 manual generator.
210 */
211 scrubOptionValue = x:
212 if isDerivation x then
213 { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
214 else if isList x then map scrubOptionValue x
215 else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
216 else x;
217
218
219 /* For use in the `defaultText` and `example` option attributes. Causes the
220 given string to be rendered verbatim in the documentation as Nix code. This
221 is necessary for complex values, e.g. functions, or values that depend on
222 other values or packages.
223 */
224 literalExpression = text:
225 if ! isString text then throw "literalExpression expects a string."
226 else { _type = "literalExpression"; inherit text; };
227
228 literalExample = lib.warn "literalExample is deprecated, use literalExpression instead, or use literalDocBook for a non-Nix description." literalExpression;
229
230
231 /* For use in the `defaultText` and `example` option attributes. Causes the
232 given DocBook text to be inserted verbatim in the documentation, for when
233 a `literalExpression` would be too hard to read.
234 */
235 literalDocBook = text:
236 if ! isString text then throw "literalDocBook expects a string."
237 else { _type = "literalDocBook"; inherit text; };
238
239 # Helper functions.
240
241 /* Convert an option, described as a list of the option parts in to a
242 safe, human readable version.
243
244 Example:
245 (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
246 (showOption ["foo" "bar.baz" "tux"]) == "foo.bar.baz.tux"
247
248 Placeholders will not be quoted as they are not actual values:
249 (showOption ["foo" "*" "bar"]) == "foo.*.bar"
250 (showOption ["foo" "<name>" "bar"]) == "foo.<name>.bar"
251
252 Unlike attributes, options can also start with numbers:
253 (showOption ["windowManager" "2bwm" "enable"]) == "windowManager.2bwm.enable"
254 */
255 showOption = parts: let
256 escapeOptionPart = part:
257 let
258 escaped = lib.strings.escapeNixString part;
259 in if escaped == "\"${part}\""
260 then part
261 else escaped;
262 in (concatStringsSep ".") (map escapeOptionPart parts);
263 showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
264
265 showDefs = defs: concatMapStrings (def:
266 let
267 # Pretty print the value for display, if successful
268 prettyEval = builtins.tryEval
269 (lib.generators.toPretty { }
270 (lib.generators.withRecursion { depthLimit = 10; throwOnDepthLimit = false; } def.value));
271 # Split it into its lines
272 lines = filter (v: ! isList v) (builtins.split "\n" prettyEval.value);
273 # Only display the first 5 lines, and indent them for better visibility
274 value = concatStringsSep "\n " (take 5 lines ++ optional (length lines > 5) "...");
275 result =
276 # Don't print any value if evaluating the value strictly fails
277 if ! prettyEval.success then ""
278 # Put it on a new line if it consists of multiple
279 else if length lines > 1 then ":\n " + value
280 else ": " + value;
281 in "\n- In `${def.file}'${result}"
282 ) defs;
283
284 unknownModule = "<unknown-file>";
285
286}