{-# language RecordWildCards #-}
{-# language OverloadedStrings #-}
module SitePipe.Pipes
  ( site
  , siteWithGlobals
  ) where


import Control.Monad.Catch as Catch
import System.Directory
import Control.Monad.Reader
import Data.Foldable
import Control.Monad.Writer
import Options.Applicative
import qualified Text.Mustache.Types as MT
import qualified Data.HashMap.Strict as HM

import SitePipe.Types

-- | Build a site generator from a set of rules embedded in a 'SiteM'.
-- Use this in your @main@ function.
--
-- > main :: IO ()
-- > main = site $ do
-- >   posts <- resourceLoader markdownReader ["posts/*.md"]
-- >   writeTemplate "templates/post.html" posts
site :: SiteM () -> IO ()
site = siteWithGlobals (MT.Object mempty)

-- | Like 'site', but allows you to pass an 'MT.Value' Object which consists
-- of an environment which is available inside your templates.
--
-- This is useful for globally providing utility functions for use in your templates.
--
-- > import qualified Text.Mustache as MT
-- > import qualified Text.Mustache.Types as MT
-- > utilityFuncs :: MT.Value
-- > utilityFuncs = MT.object
-- >   ["truncate" MT.~> MT.overText (T.take 30)
-- >   ]
-- >
-- > main :: IO ()
-- > main = siteWithGlobals utilityFuncs $ do
-- >  -- your site ...
--
-- > <!-- in your template -->
-- > {{#truncate}}
-- >   Anything inside this block will be truncated to 30 chars.
-- >   {{vars}} are interpolated before applying the function.
-- > {{/truncate}}
siteWithGlobals :: MT.Value -> SiteM () -> IO ()
siteWithGlobals globals spec = do
  settings <- execParser settingsInfo >>= adjSettings
  clean (outputDir settings)
  (result, warnings) <- runWriterT (runReaderT (Catch.try spec) settings{globalContext=globals})
  case result of
    Left err -> print (err :: SitePipeError)
    Right _ -> unless (null warnings) (traverse_ putStrLn warnings)

-- | Argument info for option parsing.
settingsInfo :: ParserInfo Settings
settingsInfo = info (settingsP <**> helper)
            ( fullDesc <>
              progDesc "Static site generator" <>
              header "SitePipe - simple static site generator")

-- | Settings parser
settingsP :: Parser Settings
settingsP = Settings <$> strOption srcD <*> strOption outputD <*> pure (MT.Object HM.empty)
  where
    srcD = mconcat [ help "The directory where site source is stored"
                   , metavar "SOURCE_DIR"
                   , short 's'
                   , value "./site"
                   , showDefault
                   ]

    outputD = mconcat [ help "Directory where site will be rendered"
                      , metavar "OUTPUT_DIR"
                      , short 'o'
                      , value "./dist"
                      , showDefault
                      ]

-- | Make given source and output dirs relative.
adjSettings :: Settings -> IO Settings
adjSettings Settings{..} = do
  outD <- makeAbsolute outputDir
  srcD <- makeAbsolute srcDir
  return Settings{outputDir=outD, srcDir=srcD, ..}

-- | Remove output directory if it exists and set up for next write.
-- This is called by 'site' automatically.
clean :: FilePath -> IO ()
clean outD = do
  putStrLn $ "Purging " ++ outD
  exists <- doesDirectoryExist outD
  when exists (removeDirectoryRecursive outD)
  createDirectoryIfMissing False outD