yesod/Yesod/Contrib/Formable.hs
2010-06-08 21:51:55 +03:00

112 lines
3.3 KiB
Haskell

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Yesod.Contrib.Formable where
import Text.Formlets
import Text.Hamlet
import Data.Time (Day)
import Control.Applicative
import Control.Applicative.Error
import Web.Routes.Quasi (SinglePiece)
import Database.Persist (Persistable)
import Data.Char (isAlphaNum)
import Language.Haskell.TH.Syntax
import Database.Persist (Table (..))
import Database.Persist.Helper (upperFirst)
import Data.Convertible.Text (cs)
class Formable a where
formable :: (Functor m, Applicative m, Monad m)
=> Formlet (Hamlet url) m a
class Fieldable a where
fieldable :: (Functor m, Applicative m, Monad m)
=> String -> Formlet (Hamlet url) m a
instance Fieldable [Char] where
fieldable label = input' go
where
go name val = [$hamlet|
%tr
%th $string.label$
%td
%input!type=text!name=$string.name$!value=$string.val$
|]
instance Fieldable Html where
fieldable label =
fmap preEscapedString
. input' go
. fmap (cs . renderHtml)
where
go name val = [$hamlet|
%tr
%th $string.label$
%td
%textarea!name=$string.name$
$string.val$
|]
instance Fieldable Day where
fieldable label x = input' go (fmap show x) `check` asDay
where
go name val = [$hamlet|
%tr
%th $string.label$
%td
%input!type=date!name=$string.name$!value=$string.val$
|]
asDay s = maybeRead' s "Invalid day"
newtype Slug = Slug { unSlug :: String }
deriving (Read, Eq, Show, SinglePiece, Persistable)
instance Fieldable Slug where
fieldable label x = input' go (fmap unSlug x) `check` asSlug
where
go name val = [$hamlet|
%tr
%th $string.label$
%td
%input!type=text!name=$string.name$!value=$string.val$
|]
asSlug [] = Failure ["Slug must be non-empty"]
asSlug x'
| all (\c -> c `elem` "-_" || isAlphaNum c) x' =
Success $ Slug x'
| otherwise = Failure ["Slug must be alphanumeric, - and _"]
share2 :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]
share2 f g a = do
f' <- f a
g' <- g a
return $ f' ++ g'
deriveFormable :: [Table] -> Q [Dec]
deriveFormable = mapM derive
where
derive :: Table -> Q Dec
derive t = do
let cols = map (upperFirst . fst) $ tableColumns t
ap <- [|(<*>)|]
just <- [|pure|]
nothing <- [|Nothing|]
let just' = just `AppE` ConE (mkName $ tableName t)
let c1 = Clause [ConP (mkName "Nothing") []]
(NormalB $ go ap just' $ zip cols $ map (const nothing) cols)
[]
xs <- mapM (const $ newName "x") cols
let xs' = map (AppE just . VarE) xs
let c2 = Clause [ConP (mkName "Just") [ConP (mkName $ tableName t)
$ map VarP xs]]
(NormalB $ go ap just' $ zip cols xs')
[]
return $ InstanceD [] (ConT ''Formable `AppT` ConT (mkName $ tableName t))
[FunD (mkName "formable") [c1, c2]]
go ap just' = foldl (ap' ap) just' . map go'
go' (label, ex) = VarE (mkName "fieldable") `AppE` LitE (StringL label) `AppE` ex
ap' ap x y = InfixE (Just x) ap (Just y)