From 183a99734f666b6bd508f4c81e887dbc746fec69 Mon Sep 17 00:00:00 2001 From: Gabriel Gonzalez Date: Wed, 11 Dec 2019 16:30:05 -0800 Subject: [PATCH 1/6] Add `pkgs.lib.renderOptions` This adds a new utility to intelligently convert Nix records to command line options to reduce boilerplate for simple use cases and to also reduce the likelihood of malformed command lines --- lib/cli.nix | 33 +++++++++++++++++++++++++++++++++ lib/default.nix | 2 ++ lib/tests/misc.nix | 16 ++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 lib/cli.nix diff --git a/lib/cli.nix b/lib/cli.nix new file mode 100644 index 00000000000..d794778b21a --- /dev/null +++ b/lib/cli.nix @@ -0,0 +1,33 @@ +{ lib }: + +{ /* Automatically convert an attribute set to command-line options. + + This helps protect against malformed command lines and also to reduce + boilerplate related to command-line construction for simple use cases. + + Example: + renderOptions { foo = "A"; bar = 1; baz = null; qux = true; v = true; } + => " --bar '1' --foo 'A' --qux -v" + */ + renderOptions = + options: + let + render = key: value: + let + hyphenate = + k: if builtins.stringLength k == 1 then "-${k}" else "--${k}"; + + renderOption = v: if v == null then "" else " ${hyphenate key} ${lib.escapeShellArg v}"; + + renderSwitch = if value then " ${hyphenate key}" else ""; + + in + if builtins.isBool value + then renderSwitch + else if builtins.isList value + then lib.concatMapStrings renderOption value + else renderOption value; + + in + lib.concatStrings (lib.mapAttrsToList render options); +} diff --git a/lib/default.nix b/lib/default.nix index 8af53152586..5798c6bba00 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -39,6 +39,7 @@ let # misc asserts = callLibs ./asserts.nix; + cli = callLibs ./cli.nix; debug = callLibs ./debug.nix; generators = callLibs ./generators.nix; misc = callLibs ./deprecated.nix; @@ -120,6 +121,7 @@ let isOptionType mkOptionType; inherit (asserts) assertMsg assertOneOf; + inherit (cli) renderOptions; inherit (debug) addErrorContextToAttrs traceIf traceVal traceValFn traceXMLVal traceXMLValMarked traceSeq traceSeqN traceValSeq traceValSeqFn traceValSeqN traceValSeqNFn traceShowVal diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index b064faa1e1b..a5f191410e5 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -441,4 +441,20 @@ runTests { expected = "«foo»"; }; + testRenderOptions = { + expr = + renderOptions + { foo = "A"; + + bar = 1; + + baz = null; + + qux = true; + + v = true; + }; + + expected = " --bar '1' --foo 'A' --qux -v"; + }; } From 8c6a05c8c99819dbd85d555cb50596637d57df44 Mon Sep 17 00:00:00 2001 From: Gabriel Gonzalez Date: Fri, 13 Dec 2019 18:19:24 -0800 Subject: [PATCH 2/6] Rename `renderOptions` to `encodeGNUCommandLine` ... as suggested by @edolstra --- lib/cli.nix | 4 ++-- lib/default.nix | 2 +- lib/tests/misc.nix | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cli.nix b/lib/cli.nix index d794778b21a..23fab8ec970 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -6,10 +6,10 @@ boilerplate related to command-line construction for simple use cases. Example: - renderOptions { foo = "A"; bar = 1; baz = null; qux = true; v = true; } + encodeGNUCommandLine { foo = "A"; bar = 1; baz = null; qux = true; v = true; } => " --bar '1' --foo 'A' --qux -v" */ - renderOptions = + encodeGNUCommandLine = options: let render = key: value: diff --git a/lib/default.nix b/lib/default.nix index 5798c6bba00..a7b00f01e0d 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -121,7 +121,7 @@ let isOptionType mkOptionType; inherit (asserts) assertMsg assertOneOf; - inherit (cli) renderOptions; + inherit (cli) encodeGNUCommandLine; inherit (debug) addErrorContextToAttrs traceIf traceVal traceValFn traceXMLVal traceXMLValMarked traceSeq traceSeqN traceValSeq traceValSeqFn traceValSeqN traceValSeqNFn traceShowVal diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index a5f191410e5..c0a48f472cc 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -443,7 +443,7 @@ runTests { testRenderOptions = { expr = - renderOptions + encodeGNUCommandLine { foo = "A"; bar = 1; From 693096d283763ce71fcd2002d965c07546aaafda Mon Sep 17 00:00:00 2001 From: Gabriel Gonzalez Date: Fri, 13 Dec 2019 18:25:52 -0800 Subject: [PATCH 3/6] Make behavior of `encodeGNUCommandLine` customizable ... based on feedback from @edolstra --- lib/cli.nix | 30 +++++++++++++++++------------- lib/tests/misc.nix | 1 + 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/cli.nix b/lib/cli.nix index 23fab8ec970..f3a81cb9e9c 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -6,27 +6,31 @@ boilerplate related to command-line construction for simple use cases. Example: - encodeGNUCommandLine { foo = "A"; bar = 1; baz = null; qux = true; v = true; } + encodeGNUCommandLine { } { foo = "A"; bar = 1; baz = null; qux = true; v = true; } => " --bar '1' --foo 'A' --qux -v" */ encodeGNUCommandLine = + { renderKey ? + key: if builtins.stringLength key == 1 then "-${key}" else "--${key}" + + , renderOption ? + key: value: + if value == null + then "" + else " ${renderKey key} ${lib.escapeShellArg value}" + + , renderBool ? key: value: if value then " ${renderKey key}" else "" + + , renderList ? key: value: lib.concatMapStrings renderOption value + }: options: let render = key: value: - let - hyphenate = - k: if builtins.stringLength k == 1 then "-${k}" else "--${k}"; - - renderOption = v: if v == null then "" else " ${hyphenate key} ${lib.escapeShellArg v}"; - - renderSwitch = if value then " ${hyphenate key}" else ""; - - in if builtins.isBool value - then renderSwitch + then renderBool key value else if builtins.isList value - then lib.concatMapStrings renderOption value - else renderOption value; + then renderList key value + else renderOption key value; in lib.concatStrings (lib.mapAttrsToList render options); diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index c0a48f472cc..021d0e88c91 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -444,6 +444,7 @@ runTests { testRenderOptions = { expr = encodeGNUCommandLine + { } { foo = "A"; bar = 1; From 5edd4dd44c5f3de1886744aeac49bd396c24f966 Mon Sep 17 00:00:00 2001 From: Gabriel Gonzalez Date: Sun, 15 Dec 2019 08:21:41 -0800 Subject: [PATCH 4/6] Use a more realistic example that exercises all encodings ... as suggested by @roberth This also caught a bug in rendering lists, which this change also fixes --- lib/cli.nix | 21 ++++++++++++++++++--- lib/tests/misc.nix | 16 ++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/cli.nix b/lib/cli.nix index f3a81cb9e9c..b6703b2ca82 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -6,8 +6,23 @@ boilerplate related to command-line construction for simple use cases. Example: - encodeGNUCommandLine { } { foo = "A"; bar = 1; baz = null; qux = true; v = true; } - => " --bar '1' --foo 'A' --qux -v" + encodeGNUCommandLine + { } + { 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; + }; + => " -X 'PUT' --data '{\"id\":0}' --retry '3' --url 'https://example.com/foo' --url 'https://example.com/bar' --verbose" */ encodeGNUCommandLine = { renderKey ? @@ -21,7 +36,7 @@ , renderBool ? key: value: if value then " ${renderKey key}" else "" - , renderList ? key: value: lib.concatMapStrings renderOption value + , renderList ? key: value: lib.concatMapStrings (renderOption key) value }: options: let diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 021d0e88c91..29c5fad91f9 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -445,17 +445,21 @@ runTests { expr = encodeGNUCommandLine { } - { foo = "A"; + { data = builtins.toJSON { id = 0; }; - bar = 1; + X = "PUT"; - baz = null; + retry = 3; - qux = true; + retry-delay = null; - v = true; + url = [ "https://example.com/foo" "https://example.com/bar" ]; + + silent = false; + + verbose = true; }; - expected = " --bar '1' --foo 'A' --qux -v"; + expected = " -X 'PUT' --data '{\"id\":0}' --retry '3' --url 'https://example.com/foo' --url 'https://example.com/bar' --verbose"; }; } From 6d584c26143f68bf6963bc7fc5661e05d90f7ab7 Mon Sep 17 00:00:00 2001 From: Gabriel Gonzalez Date: Sun, 5 Jan 2020 13:03:00 -0800 Subject: [PATCH 5/6] Factor out a `toGNUCommandLine` utility ... as suggested by @roberth --- lib/cli.nix | 18 +++++++++++------- lib/tests/misc.nix | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/cli.nix b/lib/cli.nix index b6703b2ca82..f47625d2f53 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -1,6 +1,7 @@ { lib }: -{ /* Automatically convert an attribute set to command-line options. +rec { + /* Automatically convert an attribute set to command-line options. This helps protect against malformed command lines and also to reduce boilerplate related to command-line construction for simple use cases. @@ -22,21 +23,24 @@ verbose = true; }; - => " -X 'PUT' --data '{\"id\":0}' --retry '3' --url 'https://example.com/foo' --url 'https://example.com/bar' --verbose" + => "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'" */ encodeGNUCommandLine = + options: attrs: lib.escapeShellArgs (toGNUCommandLine options attrs); + + toGNUCommandLine = { renderKey ? key: if builtins.stringLength key == 1 then "-${key}" else "--${key}" , renderOption ? key: value: if value == null - then "" - else " ${renderKey key} ${lib.escapeShellArg value}" + then [] + else [ (renderKey key) (builtins.toString value) ] - , renderBool ? key: value: if value then " ${renderKey key}" else "" + , renderBool ? key: value: lib.optional value (renderKey key) - , renderList ? key: value: lib.concatMapStrings (renderOption key) value + , renderList ? key: value: lib.concatMap (renderOption key) value }: options: let @@ -48,5 +52,5 @@ else renderOption key value; in - lib.concatStrings (lib.mapAttrsToList render options); + builtins.concatLists (lib.mapAttrsToList render options); } diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 29c5fad91f9..e47b48b5017 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -460,6 +460,6 @@ runTests { verbose = true; }; - expected = " -X 'PUT' --data '{\"id\":0}' --retry '3' --url 'https://example.com/foo' --url 'https://example.com/bar' --verbose"; + expected = "'-X' 'PUT' '--data' '{\"id\":0}' '--retry' '3' '--url' 'https://example.com/foo' '--url' 'https://example.com/bar' '--verbose'"; }; } From a46679facd38ef7031ea3a79def60b98e384f155 Mon Sep 17 00:00:00 2001 From: Gabriel Gonzalez Date: Sun, 5 Jan 2020 14:44:42 -0800 Subject: [PATCH 6/6] Export toGNUCommandLine ... as suggested by @roberth Co-Authored-By: Robert Hensing --- lib/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/default.nix b/lib/default.nix index a7b00f01e0d..be7d118969e 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -121,7 +121,7 @@ let isOptionType mkOptionType; inherit (asserts) assertMsg assertOneOf; - inherit (cli) encodeGNUCommandLine; + inherit (cli) encodeGNUCommandLine toGNUCommandLine; inherit (debug) addErrorContextToAttrs traceIf traceVal traceValFn traceXMLVal traceXMLValMarked traceSeq traceSeqN traceValSeq traceValSeqFn traceValSeqN traceValSeqNFn traceShowVal