{-# LANGUAGE TemplateHaskell #-}
-- | Convenience quasiquoter to ease the pain working with multiline strings
module System.Directory.Layout.QQ (dedent) where

import Data.Char (isSpace)
import Data.List (intercalate)
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Language.Haskell.TH.Syntax (liftString)
import Language.Haskell.TH (Q, Exp)


-- | A handy quasiquoter to work with the multiline file contents
--
-- Strips the longest common leading spaces segment. All spacey characters are treated
-- equally. The first line is ignored if it's spaces only.
--
-- >>> :set -XQuasiQuotes
-- >>> :{
-- putStr [dedent|
--   hello
--     world
--     !
--   |]
-- :}
-- hello
--   world
--   !
dedent :: QuasiQuoter
dedent = quoter $
  liftString . intercalate "\n" . stripCommonLeadingWhitespace . dropFirst (all isSpace) . lines

dropFirst :: (a -> Bool) -> [a] -> [a]
dropFirst _ [] = []
dropFirst p (x : xs)
  | p x = xs
  | otherwise = x : xs

stripCommonLeadingWhitespace :: [String] -> [String]
stripCommonLeadingWhitespace xs = map (drop (commonLeadingWhitespace xs)) xs

commonLeadingWhitespace :: [String] -> Int
commonLeadingWhitespace = minimumOr 0 . map (length . takeWhile isSpace)

minimumOr :: Ord a => a -> [a] -> a
minimumOr n [] = n
minimumOr _ xs = minimum xs

quoter :: (String -> Q Exp) -> QuasiQuoter
quoter quote = QuasiQuoter
  { quoteExp  = quote
  , quotePat  = failure "patterns"
  , quoteType = failure "types"
  , quoteDec  = failure "declarations"
  }
 where
  failure kind =
    fail $ "this quasiquoter does not support splicing " ++ kind