From cfffb9e7d04d4817715f424e5d6b8f381e47009a Mon Sep 17 00:00:00 2001 From: David Baumgartner <_davidbaumgartner@bluewin.ch> Date: Thu, 2 Jan 2014 15:23:26 +0100 Subject: [PATCH] First files of v2 --- .gitignore | 5 - LICENSE | 30 +++++ Limonad.cabal | 18 +++ Limonad/Controller/CGI.hs | 0 Limonad/Controller/Server.hs | 0 Limonad/Interface/Interface.hs | 0 Limonad/Interface/Types.hs | 215 +++++++++++++++++++++++++++++++++ Limonad/Languages/Html.hs | 3 + Limonad/Markdown/Parser.hs | 64 ++++++++++ Limonad/Markdown/Types.hs | 24 ++++ Limonad/Server/Run.hs | 24 ++++ Limonad/Server/Types.hs | 22 ++++ Limonad/Templates/Eval.hs | 52 ++++++++ Limonad/Templates/Parser.hs | 67 ++++++++++ Limonad/Templates/Shortcuts.hs | 11 ++ Limonad/Templates/Types.hs | 10 ++ Limonad/Types.hs | 30 +++++ Limonad/Utils.hs | 27 +++++ Main.hs | 21 ++++ Setup.hs | 2 + main.tpl | 10 ++ test.tpl | 1 + 22 files changed, 631 insertions(+), 5 deletions(-) delete mode 100755 .gitignore create mode 100644 LICENSE create mode 100644 Limonad.cabal create mode 100644 Limonad/Controller/CGI.hs create mode 100644 Limonad/Controller/Server.hs create mode 100644 Limonad/Interface/Interface.hs create mode 100644 Limonad/Interface/Types.hs create mode 100644 Limonad/Languages/Html.hs create mode 100644 Limonad/Markdown/Parser.hs create mode 100644 Limonad/Markdown/Types.hs create mode 100644 Limonad/Server/Run.hs create mode 100644 Limonad/Server/Types.hs create mode 100644 Limonad/Templates/Eval.hs create mode 100644 Limonad/Templates/Parser.hs create mode 100644 Limonad/Templates/Shortcuts.hs create mode 100644 Limonad/Templates/Types.hs create mode 100644 Limonad/Types.hs create mode 100644 Limonad/Utils.hs create mode 100644 Main.hs create mode 100644 Setup.hs create mode 100644 main.tpl create mode 100644 test.tpl diff --git a/.gitignore b/.gitignore deleted file mode 100755 index 87b5ba8..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.hi -*.o -.gitignore -limonad -errors.log diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..950058e --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2013, David Baumgartner + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of David Baumgartner nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Limonad.cabal b/Limonad.cabal new file mode 100644 index 0000000..47f3960 --- /dev/null +++ b/Limonad.cabal @@ -0,0 +1,18 @@ +name: Limonad +version: 1.2.0.1 +synopsis: limonad is a web interface for Darcs DCVS +-- description: +homepage: http://geekschmerzen.org/limonad/ +license: BSD3 +license-file: LICENSE +author: David Baumgartner +maintainer: ch.davidbaumgartner@gmail.com +-- copyright: +category: Web +build-type: Simple +cabal-version: >=1.8 + +executable limonad + main-is: Main + -- other-modules: + build-depends: base ==4.5.*, MissingH ==1.2.*, parsec ==3.1.*, mime==0.3.* \ No newline at end of file diff --git a/Limonad/Controller/CGI.hs b/Limonad/Controller/CGI.hs new file mode 100644 index 0000000..e69de29 diff --git a/Limonad/Controller/Server.hs b/Limonad/Controller/Server.hs new file mode 100644 index 0000000..e69de29 diff --git a/Limonad/Interface/Interface.hs b/Limonad/Interface/Interface.hs new file mode 100644 index 0000000..e69de29 diff --git a/Limonad/Interface/Types.hs b/Limonad/Interface/Types.hs new file mode 100644 index 0000000..4349391 --- /dev/null +++ b/Limonad/Interface/Types.hs @@ -0,0 +1,215 @@ +module Limonad.Interface.Types where + + import qualified Data.ByteString as B + import qualified Data.Time as Time + import qualified Codec.MIME.Type as MIME + import qualified Limonad.Templates.Shortcuts as Tpl + import Data.Functor + import Limonad.Utils + import Limonad.Types + + data Content = Content (IO B.ByteString) + + data View = View { + getStatusCode :: HttpStatus, + getMimeType :: MIME.Type, + getDate :: Maybe Time.UTCTime, + getExpire :: Maybe Time.UTCTime, + getContent :: Content + } + + data Request = Request { + getMethod :: Maybe String, + getParams :: [(String, String)], + getRefer :: Maybe String, + getHost :: Maybe String + } + + data Route = Route { + getPath :: String, + getAllowedMethod :: [String] + } + + type Routes = [Route] + + class Status a where + code :: a -> Int + string :: a -> String + + data HttpStatus = Continue + | SwitchingProtocols + | Ok + | Created + | Accepted + | NonAuthoritativeInformation + | NoContent + | ResetContent + | PartialContent + | MultipleChoices + | MovedPermanently + | Found + | SeeOther + | NotModified + | UseProxy + | TemporaryRedirect + | BadRequest + | Unauthorized + | PaymentRequired + | Forbidden + | NotFound + | MethodNotAllowed + | NotAcceptable + | ProxyAuthenticationRequired + | RequestTimeout + | Conflict + | Gone + | LengthRequired + | PreconditionFailed + | RequestEntityTooLarge + | RequestURITooLong + | UnsupportedMediaType + | RequestedRangeNotSatisfiable + | ExpectationFailed + | IMATeapot + | InternalServerError + | NotImplemented + | BadGateway + | ServiceUnavailable + | GatewayTimeout + | HTTPVersionNotSupported + + instance Status HttpStatus where + code Continue = 100 + code SwitchingProtocols = 101 + code Ok = 200 + code Created = 201 + code Accepted = 202 + code NonAuthoritativeInformation = 203 + code NoContent = 204 + code ResetContent = 205 + code PartialContent = 206 + code MultipleChoices = 300 + code MovedPermanently = 301 + code Found = 302 + code SeeOther = 303 + code NotModified = 304 + code UseProxy = 305 + code TemporaryRedirect = 307 + code BadRequest = 400 + code Unauthorized = 401 + code PaymentRequired = 402 + code Forbidden = 403 + code NotFound = 404 + code MethodNotAllowed = 405 + code NotAcceptable = 406 + code ProxyAuthenticationRequired = 407 + code RequestTimeout = 408 + code Conflict = 409 + code Gone = 410 + code LengthRequired = 411 + code PreconditionFailed = 412 + code RequestEntityTooLarge = 413 + code RequestURITooLong = 414 + code UnsupportedMediaType = 415 + code RequestedRangeNotSatisfiable = 416 + code ExpectationFailed = 417 + code IMATeapot = 418 + code InternalServerError = 500 + code NotImplemented = 501 + code BadGateway = 502 + code ServiceUnavailable = 503 + code GatewayTimeout = 504 + code HTTPVersionNotSupported = 505 + string Continue = "Continue" + string SwitchingProtocols = "Switching Protocols" + string Ok = "OK" + string Created = "Created" + string Accepted = "Accepted" + string NonAuthoritativeInformation = "Non-Authoritative Information" + string NoContent = "No Content" + string ResetContent = "Reset Content" + string PartialContent = "Partial Content" + string MultipleChoices = "Multiple Choices" + string MovedPermanently = "Moved Permanently" + string Found = "Found" + string SeeOther = "See Other" + string NotModified = "Not Modified" + string UseProxy = "Use Proxy" + string TemporaryRedirect = "Temporary Redirect" + string BadRequest = "Bad Request" + string Unauthorized = "Unauthorized" + string PaymentRequired = "Payment Required" + string Forbidden = "Forbidden" + string NotFound = "Not Found" + string MethodNotAllowed = "Method Not Allowed" + string NotAcceptable = "Not Acceptable" + string ProxyAuthenticationRequired = "Proxy Authentication Required" + string RequestTimeout = "Request Timeout" + string Conflict = "Conflict" + string Gone = "Gone" + string LengthRequired = "Length Required" + string PreconditionFailed = "Precondition Failed" + string RequestEntityTooLarge = "Request Entity Too Large" + string ExpectationFailed = "Expectation Failed" + string IMATeapot = "I'm a teapot" + string InternalServerError = "Internal Server Error" + string NotImplemented = "Not Implemented" + string BadGateway = "Bad Gateway" + string ServiceUnavailable = "Service Unavailable" + string GatewayTimeout = "Gateway Timeout" + string HTTPVersionNotSupported = "HTTP Version Not Supported" + + instance Show HttpStatus where + show a = show $ code a ++ string a + + text :: IO String -> View + text c = View { + getMimeType = MIME.nullType, + getDate = Nothing, + getExpire = Nothing, + getContent = Content (s2bs <$> c) + } + + html :: IO B.ByteString -> View + html c = View { + getMimeType = MIME.Type { mimeType = MIME.Application "xhtml+xml", mimeParams = [] }, + getDate = Nothing, + getExpire = Nothing, + getContent = Content $ c + } + + tpl :: FilePath -> Env -> View + tpl f e = View { + getMimeType = MIME.Type { mimeType = MIME.Application "xhtml+xml", mimeParams = [] }, + getDate = Nothing, + getExpire = Nothing, + getContent = Content (s2bs <$> Tpl.renderFile f e) + } + + css :: IO B.ByteString -> View + css c = View { + getMimeType = MIME.Type { mimeType = MIME.Text "css", mimeParams = [] }, + getDate = Nothing, + getExpire = Nothing, + getContent = Content $ c + } + + js :: IO B.ByteString -> View + js c = View { + getMimeType = MIME.Type { mimeType = MIME.Text "javascript", mimeParams = [] }, + getDate = Nothing, + getExpire = Nothing, + getContent = Content $ c + } + + mkEnv :: Request -> Env + mkEnv r = Env [Static a b |(a, b) <- getParams r] + + defaultRoute :: String -> Route + defaultRoute p = Route { getPath = p } + + (/+) :: Routes -> String -> Routes + (/+) r n = r /: (defaultRoute n) + + (/:) :: Routes -> Route -> Routes + (/:) r n = n:r diff --git a/Limonad/Languages/Html.hs b/Limonad/Languages/Html.hs new file mode 100644 index 0000000..29578fc --- /dev/null +++ b/Limonad/Languages/Html.hs @@ -0,0 +1,3 @@ +module Limonad.Languages.Html where + + \ No newline at end of file diff --git a/Limonad/Markdown/Parser.hs b/Limonad/Markdown/Parser.hs new file mode 100644 index 0000000..507ef87 --- /dev/null +++ b/Limonad/Markdown/Parser.hs @@ -0,0 +1,64 @@ +module Limonad.Markdown.Parser where + + import Limonad.Markdown.Types + import Text.ParserCombinators.Parsec + import Control.Applicative hiding (many, (<|>)) + import Data.Functor + + dnl = newline >> newline + + parseMarkdown :: String -> [Markdown] + parseMarkdown input = case parse parseAll "markdown error" input of + Right template -> template + Left err -> error $ show err + + parseAll :: Parser [Markdown] + parseAll = many (try parseHeaders <|> try parseQuote <|> try parseStrongEmphasis <|> parseEmphasis) + + parseHeaders :: Parser Markdown + parseHeaders = try (mkParserHeader 1 Header1) <|> try (mkParserHeader 2 Header2) <|> try (mkParserHeader 3 Header3) <|> try (mkParserHeader 4 Header4) <|> try (mkParserHeader 5 Header5) <|> try (mkParserHeader 6 Header6) <|> try parseHeader1F <|> parseHeader2F + + parseHeader1F :: Parser Markdown + parseHeader1F = do + title <- many1 alphaNum + many (char '=') + return $ Header1 title + + parseHeader2F :: Parser Markdown + parseHeader2F = do + title <- many1 alphaNum + many (char '-') + return $ Header1 title + + mkParserHeader :: Int -> (String -> Markdown) -> Parser Markdown + mkParserHeader cnt t = do + string (take cnt $ repeat '#') + spaces + title <- many1 alphaNum + try $ string (take cnt $ repeat '#') + return $ t title + + parseQuote :: Parser Markdown + parseQuote = do + c <- manyTill (char '>' <* anyChar) dnl + case (parse parseAll "markdown error" $ concat $ map (drop 1) $ lines c) of + Right a -> return $ Quote a + Left e -> error $ show e + + parseParagraph :: Parser Markdown + parseParagraph = do + c <- manyTill anyToken dnl + case (parse parseAll "markdown error" c) of + Right a -> return $ Paragraph a + Left e -> error $ show e + + parseEmphasis :: Parser Markdown + parseEmphasis = do + t <- try (between (char '*') (char '*') anyChar) <|> (between (char '_') (char '_') anyChar) + return $ Emphasis [t] + + parseStrongEmphasis :: Parser Markdown + parseStrongEmphasis = do + t <- try (between (string "**") (string "**") anyChar) <|> (between (string "__") (string "__") anyChar) + return $ StrongEmphasis [t] + diff --git a/Limonad/Markdown/Types.hs b/Limonad/Markdown/Types.hs new file mode 100644 index 0000000..cd23d4e --- /dev/null +++ b/Limonad/Markdown/Types.hs @@ -0,0 +1,24 @@ +module Limonad.Markdown.Types where + + data Markdown = Header1 String + | Header2 String + | Header3 String + | Header4 String + | Header5 String + | Header6 String + | Paragraph [Markdown] -- + | Quote [Markdown] + | Emphasis String + | StrongEmphasis String + | List [Markdown] + | OrderedList [Markdown] + | Link String (Maybe String) [Markdown] + | ReferenceCall String String + | ReferenceDef String String + | InlineReference String String + | Image String (Maybe String) String + | ImageCall String String + | ImageRef String String (Maybe String) + | Code (Maybe String) [String] + | InlineClode String + deriving (Eq, Ord, Show) \ No newline at end of file diff --git a/Limonad/Server/Run.hs b/Limonad/Server/Run.hs new file mode 100644 index 0000000..5180b7c --- /dev/null +++ b/Limonad/Server/Run.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE OverloadedStrings #-} +module Limonad.Server.Run (run) where + + import Limonad.Server.Types + import Limonad.Utils + import qualified Data.ByteString as B + import Network hiding (accept) + import Network.Socket + import Network.Socket.ByteString (sendAll) + import Control.Concurrent + + send :: View -> IO () + + handle :: Socket -> IO () + handle sock = do + sendAll + + loop sock = accept sock >>= + \(conn, addr) -> + forkIO $ handle sock >> loop sock + + run :: Config -> IO () + run config = + withSocketsDo (listenOn $ getPort config >>= loop) diff --git a/Limonad/Server/Types.hs b/Limonad/Server/Types.hs new file mode 100644 index 0000000..b806cdf --- /dev/null +++ b/Limonad/Server/Types.hs @@ -0,0 +1,22 @@ +module Limonad.Server.Types where + + import Network.Socket (PortNumber, HostAddress) + import System.Directory + import Limonad.Interface.Types + + data Config = Config { + getPort :: PortNumber, + getPhysicalRoot :: String, + getLogicalRoot :: String, + getRoutes :: Routes + } + + defaultConfig :: IO Config + defaultConfig = do + getCurrentDirectory >>= \pwd -> + Config { + getPort = PortNumber 3000, + getPhysicalRoot = pwd, + getLogicalRoot = "/", + getRoutes = [], + } \ No newline at end of file diff --git a/Limonad/Templates/Eval.hs b/Limonad/Templates/Eval.hs new file mode 100644 index 0000000..9dece35 --- /dev/null +++ b/Limonad/Templates/Eval.hs @@ -0,0 +1,52 @@ +module Limonad.Templates.Eval (evaluate, evalT) where + + import Limonad.Templates.Types + import Limonad.Templates.Parser + import qualified Data.String.Utils as U + import Data.Functor + + lookupEnv env n = + if ln == 1 then + lookupV env n + else + lookupV (Env (case lookupV env $ head n' of + Dictionnary _ l -> l + otherwise -> error "Something went wrong…")) (head $ drop 1 n') + where + n' = U.split "." n + ln = length n' + + evaluate :: Env -> [Template] -> IO String + evaluate env templates = do + v <- sequence $ map (evalT env) templates + return $ U.join "" v + + evalT :: Env -> Template -> IO String + evalT env (Var n) = + case lookupEnv env n of + Static _ v -> + return v + v -> + error "Showing a variable requires it to be a Static" + evalT env@(Env e) (For o n t) = + let + v = case lookupEnv env o of + Dictionnary _ r -> r + _ -> error $ "Unvalid dict" + in + (U.join "" <$> sequence [evaluate en t | l <- v, (case l of + Dictionnary _ _ -> True + List _ -> True + _ -> False), + let en = case l of + Dictionnary _ c -> + Env ((Dictionnary n c):e) + List c -> + Env ((Dictionnary n c):e) + _ -> env]) + evalT _ (Comment s) = + return $ "" + evalT env (Include p) = + (readFile p) >>= evaluate env . parseTemplate + evalT _ (Aside s) = + return s \ No newline at end of file diff --git a/Limonad/Templates/Parser.hs b/Limonad/Templates/Parser.hs new file mode 100644 index 0000000..b946126 --- /dev/null +++ b/Limonad/Templates/Parser.hs @@ -0,0 +1,67 @@ +module Limonad.Templates.Parser (parseTemplate) where + + import Limonad.Templates.Types + import Text.ParserCombinators.Parsec + import Control.Applicative hiding (many, (<|>)) + import Data.Functor + + parseTemplate :: String -> [Template] + parseTemplate input = case parse parseAll "template error" input of + Right template -> template + Left err -> error $ show err + + parseAll :: Parser [Template] + parseAll = many (try parseVariable <|> try parseFor <|> try parseComment <|> try parseInclude <|> parseAside) + + parseVariable :: Parser Template + parseVariable = do + string "{{" "tag begin" + spaces + id <- many1 (alphaNum <|> char '.') + spaces + string "}}" "tag end" + return $ Var id + + parseFor :: Parser Template + parseFor = do + string "{%" "call begin" + spaces + string "for" "for" + spaces + replace <- many1 (alphaNum <|> char '.') + spaces + string "in" "in" + spaces + basename <- many1 (alphaNum <|> char '.') + spaces + content <- between (string "%}") (string "{%") parseAll + spaces + string "endfor" "endfor" + spaces + string "%}" "call end" + return $ For basename replace content + + parseComment :: Parser Template + parseComment = do + string "{#" "comment" + spaces + content <- many1 (noneOf "#" <|> try (char '#' <* notFollowedBy (char '}'))) + spaces + string "#}" "end of comment" + return $ Comment content + + parseInclude :: Parser Template + parseInclude = do + string "{%" "call begin" + spaces + string "include" "include" + spaces + path <- many1 (alphaNum <|> char '.' <|> char '/' <|> char '\\') + spaces + string "%}" "call end" + return $ Include path + + parseAside :: Parser Template + parseAside = do + content <- many1 (noneOf "{" <|> try (char '{' <* notFollowedBy (char '{' <|> char '%' <|> char '#'))) + return $ Aside content \ No newline at end of file diff --git a/Limonad/Templates/Shortcuts.hs b/Limonad/Templates/Shortcuts.hs new file mode 100644 index 0000000..8f9d9f0 --- /dev/null +++ b/Limonad/Templates/Shortcuts.hs @@ -0,0 +1,11 @@ +module Limonad.Templates.Shortcuts where + + import Limonad.Templates.Types + import Limonad.Templates.Parser + import Limonad.Templates.Eval + + renderString :: Env -> String -> IO String + renderString env = evaluate env . parseTemplate + + renderFile :: Env -> FilePath -> IO String + renderFile env f = readFile f >>= renderString env \ No newline at end of file diff --git a/Limonad/Templates/Types.hs b/Limonad/Templates/Types.hs new file mode 100644 index 0000000..1a51513 --- /dev/null +++ b/Limonad/Templates/Types.hs @@ -0,0 +1,10 @@ +module Limonad.Templates.Types (Variable(..), Template(..), Env(..), lookupV) where + + import Limonad.Types + + data Template = Var String + | For String String [Template] + | Comment String + | Include String + | Aside String + deriving (Eq, Show) \ No newline at end of file diff --git a/Limonad/Types.hs b/Limonad/Types.hs new file mode 100644 index 0000000..1ab10f7 --- /dev/null +++ b/Limonad/Types.hs @@ -0,0 +1,30 @@ +module Limonad.Types where + + data Variable = Static String String + | List [Variable] + | Dictionnary String [Variable] + deriving (Eq, Show) + + data Env = Env [Variable] + deriving (Eq, Show) + + lookupV :: Env -> String -> Variable + lookupV (Env a) s = lookupV' a + where + lookupV' ((l@(Static ls _)):xs) + | ls == s = l + | otherwise = lookupV' xs + lookupV' ((l@(Dictionnary ls _)):xs) + | ls == s = l + | otherwise = lookupV' xs + lookupV' [] = error $ "Nothing matching " ++ s ++ " was found !" + lookupV' (_:xs) = lookupV' xs + + (%+) :: Env -> Env -> Env + (%+) (Env a) (Env b) = Env (a++b) + + (%:) :: Env -> Variable -> Env + (%:) (Env a) v = Env (v:a) + + emptyEnv :: Env + emptyEnv = Env [] \ No newline at end of file diff --git a/Limonad/Utils.hs b/Limonad/Utils.hs new file mode 100644 index 0000000..bb67ef1 --- /dev/null +++ b/Limonad/Utils.hs @@ -0,0 +1,27 @@ +module Limonad.Utils where + + -- |Joins the element + join :: [a] -> [[a]] -> [a] + join d = concat . intersperse d + + -- |Formats time + formatTime :: Int -> String + formatTime t + | e <= 0 = "just now" + | e <= 1 = "a second" ++ a + | e <= 59 = s e 1 " seconds" ++ a + | e <= 119 = "a minute" ++ a + | e <= 3540 = s e 60 " minutes" ++ a + | e <= 7100 = "an hour" ++ a + | e <= 82800 = s (e+99) (60*60) " hours" ++ a + | e <= 172000 = "a day" ++ a + | e <= 518400 = s (e+800) (60*60*24) "days" ++ a + | e <= 1036800 = "a week" ++ a + | otherwise = s (e+180000) (60*60*24*7) " weeks" ++ a + where + e = abs t :: Float + s m l = (++) (show $ round (m/l)) + a = if t > 0 then + " ago" + else + " in the future" \ No newline at end of file diff --git a/Main.hs b/Main.hs new file mode 100644 index 0000000..cf6e8cb --- /dev/null +++ b/Main.hs @@ -0,0 +1,21 @@ +import Limonad.Templates.Shortcuts +import Limonad.Templates.Types +import Limonad.Types + +env = emptyEnv %: + Static "coucou" "prout" + %: + Static "hello" "blabla" + %: + Dictionnary "patate" [List [Dictionnary "frite" [ + List [Static "tr" "de"], + List [Static "tr" "pain"]] + , Static "lorem" "ipsum"], + List [Dictionnary "frite" [ + List [Static "tr" "op"]] + , Static "lorem" "dolor"] + ] + +main :: IO () +main = do + renderFile env "main.tpl" >>= putStrLn \ No newline at end of file diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/main.tpl b/main.tpl new file mode 100644 index 0000000..69b7723 --- /dev/null +++ b/main.tpl @@ -0,0 +1,10 @@ +

{{ hello }}

+

{% include test.tpl %}

+{% for toto in patate %} +

{{ toto.lorem }}

+ +{% endfor %} \ No newline at end of file diff --git a/test.tpl b/test.tpl new file mode 100644 index 0000000..8649bac --- /dev/null +++ b/test.tpl @@ -0,0 +1 @@ +Lorem ipsum {{ hello }} amet. \ No newline at end of file