diff options
Diffstat (limited to 'users/sterni/nix/string')
-rw-r--r-- | users/sterni/nix/string/default.nix | 122 | ||||
-rw-r--r-- | users/sterni/nix/string/tests/default.nix | 72 |
2 files changed, 194 insertions, 0 deletions
diff --git a/users/sterni/nix/string/default.nix b/users/sterni/nix/string/default.nix new file mode 100644 index 000000000000..852ef2538fdc --- /dev/null +++ b/users/sterni/nix/string/default.nix @@ -0,0 +1,122 @@ +{ 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 + ; +} diff --git a/users/sterni/nix/string/tests/default.nix b/users/sterni/nix/string/tests/default.nix new file mode 100644 index 000000000000..e9015e95dca4 --- /dev/null +++ b/users/sterni/nix/string/tests/default.nix @@ -0,0 +1,72 @@ +{ depot, ... }: + +let + + inherit (depot.users.sterni.nix) + string + ; + + inherit (depot.nix.runTestsuite) + it + assertEq + runTestsuite + ; + + testTakeDrop = it "tests take and drop" [ + (assertEq "take" + (string.take 5 "five and more") + "five ") + (assertEq "drop" + (string.drop 2 "coin") + "in") + (assertEq "take out of bounds" + (string.take 100 "foo") + "foo") + (assertEq "drop out of bounds" + (string.drop 42 "lol") + "") + ]; + + testIndexing = it "tests string indexing" [ + (assertEq "normal charAt" + (string.charAt 3 "helo") + "o") + (assertEq "out of bounds charAt" + (string.charAt 5 "helo") + null) + ]; + + testFinding = it "tests finding in strings" [ + (assertEq "normal charIndex" + (string.charIndex "d" "abcdefghijkl") + 3) + (assertEq "charIndex no match" + (string.charIndex "w" "zZzZzzzZZZ") + null) + ]; + + dontEval = builtins.throw "this should not get evaluated"; + + testMatch = it "tests match" [ + (assertEq "basic match usage" 42 + (string.match "answer" { + "answer" = 42; + "banana" = dontEval; + "maleur" = dontEval; + })) + ]; + + f = "f"; + testPrintf = it "prints f" [ + (assertEq "basic %s usage" "print ${f}" (string.printf "print %s" f)) + (assertEq "% escaping" "100%" (string.printf "100%%")) + ]; + +in +runTestsuite "nix.string" [ + testTakeDrop + testIndexing + testFinding + testMatch + testPrintf +] |