about summary refs log tree commit diff
path: root/nix
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2020-06-27T04·11+0200
committerProfpatsch <mail@profpatsch.de>2020-06-27T17·52+0000
commit3fd583d27c090a62717bf0887499830138c1dcbb (patch)
tree588731c042d56138d849d1ad91ba4b5a68fecb6e /nix
parent22b8a49b87d2b86329add48e9af69ffd29ff6118 (diff)
feat(nix/writeExecline): add writeExecline r/1100
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 <rikingcoding@gmail.com>
Reviewed-by: tazjin <mail@tazj.in>
Diffstat (limited to 'nix')
-rw-r--r--nix/writeExecline/OWNERS3
-rw-r--r--nix/writeExecline/default.nix67
2 files changed, 70 insertions, 0 deletions
diff --git a/nix/writeExecline/OWNERS b/nix/writeExecline/OWNERS
new file mode 100644
index 000000000000..a742d0d22bf6
--- /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 000000000000..0916e2d58eac
--- /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}
+  ''