hamlet-0.7.0: Old/Hamlet.hs
module Old.Hamlet
( parse'
, render'
) where
import Control.Applicative ((<$>), Applicative (..))
import Control.Monad
import Control.Arrow
import Data.Data
import Data.List (intercalate)
import Text.ParserCombinators.Parsec hiding (Line)
import Text.Shakespeare (Deref (..), Ident (..))
import Text.Hamlet.Parse (Line (..), Content (..))
import Old.Utf8 (renderDeref)
renderAttr (mderef, name, val) = concat
[ " "
, case mderef of
Nothing -> ""
Just deref -> concat
[ ":"
, renderDeref deref
, ":"
]
, name
, "=\""
, concatMap renderContent val
, "\""
]
renderClass c = ' ': '.' : concatMap renderContent c
renderContent (ContentRaw s) = s
renderContent (ContentVar d) = concat ["#{", renderDeref d, "}"]
renderContent (ContentUrl False d) = concat ["@{", renderDeref d, "}"]
renderContent (ContentUrl True d) = concat ["@?{", renderDeref d, "}"]
renderContent (ContentEmbed d) = concat ["^{", renderDeref d, "}"]
renderLine' (indent, x) = concat [replicate indent ' ', renderLine x, "\n"]
renderLine (LineForall deref (Ident i)) = concat
[ "$forall "
, i
, " <- "
, renderDeref deref
]
renderLine (LineIf deref) = concat
[ "$if "
, renderDeref deref
]
renderLine (LineElseIf deref) = concat
[ "$elseif "
, renderDeref deref
]
renderLine LineElse = "$else"
renderLine (LineMaybe deref (Ident i)) = concat
[ "$maybe "
, i
, " <- "
, renderDeref deref
]
renderLine LineNothing = "$nothing"
renderLine (LineTag tn attrs content classes) = concat
[ "<"
, tn
, concatMap renderAttr attrs
, concatMap renderClass classes
, ">"
, concatMap renderContent content
]
renderLine (LineContent c) = '\\' : concatMap renderContent c
data Result v = Error String | Ok v
instance Monad Result where
return = Ok
Error s >>= _ = Error s
Ok v >>= f = f v
fail = Error
instance Functor Result where
fmap = liftM
instance Applicative Result where
pure = return
(<*>) = ap
parseLines :: HamletSettings -> String -> Result [(Int, Line)]
parseLines set s =
case parse (many $ parseLine set) s s of
Left e -> Error $ show e
Right x -> Ok x
parseLine :: HamletSettings -> Parser (Int, Line)
parseLine set = do
ss <- fmap sum $ many ((char ' ' >> return 1) <|>
(char '\t' >> return 4))
x <- doctype <|>
comment <|>
backslash <|>
controlIf <|>
controlElseIf <|>
(try (string "$else") >> many (oneOf " \t") >> eol >> return LineElse) <|>
controlMaybe <|>
(try (string "$nothing") >> many (oneOf " \t") >> eol >> return LineNothing) <|>
controlForall <|>
tag <|>
(eol' >> return (LineContent [])) <|>
(do
cs <- content InContent
isEof <- (eof >> return True) <|> return False
if null cs && ss == 0 && isEof
then fail "End of Hamlet template"
else return $ LineContent cs)
return (ss, x)
where
eol' = (char '\n' >> return ()) <|> (string "\r\n" >> return ())
eol = eof <|> eol'
doctype = do
try $ string "!!!" >> eol
return $ LineContent [ContentRaw $ hamletDoctype set ++ "\n"]
comment = do
_ <- try $ string "$#"
_ <- many $ noneOf "\r\n"
eol
return $ LineContent []
backslash = do
_ <- char '\\'
(eol >> return (LineContent [ContentRaw "\n"]))
<|> (LineContent <$> content InContent)
controlIf = do
_ <- try $ string "$if"
spaces
x <- deref False
_ <- many $ oneOf " \t"
eol
return $ LineIf x
controlElseIf = do
_ <- try $ string "$elseif"
spaces
x <- deref False
_ <- many $ oneOf " \t"
eol
return $ LineElseIf x
controlMaybe = do
_ <- try $ string "$maybe"
spaces
x <- deref False
spaces
y <- ident
_ <- many $ oneOf " \t"
eol
return $ LineMaybe x y
controlForall = do
_ <- try $ string "$forall"
spaces
x <- deref False
spaces
y <- ident
_ <- many $ oneOf " \t"
eol
return $ LineForall x y
tag = do
x <- tagName <|> tagIdent <|> tagClass <|> tagAttrib
xs <- many $ tagIdent <|> tagClass <|> tagAttrib
c <- (eol >> return []) <|> (do
_ <- many1 $ oneOf " \t"
content InContent)
let (tn, attr, classes) = tag' $ x : xs
return $ LineTag tn attr c classes
content cr = do
x <- many $ content' cr
case cr of
InQuotes -> char '"' >> return ()
NotInQuotes -> return ()
InContent -> (char '$' >> eol) <|> eol
return x
content' cr = try contentDollar <|> contentAt <|> contentCarrot
<|> contentReg cr
contentDollar = do
_ <- char '$'
(char '$' >> return (ContentRaw "$")) <|> (do
s <- deref True
_ <- char '$'
return $ ContentVar s)
contentAt = do
_ <- char '@'
(char '@' >> return (ContentRaw "@")) <|> (do
x <- (char '?' >> return True) <|> return False
s <- deref True
_ <- char '@'
return $ ContentUrl x s)
contentCarrot = do
_ <- char '^'
(char '^' >> return (ContentRaw "^")) <|> (do
s <- deref True
_ <- char '^'
return $ ContentEmbed s)
contentReg InContent = ContentRaw <$> many1 (noneOf "$@^\r\n")
contentReg NotInQuotes = ContentRaw <$> many1 (noneOf "$@^#.! \t\n\r")
contentReg InQuotes =
(do
_ <- char '\\'
ContentRaw . return <$> anyChar
) <|> (ContentRaw <$> many1 (noneOf "$@^\\\"\n\r"))
tagName = do
_ <- char '%'
s <- many1 $ noneOf " \t.#!\r\n"
return $ TagName s
tagAttribValue = do
cr <- (char '"' >> return InQuotes) <|> return NotInQuotes
content cr
tagIdent = char '#' >> TagIdent <$> tagAttribValue
tagClass = char '.' >> TagClass <$> tagAttribValue
tagAttrib = do
_ <- char '!'
cond <- (Just <$> tagAttribCond) <|> return Nothing
s <- many1 $ noneOf " \t.!=\r\n"
v <- (do
_ <- char '='
s' <- tagAttribValue
return s') <|> return []
return $ TagAttrib (cond, s, v)
tagAttribCond = do
_ <- char ':'
d <- deref True
_ <- char ':'
return d
tag' = foldr tag'' ("div", [], [])
tag'' (TagName s) (_, y, z) = (s, y, z)
tag'' (TagIdent s) (x, y, z) = (x, (Nothing, "id", s) : y, z)
tag'' (TagClass s) (x, y, z) = (x, y, s : z)
tag'' (TagAttrib s) (x, y, z) = (x, s : y, z)
derefParens = between (char '(') (char ')') $ deref True
derefSingle = derefParens <|> fmap DerefIdent ident
deref spaceAllowed = do
let delim = if spaceAllowed
then (char '.' <|> (many1 (char ' ') >> return ' '))
else char '.'
x <- derefSingle
xs <- many $ delim >> derefSingle
return $ foldr1 DerefBranch $ x : xs
ident = Ident <$> many1 (alphaNum <|> char '_' <|> char '\'')
data TagPiece = TagName String
| TagIdent [Content]
| TagClass [Content]
| TagAttrib (Maybe Deref, String, [Content])
data ContentRule = InQuotes | NotInQuotes | InContent
data Nest = Nest Line [Nest]
nestLines :: [(Int, Line)] -> [Nest]
nestLines [] = []
nestLines ((i, l):rest) =
let (deeper, rest') = span (\(i', _) -> i' > i) rest
in Nest l (nestLines deeper) : nestLines rest'
-- | Settings for parsing of a hamlet document.
data HamletSettings = HamletSettings
{
-- | The value to replace a \"!!!\" with. Do not include the trailing
-- newline.
hamletDoctype :: String
-- | 'True' means to close empty tags (eg, img) with a trailing slash, ie
-- XML-style empty tags. 'False' uses HTML-style.
, hamletCloseEmpties :: Bool
-- | Should we put a newline after closing a tag?
, hamletCloseNewline :: Bool
}
-- | Defaults settings: HTML5 doctype and HTML-style empty tags.
defaultHamletSettings :: HamletSettings
defaultHamletSettings = HamletSettings "<!DOCTYPE html>" False False
xhtmlHamletSettings :: HamletSettings
xhtmlHamletSettings =
HamletSettings doctype True False
where
doctype =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " ++
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
debugHamletSettings :: HamletSettings
debugHamletSettings = HamletSettings "<!DOCTYPE html>" False True
data CloseStyle = NoClose | CloseInside | CloseSeparate
-- FIXME A breaking change, but move closeTag to be a record in the
-- HamletSettings datatype. Would allow more precise XML encodings.
closeTag :: HamletSettings -> String -> CloseStyle
closeTag h s =
if canBeEmpty s
then CloseSeparate
else (if hamletCloseEmpties h then CloseInside else NoClose)
where
canBeEmpty "img" = False
canBeEmpty "link" = False
canBeEmpty "meta" = False
canBeEmpty "br" = False
canBeEmpty "hr" = False
canBeEmpty "input" = False
canBeEmpty _ = True
parse' set s =
case parseLines defaultHamletSettings s of
Error e -> error e
Ok x -> x
render' = concatMap renderLine'