packages feed

Hawk-0.0.2: src/Hawk/View/Template/DataType.hs

{-# LANGUAGE Arrows, NoMonomorphismRestriction, TemplateHaskell #-}
-- --------------------------------------------------------------------------
{- |
   Module      :  $Header$
   Copyright   :  Copyright (C) 2009-2010 Björn Peemöller, Stefan Roggensack
   License     :  BSD3

   Maintainer  :  {inf6254, inf6509}fh-wedel.de
   Stability   :  experimental
   Portability :  portable

-}
-- --------------------------------------------------------------------------
module Hawk.View.Template.DataType
    ( viewDataType
    , viewDataTypeWithPrefix
    , Bindable(..)
    , showData)
    where

import Text.XML.HXT.Arrow hiding (mkName)
import Language.Haskell.TH
import Hawk.Controller.Util.Text (firstLower, firstUpper, toCamelCase)
import Hawk.View.Template.Interpreter
import Hawk.View.Template.ToXhtml

import Control.Monad ((>=>), liftM, filterM)
import Data.Char (isAlpha)
import Text.Regex.Posix (getAllTextSubmatches, (=~))
import Data.EitherMapTree

-- | A Class which allow to write a custom function to create a BindTree
class Bindable a where
    bindable :: a -> BindTree

-- | The path of the templates
baseDir :: String
baseDir = "App/template/" --TODO make use of config

-- | Check if the node is for Hawk
isHawk :: (ArrowXml a) => a XmlTree XmlTree
isHawk = hasNamePrefix hawkPrefix -- hasNamespaceUri "http://fh-wedel.de/hawk"

-- | Calculate the function name of bind function for this File
functionName :: File -> Name
functionName = mkName . (++) "bind" . toTypeName . name2

-- | Calculate the typeNam name of bind function for this File
typeName :: File -> Name
typeName = mkName . toTypeName . name2

-- | Convert a String to type name (all . are converted to CamelCase and the first char is convertet
-- to upper)
toTypeName :: String -> String
toTypeName = firstUpper . toCamelCase '.'

-- | Calculate the Type of field of the data
dataConstructor :: Bind -> TypeQ
dataConstructor (Bind _ _ Nothing _) = [t| [XmlTree] |]
dataConstructor (Bind _ _ (Just t) _) = conT t
dataConstructor (Embed _ m n _) = appT listT $ conT $ mkName $ (++) m $ firstUpper $ toCamelCase '.' n

-- | Calculate the Name of field of the data
constructorName :: Bind -> Name
constructorName (Bind p n _ _) = mkName $ firstLower (p ++ firstUpper (toCamelCase '.' n))
constructorName (Embed p m n _) = mkName $ firstLower (p ++ firstUpper (toCamelCase '.' (m ++ firstUpper n)))

-- | Calculate the Name from a String
toTypeContructor :: String -> Name
toTypeContructor = mkName . firstLower . toCamelCase '.'

-- | Drop all not alpha (isAlpha) chars of a String
firstToAlpha :: String -> String
firstToAlpha = dropWhile $ not . isAlpha

-- | A data structure that represenst a Bind or a embed Tag
data Bind = Bind {
    prefix :: String,
    name :: String, -- ^ the name
    typeInfo :: Maybe Name, -- ^ the type info
    formatFunc :: Maybe Name -- ^ the format function
    } |
    Embed {
    prefix :: String,
    moduleName :: String,
    name :: String,
    what :: String
    }
    deriving (Show)

-- | A data structure that represents a File with a list of binds and a name
data File = File {
    name2 :: String,
    binds :: [Bind]
    }
    deriving (Show)

-- | Create a embed Data from the strings of a embed tag
embed :: String -- ^ the Prefix
    -> String -- ^ the current Module
    -> String -- ^ the value of the what attribut
    -> Bind
embed p m w = Embed p m' n w
    where
    (m', n) = parseWhat m w

-- | Parse the what attribut of a embed tag
parseWhat :: String -> String -> (String, String)
parseWhat modu s = if null withMod then (modu, fileWithout) else (withMod !! 1, withMod !! 2)
    where
    withMod = getAllTextSubmatches $ s =~ "../(.*)/(.*).xhtml" :: [String]
    [_, fileWithout] = getAllTextSubmatches $ s =~ "(.*).xhtml"

-- | Create the Bin data
bindAttr :: String -- ^ The Prefix
    -> String -- ^ The Name
    -> String -- ^ The value of the type attribute
    -> String -- ^ The value of the format attribute
    -> Bind
bindAttr p nameAtr typeAtr format = Bind p nameAtr
    (toMaybe' typeAtr >>= Just . mkName . firstUpper)
    (toMaybe' format >>= Just . mkName)

-- | Creates a DataType for a Xhtml Template
viewDataType :: String -- ^ The module of the Template
    -> String -- ^ The Name of the Template without the file extension
    -> Q [Dec]
viewDataType = viewDataTypeWithPrefix ""

-- | Creates a DataType for a Xhtml Template with a Prefix. This could be used to prevent
-- name clashes
viewDataTypeWithPrefix :: String -> String -> String -> Q [Dec]
viewDataTypeWithPrefix pre modu = runIO . readTree pre modu >=> (sequence . buildPair)

-- | Create the data and the instacne declaration for a File
buildPair :: File -> [DecQ]
buildPair f = [buildInstance f, buildData f]

-- | create the Data declaration for a File
buildData :: File -> DecQ
buildData s@(File _ l) =
    makeData name' [(name', map (constructorName &&& dataConstructor) l)]
    where
        name' = typeName s

-- | Create the instance declaration for the Bindable class
buildInstance :: File -> DecQ
buildInstance f = do
    x <- newName "x"
    instanceD (cxt [])
        (appT
            (conT ''Bindable)
            (conT $ typeName f))
        [funD 'bindable
            [clause
                [varP x]
                (normalB (appE
                    (varE 'Data.EitherMapTree.fromList)
                    (listE (map (makeTerm x) (binds f)))))
                []
             ]
         ]

-- | Create the Term for converting the Data into the BindTree
makeTerm :: Name -> Bind -> ExpQ
makeTerm x b@(Bind _ n _ f) = singleTerm f
    where
        singleTerm Nothing = [| ($(litE $ stringL n), Right $ toXhtml ($(varE $ constructorName b) $(varE x))) |]
        -- Use $(litE $ stringL n) insteed of n for readablety in pprint output
        singleTerm (Just f') = [| ($(litE $ stringL n), Right $ $(varE f') ($(varE $ constructorName b) $(varE x))) |]
makeTerm x e@(Embed _ m n w) = deeper
    where
        deeper = [| ($(litE $ stringL w), Left $
            map $(varE 'bindable)
                $(appE
                    (varE $ constructorName e)
                    (varE x))) |]

-- | Create a Data declaration
makeData :: Name                       -- ^ the name of the new Data
          -> [(Name, [(Name, TypeQ)])] -- ^ the Constructors and the containing names and types
          -> DecQ                      -- ^ the data type
makeData name' members = dataD (cxt []) name' [] (map construct members) [] -- derivingShow
    where
        cons = map (\(x,y) -> varStrictType x (strictType notStrict y))
        construct (n, m) = recC n (cons m)

-- | Convert a list maybe list which is not empty. So [] gives Nothing and [a] gives Just [a]
toMaybe' :: [a] -> Maybe [a]
toMaybe' x = toMaybe (not $ null x) x

-- * Build Tree

-- | Read a File and return the tree of a relevant Tags
readTree :: String -> String -> String -> IO File
readTree pre modu name' = liftM head (
    runX $
        constA (baseDir ++ modu ++ "/" ++ name' ++ ".xhtml")
        >>> prepareDoc' >>> makeTree pre modu name') >>= filterEmbed

-- | filter the embed for empty files. If the embeded file contain no defniton of embed or bind
-- it will remved from the file.
filterEmbed :: File -> IO File
filterEmbed s@(File _ b) = do
    b' <- filterM isNotEmtpy b
    return s {binds = b'}

-- | Check if the Embeded file contain a embed or bind tag. For a Bind alway True is returned.
isNotEmtpy :: Bind -> IO Bool
isNotEmtpy (Embed _ m n _) =
  liftM (not . null) $ runX $
    constA (baseDir ++ m ++ "/" ++ n ++ ".xhtml")
    >>> prepareDoc'
    >>> deep
        (isHawk >>>
            (hasLocalPart "bind" `orElse` hasLocalPart "embed"))
isNotEmtpy (Bind _ _ _ _) = return True


-- | Arrow for converting the XmlTree into the File data
makeTree :: (ArrowXml a, ArrowChoice a) => String -> String -> String -> a XmlTree File
makeTree pre modu name' = listA (makeHawk pre modu) >>> arr (File (modu ++ firstUpper name'))

-- | Arrow that filter the hawk Tags bind and embed and put them into buildFile
makeHawk :: (ArrowXml a, ArrowChoice a) => String -> String -> a XmlTree Bind
makeHawk pre modu = proc s -> do
    c <- getChildren -< s
    b <- deep (isHawk >>> (hasLocalPart "bind" `orElse` hasLocalPart "embed")) -< c
    buildFile pre modu -< b

-- | run buildBind or buildEmbed for the Xml tag
buildFile :: (ArrowXml a) => String -> String -> a XmlTree Bind
buildFile pre modu = choiceA
    [ hasLocalPart "bind"  :-> buildBind pre
    , hasLocalPart "embed" :-> buildEmbed pre modu]

-- | Convert a hawk bind tag into the Bind Data
buildBind :: (ArrowXml a) => String -> a XmlTree Bind
buildBind pre = (getAttrValue0 "name"
        &&& getAttrValue "type")
        &&& getAttrValue "format" >>> arr (uncurry (uncurry (bindAttr pre)))

-- | Convert a hawk embed tag into the Embed Data
buildEmbed :: (ArrowXml a) => String -> String -> a XmlTree Bind
buildEmbed pre modu = getAttrValue0 "what" >>> arr (embed pre modu)

-- * Testing Helpe

-- | Function to show a DataType
showData :: String -- ^ The Prefix
    -> String -- ^ The module name
    -> String -- ^ The template name
    -> IO ()
showData p m n = runQ (viewDataTypeWithPrefix p m n) >>= putStrLn . pprint