lib/cli: add toCommandLine

This commit is contained in:
Lukas Wurzinger
2025-10-21 15:02:15 +02:00
parent 344dfdae32
commit 73e8a483e6
3 changed files with 270 additions and 3 deletions

View File

@@ -360,3 +360,5 @@
See the neovim help page [`:help startup`](https://neovim.io/doc/user/starting.html#startup) for more information, as well as [the nixpkgs neovim wrapper documentation](#neovim-custom-configuration).
- `cloudflare-ddns`: Added package cloudflare-ddns.
- `lib.cli.toCommandLine`, `lib.cli.toCommandLineShell`, `lib.cli.toCommandLineGNU` and `lib.cli.toCommandLineShellGNU` have been added to address multiple issues in `lib.cli.toGNUCommandLine` and `lib.cli.toGNUCommandLineShell`.

View File

@@ -1,6 +1,6 @@
{ lib }:
rec {
{
/**
Automatically convert an attribute set to command-line options.
@@ -20,6 +20,7 @@ rec {
: The attributes to transform into arguments.
# Examples
:::{.example}
## `lib.cli.toGNUCommandLineShell` usage example
@@ -38,7 +39,8 @@ rec {
:::
*/
toGNUCommandLineShell = options: attrs: lib.escapeShellArgs (toGNUCommandLine options attrs);
toGNUCommandLineShell =
options: attrs: lib.escapeShellArgs (lib.cli.toGNUCommandLine options attrs);
/**
Automatically convert an attribute set to a list of command-line options.
@@ -55,7 +57,7 @@ rec {
: The attributes to transform into arguments.
# Options
## Options
`mkOptionName`
@@ -85,6 +87,7 @@ rec {
This is useful if the command requires equals, for example, `-c=5`.
# Examples
:::{.example}
## `lib.cli.toGNUCommandLine` usage example
@@ -145,4 +148,186 @@ rec {
in
builtins.concatLists (lib.mapAttrsToList render options);
/**
Converts the given attributes into a single shell-escaped command-line string.
Similar to `toCommandLineGNU`, but returns a single escaped string instead of an array of arguments.
For further reference see: [`lib.cli.toCommandLineGNU`](#function-library-lib.cli.toCommandLineGNU)
*/
toCommandLineShellGNU =
options: attrs: lib.escapeShellArgs (lib.cli.toCommandLineGNU options attrs);
/**
Converts an attribute set into a list of GNU-style command line options.
`toCommandLineGNU` returns a list of string arguments.
# Inputs
`options`
: Options, see below.
`attrs`
: The attributes to transform into arguments.
## Options
`isLong`
: A function that determines whether an option is long or short.
`explicitBool`
: Whether or not boolean option arguments should be formatted explicitly.
`formatArg`
: A function that turns the option argument into a string.
# Examples
:::{.example}
## `lib.cli.toCommandLineGNU` usage example
```nix
lib.cli.toCommandLineGNU {} {
v = true;
verbose = [true true false null];
i = ".bak";
testsuite = ["unit" "integration"];
e = ["s/a/b/" "s/b/c/"];
n = false;
data = builtins.toJSON {id = 0;};
}
=> [
"--data={\"id\":0}"
"-es/a/b/"
"-es/b/c/"
"-i.bak"
"--testsuite=unit"
"--testsuite=integration"
"-v"
"--verbose"
"--verbose"
]
```
:::
*/
toCommandLineGNU =
{
isLong ? optionName: builtins.stringLength optionName > 1,
explicitBool ? false,
formatArg ? lib.generators.mkValueStringDefault { },
}:
let
optionFormat = optionName: {
option = if isLong optionName then "--${optionName}" else "-${optionName}";
sep = if isLong optionName then "=" else "";
inherit explicitBool formatArg;
};
in
lib.cli.toCommandLine optionFormat;
/**
Converts the given attributes into a single shell-escaped command-line string.
Similar to `toCommandLine`, but returns a single escaped string instead of an array of arguments.
For further reference see: [`lib.cli.toCommandLine`](#function-library-lib.cli.toCommandLine)
*/
toCommandLineShell =
optionFormat: attrs: lib.escapeShellArgs (lib.cli.toCommandLine optionFormat attrs);
/**
Converts an attribute set into a list of command line options.
`toCommandLine` returns a list of string arguments.
# Inputs
`optionFormat`
: The option format that describes how options and their arguments should be formatted.
`attrs`
: The attributes to transform into arguments.
# Examples
:::{.example}
## `lib.cli.toCommandLine` usage example
```nix
let
optionFormat = optionName: {
option = "-${optionName}";
sep = "=";
explicitBool = true;
};
in lib.cli.toCommandLine optionFormat {
v = true;
verbose = [true true false null];
i = ".bak";
testsuite = ["unit" "integration"];
e = ["s/a/b/" "s/b/c/"];
n = false;
data = builtins.toJSON {id = 0;};
}
=> [
"-data={\"id\":0}"
"-e=s/a/b/"
"-e=s/b/c/"
"-i=.bak"
"-n=false"
"-testsuite=unit"
"-testsuite=integration"
"-v=true"
"-verbose=true"
"-verbose=true"
"-verbose=false"
]
```
:::
*/
toCommandLine =
optionFormat: attrs:
let
handlePair =
k: v:
if k == "" then
lib.throw "lib.cli.toCommandLine only accepts non-empty option names."
else if builtins.isList v then
builtins.concatMap (handleOption k) v
else
handleOption k v;
handleOption = k: renderOption (optionFormat k) k;
renderOption =
{
option,
sep,
explicitBool,
formatArg ? lib.generators.mkValueStringDefault { },
}:
k: v:
if v == null || (!explicitBool && v == false) then
[ ]
else if !explicitBool && v == true then
[ option ]
else
let
arg = formatArg v;
in
if sep != null then
[ "${option}${sep}${arg}" ]
else
[
option
arg
];
in
builtins.concatLists (lib.mapAttrsToList handlePair attrs);
}

View File

@@ -3106,6 +3106,86 @@ runTests {
expected = "-X PUT --data '{\"id\":0}' --retry 3 --url https://example.com/foo --url https://example.com/bar --verbose";
};
testToCommandLine = {
expr =
let
optionFormat = optionName: {
option = "-${optionName}";
sep = "=";
explicitBool = true;
};
in
cli.toCommandLine optionFormat {
v = true;
verbose = [
true
true
false
null
];
i = ".bak";
testsuite = [
"unit"
"integration"
];
e = [
"s/a/b/"
"s/b/c/"
];
n = false;
data = builtins.toJSON { id = 0; };
};
expected = [
"-data={\"id\":0}"
"-e=s/a/b/"
"-e=s/b/c/"
"-i=.bak"
"-n=false"
"-testsuite=unit"
"-testsuite=integration"
"-v=true"
"-verbose=true"
"-verbose=true"
"-verbose=false"
];
};
testToCommandLineGNU = {
expr = cli.toCommandLineGNU { } {
v = true;
verbose = [
true
true
false
null
];
i = ".bak";
testsuite = [
"unit"
"integration"
];
e = [
"s/a/b/"
"s/b/c/"
];
n = false;
data = builtins.toJSON { id = 0; };
};
expected = [
"--data={\"id\":0}"
"-es/a/b/"
"-es/b/c/"
"-i.bak"
"--testsuite=unit"
"--testsuite=integration"
"-v"
"--verbose"
"--verbose"
];
};
testSanitizeDerivationNameLeadingDots = testSanitizeDerivationName {
name = "..foo";
expected = "foo";