2021-02-22 14:32:45 +01:00
|
|
|
{ depot, lib, ... }:
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
inherit (depot.users.sterni.nix.char)
|
|
|
|
chr
|
|
|
|
ord
|
|
|
|
;
|
|
|
|
|
2021-03-04 12:29:26 +01:00
|
|
|
inherit (depot.users.sterni.nix)
|
|
|
|
int
|
|
|
|
flow
|
2021-02-22 14:32:45 +01:00
|
|
|
;
|
|
|
|
|
|
|
|
take = n: s:
|
|
|
|
builtins.substring 0 n s;
|
|
|
|
|
|
|
|
drop = n: s:
|
2021-03-04 12:29:26 +01:00
|
|
|
builtins.substring n int.maxBound s;
|
2021-02-22 14:32:45 +01:00
|
|
|
|
|
|
|
charAt = i: s:
|
|
|
|
let
|
|
|
|
r = builtins.substring i 1 s;
|
2022-01-30 17:06:58 +01:00
|
|
|
in
|
|
|
|
if r == "" then null else r;
|
2021-02-22 14:32:45 +01:00
|
|
|
|
|
|
|
charIndex = char: s:
|
|
|
|
let
|
|
|
|
len = builtins.stringLength s;
|
|
|
|
go = i:
|
2021-03-04 12:29:26 +01:00
|
|
|
flow.cond [
|
2021-02-22 14:32:45 +01:00
|
|
|
[ (i >= len) null ]
|
|
|
|
[ (charAt i s == char) i ]
|
|
|
|
[ true (go (i + 1)) ]
|
|
|
|
];
|
2022-01-30 17:06:58 +01:00
|
|
|
in
|
|
|
|
go 0;
|
2021-02-22 14:32:45 +01:00
|
|
|
|
|
|
|
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);
|
2022-01-30 17:06:58 +01:00
|
|
|
in
|
|
|
|
"${leftS}${s}${rightS}";
|
2021-02-22 14:32:45 +01:00
|
|
|
|
|
|
|
fit = { char ? " ", width, side ? "left" }: s:
|
|
|
|
let
|
|
|
|
diff = width - builtins.stringLength s;
|
|
|
|
in
|
2022-01-30 17:06:58 +01:00
|
|
|
if diff <= 0
|
|
|
|
then s
|
|
|
|
else pad { inherit char; "${side}" = diff; } s;
|
2021-02-22 14:32:45 +01:00
|
|
|
|
2021-03-03 01:57:00 +01:00
|
|
|
# pattern matching for strings only
|
|
|
|
match = val: matcher: matcher."${val}";
|
|
|
|
|
2021-09-11 21:56:05 +02:00
|
|
|
/* 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);
|
|
|
|
|
2022-01-30 17:06:58 +01:00
|
|
|
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;
|
2021-09-11 21:56:05 +02:00
|
|
|
|
|
|
|
accumulateArgs = argCount: args:
|
|
|
|
if argCount > 0
|
|
|
|
then arg: accumulateArgs (argCount - 1) (args ++ [ arg ])
|
|
|
|
else format args;
|
|
|
|
in
|
2022-01-30 17:06:58 +01:00
|
|
|
accumulateArgs argsNeeded [ ];
|
2021-09-11 21:56:05 +02:00
|
|
|
|
2022-01-30 17:06:58 +01:00
|
|
|
in
|
|
|
|
{
|
2021-02-22 14:32:45 +01:00
|
|
|
inherit
|
|
|
|
take
|
|
|
|
drop
|
|
|
|
charAt
|
|
|
|
charIndex
|
|
|
|
toBytes
|
|
|
|
fromBytes
|
|
|
|
toChars
|
|
|
|
fromChars
|
|
|
|
pad
|
|
|
|
fit
|
2021-03-03 01:57:00 +01:00
|
|
|
match
|
2021-09-11 21:56:05 +02:00
|
|
|
printf
|
2021-02-22 14:32:45 +01:00
|
|
|
;
|
|
|
|
}
|