diff --git a/deferred-folds.cabal b/deferred-folds.cabal
--- a/deferred-folds.cabal
+++ b/deferred-folds.cabal
@@ -1,5 +1,5 @@
 name: deferred-folds
-version: 0.9.13
+version: 0.9.14
 category: Folding
 synopsis: Abstractions over deferred folds
 description:
diff --git a/library/DeferredFolds/Defs/Unfoldr.hs b/library/DeferredFolds/Defs/Unfoldr.hs
--- a/library/DeferredFolds/Defs/Unfoldr.hs
+++ b/library/DeferredFolds/Defs/Unfoldr.hs
@@ -387,3 +387,36 @@
   where
     chunk startOffset afterEndOffset =
       TextInternal.Text arr startOffset (afterEndOffset - startOffset)
+
+{-|
+Transformer of chars,
+replaces all space-like chars with space,
+all newline-like chars with @\\n@,
+and trims their duplicate sequences to single-char.
+Oh yeah, it also trims whitespace from beginning and end.
+-}
+trimWhitespace :: Foldable f => f Char -> Unfoldr Char
+trimWhitespace =
+  \ foldable ->
+    Unfoldr $ \ substep subterm ->
+      foldr (step substep) (finalize subterm) foldable False False False
+  where
+    step substep char next notFirst spacePending newlinePending =
+      if isSpace char
+        then if char == '\n' || char == '\r'
+          then next notFirst False True
+          else next notFirst True newlinePending
+        else
+          let
+            mapper =
+              if notFirst
+                then if newlinePending
+                  then substep '\n'
+                  else if spacePending
+                    then substep ' '
+                    else id
+                else id
+            in
+              mapper $ substep char $ next True False False
+    finalize subterm notFirst spacePending newlinePending =
+      subterm
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -37,4 +37,42 @@
     testProperty "textWords" $ \ (text :: Text) ->
     Text.words text ===
     toList (Unfoldr.textWords text)
+    ,
+    testProperty "trimWhitespace 1" $ \ (text :: Text) ->
+    let
+      words =
+        Text.words text
+      run =
+        fromString . toList . Unfoldr.trimWhitespace . Unfoldr.textChars
+      spacedInput =
+        Text.map (\ c -> if isSpace c then ' ' else c) text
+      newlinedInput =
+        Text.map (\ c -> if isSpace c then '\n' else c) text
+      in
+        Text.unwords words === run spacedInput .&&.
+        Text.intercalate "\n" words === run newlinedInput
+    ,
+    testProperty "trimWhitespace 2" $ \ (text :: Text) ->
+    let
+      isNewline c =
+        c == '\n' || c == '\r'
+      isSpaceButNotNewline c =
+        isSpace c && not (isNewline c)
+      normalize separator condition =
+        Text.split condition >>>
+        filter (not . Text.null) >>>
+        Text.intercalate separator
+      expected =
+        text &
+        Text.split isNewline &
+        fmap Text.strip &
+        filter (not . Text.null) &
+        Text.intercalate "\n" &
+        Text.split isSpaceButNotNewline &
+        filter (not . Text.null) &
+        Text.intercalate " "
+      run =
+        fromString . toList . Unfoldr.trimWhitespace . Unfoldr.textChars
+      in
+        expected === run text
   ]
