tvl-depot/users/sterni/nix/string/default.nix
sterni e507b84291 feat(users/sterni/nix/string): very simple printf implementation
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>
2021-09-12 09:25:30 +00:00

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
;
}