about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsterni <sternenseemann@systemli.org>2021-09-11T19·56+0200
committersterni <sternenseemann@systemli.org>2021-09-12T09·25+0000
commite507b842918f80340257304bd9541cb3b3abc9da (patch)
tree62537407066e338491b489be3c54a40f21d77f27
parent2f750e4a14be68f275f6fe23995eb9a994e0f5de (diff)
feat(users/sterni/nix/string): very simple printf implementation r/2851
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>
-rw-r--r--users/sterni/nix/string/default.nix38
-rw-r--r--users/sterni/nix/string/tests/default.nix7
2 files changed, 45 insertions, 0 deletions
diff --git a/users/sterni/nix/string/default.nix b/users/sterni/nix/string/default.nix
index 3fe7c04618..19d2cec243 100644
--- a/users/sterni/nix/string/default.nix
+++ b/users/sterni/nix/string/default.nix
@@ -59,6 +59,43 @@ let
   # 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
@@ -72,5 +109,6 @@ in {
     pad
     fit
     match
+    printf
     ;
 }
diff --git a/users/sterni/nix/string/tests/default.nix b/users/sterni/nix/string/tests/default.nix
index 2caecbfa7b..c8aec94640 100644
--- a/users/sterni/nix/string/tests/default.nix
+++ b/users/sterni/nix/string/tests/default.nix
@@ -56,10 +56,17 @@ let
       }))
   ];
 
+  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
   ]