diff options
Diffstat (limited to 'pkgs/profpatsch')
-rw-r--r-- | pkgs/profpatsch/default.nix | 94 | ||||
-rw-r--r-- | pkgs/profpatsch/display-infos/default.nix | 50 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/escape.nix | 30 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/run-execline-tests.nix | 82 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/run-execline.nix | 19 | ||||
-rw-r--r-- | pkgs/profpatsch/execline/symlink.nix | 48 | ||||
-rw-r--r-- | pkgs/profpatsch/nman/default.nix | 4 | ||||
-rw-r--r-- | pkgs/profpatsch/sfttime/default.nix | 14 | ||||
-rwxr-xr-x | pkgs/profpatsch/sfttime/sfttime.sh | 145 | ||||
-rw-r--r-- | pkgs/profpatsch/utils-hs/default.nix | 14 | ||||
-rw-r--r-- | pkgs/profpatsch/xmonad/DhallTypedInput.hs | 232 |
11 files changed, 622 insertions, 110 deletions
diff --git a/pkgs/profpatsch/default.nix b/pkgs/profpatsch/default.nix index a4378ecf..dc746c2e 100644 --- a/pkgs/profpatsch/default.nix +++ b/pkgs/profpatsch/default.nix @@ -5,11 +5,25 @@ let # wrapper for execlineb that doesn’t need the execline commands # in PATH to work (making them appear like “builtins”) + # TODO: upstream into nixpkgs + # TODO: the grep could be nicer execlineb-with-builtins = let eldir = "${pkgs.execline}/bin"; in pkgs.writeScriptBin "execlineb" '' #!${eldir}/execlineb -s0 + # appends the execlineb bin dir to PATH if not yet in PATH ${eldir}/define eldir ${eldir} + ''${eldir}/ifelse + { + # since this is nix, we can grep for the execline drv hash in PATH + # to see whether it’s already in there + ''${eldir}/pipeline + { ${pkgs.coreutils}/bin/printenv PATH } + ${pkgs.gnugrep}/bin/grep --quiet "${eldir}" + } + # it’s there already + { ''${eldir}/execlineb $@ } + # not there yet, add it ''${eldir}/importas oldpath PATH ''${eldir}/export PATH "''${eldir}:''${oldpath}" ''${eldir}/execlineb $@ @@ -46,6 +60,52 @@ let allowSubstitutes = false; }) cmd; + testing = import ./testing { + inherit stdenv lib; + inherit (runExeclineFns) runExecline; + inherit (pkgs) runCommand; + bin = bins pkgs.s6PortableUtils [ "s6-touch" "s6-echo" ]; + }; + + runExeclineFns = + # todo: factor out calling tests + let + it = import ./execline/run-execline.nix { + bin = (bins execlineb-with-builtins [ "execlineb" ]) + // (bins pkgs.execline [ "redirfd" "importas" "exec" ]); + inherit stdenv lib; + }; + itLocal = name: args: execline: + it name (args // { + derivationArgs = args.derivationArgs or {} // { + preferLocalBuild = true; + allowSubstitutes = false; + }; + }) execline; + + tests = import ./execline/run-execline-tests.nix { + # can’t use runExeclineLocal in the tests, + # because it is tested by the tests (well, it does + # work, but then you have to run the tests every time) + runExecline = it; + inherit (testing) drvSeqL; + inherit (pkgs) coreutils; + inherit stdenv; + bin = (bins execlineb-with-builtins [ "execlineb" ]) + // (bins pkgs.execline [ + { use = "if"; as = "execlineIf"; } + "redirfd" "importas" + ]) + // (bins pkgs.s6PortableUtils + [ "s6-cat" "s6-grep" "s6-touch" "s6-test" "s6-chmod" ]); + }; + in { + runExecline = it; + runExeclineLocal = name: args: execline: + testing.drvSeqL tests (itLocal name args execline); + }; + + in rec { inherit (nixperiments) # filterSource by parsing a .gitignore file @@ -60,10 +120,11 @@ in rec { json2json json2string; backlight = callPackage ./backlight { inherit (pkgs.xorg) xbacklight; }; - display-infos = callPackage ./display-infos {}; + display-infos = callPackage ./display-infos { inherit sfttime; }; git-commit-index = callPackage ./git-commit-index { inherit script runCommandLocal; }; nix-http-serve = callPackage ./nix-http-serve {}; nman = callPackage ./nman {}; + sfttime = callPackage ./sfttime {}; show-qr-code = callPackage ./show-qr-code {}; warpspeed = callPackage ./warpspeed { inherit (pkgs.haskellPackages) ghcWithPackages; @@ -90,35 +151,8 @@ in rec { ]; }); - runExecline = - # todo: factor out calling tests - let - it = import ./execline/run-execline.nix { - bin = (bins execlineb-with-builtins [ "execlineb" ]) - // (bins pkgs.execline [ "redirfd" "importas" "exec" ]); - inherit stdenv; - }; - tests = import ./execline/run-execline-tests.nix { - runExecline = it; - inherit (testing) drvSeqL; - inherit (pkgs) coreutils; - inherit stdenv; - bin = (bins execlineb-with-builtins [ "execlineb" ]) - // (bins pkgs.execline [ - { use = "if"; as = "execlineIf"; } - "redirfd" "importas" - ]) - // (bins pkgs.s6PortableUtils - [ "s6-cat" "s6-grep" "s6-touch" "s6-test" "s6-chmod" ]); - }; - in tests; - - - testing = import ./testing { - inherit stdenv lib runExecline; - inherit (pkgs) runCommand; - bin = bins pkgs.s6PortableUtils [ "s6-touch" "s6-echo" ]; - }; + inherit (runExeclineFns) + runExecline runExeclineLocal; symlink = pkgs.callPackage ./execline/symlink.nix { inherit runExecline; diff --git a/pkgs/profpatsch/display-infos/default.nix b/pkgs/profpatsch/display-infos/default.nix index d213241c..b626e844 100644 --- a/pkgs/profpatsch/display-infos/default.nix +++ b/pkgs/profpatsch/display-infos/default.nix @@ -1,8 +1,8 @@ -{ lib, runCommand, python3, libnotify }: +{ lib, runCommand, writeText, python3, libnotify, bc, sfttime }: let name = "display-infos-0.1.0"; - script = builtins.toFile (name + "-script") '' + script = writeText (name + "-script") '' #!@python3@ import sys @@ -11,20 +11,53 @@ let import os.path as path import statistics as st + def readint(fn): + with open(fn, 'r') as f: + return int(f.read()) + + def seconds_to_sft(secs): + p = sub.Popen(["@bc@", "-l"], stdin=sub.PIPE, stdout=sub.PIPE) + (sft, _) = p.communicate(input="scale=2; obase=16; {} / 86400\n".format(secs).encode()) + p.terminate() + return str(sft.strip().decode()) + + charging = readint("/sys/class/power_supply/AC/online") + full = 0 now = 0 + # this is "to charged" if charging and "to empty" if not + seconds_remaining = 0 for bat in glob.iglob("/sys/class/power_supply/BAT*"): - def readint(fn): - with open(fn, 'r') as f: - return int(f.read()) + # these files might be different for different ACPI/battery providers + # see the full list in acpi.c of the acpi(1) tool + # unit: who knows full += readint(path.join(bat, "energy_full")) now += readint(path.join(bat, "energy_now" )) + # in unit?/hours, hopefully the same unit as above + # ACPI is a garbage fire + current_rate = readint(path.join(bat, "power_now")) + + if current_rate == 0: + continue + elif charging: + seconds_remaining += 3600 * (full - now) / current_rate + else: + seconds_remaining += 3600 * now / current_rate bat = round( now/full, 2 ) + ac = "🗲 " if charging else "" + sft_remaining = seconds_to_sft(seconds_remaining) date = sub.run(["date", "+%d.%m. %a %T"], stdout=sub.PIPE).stdout.strip().decode() - notify = "BAT: {}% | {}".format(int(bat*100), date) - sub.run(["@notify-send@", notify]) + sftdate = sub.run(["@sfttime@"], stdout=sub.PIPE).stdout.strip().decode() + notify = "BAT: {percent}% {ac}{charge}| {date} | {sftdate}".format( + percent = int(bat*100), + ac = ac, + charge = "{} ".format(sft_remaining) if seconds_remaining else "", + date = date, + sftdate = sftdate + ) + print(notify) ''; in @@ -33,6 +66,7 @@ in } '' substitute ${script} script \ --replace "@python3@" "${getBin python3}/bin/python3" \ - --replace "@notify-send@" "${getBin libnotify}/bin/notify-send" + --replace "@bc@" "${getBin bc}/bin/bc" \ + --replace "@sfttime@" "${getBin sfttime}/bin/sfttime" install -D script $out/bin/display-infos '' diff --git a/pkgs/profpatsch/execline/escape.nix b/pkgs/profpatsch/execline/escape.nix new file mode 100644 index 00000000..d9a0be0c --- /dev/null +++ b/pkgs/profpatsch/execline/escape.nix @@ -0,0 +1,30 @@ +{ 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 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/run-execline-tests.nix b/pkgs/profpatsch/execline/run-execline-tests.nix index ebfdeb20..c3f534cc 100644 --- a/pkgs/profpatsch/execline/run-execline-tests.nix +++ b/pkgs/profpatsch/execline/run-execline-tests.nix @@ -2,26 +2,24 @@ # https://www.mail-archive.com/skaware@list.skarnet.org/msg01256.html , coreutils }: -# TODO: run all of these locally! runExeclineLocal - let # lol - writeScript = name: script: runExecline { - inherit name; + writeScript = name: script: runExecline name { derivationArgs = { inherit script; passAsFile = [ "script" ]; + preferLocalBuild = true; + allowSubstitutes = false; }; - execline = '' - importas -ui s scriptPath - importas -ui out out - foreground { - ${coreutils}/bin/mv $s $out - } - ${bin.s6-chmod} 0755 $out - ''; - }; + } [ + "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 ++ [ "" ]; @@ -30,7 +28,7 @@ let # in the given file. Does not use runExecline, because # that should be tested after all. fileHasLine = line: file: derivation { - name = "file-${file.name}-has-line"; + name = "run-execline-test-file-${file.name}-has-line"; inherit (stdenv) system; builder = bin.execlineIf; args = @@ -43,41 +41,49 @@ let bin.importas "-ui" "out" "out" bin.s6-touch "$out" ]; + preferLocalBuild = true; + allowSubstitutes = false; }; # basic test that touches out - basic = runExecline { - name = "basic"; - execline = '' - importas -ui out out - ${bin.s6-touch} $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 { - name = "stdin"; + stdin = fileHasLine "foo" (runExecline "run-execline-test-stdin" { stdin = "foo\nbar\nfoo"; - execline = '' - importas -ui out out + 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} - ''; - }); + "redirfd" "-w" "1" "$out" bin.s6-cat + ]); - wrapWithVar = runExecline { - name = "wrap-with-var"; + wrapWithVar = runExecline "run-execline-test-wrap-with-var" { builderWrapper = writeScript "var-wrapper" '' #!${bin.execlineb} -S0 export myvar myvalue $@ ''; - execline = '' - importas -ui v myvar - if { ${bin.s6-test} myvalue = $v } - importas out out - ${bin.s6-touch} $out - ''; - }; + derivationArgs = { + preferLocalBuild = true; + allowSubstitutes = false; + }; + } [ + "importas" "-ui" "v" "myvar" + "if" [ bin.s6-test "myvalue" "=" "$v" ] + "importas" "out" "out" + bin.s6-touch "$out" + ]; -in args: drvSeqL [ basic stdin wrapWithVar ] (runExecline args) +in [ basic stdin wrapWithVar ] diff --git a/pkgs/profpatsch/execline/run-execline.nix b/pkgs/profpatsch/execline/run-execline.nix index dbc6f4fd..2efe43d6 100644 --- a/pkgs/profpatsch/execline/run-execline.nix +++ b/pkgs/profpatsch/execline/run-execline.nix @@ -1,15 +1,18 @@ -{ stdenv, bin }: -{ name -# the execline script as string -, execline +{ stdenv, bin, lib }: +name: +{ # a string to pass as stdin to the execline script -, stdin ? "" +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; @@ -18,6 +21,7 @@ assert !derivationArgs ? builder; assert !derivationArgs ? args; derivation (derivationArgs // { + # TODO: what about cross? inherit (stdenv) system; inherit name; @@ -26,7 +30,10 @@ derivation (derivationArgs // { # to pass the script and stdin as envvar; # this might clash with another passed envar, # so we give it a long & unique name - _runExeclineScript = execline; + _runExeclineScript = + let + escape = (import ./escape.nix { inherit lib; }); + in escape.escapeExecline execline; _runExeclineStdin = stdin; passAsFile = [ "_runExeclineScript" diff --git a/pkgs/profpatsch/execline/symlink.nix b/pkgs/profpatsch/execline/symlink.nix index ca8684d2..c6a311d8 100644 --- a/pkgs/profpatsch/execline/symlink.nix +++ b/pkgs/profpatsch/execline/symlink.nix @@ -11,9 +11,7 @@ let "${toString (builtins.stringLength s)}:${s},"; in -runExecline { - inherit name; - +runExecline name { derivationArgs = { pathTuples = lib.concatMapStrings ({dest, orig}: toNetstring @@ -23,28 +21,26 @@ runExecline { # 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}'' - execline = '' - 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 - } + # 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 - } - ''; -} + "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/nman/default.nix b/pkgs/profpatsch/nman/default.nix index b9b967cf..bbf4f00f 100644 --- a/pkgs/profpatsch/nman/default.nix +++ b/pkgs/profpatsch/nman/default.nix @@ -6,7 +6,9 @@ runCommandNoCC "nman" { license = licenses.gpl3; }; } '' - ${lib.getBin go}/bin/go build -o nman ${./nman.go} + mkdir cache + env GOCACHE="$PWD/cache" \ + ${lib.getBin go}/bin/go build -o nman ${./nman.go} install -D nman $out/bin/nman '' diff --git a/pkgs/profpatsch/sfttime/default.nix b/pkgs/profpatsch/sfttime/default.nix new file mode 100644 index 00000000..29d9170b --- /dev/null +++ b/pkgs/profpatsch/sfttime/default.nix @@ -0,0 +1,14 @@ +{ stdenv, makeWrapper, bc }: + +stdenv.mkDerivation { + name = "sfttime"; + + phases = [ "installPhase" "fixupPhase" ]; + buildInputs = [ makeWrapper ]; + + installPhase = '' + install -D ${./sfttime.sh} $out/bin/sfttime + wrapProgram $out/bin/sfttime \ + --prefix PATH : ${stdenv.lib.makeBinPath [ bc ]} + ''; +} diff --git a/pkgs/profpatsch/sfttime/sfttime.sh b/pkgs/profpatsch/sfttime/sfttime.sh new file mode 100755 index 00000000..949341b7 --- /dev/null +++ b/pkgs/profpatsch/sfttime/sfttime.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# this script was created in 3BBE.81[sft]. +# usage: +# to convert to sfttime: +# $0 [c date] [digitcount] [nodate] +# if date is given, converts the given date to sfttime. +# if date is not given, converts the current date to sfttime. +# digitcount specifies the accuracy for the time part. +# nodate hides the date part. +# to convert from sfttime: +# $0 r sfttime [unix] +# converts the given sfttime to 'standard' time. +# if 'unix' is provided, the output will be in unix time. +# to show info about sfttime units +# $0 i [[sft]]$num +# displays name of unit [sft]$num, as well as it's value +# in both days and 'standard' units. + +SFT_EPOCH_UNIX=49020 + +case $1 in + "c") + unixtime=$(date --date="$2" +%s.%N) + shift + shift + mode=fw + ;; + "r") + shift + sfttime=$1 + if [[ $sfttime =~ ^([0-9A-F]*(.[0-9A-F]+)?)(\[[sS][fF][tT]\])?$ ]] && [[ $sfttime ]]; then + sfttime=${BASH_REMATCH[1]} + else + echo "error" 2>&1 + exit 1 + fi + shift + mode=bw + ;; + "i") + shift + inforeq=$1 + if [[ $inforeq =~ ^(\[[sS][fF][tT]\])?(-?[0-9]+)$ ]]; then + inforeq=${BASH_REMATCH[2]} + let inforeq=$inforeq + mode=in + elif [[ $inforeq =~ ^(\[[sS][fF][tT]\])?[eE][pP][oO][cC][hH]$ ]]; then + echo "[sft]epoch:" + echo "unix time $SFT_EPOCH_UNIX" + echo "1970-01-01 13:37:00 UTC" + exit 0 + else + echo "error" 2>&1 + exit 1 + fi + shift + mode=in + ;; + *) + unixtime=$(date +%s.%N) + mode=fw + ;; +esac + +case $mode in + "fw") + sfttime=$(echo "obase=16; ($unixtime-$SFT_EPOCH_UNIX)/86400" | bc -l) + if [[ $1 -ge 1 ]]; then + digits=$1 + shift + elif [[ ! $1 ]] || [[ $1 == nodate ]]; then + digits=3 + else + digits=0 + fi + + if [[ $sfttime =~ ^([0-9A-F]+)[.]([0-9A-F]{$digits}).*$ ]]; then + date=${BASH_REMATCH[1]} + time=${BASH_REMATCH[2]} + else + echo "Error" &1>2 + exit 1 + fi + + if [[ $digits -eq 0 ]]; then + echo "$date[sft]" + else + if [[ $1 == nodate ]]; then + echo ".$time[sft]" + shift + else + echo "$date.$time[sft]" + fi + fi + ;; + "bw") + unixtime=$(echo "ibase=16; $sfttime*15180+BF7C" | bc -l) + case $1 in + unix) + shift + echo $unixtime + ;; + *) + date --date="1970-01-01 $unixtime sec" + ;; + esac + ;; + "in") + name="[sft]$inforeq" + case $inforeq in + -4) newname="[sft]tick";; + -3) newname="[sft]tentacle";; + -2) newname="[sft]schinken";; + -1) newname="[sft]major";; + 0) newname="day";; + 1) newname="[sft]vergil";; + 2) newname="[sft]stallman";; + 3) newname="[sft]odin";; + esac + if [[ $newname ]]; then + echo "alternative name for $name: $newname" + name="$newname" + fi + one="1 $name" + echo "$one after [sft]epoch:" + sfttime=$(echo "obase=16; 16^$inforeq" | bc -l)[sft] + echo $sfttime + echo "time equivalent of $one:" + echo "the duration of $(echo 794243384928000*16^$inforeq | bc -l) periods of the radiation corresponding to the transition between the two hyperfine levels of the ground state of the caesium 133 atom" + + echo "standard time units equivalent:" + seconds=$(echo "86400*16^$inforeq" | bc -l) + if [[ $(echo "$seconds < 60" | bc -l) == 1 ]]; then + echo "$seconds seconds" + elif [[ $(echo "$seconds < 3600" | bc -l) == 1 ]]; then + echo "$(echo $seconds/60 | bc -l) minutes" + elif [[ $(echo "$seconds < 86400" | bc -l) == 1 ]]; then + echo "$(echo $seconds/3600 | bc -l) hours" + elif [[ $(echo "$seconds < 86400*365.2425" | bc -l) == 1 ]]; then + echo "$(echo $seconds/86400 | bc -l) days" + else + echo "$(echo $seconds/86400/365.2425 | bc -l) years" + fi + ;; +esac diff --git a/pkgs/profpatsch/utils-hs/default.nix b/pkgs/profpatsch/utils-hs/default.nix index 423feb03..9ddd50ec 100644 --- a/pkgs/profpatsch/utils-hs/default.nix +++ b/pkgs/profpatsch/utils-hs/default.nix @@ -53,11 +53,23 @@ let rev = "e7efbb4f0624e86109acd818942c8cd18a7d9d3d"; sha256 = "0dismb9vl5fxynasc2kv5baqyzp6gpyybmd5p9g1hlcq3p7pfi24"; }; + broken = false; buildDepends = old.buildDepends or [] ++ (with hself; [ dependent-sum prettyprinter (hlib.doJailbreak ref-tf) ]); }); - } );# // (import /home/philip/kot/dhall/overlay.nix { inherit haskell fetchFromGitHub; } hself hsuper)); + + dhall-nix = hlib.justStaticExecutables (hlib.overrideCabal hsuper.dhall-nix (old: { + src = fetchFromGitHub { + owner = "Profpatsch"; + repo = "dhall-nix"; + # manual update to dhall @0.19 + rev = "feae0ce5b2ecf4daeeae15c39f427f126c33da7c"; + sha256 = "1kdsbnj681lf65dsdclcrzj4cab1hh0v22n2140386zvwmawyp6r"; + }; + broken = false; + })); + }); }; haskellDrv = { name, subfolder, deps }: hps.mkDerivation { diff --git a/pkgs/profpatsch/xmonad/DhallTypedInput.hs b/pkgs/profpatsch/xmonad/DhallTypedInput.hs new file mode 100644 index 00000000..18c32b22 --- /dev/null +++ b/pkgs/profpatsch/xmonad/DhallTypedInput.hs @@ -0,0 +1,232 @@ +{-# language RecordWildCards, NamedFieldPuns, OverloadedStrings, ScopedTypeVariables, KindSignatures, DataKinds, ScopedTypeVariables, RankNTypes, GADTs, TypeApplications, AllowAmbiguousTypes, LambdaCase #-} +{- Exports the `inputWithTypeArgs` function, which is able to read dhall files of the normalized form + +@ +\(CustomType: Type) -> +\(AnotherType: Type) -> +… +@ + +and set their actual representation on the Haskell side: + +This has various advantages: + +- dhall files still type check & normalize with the normal dhall + tooling, they are standalone (and the types can be instantiated from + dhall as well without any workarounds) +- It can be used like the default `input` function, no injection of + custom symbols in the Normalizer is reqired +- Brings this style of dhall integration to Haskell, where it was only + feasible in nix before, because that is untyped + +The dhall types can be instantiated by every Haskell type that has an +`Interpret` instance. The “name” of the type lambda variable is +compared on the Haskell side with a type-level string that the user +provides, to prevent mixups. + +TODO: +- Improve error messages (!) +- Provide a way to re-use the type mapping on the Haskell side, so + that the returned values are not just the normal `Interpret` types, + but the mapped ones (with name phantom type) +-} +module DhallTypedInput +( inputWithTypeArgs, TypeArg(..), TypeArgError(..), TypeArgEx(..), typeArg +) +where + +import Control.Monad.Trans.State.Strict as State +import Data.List (foldl') +import Control.Exception (Exception) +import qualified Control.Exception +import qualified Data.Text as Text + +import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) +import Data.Proxy (Proxy(Proxy)) + +import Dhall (Type(..), InvalidType(..), InputSettings(..), EvaluateSettings(..), rootDirectory, startingContext, normalizer, standardVersion, sourceName, defaultEvaluateSettings, Interpret(..), auto) +import Dhall.TypeCheck (X) +import Dhall.Core +import Dhall.Parser (Src(..)) +import qualified Dhall.Import +import qualified Dhall.Pretty +import qualified Dhall.TypeCheck +import qualified Dhall.Parser + +import Lens.Family (LensLike', set, view) + +import Data.Text.Prettyprint.Doc (Pretty) +import qualified Data.Text.Prettyprint.Doc as Pretty +import qualified Data.Text.Prettyprint.Doc.Render.Text as Pretty +import qualified Data.Text.Prettyprint.Doc.Render.String as Pretty + + +-- | Information about a type argument in the dhall input +-- +-- If the dhall file starts with @\(CustomType : Type) ->@, +-- that translates to @TypeArg "CustomType" interpretionType@ +-- where @"CustomType"@ is a type-level string describing the +-- name of the type in the dhall file (as a sanity check) and +-- @interpretationType@ is any type which implements +-- 'Dhall.Interpret'. +-- +-- This is basically a specialized 'Data.Proxy'. +data TypeArg (sym :: Symbol) t = TypeArg + +-- | Existential wrapper of a 'TypeArg', allows to create a list +-- of heterogenous 'TypeArg's. +data TypeArgEx + where TypeArgEx :: (KnownSymbol sym, Interpret t) => TypeArg sym t -> TypeArgEx + +-- | Shortcut for creating a 'TypeArgEx'. +-- +-- Use with @TypeApplications@: +-- +-- @ +-- typeArg @"CustomType" @Integer +-- @ +typeArg :: forall sym t. (KnownSymbol sym, Interpret t) => TypeArgEx +typeArg = TypeArgEx (TypeArg :: TypeArg sym t) + +-- | Possible errors returned when applying a 'TypeArg' +-- to a 'Dhall.Expr'. +data TypeArgError + = WrongLabel Text.Text + -- ^ The name (label) of the type was different, + -- the text value is the expected label. + | NoLambda + -- ^ The 'Dhall.Expr' does not start with 'Dhall.Lam'. + +-- | Apply a 'TypeArg' to a 'Dhall.Expr'. +-- +-- Checks that the dhall file starts with the 'Dhall.Lam' +-- corresponding to 'TypeArg`, then applies @t@ (dhall type application) +-- and normalizes, effectively stripping the 'Dhall.Lam'. +applyTypeArg + :: forall sym t. (KnownSymbol sym, Interpret t) + => Expr Src X + -> TypeArg sym t + -> Either TypeArgError (Expr Src X) +applyTypeArg expr ta@(TypeArg) = case expr of + (Lam label (Const Dhall.Core.Type) _) + -> let expectedLabel = getLabel ta + in if label /= getLabel ta + then Left (WrongLabel expectedLabel) + else let expr' = (normalize (App expr tExpect)) + in Right expr' + where + Dhall.Type _ tExpect = Dhall.auto :: Dhall.Type t + expr -> Left NoLambda + +-- | Inflect the type-level string @sym@ to a text value. +getLabel :: forall sym t. (KnownSymbol sym) => TypeArg sym t -> Text.Text +getLabel _ = Text.pack $ symbolVal (Proxy :: (Proxy :: Symbol -> *) sym) + +instance (KnownSymbol sym) => Show (TypeArg sym t) where + show TypeArg = + "TypeArg " + ++ (symbolVal (Proxy :: (Proxy :: Symbol -> *) sym)) + +-- | Takes a list of 'TypeArg's and parses the given +-- dhall string, applying the given 'TypeArg's in order +-- to the opaque dhall type arguments (see 'TypeArg' for +-- how these should look). +-- +-- This is a slightly changed 'Dhall.inputWith'. +-- +-- Discussion: Any trace of our custom type is removed from +-- the resulting +inputWithTypeArgs + :: InputSettings + -> [TypeArgEx] + -> Dhall.Type a + -> Text.Text + -> IO a +inputWithTypeArgs settings typeArgs (Dhall.Type {extract, expected}) txt = do + expr <- throws (Dhall.Parser.exprFromText (view sourceName settings) txt) + + -- TODO: evaluateSettings not exposed + -- let evSettings = view evaluateSettings settings + let evSettings :: EvaluateSettings = defaultEvaluateSettings + + -- -vvv copied verbatim from 'Dhall.inputWith' vvv- + let transform = + set Dhall.Import.standardVersion + (view standardVersion evSettings) + . set Dhall.Import.normalizer + (view normalizer evSettings) + . set Dhall.Import.startingContext + (view startingContext evSettings) + + let status = transform (Dhall.Import.emptyStatus + (view rootDirectory settings)) + + expr' <- State.evalStateT (Dhall.Import.loadWith expr) status + -- -^^^ copied verbatim ^^^- + + let + -- | if there’s a note, run the transformation and rewrap with the note + skipNote e f = case e of + Note n e -> Note n $ f e + e -> f e + + let + -- | strip one 'TypeArg' + stripTypeArg :: Expr Src X -> TypeArgEx -> Expr Src X + stripTypeArg e (TypeArgEx ta) = skipNote e $ \e' -> case e' of + (Lam label _ _) -> + case applyTypeArg e' ta of + Right e'' -> e'' + -- TODO obvously improve error messages + Left (WrongLabel l) -> + error $ "Wrong label, should have been `" ++ Text.unpack l ++ "` but was `" ++ Text.unpack label ++ "`" + Left NoLambda -> error $ "I expected a lambda of the form λ(" ++ Text.unpack label ++ ": Type) → but got: " ++ show e + e' -> error $ show e' + + -- strip all 'TypeArg's + let expr'' = foldl' stripTypeArg expr' typeArgs + + -- -vvv copied verbatim as well (expr' -> expr'') vvv- + let suffix = prettyToStrictText expected + let annot = case expr'' of + Note (Src begin end bytes) _ -> + Note (Src begin end bytes') (Annot expr'' expected) + where + bytes' = bytes <> " : " <> suffix + _ -> + Annot expr'' expected + + _ <- throws (Dhall.TypeCheck.typeWith (view startingContext settings) annot) + case extract (Dhall.Core.normalizeWith (Dhall.Core.getReifiedNormalizer (view normalizer settings)) expr'') of + Just x -> return x + Nothing -> Control.Exception.throwIO InvalidType + + +-- copied from Dhall.Pretty.Internal +prettyToStrictText :: Pretty a => a -> Text.Text +prettyToStrictText = docToStrictText . Pretty.pretty + +-- copied from Dhall.Pretty.Internal +docToStrictText :: Pretty.Doc ann -> Text.Text +docToStrictText = Pretty.renderStrict . Pretty.layoutPretty options + where + options = Pretty.LayoutOptions { Pretty.layoutPageWidth = Pretty.Unbounded } + +-- copied from somewhere in Dhall +throws :: Exception e => Either e a -> IO a +throws (Left e) = Control.Exception.throwIO e +throws (Right r) = return r + + +-- TODO: add errors like these +-- data WrongTypeLabel = WrongTypeLabel deriving (Typeable) + +-- _ERROR :: String +-- _ERROR = "\ESC[1;31mError\ESC[0m" + +-- instance Show WrongTypeLabel where +-- show WrongTypeLabel = +-- _ERROR <> ": Mislabelled type lambda +-- \ \n\ +-- \Expected your t provide an extract function that succeeds if an expression \n\ +-- \matches the expected type. You provided a Type that disobeys this contract \n" |