e507b84291
This is mostly to yet another silly idea which turns out to be possible. This may be actually useful should I implement more sophisticated format specifiers like "%xd" or "%f". Change-Id: Ia56cd6f5793a09fe5e19c91a8e8f9098f3244d57 Reviewed-on: https://cl.tvl.fyi/c/depot/+/3537 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
114 lines
2.6 KiB
Nix
114 lines
2.6 KiB
Nix
{ depot, lib, ... }:
|
|
|
|
let
|
|
|
|
inherit (depot.users.sterni.nix.char)
|
|
chr
|
|
ord
|
|
;
|
|
|
|
inherit (depot.users.sterni.nix)
|
|
int
|
|
flow
|
|
;
|
|
|
|
take = n: s:
|
|
builtins.substring 0 n s;
|
|
|
|
drop = n: s:
|
|
builtins.substring n int.maxBound s;
|
|
|
|
charAt = i: s:
|
|
let
|
|
r = builtins.substring i 1 s;
|
|
in if r == "" then null else r;
|
|
|
|
charIndex = char: s:
|
|
let
|
|
len = builtins.stringLength s;
|
|
go = i:
|
|
flow.cond [
|
|
[ (i >= len) null ]
|
|
[ (charAt i s == char) i ]
|
|
[ true (go (i + 1)) ]
|
|
];
|
|
in go 0;
|
|
|
|
toChars = lib.stringToCharacters;
|
|
fromChars = lib.concatStrings;
|
|
|
|
toBytes = str:
|
|
builtins.map ord (toChars str);
|
|
|
|
fromBytes = is: lib.concatMapStrings chr is;
|
|
|
|
pad = { left ? 0, right ? 0, char ? " " }: s:
|
|
let
|
|
leftS = fromChars (builtins.genList (_: char) left);
|
|
rightS = fromChars (builtins.genList (_: char) right);
|
|
in "${leftS}${s}${rightS}";
|
|
|
|
fit = { char ? " ", width, side ? "left" }: s:
|
|
let
|
|
diff = width - builtins.stringLength s;
|
|
in
|
|
if diff <= 0
|
|
then s
|
|
else pad { inherit char; "${side}" = diff; } s;
|
|
|
|
# pattern matching for strings only
|
|
match = val: matcher: matcher."${val}";
|
|
|
|
/* Bare-bones printf implementation. Supported format specifiers:
|
|
|
|
* `%%` escapes `%`
|
|
* `%s` is substituted by a string
|
|
|
|
As expected, the first argument is a format string and the values
|
|
for its format specifiers need to provided as the next arguments
|
|
in order.
|
|
|
|
Type: string -> (printfVal : either string (a -> printfVal))
|
|
*/
|
|
printf = formatString:
|
|
let
|
|
specifierWithArg = token: builtins.elem token [
|
|
"%s"
|
|
];
|
|
isSpecifier = lib.hasPrefix "%";
|
|
|
|
tokens = lib.flatten (builtins.split "(%.)" formatString);
|
|
argsNeeded = builtins.length (builtins.filter specifierWithArg tokens);
|
|
|
|
format = args: (builtins.foldl' ({ out ? "", argIndex ? 0 }: token: {
|
|
argIndex = argIndex + (if specifierWithArg token then 1 else 0);
|
|
out =
|
|
/**/ if token == "%s" then out + builtins.elemAt args argIndex
|
|
else if token == "%%" then out + "%"
|
|
else if isSpecifier token then throw "Unsupported format specifier ${token}"
|
|
else out + token;
|
|
}) {} tokens).out;
|
|
|
|
accumulateArgs = argCount: args:
|
|
if argCount > 0
|
|
then arg: accumulateArgs (argCount - 1) (args ++ [ arg ])
|
|
else format args;
|
|
in
|
|
accumulateArgs argsNeeded [];
|
|
|
|
in {
|
|
inherit
|
|
take
|
|
drop
|
|
charAt
|
|
charIndex
|
|
toBytes
|
|
fromBytes
|
|
toChars
|
|
fromChars
|
|
pad
|
|
fit
|
|
match
|
|
printf
|
|
;
|
|
}
|