/yesod-core/Yesod/Message.hs

https://github.com/repos-haskell/yesod · Haskell · 259 lines · 229 code · 25 blank · 5 comment · 15 complexity · d24939fce7a1a9ed2810677ba25c67f0 MD5 · raw file

  1. {-# LANGUAGE TemplateHaskell #-}
  2. {-# LANGUAGE MultiParamTypeClasses #-}
  3. {-# LANGUAGE FlexibleInstances #-}
  4. {-# LANGUAGE TypeSynonymInstances #-}
  5. {-# LANGUAGE ExistentialQuantification #-}
  6. module Yesod.Message
  7. ( mkMessage
  8. , RenderMessage (..)
  9. , ToMessage (..)
  10. , SomeMessage (..)
  11. ) where
  12. import Language.Haskell.TH.Syntax
  13. import Data.Text (Text, pack, unpack)
  14. import System.Directory
  15. import Data.Maybe (catMaybes)
  16. import Data.List (isSuffixOf, sortBy, foldl')
  17. import qualified Data.ByteString as S
  18. import Data.Text.Encoding (decodeUtf8)
  19. import Data.Char (isSpace, toLower, toUpper)
  20. import Data.Ord (comparing)
  21. import Text.Shakespeare.Base (Deref (..), Ident (..), parseHash, derefToExp)
  22. import Text.ParserCombinators.Parsec (parse, many, eof, many1, noneOf, (<|>))
  23. import Control.Arrow ((***))
  24. import Data.Monoid (mempty, mappend)
  25. import qualified Data.Text as T
  26. import Data.String (IsString (fromString))
  27. class ToMessage a where
  28. toMessage :: a -> Text
  29. instance ToMessage Text where
  30. toMessage = id
  31. instance ToMessage String where
  32. toMessage = Data.Text.pack
  33. class RenderMessage master message where
  34. renderMessage :: master
  35. -> [Text] -- ^ languages
  36. -> message
  37. -> Text
  38. instance RenderMessage master Text where
  39. renderMessage _ _ = id
  40. type Lang = Text
  41. mkMessage :: String
  42. -> FilePath
  43. -> Lang
  44. -> Q [Dec]
  45. mkMessage dt folder lang = do
  46. files <- qRunIO $ getDirectoryContents folder
  47. contents <- qRunIO $ fmap catMaybes $ mapM (loadLang folder) files
  48. sdef <-
  49. case lookup lang contents of
  50. Nothing -> error $ "Did not find main language file: " ++ unpack lang
  51. Just def -> toSDefs def
  52. mapM_ (checkDef sdef) $ map snd contents
  53. let dt' = ConT $ mkName dt
  54. let mname = mkName $ dt ++ "Message"
  55. c1 <- fmap concat $ mapM (toClauses dt) contents
  56. c2 <- mapM (sToClause dt) sdef
  57. c3 <- defClause
  58. return
  59. [ DataD [] mname [] (map (toCon dt) sdef) []
  60. , InstanceD
  61. []
  62. (ConT ''RenderMessage `AppT` dt' `AppT` ConT mname)
  63. [ FunD (mkName "renderMessage") $ c1 ++ c2 ++ [c3]
  64. ]
  65. ]
  66. toClauses :: String -> (Lang, [Def]) -> Q [Clause]
  67. toClauses dt (lang, defs) =
  68. mapM go defs
  69. where
  70. go def = do
  71. a <- newName "lang"
  72. (pat, bod) <- mkBody dt (constr def) (map fst $ vars def) (content def)
  73. guard <- fmap NormalG [|$(return $ VarE a) == pack $(lift $ unpack lang)|]
  74. return $ Clause
  75. [WildP, ConP (mkName ":") [VarP a, WildP], pat]
  76. (GuardedB [(guard, bod)])
  77. []
  78. mkBody :: String -- ^ datatype
  79. -> String -- ^ constructor
  80. -> [String] -- ^ variable names
  81. -> [Content]
  82. -> Q (Pat, Exp)
  83. mkBody dt cs vs ct = do
  84. vp <- mapM go vs
  85. let pat = RecP (mkName $ "Msg" ++ cs) (map (varName dt *** VarP) vp)
  86. let ct' = map (fixVars vp) ct
  87. pack' <- [|Data.Text.pack|]
  88. tomsg <- [|toMessage|]
  89. let ct'' = map (toH pack' tomsg) ct'
  90. mapp <- [|mappend|]
  91. let app a b = InfixE (Just a) mapp (Just b)
  92. e <-
  93. case ct'' of
  94. [] -> [|mempty|]
  95. [x] -> return x
  96. (x:xs) -> return $ foldl' app x xs
  97. return (pat, e)
  98. where
  99. toH pack' _ (Raw s) = pack' `AppE` SigE (LitE (StringL s)) (ConT ''String)
  100. toH _ tomsg (Var d) = tomsg `AppE` derefToExp [] d
  101. go x = do
  102. let y = mkName $ '_' : x
  103. return (x, y)
  104. fixVars vp (Var d) = Var $ fixDeref vp d
  105. fixVars _ (Raw s) = Raw s
  106. fixDeref vp (DerefIdent (Ident i)) = DerefIdent $ Ident $ fixIdent vp i
  107. fixDeref vp (DerefBranch a b) = DerefBranch (fixDeref vp a) (fixDeref vp b)
  108. fixDeref _ d = d
  109. fixIdent vp i =
  110. case lookup i vp of
  111. Nothing -> i
  112. Just y -> nameBase y
  113. sToClause :: String -> SDef -> Q Clause
  114. sToClause dt sdef = do
  115. (pat, bod) <- mkBody dt (sconstr sdef) (map fst $ svars sdef) (scontent sdef)
  116. return $ Clause
  117. [WildP, ConP (mkName "[]") [], pat]
  118. (NormalB bod)
  119. []
  120. defClause :: Q Clause
  121. defClause = do
  122. a <- newName "sub"
  123. c <- newName "langs"
  124. d <- newName "msg"
  125. rm <- [|renderMessage|]
  126. return $ Clause
  127. [VarP a, ConP (mkName ":") [WildP, VarP c], VarP d]
  128. (NormalB $ rm `AppE` VarE a `AppE` VarE c `AppE` VarE d)
  129. []
  130. toCon :: String -> SDef -> Con
  131. toCon dt (SDef c vs _) =
  132. RecC (mkName $ "Msg" ++ c) $ map go vs
  133. where
  134. go (n, t) = (varName dt n, NotStrict, ConT $ mkName t)
  135. varName :: String -> String -> Name
  136. varName a y =
  137. mkName $ concat [lower a, "Message", upper y]
  138. where
  139. lower (x:xs) = toLower x : xs
  140. lower [] = []
  141. upper (x:xs) = toUpper x : xs
  142. upper [] = []
  143. checkDef :: [SDef] -> [Def] -> Q ()
  144. checkDef x y =
  145. go (sortBy (comparing sconstr) x) (sortBy (comparing constr) y)
  146. where
  147. go _ [] = return ()
  148. go [] (b:_) = error $ "Extra message constructor: " ++ constr b
  149. go (a:as) (b:bs)
  150. | sconstr a < constr b = go as (b:bs)
  151. | sconstr a > constr b = error $ "Extra message constructor: " ++ constr b
  152. | otherwise = do
  153. go' (svars a) (vars b)
  154. go as bs
  155. go' ((an, at):as) ((bn, mbt):bs)
  156. | an /= bn = error "Mismatched variable names"
  157. | otherwise =
  158. case mbt of
  159. Nothing -> go' as bs
  160. Just bt
  161. | at == bt -> go' as bs
  162. | otherwise -> error "Mismatched variable types"
  163. go' [] [] = return ()
  164. go' _ _ = error "Mistmached variable count"
  165. toSDefs :: [Def] -> Q [SDef]
  166. toSDefs = mapM toSDef
  167. toSDef :: Def -> Q SDef
  168. toSDef d = do
  169. vars' <- mapM go $ vars d
  170. return $ SDef (constr d) vars' (content d)
  171. where
  172. go (a, Just b) = return (a, b)
  173. go (a, Nothing) = error $ "Main language missing type for " ++ show (constr d, a)
  174. data SDef = SDef
  175. { sconstr :: String
  176. , svars :: [(String, String)]
  177. , scontent :: [Content]
  178. }
  179. data Def = Def
  180. { constr :: String
  181. , vars :: [(String, Maybe String)]
  182. , content :: [Content]
  183. }
  184. loadLang :: FilePath -> FilePath -> IO (Maybe (Lang, [Def]))
  185. loadLang folder file = do
  186. let file' = folder ++ '/' : file
  187. e <- doesFileExist file'
  188. if e && ".msg" `isSuffixOf` file
  189. then do
  190. let lang = pack $ reverse $ drop 4 $ reverse file
  191. bs <- S.readFile file'
  192. let s = unpack $ decodeUtf8 bs
  193. defs <- fmap catMaybes $ mapM parseDef $ lines s
  194. return $ Just (lang, defs)
  195. else return Nothing
  196. parseDef :: String -> IO (Maybe Def)
  197. parseDef "" = return Nothing
  198. parseDef ('#':_) = return Nothing
  199. parseDef s =
  200. case end of
  201. ':':end' -> do
  202. content' <- fmap compress $ parseContent $ dropWhile isSpace end'
  203. case words begin of
  204. [] -> error $ "Missing constructor: " ++ s
  205. (w:ws) -> return $ Just Def
  206. { constr = w
  207. , vars = map parseVar ws
  208. , content = content'
  209. }
  210. _ -> error $ "Missing colon: " ++ s
  211. where
  212. (begin, end) = break (== ':') s
  213. data Content = Var Deref | Raw String
  214. compress :: [Content] -> [Content]
  215. compress [] = []
  216. compress (Raw a:Raw b:rest) = compress $ Raw (a ++ b) : rest
  217. compress (x:y) = x : compress y
  218. parseContent :: String -> IO [Content]
  219. parseContent s =
  220. either (error . show) return $ parse go s s
  221. where
  222. go = do
  223. x <- many go'
  224. eof
  225. return x
  226. go' = (Raw `fmap` many1 (noneOf "#")) <|> (fmap (either Raw Var) parseHash)
  227. parseVar :: String -> (String, Maybe String)
  228. parseVar s =
  229. case break (== '@') s of
  230. (x, '@':y) -> (x, Just y)
  231. _ -> (s, Nothing)
  232. data SomeMessage master = forall msg. RenderMessage master msg => SomeMessage msg
  233. instance IsString (SomeMessage master) where
  234. fromString = SomeMessage . T.pack