diff options
Diffstat (limited to 'pkgs/profpatsch/execline')
-rw-r--r-- | pkgs/profpatsch/execline/e.nix | 24 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/escape.nix | 31 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/importer.nix | 45 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/run-execline-tests.nix | 89 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/run-execline.nix | 71 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/runblock.nix | 69 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/symlink.nix | 46 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/write-execline.nix | 35 |
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; +} |