From 3fd583d27c090a62717bf0887499830138c1dcbb Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sat, 27 Jun 2020 06:11:47 +0200 Subject: feat(nix/writeExecline): add writeExecline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a writer, similar to `pkgs.writeBashScript` or `pkgs.writers.writePython3`. The difference is that we can correctly write all execline scripts by using nix lists of lists, so the user doesn’t have to care about escaping arguments (like they have to in bash scripts with `lib.escapeShellArg` for example). Change-Id: I2f2874cf61170ddca07b89b692f762725f4a75dc Reviewed-on: https://cl.tvl.fyi/c/depot/+/625 Reviewed-by: Kane York Reviewed-by: tazjin --- nix/writeExecline/OWNERS | 3 ++ nix/writeExecline/default.nix | 67 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 nix/writeExecline/OWNERS create mode 100644 nix/writeExecline/default.nix (limited to 'nix') diff --git a/nix/writeExecline/OWNERS b/nix/writeExecline/OWNERS new file mode 100644 index 0000000000..a742d0d22b --- /dev/null +++ b/nix/writeExecline/OWNERS @@ -0,0 +1,3 @@ +inherited: true +owners: + - Profpatsch diff --git a/nix/writeExecline/default.nix b/nix/writeExecline/default.nix new file mode 100644 index 0000000000..0916e2d58e --- /dev/null +++ b/nix/writeExecline/default.nix @@ -0,0 +1,67 @@ +{ pkgs, lib, ... }: + +# Write an execline script, represented as nested nix lists. +# Everything is escaped correctly. +# https://skarnet.org/software/execline/ + +# TODO(Profpatsch) upstream into nixpkgs + +let + # replaces " and \ to \" and \\ respectively and quote with " + # e.g. + # a"b\c -> "a\"b\\c" + # a\"bc -> "a\\\"bc" + escapeExeclineArg = arg: + ''"${builtins.replaceStrings [ ''"'' ''\'' ] [ ''\"'' ''\\'' ] (toString arg)}"''; + + # Escapes an execline (list of execline strings) to be passed to execlineb + # Give it a nested list of strings. Nested lists are interpolated as execline + # blocks ({}). + # Everything is quoted correctly. + # + # Example: + # escapeExecline [ "if" [ "somecommand" ] "true" ] + # == ''"if" { "somecommand" } "true"'' + escapeExecline = execlineList: lib.concatStringsSep " " + (let + go = arg: + if builtins.isString arg then [(escapeExeclineArg arg)] + else if builtins.isPath arg then [(escapeExeclineArg "${arg}")] + else if lib.isDerivation arg then [(escapeExeclineArg arg)] + else if builtins.isList arg then [ "{" ] ++ builtins.concatMap go arg ++ [ "}" ] + else abort "escapeExecline can only hande nested lists of strings, was ${lib.generators.toPretty {} arg}"; + in builtins.concatMap go execlineList); + +in + +name: +{ + # "var": substitute readNArgs variables and start $@ + # from the (readNArgs+1)th argument + # "var-full": substitute readNArgs variables and start $@ from $0 + # "env": don’t substitute, set # and 0…n environment vaariables, where n=$# + # "none": don’t substitute or set any positional arguments + # "env-no-push": like "env", but bypass the push-phase. Not recommended. + argMode ? "var", + # Number of arguments to be substituted as variables (passed to "var"/"-s" or "var-full"/"-S" + readNArgs ? 0, +}: +# Nested list of lists of commands. +# Inner lists are translated to execline blocks. +argList: + +let + env = + if argMode == "var" then "s${toString readNArgs}" + else if argMode == "var-full" then "S${toString readNArgs}" + else if argMode == "env" then "" + else if argMode == "none" then "P" + else if argMode == "env-no-push" then "p" + else abort ''"${toString argMode}" is not a valid argMode, use one of "var", "var-full", "env", "none", "env-no-push".''; + +in + # TODO(Profpatsch): rewrite `writeScript` with `runExecline` + pkgs.writeScript name '' + #!${pkgs.execline}/bin/execlineb -W${env} + ${escapeExecline argList} + '' -- cgit 1.4.1