packages feed

kontrakcja-templates-0.1: src/Text/StringTemplates/Templates.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Templates.Templates
-- Maintainer  :  bartek@skrivapa.se
-- Stability   :  development
-- Portability :  portable
--
-- This is main templating module. It provides basic interface for
-- generation templates (RenderTemplate) with 'renderTemplate'
-- function. We also provide types for templates and few functions for
-- loading and testing
--
-- HOW TO USE TEMPLATING SYSTEM
--
-- 1) There is a folder called templates, All templates files (*.st) are there. Each files contains many templates
--    definitions, and there must be line starting with # between each two templates.
--    Template definition has form 'nameOfTemplate=bodyOfTemplate'.
--
-- 2) Template body is just String Template and you should be able to find more
--    info at http://www.haskell.org/haskellwiki/HStringTemplate
--
-- 3) All templates are in a global scope. Watch out for conflicting names.
--    On dev computers they are loaded on every request, so one can change them without stoping server.
--
-- 4) To generate a template in haskell call renderTemplate.
--       First param is a set of all templates. Usually you can get it from 'Context'.
--       Next is a name of template that You wan't to render.
--       Last one is some for of list of params.
--       As a result you get IO String.
--        If template will fail You will get error info inside. But this is only for syntax errors.
--        If You will forget a param there will be info in log, and template set param value to something empty.
--
--
-- FIELDS
--
-- Current policy is to use fields. You can find usage of
-- [(String,String)] as params and also composition of setAttribute
-- functions in a code.  This are old concepts and will be droped at
-- some point.
--
-- How to user fields:
--  - there is one function ('field') that sets one field
--  - fields form a monad so you can use do notation for setting many fields
--  - value of a field can be almoust everything (String, Int, Maybe, List, Map, types that are instances of Data and Typeable etc)
--  - IO wrapped values and fields can be also a values of a field.
--
-- Example
--
-- >      userView tempates user =
-- >        renderTemplate templates "userView" $ do
-- >          userFields
-- >
-- >      userFields user = do
-- >        field "name" $ username user
-- >        field "company" $ usercompany user
-- >        field "documents" $ map (documentFields) getUserDocumentsFromDB
-- >
-- >      documentFields document = do
-- >        field "id" $ documentid document
-- >        field "title" $ documenttitle document
--
--
-- Why we want to use fields
--      - They force reuse. We write documentFields, and reuse it every time we want to pass document info to template.
--      - Fields can be extended. If I want to have extended info about user I use 'userFields' to set basic info and
--        then add advanced fields
--      - No need to first bind from IO, then pass to template
--      - They support advanced structures like lists and maybe's
--
--
-- Some extra info:
--  In templates use maybe. You can use 'if' in template body to check for Nothing
--  Always change ByteString to String. We have a problems with encoding, so please watch for this.
--
-- Please also see example.hs for a running example
-----------------------------------------------------------------------------
module Text.StringTemplates.Templates ( Fields
                                      , runFields
                                      , TemplatesMonad(..)
                                      , renderTemplate
                                      , renderTemplate_
                                      , renderTemplateI
                                      , TemplatesT(..)
                                      , runTemplatesT
                                      , renderHelper
                                      ) where

import Control.Monad.Trans.Maybe
import Text.StringTemplate.Base hiding (ToSElem, toSElem, render)
import Text.StringTemplates.TemplatesLoader

import Text.StringTemplates.Fields
import Control.Applicative
import Control.Monad.Reader
import Control.Monad.Identity
import Control.Monad.Error

-- | simple reader monad class that provides access to templates
class (Functor m, Monad m) => TemplatesMonad m where
  getTemplates      :: m Templates -- ^ get templates (for text templates default column name is used)
  getTextTemplatesByColumn :: String -> m Templates -- ^ get templates (for text templates specified column name is used)

instance TemplatesMonad m => TemplatesMonad (MaybeT m) where
  getTemplates = lift getTemplates
  getTextTemplatesByColumn = lift . getTextTemplatesByColumn

instance (TemplatesMonad m , Error e) => TemplatesMonad (ErrorT e m) where
  getTemplates = lift getTemplates
  getTextTemplatesByColumn = lift . getTextTemplatesByColumn

-- | renders a template by name
renderTemplate :: TemplatesMonad m =>
                 String     -- ^ template name
               -> Fields m () -- ^ template params
               -> m String
renderTemplate name fields = do
  ts <- getTemplates
  renderHelper ts name fields

-- | renders a template by name (params function cannot use side effects)
renderTemplateI :: TemplatesMonad m =>
                  String            -- ^ template name
                -> Fields Identity () -- ^ template params
                -> m String
renderTemplateI name fields = do
  ts <- getTemplates
  return $ renderTemplateMain ts name ([]::[(String, String)]) (setManyAttrib $ runIdentity $ runFields fields)

-- | renders a template by name without any params
renderTemplate_ :: TemplatesMonad m =>
                  String -- ^ template name
                -> m String
renderTemplate_ name = renderTemplate name $ return ()

renderHelper :: Monad m => Templates -> String -> Fields m () -> m String
renderHelper ts name fields = do
  attrs <- runFields fields
  return $ renderTemplateMain ts name ([]::[(String, String)]) (setManyAttrib attrs)

-- | Simple implementation of TemplatesMonad
newtype TemplatesT m a = TemplatesT { unTT :: ReaderT (String, GlobalTemplates) m a }
    deriving (Applicative, Functor, Monad, MonadIO, MonadTrans)

runTemplatesT :: (Functor m, Monad m) =>
                (String, GlobalTemplates) -- ^ (default column name, global templates)
              -> TemplatesT m a -> m a
runTemplatesT ts action = runReaderT (unTT action) ts

instance (Functor m, Monad m) => TemplatesMonad (TemplatesT m) where
  getTemplates = TemplatesT $ do
    (column, ts) <- ask
    return $ localizedVersion column ts
  getTextTemplatesByColumn column = TemplatesT $ do
    (_, ts) <- ask
    return $ localizedVersion column ts