From c9ebd7d85bfc0c808b2abd7f24e08fa3e5a239ba Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 12 Jan 2025 19:20:11 -0500 Subject: [PATCH 1/4] lib/cli: refactor Use `lib.concatStrings` for readability, and move `optionValueSeparator` to the top. --- lib/cli.nix | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/cli.nix b/lib/cli.nix index 590ba691d386a..0d3027a1ef29a 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -118,6 +118,8 @@ rec { mkList ? k: v: lib.concatMap (mkOption k) v, + optionValueSeparator ? null, + mkOption ? k: v: if v == null then @@ -128,9 +130,13 @@ rec { (lib.generators.mkValueStringDefault { } v) ] else - [ "${mkOptionName k}${optionValueSeparator}${lib.generators.mkValueStringDefault { } v}" ], - - optionValueSeparator ? null, + [ + (lib.concatStrings [ + (mkOptionName k) + optionValueSeparator + (lib.generators.mkValueStringDefault { } v) + ]) + ], }: options: let From 3e03bcb20ecfc8f67956e6d36ba096a0be67e1a4 Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 12 Jan 2025 19:59:14 -0500 Subject: [PATCH 2/4] lib/cli: add `wrappingValueString` option The function currently doesn't provide a way to create a result like `--foo "bar"` or `--foo="bar"`, where the argument is quoted. This option resolves that. We set it to an empty string by default, so we can always interpolate it into the string, even if the user didn't set it. --- lib/cli.nix | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/cli.nix b/lib/cli.nix index 0d3027a1ef29a..947e9a69dccea 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -84,6 +84,13 @@ rec { By default, there is no separator, so option `-c` and value `5` would become ["-c" "5"]. This is useful if the command requires equals, for example, `-c=5`. + `wrappingValueString` + + : The string to surround an option's value with. + Values aren't quoted by default, so option `foo` and value `bar` would become ["--foo bar"] + This is useful if your value could be misinterpreted by your shell without quoting. + For example, if you wanted a result of `["--foo \"bar\" "]`, you could set `wrappingValueString` to a literal quote. + # Examples :::{.example} ## `lib.cli.toGNUCommandLine` usage example @@ -120,21 +127,27 @@ rec { optionValueSeparator ? null, + wrappingValueString ? "", + mkOption ? k: v: + let + surroundedString = + wrappingValueString + lib.generators.mkValueStringDefault { } v + wrappingValueString; + in if v == null then [ ] else if optionValueSeparator == null then [ (mkOptionName k) - (lib.generators.mkValueStringDefault { } v) + surroundedString ] else [ (lib.concatStrings [ (mkOptionName k) optionValueSeparator - (lib.generators.mkValueStringDefault { } v) + surroundedString ]) ], }: From 032647e967c0b5d15fceb6a1746e6731202f2d9e Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:01:35 -0500 Subject: [PATCH 3/4] lib/cli: add comment clarifying behavior It wasn't clear to me why `toGNUCommandLine` would take an input like this `foo = "bar"`, and turn it into `[ "foo" "bar" ]`. I implemented a change to this, only to realize that this behavior is actually for `toGNUCommandLineShell`. It uses `lib.escapeShellArgs`, which treats the input `[ "--foo" "bar" ]` and `[ "--foo bar" ]` differently. The comment will hopefully prevent others from going down the same rabbit hole that I did. --- lib/cli.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cli.nix b/lib/cli.nix index 947e9a69dccea..c4182a7351c7f 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -137,6 +137,7 @@ rec { in if v == null then [ ] + # We need the option and its value in different list elements, to play nice with `lib.escapeShellArgs` else if optionValueSeparator == null then [ (mkOptionName k) From 86e73c3116b03dc8858105563468c893873a5e8f Mon Sep 17 00:00:00 2001 From: Eman Resu <78693624+quatquatt@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:15:46 -0500 Subject: [PATCH 4/4] lib.cli.toGNUCommandLine: add wrapped test --- lib/tests/misc.nix | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index f0c4b6f2c5e1c..345137d0d1949 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -1832,6 +1832,27 @@ runTests { ]; }; + testToGNUCommandLineWrapped = { + expr = cli.toGNUCommandLine { wrappingValueString = "\""; } { + data = builtins.toJSON { id = 0; }; + X = "PUT"; + retry = 3; + retry-delay = null; + url = [ "https://example.com/foo" "https://example.com/bar" ]; + silent = false; + verbose = true; + }; + + expected = [ + "-X" "\"PUT\"" + "--data" "\"{\"id\":0}\"" + "--retry" "\"3\"" + "--url" "\"https://example.com/foo\"" + "--url" "\"https://example.com/bar\"" + "--verbose" + ]; + }; + testToGNUCommandLineShell = { expr = cli.toGNUCommandLineShell {} { data = builtins.toJSON { id = 0; };