{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
module Hearth (renderPage, handleForm) where
import Text.Ginger.Parse (parseGingerFile, SourcePos)
import Text.Ginger.Run (runGinger, makeContextHtml, Run)
import Text.Ginger.Html (htmlSource, Html)
import Text.Ginger.GVal as V (toGVal, orderedDict, (~>), GVal)
import Control.Monad.Writer.Lazy (Writer)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as Txt
import qualified Data.Text.Encoding as Txt
import Data.FileEmbed
import System.FilePath (normalise, (</>))
import qualified Data.Aeson as JS
import Data.Maybe (fromMaybe)
import Debug.Trace (traceShow) -- For error reporting!
import Hearth.TopSites
import Network.URI (uriToString)
renderPage :: ByteString -> [(ByteString, Maybe ByteString)] -> [Txt.Text] -> [Link]
-> Maybe Txt.Text
renderPage path query langs hist = case parseGingerFile resolveSource $ utf8 path of
Just (Right tpl) -> do
Just $ htmlSource $ flip runGinger tpl $ makeContextHtml ctxt
Just (Left err) -> traceShow err Nothing
Nothing -> Nothing
where
ctxt :: Txt.Text -> GVal (Run SourcePos (Writer Html) Html)
ctxt "Q" = orderedDict [utf8' k~>v | (k, v) <- query]
ctxt "_" = toGVal $ translations langs
ctxt "tops" = list' [hist2gval entry | entry <- take 20 $ topsites hist]
ctxt "hist" = list' $ map hist2gval $ siteHistory hist
ctxt _ = toGVal ()
hist2gval entry@(label, href, time) = orderedDict [
"label"~>label,
"href"~>uriToString id href "",
"time"~>time,
"count"~>countVisits entry hist]
resolveSource :: FilePath -> Maybe (Maybe [Char])
resolveSource path
| ret@(Just _) <- resolveSource' path = Just ret
| ret@(Just _) <- resolveSource' (path </> "index.html") = Just ret
| otherwise = Nothing
resolveSource' :: FilePath -> Maybe [Char]
resolveSource' = fmap utf8 .
flip lookup $(makeRelativeToProject "tpl" >>= embedDir) . tail . normalise
-- | Convert from UTF-8 bytestring to a string.
utf8 :: ByteString -> String
utf8 = Txt.unpack . Txt.decodeUtf8
utf8' :: ByteString -> Txt.Text
utf8' = Txt.decodeUtf8
translations :: [Txt.Text] -> JS.Value
translations (lang:langs)
| Just file <- lookup (Txt.unpack lang ++ ".json") files,
Just ret <- JS.decode $ LBS.fromStrict file = ret
| "-" `Txt.isInfixOf` lang = let (lang', _) = Txt.breakOn "-" lang
in translations (lang':langs)
| otherwise = translations langs
where files = $(makeRelativeToProject "i18n" >>= embedDir)
translations [] = JS.Null
-- | Type-constrained conversion of a list to Ginger's datamodel,
-- serves to avoid type-inference issues.
list' :: [GVal m] -> GVal m
list' = toGVal
------
--- Interactive features
------
handleForm :: ByteString -> [(ByteString, ByteString)] -> IO ByteString
handleForm "/" query = return $ fromMaybe "/" $ lookup "url" query
handleForm "/index.html" query = return $ fromMaybe "/" $ lookup "url" query
handleForm path _ = return path