{-# LANGUAGE ViewPatterns #-}
-- | A simplistic templating engine, used for generating profiling reports.
module General.Template(runTemplate) where
import System.FilePath.Posix
import Control.Exception.Extra
import Data.Char
import Data.Time
import System.IO.Unsafe
import General.Paths
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Language.Javascript.DGTable as DGTable
import qualified Language.Javascript.Flot as Flot
import qualified Language.Javascript.JQuery as JQuery
libraries =
[("jquery.js", JQuery.file)
,("jquery.dgtable.js", DGTable.file)
,("jquery.flot.js", Flot.file Flot.Flot)
,("jquery.flot.stack.js", Flot.file Flot.FlotStack)
]
-- | Template Engine. Perform the following replacements on a line basis:
--
-- * ==>
--
-- * ==>
runTemplate :: (FilePath -> IO LBS.ByteString) -> LBS.ByteString -> IO LBS.ByteString
runTemplate ask = lbsMapLinesIO f
where
link = LBS.pack "\n" `LBS.append` res `LBS.append` LBS.pack "\n"
| Just file <- LBS.stripPrefix link y = do res <- grab file; pure $ LBS.pack ""
| otherwise = pure x
where
y = LBS.dropWhile isSpace x
grab = asker . takeWhile (/= '\"') . LBS.unpack
asker o@(splitFileName -> ("lib/",x)) = case lookup x libraries of
Just act -> LBS.readFile =<< act
Nothing -> errorIO $ "Template library, unknown library: " ++ o
asker "rattle.js" = readDataFileHTML "rattle.js"
asker "data/metadata.js" = do
time <- getCurrentTime
pure $ LBS.pack $
"var version = " ++ show rattleVersionString ++
"\nvar generated = " ++ show (formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")) time)
asker x = ask x
-- Perform a mapM on each line and put the result back together again
lbsMapLinesIO :: (LBS.ByteString -> IO LBS.ByteString) -> LBS.ByteString -> IO LBS.ByteString
-- If we do the obvious @fmap LBS.unlines . mapM f@ then all the monadic actions are run on all the lines
-- before it starts producing the lazy result, killing streaming and having more stack usage.
-- The real solution (albeit with too many dependencies for something small) is a streaming library,
-- but a little bit of unsafePerformIO does the trick too.
lbsMapLinesIO f = pure . LBS.unlines . map (unsafePerformIO . f) . LBS.lines