packages feed

heterocephalus 0.1.0.0 → 1.0.0

raw patch · 4 files changed

+144/−828 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Text.Heterocephalus: compileHtml :: QuasiQuoter
+ Text.Heterocephalus: compileHtmlFile :: FilePath -> Q Exp
+ Text.Heterocephalus: compileText :: QuasiQuoter
+ Text.Heterocephalus: compileTextFile :: FilePath -> Q Exp
+ Text.Heterocephalus: data HeterocephalusSetting
- Text.Heterocephalus: compile :: QuasiQuoter
+ Text.Heterocephalus: compile :: HeterocephalusSetting -> QuasiQuoter
- Text.Heterocephalus: compileFile :: FilePath -> Q Exp
+ Text.Heterocephalus: compileFile :: HeterocephalusSetting -> FilePath -> Q Exp
- Text.Heterocephalus: compileFromString :: Q HamletRules -> HamletSettings -> String -> Q Exp
+ Text.Heterocephalus: compileFromString :: HeterocephalusSetting -> String -> Q Exp

Files

heterocephalus.cabal view
@@ -1,5 +1,5 @@ name:                heterocephalus-version:             0.1.0.0+version:             1.0.0 synopsis:            A flexible and type safe template engine for Haskell. description:         Please see README.md homepage:            https://github.com/arowM/heterocephalus#readme
src/Text/Hamlet/Parse.hs view
@@ -5,15 +5,7 @@ {-# LANGUAGE FlexibleInstances #-} module Text.Hamlet.Parse     ( Result (..)-    , Content (..)-    , Doc (..)-    , parseDoc-    , HamletSettings (..)-    , defaultHamletSettings-    , xhtmlHamletSettings-    , CloseStyle (..)     , Binding (..)-    , NewlineStyle (..)     , specialOrIdent     , DataConstr (..)     , Module (..)@@ -21,16 +13,9 @@     where  import Text.Shakespeare.Base-import Control.Applicative ((<$>), Applicative (..))+import Control.Applicative (Applicative (..)) import Control.Monad-import Control.Arrow-import Data.Char (isUpper) import Data.Data-import Text.ParserCombinators.Parsec hiding (Line)-import Data.Set (Set)-import qualified Data.Set as Set-import Data.Maybe (mapMaybe, fromMaybe, isNothing)-import Language.Haskell.TH.Syntax (Lift (..))  data Result v = Error String | Ok v     deriving (Show, Eq, Read, Data, Typeable)@@ -45,661 +30,6 @@     pure = return     (<*>) = ap -data Content = ContentRaw String-             | ContentVar Deref-             | ContentUrl Bool Deref -- ^ bool: does it include params?-             | ContentEmbed Deref-             | ContentMsg Deref-             | ContentAttrs Deref-    deriving (Show, Eq, Read, Data, Typeable)--data Line = LineForall Deref Binding-          | LineIf Deref-          | LineElseIf Deref-          | LineElse-          | LineWith [(Deref, Binding)]-          | LineMaybe Deref Binding-          | LineNothing-          | LineCase Deref-          | LineOf Binding-          | LineTag-            { _lineTagName :: String-            , _lineAttr :: [(Maybe Deref, String, Maybe [Content])]-            , _lineContent :: [Content]-            , _lineClasses :: [(Maybe Deref, [Content])]-            , _lineAttrs :: [Deref]-            , _lineNoNewline :: Bool-            }-          | LineContent [Content] Bool -- ^ True == avoid newlines-    deriving (Eq, Show, Read)--parseLines :: HamletSettings -> String -> Result (Maybe NewlineStyle, HamletSettings, [(Int, Line)])-parseLines set s =-    case parse parser s s of-        Left e -> Error $ show e-        Right x -> Ok x-  where-    parser = do-        mnewline <- parseNewline-        let set' =-                case mnewline of-                    Nothing ->-                        case hamletNewlines set of-                            DefaultNewlineStyle -> set { hamletNewlines = AlwaysNewlines }-                            _ -> set-                    Just n -> set { hamletNewlines = n }-        res <- many (parseLine set')-        return (mnewline, set', res)--    parseNewline =-        (try (many eol' >> spaceTabs >> string "$newline ") >> parseNewline' >>= \nl -> eol' >> return nl) <|>-        return Nothing-    parseNewline' =-        (try (string "always") >> return (Just AlwaysNewlines)) <|>-        (try (string "never") >> return (Just NoNewlines)) <|>-        (try (string "text") >> return (Just NewlinesText))--    eol' = (char '\n' >> return ()) <|> (string "\r\n" >> return ())--parseLine :: HamletSettings -> Parser (Int, Line)-parseLine set = do-    ss <- fmap sum $ many ((char ' ' >> return 1) <|>-                           (char '\t' >> fail "Tabs are not allowed in Hamlet indentation"))-    x <- doctype <|>-         doctypeDollar <|>-         comment <|>-         ssiInclude <|>-         htmlComment <|>-         doctypeRaw <|>-         backslash <|>-         controlIf <|>-         controlElseIf <|>-         (try (string "$else") >> spaceTabs >> eol >> return LineElse) <|>-         controlMaybe <|>-         (try (string "$nothing") >> spaceTabs >> eol >> return LineNothing) <|>-         controlForall <|>-         controlWith <|>-         controlCase <|>-         controlOf <|>-         angle <|>-         invalidDollar <|>-         (eol' >> return (LineContent [] True)) <|>-         (do-            (cs, avoidNewLines) <- content InContent-            isEof <- (eof >> return True) <|> return False-            if null cs && ss == 0 && isEof-                then fail "End of Hamlet template"-                else return $ LineContent cs avoidNewLines)-    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"] True-    doctypeDollar = do-        _ <- try $ string "$doctype "-        name <- many $ noneOf "\r\n"-        eol-        case lookup name $ hamletDoctypeNames set of-            Nothing -> fail $ "Unknown doctype name: " ++ name-            Just val -> return $ LineContent [ContentRaw $ val ++ "\n"] True--    doctypeRaw = do-        x <- try $ string "<!"-        y <- many $ noneOf "\r\n"-        eol-        return $ LineContent [ContentRaw $ concat [x, y, "\n"]] True--    invalidDollar = do-        _ <- char '$'-        fail "Received a command I did not understand. If you wanted a literal $, start the line with a backslash."-    comment = do-        _ <- try $ string "$#"-        _ <- many $ noneOf "\r\n"-        eol-        return $ LineContent [] True-    ssiInclude = do-        x <- try $ string "<!--#"-        y <- many $ noneOf "\r\n"-        eol-        return $ LineContent [ContentRaw $ x ++ y] False-    htmlComment = do-        _ <- try $ string "<!--"-        _ <- manyTill anyChar $ try $ string "-->"-        x <- many nonComments-        eol-        return $ LineContent [ContentRaw $ concat x] False {- FIXME -} -- FIXME handle variables?-    nonComments = (many1 $ noneOf "\r\n<") <|> (do-        _ <- char '<'-        (do-            _ <- try $ string "!--"-            _ <- manyTill anyChar $ try $ string "-->"-            return "") <|> return "<")-    backslash = do-        _ <- char '\\'-        (eol >> return (LineContent [ContentRaw "\n"] True))-            <|> (uncurry LineContent <$> content InContent)-    controlIf = do-        _ <- try $ string "$if"-        spaces-        x <- parseDeref-        _ <- spaceTabs-        eol-        return $ LineIf x-    controlElseIf = do-        _ <- try $ string "$elseif"-        spaces-        x <- parseDeref-        _ <- spaceTabs-        eol-        return $ LineElseIf x-    binding = do-        y <- identPattern-        spaces-        _ <- string "<-"-        spaces-        x <- parseDeref-        _ <- spaceTabs-        return (x,y)-    bindingSep = char ',' >> spaceTabs-    controlMaybe = do-        _ <- try $ string "$maybe"-        spaces-        (x,y) <- binding-        eol-        return $ LineMaybe x y-    controlForall = do-        _ <- try $ string "$forall"-        spaces-        (x,y) <- binding-        eol-        return $ LineForall x y-    controlWith = do-        _ <- try $ string "$with"-        spaces-        bindings <- (binding `sepBy` bindingSep) `endBy` eol-        return $ LineWith $ concat bindings -- concat because endBy returns a [[(Deref,Ident)]]-    controlCase = do-        _ <- try $ string "$case"-        spaces-        x <- parseDeref-        _ <- spaceTabs-        eol-        return $ LineCase x-    controlOf = do-        _   <- try $ string "$of"-        spaces-        x <- identPattern-        _   <- spaceTabs-        eol-        return $ LineOf x-    content cr = do-        x <- many $ content' cr-        case cr of-            InQuotes -> void $ char '"'-            NotInQuotes -> return ()-            NotInQuotesAttr -> return ()-            InContent -> eol-        return (cc $ map fst x, any snd x)-      where-        cc [] = []-        cc (ContentRaw a:ContentRaw b:c) = cc $ ContentRaw (a ++ b) : c-        cc (a:b) = a : cc b-    content' cr = contentHash <|> contentAt <|> contentCaret-                              <|> contentUnder-                              <|> contentReg' cr-    contentHash = do-        x <- parseHash-        case x of-            Left str -> return (ContentRaw str, null str)-            Right deref -> return (ContentVar deref, False)-    contentAt = do-        x <- parseAt-        return $ case x of-                    Left str -> (ContentRaw str, null str)-                    Right (s, y) -> (ContentUrl y s, False)-    contentCaret = do-        x <- parseCaret-        case x of-            Left str -> return (ContentRaw str, null str)-            Right deref -> return (ContentEmbed deref, False)-    contentUnder = do-        x <- parseUnder-        case x of-            Left str -> return (ContentRaw str, null str)-            Right deref -> return (ContentMsg deref, False)-    contentReg' x = (flip (,) False) <$> contentReg x-    contentReg InContent = (ContentRaw . return) <$> noneOf "#@^\r\n"-    contentReg NotInQuotes = (ContentRaw . return) <$> noneOf "@^#. \t\n\r>"-    contentReg NotInQuotesAttr = (ContentRaw . return) <$> noneOf "@^ \t\n\r>"-    contentReg InQuotes = (ContentRaw . return) <$> noneOf "#@^\"\n\r"-    tagAttribValue notInQuotes = do-        cr <- (char '"' >> return InQuotes) <|> return notInQuotes-        fst <$> content cr-    tagIdent = char '#' >> TagIdent <$> tagAttribValue NotInQuotes-    tagCond = do-        d <- between (char ':') (char ':') parseDeref-        tagClass (Just d) <|> tagAttrib (Just d)-    tagClass x = do-        clazz <- char '.' >> tagAttribValue NotInQuotes-        let hasHash (ContentRaw s) = any (== '#') s-            hasHash _ = False-        if any hasHash clazz-            then fail $ "Invalid class: " ++ show clazz ++ ". Did you want a space between a class and an ID?"-            else return (TagClass (x, clazz))-    tagAttrib cond = do-        s <- many1 $ noneOf " \t=\r\n><"-        v <- (char '=' >> Just <$> tagAttribValue NotInQuotesAttr) <|> return Nothing-        return $ TagAttrib (cond, s, v)--    tagAttrs = do-        _ <- char '*'-        d <- between (char '{') (char '}') parseDeref-        return $ TagAttribs d--    tag' = foldr tag'' ("div", [], [], [])-    tag'' (TagName s) (_, y, z, as) = (s, y, z, as)-    tag'' (TagIdent s) (x, y, z, as) = (x, (Nothing, "id", Just s) : y, z, as)-    tag'' (TagClass s) (x, y, z, as) = (x, y, s : z, as)-    tag'' (TagAttrib s) (x, y, z, as) = (x, s : y, z, as)-    tag'' (TagAttribs s) (x, y, z, as) = (x, y, z, s : as)--    ident :: Parser Ident-    ident = do-      i <- many1 (alphaNum <|> char '_' <|> char '\'')-      white-      return (Ident i)-     <?> "identifier"--    parens = between (char '(' >> white) (char ')' >> white)--    brackets = between (char '[' >> white) (char ']' >> white)--    braces = between (char '{' >> white) (char '}' >> white)--    comma = char ',' >> white--    atsign = char '@' >> white--    equals = char '=' >> white--    white = skipMany $ char ' '--    wildDots = string ".." >> white--    isVariable (Ident (x:_)) = not (isUpper x)-    isVariable (Ident []) = error "isVariable: bad identifier"--    isConstructor (Ident (x:_)) = isUpper x-    isConstructor (Ident []) = error "isConstructor: bad identifier"--    identPattern :: Parser Binding-    identPattern = gcon True <|> apat-      where-      apat = choice-        [ varpat-        , gcon False-        , parens tuplepat-        , brackets listpat-        ]--      varpat = do-        v <- try $ do v <- ident-                      guard (isVariable v)-                      return v-        option (BindVar v) $ do-          atsign-          b <- apat-          return (BindAs v b)-       <?> "variable"--      gcon :: Bool -> Parser Binding-      gcon allowArgs = do-        c <- try $ do c <- dataConstr-                      return c-        choice-          [ record c-          , fmap (BindConstr c) (guard allowArgs >> many apat)-          , return (BindConstr c [])-          ]-       <?> "constructor"--      dataConstr = do-        p <- dcPiece-        ps <- many dcPieces-        return $ toDataConstr p ps--      dcPiece = do-        x@(Ident y) <- ident-        guard $ isConstructor x-        return y--      dcPieces = do-        _ <- char '.'-        dcPiece--      toDataConstr x [] = DCUnqualified $ Ident x-      toDataConstr x (y:ys) =-          go (x:) y ys-        where-          go front next [] = DCQualified (Module $ front []) (Ident next)-          go front next (rest:rests) = go (front . (next:)) rest rests--      record c = braces $ do-        (fields, wild) <- option ([], False) $ go-        return (BindRecord c fields wild)-        where-        go = (wildDots >> return ([], True))-           <|> (do x         <- recordField-                   (xs,wild) <- option ([],False) (comma >> go)-                   return (x:xs,wild))--      recordField = do-        field <- ident-        p <- option (BindVar field) -- support punning-                    (equals >> identPattern)-        return (field,p)--      tuplepat = do-        xs <- identPattern `sepBy` comma-        return $ case xs of-          [x] -> x-          _   -> BindTuple xs--      listpat = BindList <$> identPattern `sepBy` comma--    angle = do-        _ <- char '<'-        name' <- many  $ noneOf " \t.#\r\n!>"-        let name = if null name' then "div" else name'-        xs <- many $ try ((many $ oneOf " \t\r\n") >>-              (tagIdent <|> tagCond <|> tagClass Nothing <|> tagAttrs <|> tagAttrib Nothing))-        _ <- many $ oneOf " \t\r\n"-        _ <- char '>'-        (c, avoidNewLines) <- content InContent-        let (tn, attr, classes, attrsd) = tag' $ TagName name : xs-        if '/' `elem` tn-          then fail "A tag name may not contain a slash. Perhaps you have a closing tag in your HTML."-          else return $ LineTag tn attr c classes attrsd avoidNewLines--data TagPiece = TagName String-              | TagIdent [Content]-              | TagClass (Maybe Deref, [Content])-              | TagAttrib (Maybe Deref, String, Maybe [Content])-              | TagAttribs Deref-    deriving Show--data ContentRule = InQuotes | NotInQuotes | NotInQuotesAttr | 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'--data Doc = DocForall Deref Binding [Doc]-         | DocWith [(Deref, Binding)] [Doc]-         | DocCond [(Deref, [Doc])] (Maybe [Doc])-         | DocMaybe Deref Binding [Doc] (Maybe [Doc])-         | DocCase Deref [(Binding, [Doc])]-         | DocContent Content-    deriving (Show, Eq, Read, Data, Typeable)--nestToDoc :: HamletSettings -> [Nest] -> Result [Doc]-nestToDoc _set [] = Ok []-nestToDoc set (Nest (LineForall d i) inside:rest) = do-    inside' <- nestToDoc set inside-    rest' <- nestToDoc set rest-    Ok $ DocForall d i inside' : rest'-nestToDoc set (Nest (LineWith dis) inside:rest) = do-    inside' <- nestToDoc set inside-    rest' <- nestToDoc set rest-    Ok $ DocWith dis inside' : rest'-nestToDoc set (Nest (LineIf d) inside:rest) = do-    inside' <- nestToDoc set inside-    (ifs, el, rest') <- parseConds set ((:) (d, inside')) rest-    rest'' <- nestToDoc set rest'-    Ok $ DocCond ifs el : rest''-nestToDoc set (Nest (LineMaybe d i) inside:rest) = do-    inside' <- nestToDoc set inside-    (nothing, rest') <--        case rest of-            Nest LineNothing ninside:x -> do-                ninside' <- nestToDoc set ninside-                return (Just ninside', x)-            _ -> return (Nothing, rest)-    rest'' <- nestToDoc set rest'-    Ok $ DocMaybe d i inside' nothing : rest''-nestToDoc set (Nest (LineCase d) inside:rest) = do-    let getOf (Nest (LineOf x) insideC) = do-            insideC' <- nestToDoc set insideC-            Ok (x, insideC')-        getOf _ = Error "Inside a $case there may only be $of.  Use '$of _' for a wildcard."-    cases <- mapM getOf inside-    rest' <- nestToDoc set rest-    Ok $ DocCase d cases : rest'-nestToDoc set (Nest (LineTag tn attrs content classes attrsD avoidNewLine) inside:rest) = do-    let attrFix (x, y, z) = (x, y, [(Nothing, z)])-    let takeClass (a, "class", b) = Just (a, fromMaybe [] b)-        takeClass _ = Nothing-    let clazzes = classes ++ mapMaybe takeClass attrs-    let notClass (_, x, _) = x /= "class"-    let noclass = filter notClass attrs-    let attrs' =-            case clazzes of-              [] -> map attrFix noclass-              _ -> (testIncludeClazzes clazzes, "class", map (second Just) clazzes)-                       : map attrFix noclass-    let closeStyle =-            if not (null content) || not (null inside)-                then CloseSeparate-                else hamletCloseStyle set tn-    let end = case closeStyle of-                CloseSeparate ->-                    DocContent $ ContentRaw $ "</" ++ tn ++ ">"-                _ -> DocContent $ ContentRaw ""-        seal = case closeStyle of-                 CloseInside -> DocContent $ ContentRaw "/>"-                 _ -> DocContent $ ContentRaw ">"-        start = DocContent $ ContentRaw $ "<" ++ tn-        attrs'' = concatMap attrToContent attrs'-        newline' = DocContent $ ContentRaw-                 $ case hamletNewlines set of { AlwaysNewlines | not avoidNewLine -> "\n"; _ -> "" }-    inside' <- nestToDoc set inside-    rest' <- nestToDoc set rest-    Ok $ start-       : attrs''-      ++ map (DocContent . ContentAttrs) attrsD-      ++ seal-       : map DocContent content-      ++ inside'-      ++ end-       : newline'-       : rest'-nestToDoc set (Nest (LineContent content avoidNewLine) inside:rest) = do-    inside' <- nestToDoc set inside-    rest' <- nestToDoc set rest-    let newline' = DocContent $ ContentRaw-                   $ case hamletNewlines set of { NoNewlines -> ""; _ -> if nextIsContent && not avoidNewLine then "\n" else "" }-        nextIsContent =-            case (inside, rest) of-                ([], Nest LineContent{} _:_) -> True-                ([], Nest LineTag{} _:_) -> True-                _ -> False-    Ok $ map DocContent content ++ newline':inside' ++ rest'-nestToDoc _set (Nest (LineElseIf _) _:_) = Error "Unexpected elseif"-nestToDoc _set (Nest LineElse _:_) = Error "Unexpected else"-nestToDoc _set (Nest LineNothing _:_) = Error "Unexpected nothing"-nestToDoc _set (Nest (LineOf _) _:_) = Error "Unexpected 'of' (did you forget a $case?)"--compressDoc :: [Doc] -> [Doc]-compressDoc [] = []-compressDoc (DocForall d i doc:rest) =-    DocForall d i (compressDoc doc) : compressDoc rest-compressDoc (DocWith dis doc:rest) =-    DocWith dis (compressDoc doc) : compressDoc rest-compressDoc (DocMaybe d i doc mnothing:rest) =-    DocMaybe d i (compressDoc doc) (fmap compressDoc mnothing)-  : compressDoc rest-compressDoc (DocCond [(a, x)] Nothing:DocCond [(b, y)] Nothing:rest)-    | a == b = compressDoc $ DocCond [(a, x ++ y)] Nothing : rest-compressDoc (DocCond x y:rest) =-    DocCond (map (second compressDoc) x) (compressDoc `fmap` y)-    : compressDoc rest-compressDoc (DocCase d cs:rest) =-    DocCase d (map (second compressDoc) cs) : compressDoc rest-compressDoc (DocContent (ContentRaw ""):rest) = compressDoc rest-compressDoc ( DocContent (ContentRaw x)-            : DocContent (ContentRaw y)-            : rest-            ) = compressDoc $ (DocContent $ ContentRaw $ x ++ y) : rest-compressDoc (DocContent x:rest) = DocContent x : compressDoc rest--parseDoc :: HamletSettings -> String -> Result (Maybe NewlineStyle, [Doc])-parseDoc set s = do-    (mnl, set', ls) <- parseLines set s-    let notEmpty (_, LineContent [] _) = False-        notEmpty _ = True-    let ns = nestLines $ filter notEmpty ls-    ds <- nestToDoc set' ns-    return (mnl, compressDoc ds)--attrToContent :: (Maybe Deref, String, [(Maybe Deref, Maybe [Content])]) -> [Doc]-attrToContent (Just cond, k, v) =-    [DocCond [(cond, attrToContent (Nothing, k, v))] Nothing]-attrToContent (Nothing, k, []) = [DocContent $ ContentRaw $ ' ' : k]-attrToContent (Nothing, k, [(Nothing, Nothing)]) = [DocContent $ ContentRaw $ ' ' : k]-attrToContent (Nothing, k, [(Nothing, Just v)]) =-    DocContent (ContentRaw (' ' : k ++ "=\""))-  : map DocContent v-  ++ [DocContent $ ContentRaw "\""]-attrToContent (Nothing, k, v) = -- only for class-      DocContent (ContentRaw (' ' : k ++ "=\""))-    : concatMap go (init v)-    ++ go' (last v)-    ++ [DocContent $ ContentRaw "\""]-  where-    go (Nothing, x) = map DocContent (fromMaybe [] x) ++ [DocContent $ ContentRaw " "]-    go (Just b, x) =-        [ DocCond-            [(b, map DocContent (fromMaybe [] x) ++ [DocContent $ ContentRaw " "])]-            Nothing-        ]-    go' (Nothing, x) = maybe [] (map DocContent) x-    go' (Just b, x) =-        [ DocCond-            [(b, maybe [] (map DocContent) x)]-            Nothing-        ]---- | Settings for parsing of a hamlet document.-data HamletSettings = HamletSettings-    {-      -- | The value to replace a \"!!!\" with. Do not include the trailing-      -- newline.-      hamletDoctype :: String-      -- | Should we add newlines to the output, making it more human-readable?-      --  Useful for client-side debugging but may alter browser page layout.-    , hamletNewlines :: NewlineStyle-      -- | How a tag should be closed. Use this to switch between HTML, XHTML-      -- or even XML output.-    , hamletCloseStyle :: String -> CloseStyle-      -- | Mapping from short names in \"$doctype\" statements to full doctype.-    , hamletDoctypeNames :: [(String, String)]-    }--data NewlineStyle = NoNewlines -- ^ never add newlines-                  | NewlinesText -- ^ add newlines between consecutive text lines-                  | AlwaysNewlines -- ^ add newlines everywhere-                  | DefaultNewlineStyle-    deriving Show--instance Lift NewlineStyle where-    lift NoNewlines = [|NoNewlines|]-    lift NewlinesText = [|NewlinesText|]-    lift AlwaysNewlines = [|AlwaysNewlines|]-    lift DefaultNewlineStyle = [|DefaultNewlineStyle|]--instance Lift (String -> CloseStyle) where-    lift _ = [|\s -> htmlCloseStyle s|]--instance Lift HamletSettings where-    lift (HamletSettings a b c d) = [|HamletSettings $(lift a) $(lift b) $(lift c) $(lift d)|]----- See the html specification for a list of all void elements:--- https://www.w3.org/TR/html/syntax.html#void-elements-htmlEmptyTags :: Set String-htmlEmptyTags = Set.fromAscList-    [ "area"-    , "base"-    , "basefont" -- not html 5-    , "br"-    , "col"-    , "embed"-    , "frame"    -- not html 5-    , "hr"-    , "img"-    , "input"-    , "isindex"  -- not html 5-    , "keygen"-    , "link"-    , "meta"-    , "param"-    , "source"-    , "track"-    , "wbr"-    ]---- | Defaults settings: HTML5 doctype and HTML-style empty tags.-defaultHamletSettings :: HamletSettings-defaultHamletSettings = HamletSettings "<!DOCTYPE html>" DefaultNewlineStyle htmlCloseStyle doctypeNames--xhtmlHamletSettings :: HamletSettings-xhtmlHamletSettings =-    HamletSettings doctype DefaultNewlineStyle xhtmlCloseStyle doctypeNames-  where-    doctype =-      "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " ++-      "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"--htmlCloseStyle :: String -> CloseStyle-htmlCloseStyle s =-    if Set.member s htmlEmptyTags-        then NoClose-        else CloseSeparate--xhtmlCloseStyle :: String -> CloseStyle-xhtmlCloseStyle s =-    if Set.member s htmlEmptyTags-        then CloseInside-        else CloseSeparate--data CloseStyle = NoClose | CloseInside | CloseSeparate--parseConds :: HamletSettings-           -> ([(Deref, [Doc])] -> [(Deref, [Doc])])-           -> [Nest]-           -> Result ([(Deref, [Doc])], Maybe [Doc], [Nest])-parseConds set front (Nest LineElse inside:rest) = do-    inside' <- nestToDoc set inside-    Ok (front [], Just inside', rest)-parseConds set front (Nest (LineElseIf d) inside:rest) = do-    inside' <- nestToDoc set inside-    parseConds set (front . (:) (d, inside')) rest-parseConds _ front rest = Ok (front [], Nothing, rest)--doctypeNames :: [(String, String)]-doctypeNames =-    [ ("5", "<!DOCTYPE html>")-    , ("html", "<!DOCTYPE html>")-    , ("1.1", "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">")-    , ("strict", "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")-    ]- data Binding = BindVar Ident              | BindAs Ident Binding              | BindConstr DataConstr [Binding]@@ -714,24 +44,6 @@  newtype Module = Module [String]     deriving (Eq, Show, Read, Data, Typeable)--spaceTabs :: Parser String-spaceTabs = many $ oneOf " \t"---- | When using conditional classes, it will often be a single class, e.g.:------ > <div :isHome:.homepage>------ If isHome is False, we do not want any class attribute to be present.--- However, due to combining multiple classes together, the most obvious--- implementation would produce a class="". The purpose of this function is to--- work around that. It does so by checking if all the classes on this tag are--- optional. If so, it will only include the class attribute if at least one--- conditional is true.-testIncludeClazzes :: [(Maybe Deref, [Content])] -> Maybe Deref-testIncludeClazzes cs-    | any (isNothing . fst) cs = Nothing-    | otherwise = Just $ DerefBranch (DerefIdent specialOrIdent) $ DerefList $ mapMaybe fst cs  -- | This funny hack is to allow us to refer to the 'or' function without -- requiring the user to have it in scope. See how this function is used in
src/Text/Heterocephalus.hs view
@@ -3,10 +3,15 @@ module Text.Heterocephalus   (   -- * Core functions-    compile-  , compileFile+    compileText+  , compileTextFile+  , compileHtml+  , compileHtmlFile    -- * low-level+  , HeterocephalusSetting(escapeExp)+  , compile+  , compileFile   , compileFromString   ) where @@ -17,75 +22,163 @@ import qualified Data.Text.Lazy as TL import Language.Haskell.TH.Quote import Language.Haskell.TH.Syntax hiding (Module)+import Text.Blaze (preEscapedToMarkup) import Text.Blaze.Html (toHtml) import Text.Blaze.Internal (preEscapedText) import Text.Hamlet import Text.Hamlet.Parse-       hiding (defaultHamletSettings, parseDoc, HamletSettings) import Text.Shakespeare.Base -import Text.Heterocephalus.Parse (parseDoc)+import Text.Heterocephalus.Parse (Doc(..), Content(..), parseDoc)  {- $setup   >>> :set -XTemplateHaskell -XQuasiQuotes-  >>> import Text.Blaze.Html.Renderer.String+  >>> import Text.Blaze.Renderer.String -}  {-| Heterocephalus quasi-quoter.+  This function DOES NOT escape template variables.+  To render the compiled file, use @Text.Blaze.Renderer.*.renderMarkup@. -  >>> renderHtml (let as = ["a", "b"] in [compile|sample %{ forall a <- as }key: #{a}, %{ endforall }|] "")-  "sample key: a, key: b, "+  >>> renderMarkup (let as = ["<a>", "b"] in [compileText|sample %{ forall a <- as }key: #{a}, %{ endforall }|])+  "sample key: <a>, key: b, " -  >>> renderHtml (let num=2 in [compile|#{num} is %{ if even num }even number.%{ else }odd number.%{ endif }|] "")+  >>> renderMarkup (let num=2 in [compileText|#{num} is %{ if even num }even number.%{ else }odd number.%{ endif }|])   "2 is even number."  -}-compile :: QuasiQuoter-compile = compileWithSettings hamletRules defaultHamletSettings+compileText :: QuasiQuoter+compileText = compile textSetting -{-| Compile a template file.--}-compileFile :: FilePath -> Q Exp-compileFile = compileFileWithSettings hamletRules defaultHamletSettings+{-| Heterocephalus quasi-quoter for Html.+  Same as 'compileText' but this function do escape template variables for Html. -compileWithSettings :: Q HamletRules -> HamletSettings -> QuasiQuoter-compileWithSettings hr set =+  >>> renderMarkup (let as = ["<a>", "b"] in [compileHtml|sample %{ forall a <- as }key: #{a}, %{ endforall }|])+  "sample key: &lt;a&gt;, key: b, "+ -}+compileHtml :: QuasiQuoter+compileHtml = compile htmlSetting++{-| Heterocephalus quasi-quoter.+  Same as 'compileText' but this function read template literal from an external file.++  >>> putStr $ renderMarkup (let as = ["<a>", "b"] in $(compileTextFile "templates/sample.txt"))+  sample+  key: <a>,+  key: b,+ -}+compileTextFile :: FilePath -> Q Exp+compileTextFile = compileFile textSetting++{-| Heterocephalus quasi-quoter.+  Same as 'compileTextFile' but escapes template variables for Html.++  >>> putStr $ renderMarkup (let as = ["<a>", "b"] in $(compileHtmlFile "templates/sample.txt"))+  sample+  key: &lt;a&gt;,+  key: b,+ -}+compileHtmlFile :: FilePath -> Q Exp+compileHtmlFile = compileFile htmlSetting++compile :: HeterocephalusSetting -> QuasiQuoter+compile set =   QuasiQuoter-  { quoteExp = compileFromString hr set+  { quoteExp = compileFromString set   , quotePat = error "not used"   , quoteType = error "not used"   , quoteDec = error "not used"   } -compileFileWithSettings :: Q HamletRules -> HamletSettings -> FilePath -> Q Exp-compileFileWithSettings qhr set fp = do+{-| Compile a template file.+-}+compileFile :: HeterocephalusSetting -> FilePath -> Q Exp+compileFile set fp = do   qAddDependentFile fp   contents <- fmap TL.unpack $ qRunIO $ readUtf8File fp-  compileFromString qhr set contents+  compileFromString set contents -compileFromString :: Q HamletRules -> HamletSettings -> String -> Q Exp-compileFromString qhr set s = do-  hr <- qhr-  hrWithEnv hr $ \env -> docsToExp env hr [] $ docFromString set s+compileFromString :: HeterocephalusSetting -> String -> Q Exp+compileFromString set s =+  docsToExp set [] $ docFromString s -docFromString :: HamletSettings -> String -> [Doc]-docFromString _ s =+docFromString :: String -> [Doc]+docFromString s =   case parseDoc s of     Error s' -> error s'     Ok d -> d +data HeterocephalusSetting = HeterocephalusSetting+  { escapeExp :: Q Exp+  } +{-| A setting that escapes template variables for Html+ -}+htmlSetting :: HeterocephalusSetting+htmlSetting = HeterocephalusSetting+  { escapeExp = [|toHtml|]+  }++{-| A setting that DOES NOT escape template variables+ -}+textSetting :: HeterocephalusSetting+textSetting = HeterocephalusSetting+  { escapeExp = [|preEscapedToMarkup|]+  }+ -- ==============================================--- Codes from Text.Hamlet that is not exposed+--  Helper functions -- ============================================== -docsToExp :: Env -> HamletRules -> Scope -> [Doc] -> Q Exp-docsToExp env hr scope docs = do-  exps <- mapM (docToExp env hr scope) docs+docsToExp :: HeterocephalusSetting -> Scope -> [Doc] -> Q Exp+docsToExp set scope docs = do+  exps <- mapM (docToExp set scope) docs   case exps of     [] -> [|return ()|]     [x] -> return x     _ -> return $ DoE $ map NoBindS exps +-- TODO What's scope?+docToExp :: HeterocephalusSetting -> Scope -> Doc -> Q Exp+docToExp set scope (DocForall list idents inside) = do+  let list' = derefToExp scope list+  (pat, extraScope) <- bindingPattern idents+  let scope' = extraScope ++ scope+  mh <- [|F.mapM_|]+  inside' <- docsToExp set scope' inside+  let lam = LamE [pat] inside'+  return $ mh `AppE` lam `AppE` list'+docToExp set scope (DocCond conds final) = do+  conds' <- mapM go conds+  final' <-+    case final of+      Nothing -> [|Nothing|]+      Just f -> do+        f' <- docsToExp set scope f+        j <- [|Just|]+        return $ j `AppE` f'+  ch <- [|condH|]+  return $ ch `AppE` ListE conds' `AppE` final'+  where+    go :: (Deref, [Doc]) -> Q Exp+    go (d, docs) = do+      let d' = derefToExp ((specialOrIdent, VarE 'or) : scope) d+      docs' <- docsToExp set scope docs+      return $ TupE [d', docs']+docToExp set v (DocContent c) = contentToExp set v c++contentToExp :: HeterocephalusSetting -> Scope -> Content -> Q Exp+contentToExp _ _ (ContentRaw s) = do+  os <- [|preEscapedText . pack|]+  let s' = LitE $ StringL s+  return $ os `AppE` s'+contentToExp set scope (ContentVar d) = do+  str <- escapeExp set+  return $ str `AppE` derefToExp scope d++-- ==============================================+-- Codes from Text.Hamlet that is not exposed+-- ==============================================+ unIdent :: Ident -> String unIdent (Ident s) = s @@ -159,103 +252,6 @@   TyConI (DataD _ _ _ _ cons _) <- reify typeName   [fields] <- return [fields | RecC name fields <- cons, name == conName]   return [fieldName | (fieldName, _, _) <- fields]--docToExp :: Env -> HamletRules -> Scope -> Doc -> Q Exp-docToExp env hr scope (DocForall list idents inside) = do-  let list' = derefToExp scope list-  (pat, extraScope) <- bindingPattern idents-  let scope' = extraScope ++ scope-  mh <- [|F.mapM_|]-  inside' <- docsToExp env hr scope' inside-  let lam = LamE [pat] inside'-  return $ mh `AppE` lam `AppE` list'-docToExp env hr scope (DocWith [] inside) = do-  inside' <- docsToExp env hr scope inside-  return $ inside'-docToExp env hr scope (DocWith ((deref, idents):dis) inside) = do-  let deref' = derefToExp scope deref-  (pat, extraScope) <- bindingPattern idents-  let scope' = extraScope ++ scope-  inside' <- docToExp env hr scope' (DocWith dis inside)-  let lam = LamE [pat] inside'-  return $ lam `AppE` deref'-docToExp env hr scope (DocMaybe val idents inside mno) = do-  let val' = derefToExp scope val-  (pat, extraScope) <- bindingPattern idents-  let scope' = extraScope ++ scope-  inside' <- docsToExp env hr scope' inside-  let inside'' = LamE [pat] inside'-  ninside' <--    case mno of-      Nothing -> [|Nothing|]-      Just no -> do-        no' <- docsToExp env hr scope no-        j <- [|Just|]-        return $ j `AppE` no'-  mh <- [|maybeH|]-  return $ mh `AppE` val' `AppE` inside'' `AppE` ninside'-docToExp env hr scope (DocCond conds final) = do-  conds' <- mapM go conds-  final' <--    case final of-      Nothing -> [|Nothing|]-      Just f -> do-        f' <- docsToExp env hr scope f-        j <- [|Just|]-        return $ j `AppE` f'-  ch <- [|condH|]-  return $ ch `AppE` ListE conds' `AppE` final'-  where-    go :: (Deref, [Doc]) -> Q Exp-    go (d, docs) = do-      let d' = derefToExp ((specialOrIdent, VarE 'or) : scope) d-      docs' <- docsToExp env hr scope docs-      return $ TupE [d', docs']-docToExp env hr scope (DocCase deref cases) = do-  let exp_ = derefToExp scope deref-  matches <- mapM toMatch cases-  return $ CaseE exp_ matches-  where-    toMatch :: (Binding, [Doc]) -> Q Match-    toMatch (idents, inside) = do-      (pat, extraScope) <- bindingPattern idents-      let scope' = extraScope ++ scope-      insideExp <- docsToExp env hr scope' inside-      return $ Match pat (NormalB insideExp) []-docToExp env hr v (DocContent c) = contentToExp env hr v c--contentToExp :: Env -> HamletRules -> Scope -> Content -> Q Exp-contentToExp _ hr _ (ContentRaw s) = do-  os <- [|preEscapedText . pack|]-  let s' = LitE $ StringL s-  return $ hrFromHtml hr `AppE` (os `AppE` s')-contentToExp _ hr scope (ContentVar d) = do-  str <- [|toHtml|]-  return $ hrFromHtml hr `AppE` (str `AppE` derefToExp scope d)-contentToExp env hr scope (ContentUrl hasParams d) =-  case urlRender env of-    Nothing -> error "URL interpolation used, but no URL renderer provided"-    Just wrender ->-      wrender $ \render -> do-        let render' = return render-        ou <--          if hasParams-            then [|\(u, p) -> $(render') u p|]-            else [|\u -> $(render') u []|]-        let d' = derefToExp scope d-        pet <- [|toHtml|]-        return $ hrFromHtml hr `AppE` (pet `AppE` (ou `AppE` d'))-contentToExp env hr scope (ContentEmbed d) = hrEmbed hr env $ derefToExp scope d-contentToExp env hr scope (ContentMsg d) =-  case msgRender env of-    Nothing ->-      error "Message interpolation used, but no message renderer provided"-    Just wrender ->-      wrender $ \render ->-        return $ hrFromHtml hr `AppE` (render `AppE` derefToExp scope d)-contentToExp _ hr scope (ContentAttrs d) = do-  html <- [|attrsToHtml . toAttributes|]-  return $ hrFromHtml hr `AppE` (html `AppE` derefToExp scope d)  type QueryParameters = [(Text, Text)] 
src/Text/Heterocephalus/Parse.hs view
@@ -13,7 +13,7 @@ import Text.ParserCombinators.Parsec hiding (Line) import Text.Shakespeare.Base -import Text.Hamlet.Parse hiding (parseDoc)+import Text.Hamlet.Parse  data Control   = ControlForall Deref Binding@@ -25,6 +25,15 @@   | NoControl Content   deriving (Show, Eq, Read, Data, Typeable) +data Doc = DocForall Deref Binding [Doc]+         | DocCond [(Deref, [Doc])] (Maybe [Doc])+         | DocContent Content+    deriving (Show, Eq, Read, Data, Typeable)++data Content = ContentRaw String+             | ContentVar Deref+    deriving (Show, Eq, Read, Data, Typeable)+ type UserParser a = Parsec String a  docFromString :: String -> [Doc]@@ -35,7 +44,7 @@  parseDoc :: String -> Result [Doc] parseDoc s = do-  controls <- concat <$> mapM parseLineControl (lines s)+  controls <- parseLineControl s   return $ controlsToDocs controls  controlsToDocs :: [Control] -> [Doc]@@ -111,7 +120,7 @@ lineControl = manyTill control $ try eof >> return ()  control :: UserParser () Control-control = controlHash <|> controlCaret <|> controlPercent <|> controlReg+control = controlHash <|> controlPercent <|> controlReg   where     controlPercent = do       x <- parsePercent@@ -124,16 +133,15 @@         case x of           Left str -> ContentRaw str           Right deref -> ContentVar deref-    controlCaret = do-      x <- parseCaret-      return . NoControl $-        case x of-          Left str -> ContentRaw str-          Right deref -> ContentEmbed deref-    controlReg = (NoControl . ContentRaw) <$> many (noneOf "#%^\r\n")+    controlReg = (NoControl . ContentRaw) <$> many (noneOf "#%")  parsePercent :: UserParser () (Either String Control)-parsePercent = parseControl '%'+parsePercent = do+  a <- parseControl '%'+  optional eol+  return a+ where+  eol = (char '\n' >> return ()) <|> (string "\r\n" >> return ())  parseControl :: Char -> UserParser () (Either String Control) parseControl c = do