{- |

Module:		Text.Nowdoc
Description:	3 QuasiQuoter: nowdoc, txtfile, binfile
Copyright:	(c) Yoshikuni Jujo, 2018
License:	BSD-3-Clause
Maintainer:	PAF01143@nifty.ne.jp

With QuasiQuotes language extension.

-}

{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

module Text.Nowdoc (nowdoc, txtfile, binfile) where

import Data.List (dropWhileEnd)
import Data.Char (isSpace)
import Language.Haskell.TH (ExpQ, stringE, runIO)
import Language.Haskell.TH.Syntax (addDependentFile)
import Language.Haskell.TH.Quote (QuasiQuoter(..))

import qualified Data.ByteString.Char8 as BSC

{- |
Simplest here document. Only two transformation.

* remove head newline if exist
* remove one space from '|', space, space, ..., ']'

@
main :: IO ()
main = putStrLn [nowdoc|hello|]
@

@
main :: IO ()
main = putStr [nowdoc|
Hello, world!
|]
@

@
main :: IO ()
main = putStr [nowdoc|
main :: IO ()
main = putStr [nowdoc|
Hello, world!
| ]
|]
@
-}

nowdoc :: QuasiQuoter
nowdoc = qq { quoteExp = stringE . unescape . \case '\n' : cs -> cs; cs -> cs }

unescape :: String -> String
unescape ('|' : cs) = case span (== ' ') cs of
        (_ : ss, ']' : cs') -> '|' : ss ++ "]" ++ unescape cs'
        (ss, cs') -> '|' : ss ++ unescape cs'
unescape (c : cs) = c : unescape cs
unescape "" = ""

{- |
QuasiQuoter txtfile incerts file contents as string without transformation.
It read file as text file (with default encoding on your system).

@
main :: IO ()
main = putStr [txtfile|foo.txt|]
@
-}

txtfile :: QuasiQuoter
txtfile = qq { quoteExp = toQ readFile }

toQ :: (FilePath -> IO String) -> FilePath -> ExpQ
toQ rf fp_ = do
        cnt <- runIO $ rf fp
        addDependentFile fp
        stringE cnt
        where fp = dropBoth isSpace fp_

{- |
QuasiQuoter binfile incerts file contents as string without transformation.
It read file as binary file.

@
main :: IO ()
main = print [binfile|foo.dat|]
@
-}

binfile :: QuasiQuoter
binfile = qq { quoteExp = toQ $ (BSC.unpack <$>) . BSC.readFile }

-- ERROR MESSAGE

qq :: QuasiQuoter
qq = QuasiQuoter {
        quoteExp = undefined,
        quotePat = const $ err "pattern",
        quoteType = const $ err "type",
        quoteDec = const $ err "declaration" }

err :: String -> a
err ctx = error $
        "You have used the `nowdoc' QuasiQoter in a " ++ ctx ++
        " context; you must only use it in an expression context"

-- TOOLS

dropBoth :: (a -> Bool) -> [a] -> [a]
dropBoth = (.) <$> dropWhile <*> dropWhileEnd