nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at staging-next 462 lines 12 kB view raw
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 its 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}