diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0c06c59f378..f3ac8ad96b7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,7 +14,9 @@ /lib @edolstra @nbp @infinisil /lib/systems @nbp @ericson2314 @matthewbauer /lib/generators.nix @edolstra @nbp @Profpatsch +/lib/cli.nix @edolstra @nbp @Profpatsch /lib/debug.nix @edolstra @nbp @Profpatsch +/lib/asserts.nix @edolstra @nbp @Profpatsch # Nixpkgs Internals /default.nix @nbp diff --git a/lib/cli.nix b/lib/cli.nix index f47625d2f53..c96d4dbb043 100644 --- a/lib/cli.nix +++ b/lib/cli.nix @@ -6,50 +6,77 @@ rec { This helps protect against malformed command lines and also to reduce boilerplate related to command-line construction for simple use cases. + `toGNUCommandLine` returns a list of nix strings. + `toGNUCommandLineShell` returns an escaped shell string. + Example: - encodeGNUCommandLine - { } - { data = builtins.toJSON { id = 0; }; + cli.toGNUCommandLine {} { + 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" + ] - 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'" + cli.toGNUCommandLineShell {} { + 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 = + toGNUCommandLineShell = options: attrs: lib.escapeShellArgs (toGNUCommandLine options attrs); - toGNUCommandLine = - { renderKey ? - key: if builtins.stringLength key == 1 then "-${key}" else "--${key}" + toGNUCommandLine = { + # how to string-format the option name; + # by default one character is a short option (`-`), + # more than one characters a long option (`--`). + mkOptionName ? + k: if builtins.stringLength k == 1 + then "-${k}" + else "--${k}", - , renderOption ? - key: value: - if value == null - then [] - else [ (renderKey key) (builtins.toString value) ] + # how to format a boolean value to a command list; + # by default it’s a flag option + # (only the option name if true, left out completely if false). + mkBool ? k: v: lib.optional v (mkOptionName k), - , renderBool ? key: value: lib.optional value (renderKey key) + # how to format a list value to a command list; + # by default the option name is repeated for each value + # and `mkOption` is applied to the values themselves. + mkList ? k: v: lib.concatMap (mkOption k) v, - , renderList ? key: value: lib.concatMap (renderOption key) value + # how to format any remaining value to a command list; + # on the toplevel, booleans and lists are handled by `mkBool` and `mkList`, + # though they can still appear as values of a list. + # By default, everything is printed verbatim and complex types + # are forbidden (lists, attrsets, functions). `null` values are omitted. + mkOption ? + k: v: if v == null + then [] + else [ (mkOptionName k) (lib.generators.mkValueStringDefault {} v) ] }: options: let - render = key: value: - if builtins.isBool value - then renderBool key value - else if builtins.isList value - then renderList key value - else renderOption key value; + render = k: v: + if builtins.isBool v then mkBool k v + else if builtins.isList v then mkList k v + else mkOption k v; in builtins.concatLists (lib.mapAttrsToList render options); diff --git a/lib/default.nix b/lib/default.nix index d2e9f0e8086..d2fe018aa6a 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -37,11 +37,13 @@ let licenses = callLibs ./licenses.nix; systems = callLibs ./systems; + # serialization + cli = callLibs ./cli.nix; + generators = callLibs ./generators.nix; + # misc asserts = callLibs ./asserts.nix; - cli = callLibs ./cli.nix; debug = callLibs ./debug.nix; - generators = callLibs ./generators.nix; misc = callLibs ./deprecated.nix; # domain-specific @@ -121,7 +123,6 @@ let isOptionType mkOptionType; inherit (asserts) assertMsg assertOneOf; - inherit (cli) encodeGNUCommandLine toGNUCommandLine; inherit (debug) addErrorContextToAttrs traceIf traceVal traceValFn traceXMLVal traceXMLValMarked traceSeq traceSeqN traceValSeq traceValSeqFn traceValSeqN traceValSeqNFn traceShowVal diff --git a/lib/generators.nix b/lib/generators.nix index a71654bec6c..a64e94bd5cb 100644 --- a/lib/generators.nix +++ b/lib/generators.nix @@ -46,7 +46,10 @@ rec { else if isList v then err "lists" v # same as for lists, might want to replace else if isAttrs v then err "attrsets" v + # functions can’t be printed of course else if isFunction v then err "functions" v + # let’s not talk about floats. There is no sensible `toString` for them. + else if isFloat v then err "floats" v else err "this value is" (toString v); diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index e47b48b5017..59ed1e507e2 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -441,24 +441,40 @@ runTests { expected = "«foo»"; }; - testRenderOptions = { - expr = - encodeGNUCommandLine - { } - { data = builtins.toJSON { id = 0; }; - X = "PUT"; +# CLI - retry = 3; + testToGNUCommandLine = { + expr = cli.toGNUCommandLine {} { + 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; + }; - retry-delay = null; + expected = [ + "-X" "PUT" + "--data" "{\"id\":0}" + "--retry" "3" + "--url" "https://example.com/foo" + "--url" "https://example.com/bar" + "--verbose" + ]; + }; - url = [ "https://example.com/foo" "https://example.com/bar" ]; - - silent = false; - - verbose = true; - }; + testToGNUCommandLineShell = { + expr = cli.toGNUCommandLineShell {} { + 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'"; };