nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1{ lib }:
2
3{
4 /**
5 Automatically convert an attribute set to command-line options.
6
7 This helps protect against malformed command lines and also to reduce
8 boilerplate related to command-line construction for simple use cases.
9
10 `toGNUCommandLineShell` returns an escaped shell string.
11
12 # Inputs
13
14 `options`
15
16 : How to format the arguments, see `toGNUCommandLine`
17
18 `attrs`
19
20 : The attributes to transform into arguments.
21
22 # Examples
23
24 :::{.example}
25 ## `lib.cli.toGNUCommandLineShell` usage example
26
27 ```nix
28 cli.toGNUCommandLineShell {} {
29 data = builtins.toJSON { id = 0; };
30 X = "PUT";
31 retry = 3;
32 retry-delay = null;
33 url = [ "https://example.com/foo" "https://example.com/bar" ];
34 silent = false;
35 verbose = true;
36 }
37 => "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'";
38 ```
39
40 :::
41 */
42 toGNUCommandLineShell =
43 lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2511)
44 "lib.cli.toGNUCommandLineShell is deprecated, please use lib.cli.toCommandLineShell or lib.cli.toCommandLineShellGNU instead."
45 (options: attrs: lib.escapeShellArgs (lib.cli.toGNUCommandLine options attrs));
46
47 /**
48 Automatically convert an attribute set to a list of command-line options.
49
50 `toGNUCommandLine` returns a list of string arguments.
51
52 # Inputs
53
54 `options`
55
56 : How to format the arguments, see below.
57
58 `attrs`
59
60 : The attributes to transform into arguments.
61
62 ## Options
63
64 `mkOptionName`
65
66 : How to string-format the option name;
67 By default one character is a short option (`-`), more than one characters a long option (`--`).
68
69 `mkBool`
70
71 : How to format a boolean value to a command list;
72 By default it’s a flag option (only the option name if true, left out completely if false).
73
74 `mkList`
75
76 : How to format a list value to a command list;
77 By default the option name is repeated for each value and `mkOption` is applied to the values themselves.
78
79 `mkOption`
80
81 : How to format any remaining value to a command list;
82 On the toplevel, booleans and lists are handled by `mkBool` and `mkList`, though they can still appear as values of a list.
83 By default, everything is printed verbatim and complex types are forbidden (lists, attrsets, functions). `null` values are omitted.
84
85 `optionValueSeparator`
86
87 : How to separate an option from its flag;
88 By default, there is no separator, so option `-c` and value `5` would become `["-c" "5"]`.
89 This is useful if the command requires equals, for example, `-c=5`.
90
91 # Examples
92
93 :::{.example}
94 ## `lib.cli.toGNUCommandLine` usage example
95
96 ```nix
97 cli.toGNUCommandLine {} {
98 data = builtins.toJSON { id = 0; };
99 X = "PUT";
100 retry = 3;
101 retry-delay = null;
102 url = [ "https://example.com/foo" "https://example.com/bar" ];
103 silent = false;
104 verbose = true;
105 }
106 => [
107 "-X" "PUT"
108 "--data" "{\"id\":0}"
109 "--retry" "3"
110 "--url" "https://example.com/foo"
111 "--url" "https://example.com/bar"
112 "--verbose"
113 ]
114 ```
115
116 :::
117 */
118 toGNUCommandLine =
119 lib.warnIf (lib.oldestSupportedReleaseIsAtLeast 2511)
120 "lib.cli.toGNUCommandLine is deprecated, please use lib.cli.toCommandLine or lib.cli.toCommandLineShellGNU instead."
121 (
122 {
123 mkOptionName ? k: if builtins.stringLength k == 1 then "-${k}" else "--${k}",
124
125 mkBool ? k: v: lib.optional v (mkOptionName k),
126
127 mkList ? k: v: lib.concatMap (mkOption k) v,
128
129 mkOption ?
130 k: v:
131 if v == null then
132 [ ]
133 else if optionValueSeparator == null then
134 [
135 (mkOptionName k)
136 (lib.generators.mkValueStringDefault { } v)
137 ]
138 else
139 [ "${mkOptionName k}${optionValueSeparator}${lib.generators.mkValueStringDefault { } v}" ],
140
141 optionValueSeparator ? null,
142 }:
143 options:
144 let
145 render =
146 k: v:
147 if builtins.isBool v then
148 mkBool k v
149 else if builtins.isList v then
150 mkList k v
151 else
152 mkOption k v;
153
154 in
155 builtins.concatLists (lib.mapAttrsToList render options)
156 );
157
158 /**
159 Converts the given attributes into a single shell-escaped command-line
160 string.
161 Similar to `toCommandLineGNU`, but returns a single escaped string instead
162 of a list of arguments.
163 For further reference see:
164 [`lib.cli.toCommandLineGNU`](#function-library-lib.cli.toCommandLineGNU)
165 */
166 toCommandLineShellGNU =
167 options: attrs: lib.escapeShellArgs (lib.cli.toCommandLineGNU options attrs);
168
169 /**
170 Converts an attribute set into a list of GNU-style command-line arguments.
171
172 `toCommandLineGNU` returns a list of string arguments.
173
174 # Inputs
175
176 `options`
177
178 : Options, see below.
179
180 `attrs`
181
182 : The attributes to transform into arguments.
183
184 ## Options
185
186 `isLong`
187
188 : A function that determines whether an option is long or short.
189
190 `explicitBool`
191
192 : Whether or not boolean option arguments should be formatted explicitly.
193
194 `formatArg`
195
196 : A function that turns the option argument into a string.
197
198 # Examples
199
200 :::{.example}
201 ## `lib.cli.toCommandLineGNU` usage example
202
203 ```nix
204 lib.cli.toCommandLineGNU {} {
205 v = true;
206 verbose = [true true false null];
207 i = ".bak";
208 testsuite = ["unit" "integration"];
209 e = ["s/a/b/" "s/b/c/"];
210 n = false;
211 data = builtins.toJSON {id = 0;};
212 }
213 => [
214 "--data={\"id\":0}"
215 "-es/a/b/"
216 "-es/b/c/"
217 "-i.bak"
218 "--testsuite=unit"
219 "--testsuite=integration"
220 "-v"
221 "--verbose"
222 "--verbose"
223 ]
224 ```
225
226 :::
227 */
228 toCommandLineGNU =
229 {
230 isLong ? optionName: builtins.stringLength optionName > 1,
231 explicitBool ? false,
232 formatArg ? lib.generators.mkValueStringDefault { },
233 }:
234 let
235 optionFormat = optionName: {
236 option = if isLong optionName then "--${optionName}" else "-${optionName}";
237 sep = if isLong optionName then "=" else "";
238 inherit explicitBool formatArg;
239 };
240 in
241 lib.cli.toCommandLine optionFormat;
242
243 /**
244 Converts the given attributes into a single shell-escaped command-line
245 string.
246 Similar to `toCommandLine`, but returns a single escaped string instead of
247 a list of arguments.
248 For further reference see:
249 [`lib.cli.toCommandLine`](#function-library-lib.cli.toCommandLine)
250 */
251 toCommandLineShell =
252 optionFormat: attrs: lib.escapeShellArgs (lib.cli.toCommandLine optionFormat attrs);
253
254 /**
255 Converts an attribute set into a list of command-line arguments.
256
257 This is the most general command-line construction helper in `lib.cli`.
258 It is parameterized by an `optionFormat` function, which defines how each
259 option name and its value are rendered.
260
261 All other helpers in this file are thin wrappers around this function.
262
263 `toCommandLine` returns a *flat list of strings*, suitable for use as `argv`
264 arguments or for further processing (e.g. shell escaping).
265
266 # Inputs
267
268 `optionFormat`
269
270 : A function that takes the option name and returns an option spec, where
271 the option spec is an attribute set describing how the option should be
272 rendered.
273
274 The returned attribute set must contain:
275
276 - `option` (string):
277 The option flag itself, e.g. `"-v"` or `"--verbose"`.
278
279 - `sep` (string or null):
280 How to separate the option from its argument.
281 If `null`, the option and its argument are returned as two separate
282 list elements.
283 If a string (e.g. `"="`), the option and argument are concatenated.
284
285 - `explicitBool` (bool):
286 Controls how boolean values are handled:
287 - `false`:
288 `true` emits only the option flag, `false` emits nothing.
289 - `true`:
290 both `true` and `false` are rendered as explicit arguments via
291 `formatArg`.
292
293 Optional fields:
294
295 - `formatArg`:
296 Converts the option value to a string.
297 Defaults to `lib.generators.mkValueStringDefault { }`.
298
299 `attrs`
300
301 : An attribute set mapping option names to values.
302
303 Supported value types:
304 - null: omitted entirely
305 - bool: handled according to `explicitBool`
306 - list: each element is rendered as a separate occurrence of the option
307 - any other value: rendered as a single option argument
308
309 Empty attribute names are rejected.
310
311 # Examples
312
313 :::{.example}
314 ## `lib.cli.toCommandLine` basic usage example
315
316 ```nix
317 let
318 optionFormat = optionName: {
319 option = "-${optionName}";
320 sep = "=";
321 explicitBool = true;
322 };
323 in
324 lib.cli.toCommandLine optionFormat {
325 v = true;
326 verbose = [
327 true
328 true
329 false
330 null
331 ];
332 i = ".bak";
333 testsuite = [
334 "unit"
335 "integration"
336 ];
337 e = [
338 "s/a/b/"
339 "s/b/c/"
340 ];
341 n = false;
342 data = builtins.toJSON { id = 0; };
343 }
344 => [
345 "-data={\"id\":0}"
346 "-e=s/a/b/"
347 "-e=s/b/c/"
348 "-i=.bak"
349 "-n=false"
350 "-testsuite=unit"
351 "-testsuite=integration"
352 "-v=true"
353 "-verbose=true"
354 "-verbose=true"
355 "-verbose=false"
356 ]
357 ```
358 :::
359
360 :::{.example}
361 ## `lib.cli.toCommandLine` usage with a more complex option format
362
363 ```nix
364 let
365 optionFormat =
366 optionName:
367 let
368 isLong = builtins.stringLength optionName > 1;
369 in
370 {
371 option = if isLong then "--${optionName}" else "-${optionName}";
372 sep = if isLong then "=" else null;
373 explicitBool = true;
374 formatArg =
375 value:
376 if builtins.isAttrs value then
377 builtins.toJSON value
378 else
379 lib.generators.mkValueStringDefault { } value;
380 };
381 in
382 lib.cli.toCommandLine optionFormat {
383 v = true;
384 verbose = [
385 true
386 true
387 false
388 null
389 ];
390 n = false;
391 output = "result.txt";
392 testsuite = [
393 "unit"
394 "integration"
395 ];
396 data = {
397 id = 0;
398 name = "test";
399 };
400 }
401 => [
402 "--data={\"id\":0,\"name\":\"test\"}"
403 "-n"
404 "false"
405 "--output=result.txt"
406 "--testsuite=unit"
407 "--testsuite=integration"
408 "-v"
409 "true"
410 "--verbose=true"
411 "--verbose=true"
412 "--verbose=false"
413 ]
414 ```
415 :::
416
417 # See also
418
419 - `lib.cli.toCommandLineShell`
420 - `lib.cli.toCommandLineGNU`
421 - `lib.cli.toCommandLineShellGNU`
422 */
423 toCommandLine =
424 optionFormat: attrs:
425 let
426 handlePair =
427 k: v:
428 if k == "" then
429 lib.throw "lib.cli.toCommandLine only accepts non-empty option names."
430 else if builtins.isList v then
431 builtins.concatMap (handleOption k) v
432 else
433 handleOption k v;
434
435 handleOption = k: renderOption (optionFormat k) k;
436
437 renderOption =
438 {
439 option,
440 sep,
441 explicitBool,
442 formatArg ? lib.generators.mkValueStringDefault { },
443 }:
444 k: v:
445 if v == null || (!explicitBool && v == false) then
446 [ ]
447 else if !explicitBool && v == true then
448 [ option ]
449 else
450 let
451 arg = formatArg v;
452 in
453 if sep != null then
454 [ "${option}${sep}${arg}" ]
455 else
456 [
457 option
458 arg
459 ];
460 in
461 builtins.concatLists (lib.mapAttrsToList handlePair attrs);
462}