summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--lib/default.nix4
-rw-r--r--lib/tests/misc.nix25
-rw-r--r--lib/trivial.nix37
3 files changed, 64 insertions, 2 deletions
diff --git a/lib/default.nix b/lib/default.nix
index f293a1defb1..74d508ec1d6 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -57,8 +57,8 @@ let
       hasAttr head isAttrs isBool isInt isList isString length
       lessThan listToAttrs pathExists readFile replaceStrings seq
       stringLength sub substring tail;
-    inherit (trivial) id const concat or and bitAnd bitOr bitXor bitNot
-      boolToString mergeAttrs flip mapNullable inNixShell min max
+    inherit (trivial) id const pipe concat or and bitAnd bitOr bitXor
+      bitNot boolToString mergeAttrs flip mapNullable inNixShell min max
       importJSON warn info showWarnings nixpkgsVersion version mod compare
       splitByAndCompare functionArgs setFunctionArgs isFunction;
     inherit (fixedPoints) fix fix' converge extends composeExtensions
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index e5d76d4e57b..b064faa1e1b 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -18,6 +18,31 @@ runTests {
     expected = 2;
   };
 
+  testPipe = {
+    expr = pipe 2 [
+      (x: x + 2) # 2 + 2 = 4
+      (x: x * 2) # 4 * 2 = 8
+    ];
+    expected = 8;
+  };
+
+  testPipeEmpty = {
+    expr = pipe 2 [];
+    expected = 2;
+  };
+
+  testPipeStrings = {
+    expr = pipe [ 3 4 ] [
+      (map toString)
+      (map (s: s + "\n"))
+      concatStrings
+    ];
+    expected = ''
+      3
+      4
+    '';
+  };
+
   /*
   testOr = {
     expr = or true false;
diff --git a/lib/trivial.nix b/lib/trivial.nix
index 54c66cfce7b..3a25e31fb05 100644
--- a/lib/trivial.nix
+++ b/lib/trivial.nix
@@ -29,6 +29,43 @@ rec {
     # Value to ignore
     y: x;
 
+  /* Pipes a value through a list of functions, left to right.
+
+     Type: pipe :: a -> [<functions>] -> <return type of last function>
+     Example:
+       pipe 2 [
+         (x: x + 2)  # 2 + 2 = 4
+         (x: x * 2)  # 4 * 2 = 8
+       ]
+       => 8
+
+       # ideal to do text transformations
+       pipe [ "a/b" "a/c" ] [
+
+         # create the cp command
+         (map (file: ''cp "${src}/${file}" $out\n''))
+
+         # concatenate all commands into one string
+         lib.concatStrings
+
+         # make that string into a nix derivation
+         (pkgs.runCommand "copy-to-out" {})
+
+       ]
+       => <drv which copies all files to $out>
+
+     The output type of each function has to be the input type
+     of the next function, and the last function returns the
+     final value.
+  */
+  pipe = val: functions:
+    let reverseApply = x: f: f x;
+    in builtins.foldl' reverseApply val functions;
+  /* note please don’t add a function like `compose = flip pipe`.
+     This would confuse users, because the order of the functions
+     in the list is not clear. With pipe, it’s obvious that it
+     goes first-to-last. With `compose`, not so much.
+  */
 
   ## Named versions corresponding to some builtin operators.