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