interpolate-0.1.0: src/Data/String/Interpolate/Util.hs
module Data.String.Interpolate.Util (unindent) where
import Control.Arrow ((>>>))
import Data.Char
-- | Remove indentation as much as possible while preserving relative
-- indentation levels.
--
-- `unindent` is useful in combination with `Data.String.Interpolate.i` to remove leading spaces that
-- resulted from code indentation. That way you can freely indent your string
-- literals without the indentation ending up in the resulting strings.
--
-- Here is an example:
--
-- >>> :set -XQuasiQuotes
-- >>> import Data.String.Interpolate
-- >>> import Data.String.Interpolate.Util
-- >>> :{
-- putStr $ unindent [i|
-- def foo
-- 23
-- end
-- |]
-- :}
-- def foo
-- 23
-- end
--
-- To allow this, two additional things are being done, apart from removing
-- indentation:
--
-- - One empty line at the beginning will be removed and
-- - if the last newline character (@"\\n"@) is followed by spaces, the spaces are removed.
unindent :: String -> String
unindent =
lines_
>>> removeLeadingEmptyLine
>>> trimLastLine
>>> removeIndentation
>>> concat
where
isEmptyLine :: String -> Bool
isEmptyLine = all isSpace
lines_ :: String -> [String]
lines_ [] = []
lines_ s = case span (/= '\n') s of
(first, '\n' : rest) -> (first ++ "\n") : lines_ rest
(first, rest) -> first : lines_ rest
removeLeadingEmptyLine :: [String] -> [String]
removeLeadingEmptyLine xs = case xs of
y:ys | isEmptyLine y -> ys
_ -> xs
trimLastLine :: [String] -> [String]
trimLastLine (a : b : r) = a : trimLastLine (b : r)
trimLastLine [a] = if all (== ' ') a
then []
else [a]
trimLastLine [] = []
removeIndentation :: [String] -> [String]
removeIndentation ys = map (dropSpaces indentation) ys
where
dropSpaces 0 s = s
dropSpaces n (' ' : r) = dropSpaces (n - 1) r
dropSpaces _ s = s
indentation = minimalIndentation ys
minimalIndentation =
safeMinimum 0
. map (length . takeWhile (== ' '))
. removeEmptyLines
removeEmptyLines = filter (not . isEmptyLine)
safeMinimum :: Ord a => a -> [a] -> a
safeMinimum x xs = case xs of
[] -> x
_ -> minimum xs