about summary refs log tree commit diff
path: root/pkgs/profpatsch/execline
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/profpatsch/execline')
-rw-r--r--pkgs/profpatsch/execline/e.nix24
-rw-r--r--pkgs/profpatsch/execline/escape.nix31
-rw-r--r--pkgs/profpatsch/execline/importer.nix45
-rw-r--r--pkgs/profpatsch/execline/run-execline-tests.nix89
-rw-r--r--pkgs/profpatsch/execline/run-execline.nix71
-rw-r--r--pkgs/profpatsch/execline/runblock.nix69
-rw-r--r--pkgs/profpatsch/execline/symlink.nix46
-rw-r--r--pkgs/profpatsch/execline/write-execline.nix35
8 files changed, 410 insertions, 0 deletions
diff --git a/pkgs/profpatsch/execline/e.nix b/pkgs/profpatsch/execline/e.nix
new file mode 100644
index 00000000..ca72c6f0
--- /dev/null
+++ b/pkgs/profpatsch/execline/e.nix
@@ -0,0 +1,24 @@
+{ writeExecline, getBins, pkgs }:
+let
+
+  bins = getBins pkgs.rlwrap [ "rlwrap" ]
+    // getBins pkgs.s6-portable-utils [ { use = "s6-cat"; as = "cat"; } ]
+    // getBins pkgs.execline [ "execlineb" ];
+
+  # minimal execline shell
+  e =
+    let
+      prompt = [ "if" [ "printf" ''\e[0;33me>\e[0m '' ] ];
+    in
+      writeExecline "e" {} ([
+        bins.rlwrap
+          "--remember"
+          "--quote-characters" "\""
+          "--complete-filenames"
+      ] ++ prompt ++ [
+        "forstdin" "-d\n" "cmd"
+        "importas" "cmd" "cmd"
+        "foreground" [ bins.execlineb "-Pc" "$cmd" ]
+      ] ++ prompt);
+
+in { inherit e; }
diff --git a/pkgs/profpatsch/execline/escape.nix b/pkgs/profpatsch/execline/escape.nix
new file mode 100644
index 00000000..dff07a19
--- /dev/null
+++ b/pkgs/profpatsch/execline/escape.nix
@@ -0,0 +1,31 @@
+{ lib }:
+let
+  # replaces " and \ to \" and \\ respectively and quote with "
+  # e.g.
+  #   a"b\c -> "a\"b\\c"
+  #   a\"bc -> "a\\\"bc"
+  # TODO upsteam into nixpkgs
+  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 {
+  inherit escapeExecline;
+}
diff --git a/pkgs/profpatsch/execline/importer.nix b/pkgs/profpatsch/execline/importer.nix
new file mode 100644
index 00000000..67464d17
--- /dev/null
+++ b/pkgs/profpatsch/execline/importer.nix
@@ -0,0 +1,45 @@
+{ lib, coreutils, s6-portable-utils, symlink }:
+let
+  example = {from, as, just, ...}:
+    [
+      (from coreutils [
+        (just "echo")
+        (as "core-ls" "ls")
+      ])
+      (from s6-portable-utils [
+        (as "ls" "s6-ls")
+        (just "s6-echo")
+      ])
+    ];
+
+  runImport = impsFn:
+    let
+      combinators = rec {
+        from = source: imports: {
+          inherit source imports;
+        };
+        as = newname: oldname: {
+          inherit oldname newname;
+        };
+        just = x: as x x;
+      };
+
+      # Drv -> As -> Symlink
+      toBin = module: {oldname, newname}: {
+        dest = "bin/${newname}";
+        orig = "${module}/bin/${oldname}";
+      };
+      # List (Import { source: Drv
+      #              , imports: List (As { oldname: String
+      #                                  , newname: String }))
+      # -> Drv
+      run = imps:
+        symlink "foo" (lib.concatLists
+          (map ({source, imports}:
+                   map (toBin source) imports)
+               imps));
+
+    # TODO: typecheck w/ newtypes
+    in run (impsFn combinators);
+
+in runImport example
diff --git a/pkgs/profpatsch/execline/run-execline-tests.nix b/pkgs/profpatsch/execline/run-execline-tests.nix
new file mode 100644
index 00000000..c3f534cc
--- /dev/null
+++ b/pkgs/profpatsch/execline/run-execline-tests.nix
@@ -0,0 +1,89 @@
+{ stdenv, drvSeqL, runExecline, bin
+# https://www.mail-archive.com/skaware@list.skarnet.org/msg01256.html
+, coreutils }:
+
+let
+
+  # lol
+  writeScript = name: script: runExecline name {
+    derivationArgs = {
+      inherit script;
+      passAsFile = [ "script" ];
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+    };
+  } [
+      "importas" "-ui" "s" "scriptPath"
+      "importas" "-ui" "out" "out"
+      "foreground" [
+        "${coreutils}/bin/mv" "$s" "$out"
+      ]
+      "${bin.s6-chmod}" "0755" "$out"
+  ];
+
+  # execline block of depth 1
+  block = args: builtins.map (arg: " ${arg}") args ++ [ "" ];
+
+  # derivation that tests whether a given line exists
+  # in the given file. Does not use runExecline, because
+  # that should be tested after all.
+  fileHasLine = line: file: derivation {
+    name = "run-execline-test-file-${file.name}-has-line";
+    inherit (stdenv) system;
+    builder = bin.execlineIf;
+    args =
+      (block [
+        bin.redirfd "-r" "0" file   # read file to stdin
+        bin.s6-grep "-F" "-q" line   # and grep for the line
+      ])
+      ++ [
+        # if the block succeeded, touch $out
+        bin.importas "-ui" "out" "out"
+        bin.s6-touch "$out"
+      ];
+    preferLocalBuild = true;
+    allowSubstitutes = false;
+  };
+
+  # basic test that touches out
+  basic = runExecline "run-execline-test-basic" {
+    derivationArgs = {
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+    };
+  } [
+      "importas" "-ui" "out" "out"
+      "${bin.s6-touch}" "$out"
+  ];
+
+  # whether the stdin argument works as intended
+  stdin = fileHasLine "foo" (runExecline "run-execline-test-stdin" {
+    stdin = "foo\nbar\nfoo";
+    derivationArgs = {
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+    };
+  } [
+      "importas" "-ui" "out" "out"
+      # this pipes stdout of s6-cat to $out
+      # and s6-cat redirects from stdin to stdout
+      "redirfd" "-w" "1" "$out" bin.s6-cat
+  ]);
+
+  wrapWithVar = runExecline "run-execline-test-wrap-with-var" {
+    builderWrapper = writeScript "var-wrapper" ''
+      #!${bin.execlineb} -S0
+      export myvar myvalue $@
+    '';
+    derivationArgs = {
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+    };
+  } [
+    "importas" "-ui" "v" "myvar"
+    "if" [ bin.s6-test "myvalue" "=" "$v" ]
+      "importas" "out" "out"
+      bin.s6-touch "$out"
+  ];
+
+in [ basic stdin wrapWithVar ]
diff --git a/pkgs/profpatsch/execline/run-execline.nix b/pkgs/profpatsch/execline/run-execline.nix
new file mode 100644
index 00000000..2efe43d6
--- /dev/null
+++ b/pkgs/profpatsch/execline/run-execline.nix
@@ -0,0 +1,71 @@
+{ stdenv, bin, lib }:
+name:
+{
+# a string to pass as stdin to the execline script
+stdin ? ""
+# a program wrapping the acutal execline invocation;
+# should be in Bernstein-chaining style
+, builderWrapper ? bin.exec
+# additional arguments to pass to the derivation
+, derivationArgs ? {}
+}:
+# the execline script as a nested list of string,
+# representing the blocks;
+# see docs of `escapeExecline`.
+ execline:
+
+# those arguments can’t be overwritten
+assert !derivationArgs ? system;
+assert !derivationArgs ? name;
+assert !derivationArgs ? builder;
+assert !derivationArgs ? args;
+
+derivation (derivationArgs // {
+  # TODO: what about cross?
+  inherit (stdenv) system;
+  inherit name;
+
+  # okay, `builtins.toFile` does not accept strings
+  # that reference drv outputs. This means we need
+  # to pass the script and stdin as envvar;
+  # this might clash with another passed envar,
+  # so we give it a long & unique name
+  _runExeclineScript =
+    let
+      escape = (import ./escape.nix { inherit lib; });
+    in escape.escapeExecline execline;
+  _runExeclineStdin = stdin;
+  passAsFile = [
+    "_runExeclineScript"
+    "_runExeclineStdin"
+  ] ++ derivationArgs.passAsFile or [];
+
+  # the default, exec acts as identity executable
+  builder = builderWrapper;
+
+  args = [
+    bin.importas             # import script file as $script
+    "-ui"                    # drop the envvar afterwards
+    "script"                 # substitution name
+    "_runExeclineScriptPath" # passed script file
+
+    # TODO: can we scrap stdin via builderWrapper?
+    bin.importas             # do the same for $stdin
+    "-ui"
+    "stdin"
+    "_runExeclineStdinPath"
+
+    bin.redirfd              # now we
+    "-r"                     # read the file
+    "0"                      # into the stdin of execlineb
+    "$stdin"                 # that was given via stdin
+
+    bin.execlineb            # the actual invocation
+    # TODO: depending on the use-case, -S0 might not be enough
+    # in all use-cases, then a wrapper for execlineb arguments
+    # should be added (-P, -S, -s).
+    "-S0"                    # set $@ inside the execline script
+    "-W"                     # die on syntax error
+    "$script"                # substituted by importas
+  ];
+})
diff --git a/pkgs/profpatsch/execline/runblock.nix b/pkgs/profpatsch/execline/runblock.nix
new file mode 100644
index 00000000..0b380229
--- /dev/null
+++ b/pkgs/profpatsch/execline/runblock.nix
@@ -0,0 +1,69 @@
+{ pkgs }:
+let
+
+  # This is a rewrite of execline’s runblock.
+  # It adds the feature that instead of just
+  # executing the block it reads, it can also
+  # pass it as argv to given commands.
+  #
+  # This is going to be added to a future version
+  # of execline by skarnet, but for now it’s easier
+  # to just dirtily reimplement it in Python.
+  runblock = pkgs.writers.writePython3 "runblock" {} ''
+    import sys
+    import os
+
+    skip = False
+    one = sys.argv[1]
+    if one == "-r":
+        skip = True
+        block_number = int(sys.argv[2])
+        block_start = 3
+    elif one.startswith("-"):
+        print("only -r supported", file=sys.stderr)
+        sys.exit(100)
+    else:
+        block_number = int(one)
+        block_start = 2
+
+    execline_argv_no = int(os.getenvb(b"#"))
+    runblock_argv = [os.getenv(str(no)) for no in range(1, execline_argv_no + 1)]
+
+
+    def parse_block(args):
+        new_args = []
+        for arg in args:
+            if arg == "":
+                break
+            elif arg.startswith(" "):
+                new_args.append(arg[1:])
+            else:
+                print(
+                  "unterminated block: {}".format(args),
+                  file=sys.stderr
+                )
+                sys.exit(100)
+        args_rest = args[len(new_args)+1:]
+        return (new_args, args_rest)
+
+
+    if skip:
+        rest = runblock_argv
+        for _ in range(0, block_number-1):
+            (_, rest) = parse_block(rest)
+        new_argv = rest
+    else:
+        new_argv = []
+        rest = runblock_argv
+        for _ in range(0, block_number):
+            (new_argv, rest) = parse_block(rest)
+
+    given_argv = sys.argv[block_start:]
+    run = given_argv + new_argv
+    os.execvp(
+        file=run[0],
+        args=run
+    )
+  '';
+
+in { inherit runblock; }
diff --git a/pkgs/profpatsch/execline/symlink.nix b/pkgs/profpatsch/execline/symlink.nix
new file mode 100644
index 00000000..c6a311d8
--- /dev/null
+++ b/pkgs/profpatsch/execline/symlink.nix
@@ -0,0 +1,46 @@
+{ lib, s6-portable-utils, coreutils, runExecline }:
+# DrvPath :: path relative to the derivation
+# AbsPath :: absolute path in the store
+#    Name
+# -> List (Symlink { dest: DrvPath, orig: AbsPath })
+# -> Drv
+name: links:
+
+let
+  toNetstring = s:
+    "${toString (builtins.stringLength s)}:${s},";
+
+in
+runExecline name {
+  derivationArgs = {
+    pathTuples = lib.concatMapStrings
+      ({dest, orig}: toNetstring
+        (toNetstring dest + (toNetstring orig)))
+      links;
+    passAsFile = [ "pathTuples" ];
+    # bah! coreutils just for cat :(
+    PATH = lib.makeBinPath [ s6-portable-utils ];
+  };
+} [
+  "importas" "-ui" "p" "pathTuplesPath"
+  "importas" "-ui" "out" "out"
+  "forbacktickx" "-d" "" "destorig" [ "${coreutils}/bin/cat" "$p" ]
+    "importas" "-ui" "do" "destorig"
+    "multidefine" "-d" "" "$do" [ "destsuffix" "orig" ]
+    "define" "dest" ''''${out}/''${destsuffix}''
+
+    # this call happens for every file, not very efficient
+    "foreground" [
+      "backtick" "-n" "d" [ "s6-dirname" "$dest" ]
+      "importas" "-ui" "d" "d"
+      "s6-mkdir" "-p" "$d"
+    ]
+
+    "ifthenelse" [ "s6-test" "-L" "$orig" ] [
+      "backtick" "-n" "res" [ "s6-linkname" "-f" "$orig" ]
+      "importas" "-ui" "res" "res"
+      "s6-ln" "-fs" "$res" "$dest"
+    ] [
+      "s6-ln" "-fs" "$orig" "$dest"
+    ]
+]
diff --git a/pkgs/profpatsch/execline/write-execline.nix b/pkgs/profpatsch/execline/write-execline.nix
new file mode 100644
index 00000000..23c96454
--- /dev/null
+++ b/pkgs/profpatsch/execline/write-execline.nix
@@ -0,0 +1,35 @@
+{ pkgs }:
+let
+  escape = import ./escape.nix { inherit (pkgs) lib; };
+
+  # Write a list of execline argv parameters to an execline script.
+  # Everything is escaped correctly.
+  # TODO upstream into nixpkgs
+  writeExeclineCommon = writer: 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,
+  }: 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 writer name ''
+    #!${pkgs.execline}/bin/execlineb -W${env}
+    ${escape.escapeExecline argList}
+  '';
+  writeExecline = writeExeclineCommon pkgs.writeScript;
+  writeExeclineBin = writeExeclineCommon pkgs.writeScriptBin;
+
+in {
+  inherit writeExecline writeExeclineBin;
+}