Unverified Commit 73e8a483 authored by Lukas Wurzinger's avatar Lukas Wurzinger
Browse files

lib/cli: add toCommandLine

parent 344dfdae
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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`.
+188 −3
Original line number Diff line number Diff line
{ 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);
}
+80 −0
Original line number Diff line number Diff line
@@ -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";