packages feed

mustache-haskell (empty) → 0.1.0.0

raw patch · 10 files changed

+854/−0 lines, 10 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, directory, mustache-haskell, optparse-applicative, parsec, pretty-show, scientific, text, transformers, unordered-containers, vector

Files

+ 5.txt view
@@ -0,0 +1,336 @@+   NAME SYNOPSIS DESCRIPTION TAG TYPES COPYRIGHT SEE ALSO++                                 1. mustache(5)+                                                           2. Mustache Manual+                                                               3. mustache(5)++NAME++   mustache - Logic-less templates.++SYNOPSIS++   A typical Mustache template:++ Hello {{name}}+ You have just won {{value}} dollars!+ {{#in_ca}}+ Well, {{taxed_value}} dollars, after taxes.+ {{/in_ca}}++   Given the following hash:++ {+   "name": "Chris",+   "value": 10000,+   "taxed_value": 10000 - (10000 * 0.4),+   "in_ca": true+ }++   Will produce the following:++ Hello Chris+ You have just won 10000 dollars!+ Well, 6000.0 dollars, after taxes.++DESCRIPTION++   Mustache can be used  for HTML, config files,  source code - anything.  It+   works by expanding tags in a template  using values provided in a hash  or+   object.++   We call it "logic-less" because there are no if statements, else  clauses,+   or for loops. Instead there are only  tags. Some tags are replaced with  a+   value, some nothing, and others a series of values. This document explains+   the different types of Mustache tags.++TAG TYPES++   Tags are indicated  by the double  mustaches. {{person}} is  a tag, as  is+   {{#person}}. In both examples, we'd refer to person as the key or tag key.+   Let's talk about the different types of tags.++  Variables++   The most  basic tag  type  is the  variable. A  {{name}}  tag in  a  basic+   template will try to find the name key in the current context. If there is+   no name key, the parent contexts  will be checked recursively. If the  top+   context is reached and the  name key is still  not found, nothing will  be+   rendered.++   All variables are HTML escaped by default. If you want to return unescaped+   HTML, use the triple mustache: {{{name}}}.++   You can also use & to unescape a variable: {{& name}}. This may be  useful+   when changing delimiters (see "Set Delimiter" below).++   By default a variable "miss" returns an empty string. This can usually  be+   configured in your Mustache library. The Ruby version of Mustache supports+   raising an exception in this situation, for instance.++   Template:++ * {{name}}+ * {{age}}+ * {{company}}+ * {{{company}}}++   Hash:++ {+   "name": "Chris",+   "company": "<b>GitHub</b>"+ }++   Output:++ * Chris+ *+ * &lt;b&gt;GitHub&lt;/b&gt;+ * <b>GitHub</b>++  Sections++   Sections render blocks of text one  or more times, depending on the  value+   of the key in the current context.++   A section begins with a pound and ends with a slash. That is,  {{#person}}+   begins a "person" section while {{/person}} ends it.++   The behavior of the section is determined by the value of the key.++   False Values or Empty Lists++   If the person key exists  and has a value of  false or an empty list,  the+   HTML between the pound and slash will not be displayed.++   Template:++ Shown.+ {{#person}}+   Never shown!+ {{/person}}++   Hash:++ {+   "person": false+ }++   Output:++ Shown.++   Non-Empty Lists++   If the person key exists and has  a non-false value, the HTML between  the+   pound and slash will be rendered and displayed one or more times.++   When the  value  is a  non-empty  list, the  text  in the  block  will  be+   displayed once for each item in the list. The context of the block will be+   set to the current item for each  iteration. In this way we can loop  over+   collections.++   Template:++ {{#repo}}+   <b>{{name}}</b>+ {{/repo}}++   Hash:++ {+   "repo": [+     { "name": "resque" },+     { "name": "hub" },+     { "name": "rip" }+   ]+ }++   Output:++ <b>resque</b>+ <b>hub</b>+ <b>rip</b>++   Lambdas++   When the value is  a callable object,  such as a  function or lambda,  the+   object will be invoked and  passed the block of  text. The text passed  is+   the literal block, unrendered. {{tags}} will not have been expanded -  the+   lambda should do that on its own. In this way you can implement filters or+   caching.++   Template:++ {{#wrapped}}+   {{name}} is awesome.+ {{/wrapped}}++   Hash:++ {+   "name": "Willy",+   "wrapped": function() {+     return function(text, render) {+       return "<b>" + render(text) + "</b>"+     }+   }+ }++   Output:++ <b>Willy is awesome.</b>++   Non-False Values++   When the value is non-false but not a list, it will be used as the context+   for a single rendering of the block.++   Template:++ {{#person?}}+   Hi {{name}}!+ {{/person?}}++   Hash:++ {+   "person?": { "name": "Jon" }+ }++   Output:++ Hi Jon!++  Inverted Sections++   An inverted section begins with a caret (hat) and ends with a slash.  That+   is {{^person}} begins a "person"  inverted section while {{/person}}  ends+   it.++   While sections can be used to render  text one or more times based on  the+   value of the  key, inverted  sections may render  text once  based on  the+   inverse value  of the  key. That  is, they  will be  rendered if  the  key+   doesn't exist, is false, or is an empty list.++   Template:++ {{#repo}}+   <b>{{name}}</b>+ {{/repo}}+ {{^repo}}+   No repos :(+ {{/repo}}++   Hash:++ {+   "repo": []+ }++   Output:++ No repos :(++  Comments++   Comments begin with a bang and are ignored. The following template:++ <h1>Today{{! ignore me }}.</h1>++   Will render as follows:++ <h1>Today.</h1>++   Comments may contain newlines.++  Partials++   Partials begin with a greater than sign, like {{> box}}.++   Partials are  rendered  at  runtime  (as  opposed  to  compile  time),  so+   recursive partials are possible. Just avoid infinite loops.++   They   also    inherit    the    calling   context.    Whereas    in    an+   [ERB](http://en.wikipedia.org/wiki/ERuby) file you may have this:++ <%= partial :next_more, :start => start, :size => size %>++   Mustache requires only this:++ {{> next_more}}++   Why? Because the next_more.mustache file  will inherit the size and  start+   methods from the calling context.++   In this way you  may want to  think of partials  as includes, or  template+   expansion, even though it's not literally true.++   For example, this template and partial:++ base.mustache:+ <h2>Names</h2>+ {{#names}}+   {{> user}}+ {{/names}}++ user.mustache:+ <strong>{{name}}</strong>++   Can be thought of as a single, expanded template:++ <h2>Names</h2>+ {{#names}}+   <strong>{{name}}</strong>+ {{/names}}++  Set Delimiter++   Set Delimiter tags start with an equal sign and change the tag  delimiters+   from {{ and }} to custom strings.++   Consider the following contrived example:++ * {{default_tags}}+ {{=<% %>=}}+ * <% erb_style_tags %>+ <%={{ }}=%>+ * {{ default_tags_again }}++   Here we have a list with three items. The first item uses the default  tag+   style, the second uses erb style as defined by the Set Delimiter tag,  and+   the third returns  to the default  style after yet  another Set  Delimiter+   declaration.++   According to ctemplates,  this "is  useful for languages  like TeX,  where+   double-braces may occur in the text and are awkward to use for markup."++   Custom delimiters may not contain whitespace or the equals sign.++COPYRIGHT++   Mustache is Copyright (C) 2009 Chris Wanstrath++   Original CTemplate by Google++SEE ALSO++   mustache(1), http://mustache.github.io/++                                   1. DEFUNKT+                                                                2. April 2014+                                                               3. mustache(5)++References++   Visible links+   . http://mustache.github.io/mustache.5.html#NAME+   . http://mustache.github.io/mustache.5.html#SYNOPSIS+   . http://mustache.github.io/mustache.5.html#DESCRIPTION+   . http://mustache.github.io/mustache.5.html#TAG-TYPES+   . http://mustache.github.io/mustache.5.html#COPYRIGHT+   . http://mustache.github.io/mustache.5.html#SEE-ALSO+   . http://google-ctemplate.googlecode.com/svn/trunk/doc/howto.html+   . http://mustache.github.io/mustache.1.ron.html+   . http://mustache.github.io/
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 Daniel Choi++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Main.hs view
@@ -0,0 +1,43 @@+module Main where+import Text.Mustache+import qualified Text.Show.Pretty as Pr+import System.Environment+import Data.Aeson+import qualified Data.Text.Lazy.IO as TL+import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.Text.Lazy.IO as TL+import qualified Data.Text.Lazy.Builder as B+import Data.Maybe+import qualified Text.Show.Pretty as Pr+import Options.Applicative++data Options = Options Bool FilePath deriving (Show)++parseOpts :: Parser Options+parseOpts = Options +    <$> (flag False True (short 'c' <> help "Just output parse tree of template file"))+    <*> (argument str (metavar "FILE"))++opts = info +          (helper <*> parseOpts)+          (fullDesc +            <> progDesc "A Haskell implementation of Mustache templates.\nOn STDIN provide the JSON to insert into the template."+            <> header "mus v0.1.0.0")++main = do+    opts' <- execParser opts +    case opts' of+      Options True file -> do+        s <- readFile file+        xs <- readTemplate s+        putStrLn $ Pr.ppShow xs+      Options False file -> do+        s <- readFile file+        chunks <- readTemplate s+        input <- BL.getContents+        let value = fromJust $ decode input+        let res = runTemplate chunks value+        TL.putStrLn . B.toLazyText $ res+++
+ README.md view
@@ -0,0 +1,67 @@+# mustache-haskell++A Haskell implementation of mustache templates.++Should be compatible with the the [mustache+specification](http://mustache.github.io/mustache.5.html).+++## Install++You need the Haskell platform on your system.++```+cabal install mustache+```++Or alternatively++``` +git clone git@github.com:danchoi/mustache-haskell.git+cd mustache-haskell+cabal sandbox init+cabal install+# Now copy .cabal-sandbox/bin/mus to your PATH+```++## Usage++```+mus template.mustache < input.json+```++```+mus v0.1.0.0++Usage: mus [-c] FILE+  A Haskell implementation of Mustache templates. On STDIN provide the JSON to+  insert into the template.++Available options:+  -h,--help                Show this help text+  -c                       Just output parse tree of template file+```++## List separator syntax++mustache-haskell adds one additional feature to the mustache specification.  If+you are outputing elements of a list, you can designate an optional list+separator with the following syntax:+++```+{{#hobbies, }}{{#name}}{{/hobbies}}+```++This designates `", "` as the list separator and will output++```+sewing, brewing, cooking+```++when the input is ++```json+{"hobbies":[{"name":"sewing"},{"name":"brewing"},{"name":"cooking"}]}+```+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/Mustache.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Text.Mustache (+  runTemplate+, module Text.Mustache.Parse+) where+import Text.Mustache.Types+import Text.Mustache.Parse+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Lazy.IO as TL+import qualified Data.Text.Lazy.Builder as B+import qualified Data.Text.Lazy.Builder.Int as B+import qualified Data.Text.Lazy.Builder.RealFloat as B+import Data.List (intersperse)+import Data.Monoid+import Data.Aeson+import qualified Data.HashMap.Lazy as HM+import qualified Data.Vector as V+import Data.Scientific ++------------------------------------------------------------------------ +-- | Evaluation functions++runTemplate :: [Chunk] -> Value -> B.Builder+runTemplate xs v = mconcat+    $ map (chunkToBuilder v) xs++chunkToBuilder :: Value -> Chunk -> B.Builder+chunkToBuilder v (Var k) = evalToBuilder True k v+chunkToBuilder v (UnescapedVar k) = evalToBuilder False k v  +chunkToBuilder v (Comment _) = mempty+chunkToBuilder v (SetDelimiter _ _) = mempty+chunkToBuilder v (Plain x) = B.fromText x+chunkToBuilder v (Section ks chunks sep) = +    case evalKeyPath ks v of +      Array v' -> +          let evalItem :: Value -> B.Builder+              evalItem loopValue = mconcat $ map (chunkToBuilder $ mergeValues v loopValue) chunks+          in mconcat $ intersperse (maybe mempty B.fromText sep) $ map evalItem $ V.toList v'+      x@(Object _) -> mconcat $ map (chunkToBuilder $ mergeValues v x) chunks +      _ -> mempty+chunkToBuilder v (InvertedSection ks chunks) = +    case evalKeyPath ks v of+      Null -> chunkToBuilder v (Section (init ks) chunks Nothing)+      Bool False -> chunkToBuilder v (Section (init ks) chunks Nothing)+      _ -> mempty+chunkToBuilder v (Partial s) = B.fromText $ "{{ERROR: include partial " <> T.pack s <> "}}"++mergeValues :: Value -> Value -> Value+mergeValues (Object outer) (Object inner) = Object $ HM.union inner outer+mergeValues _ inner = inner++evalToBuilder :: Bool -> KeyPath -> Value -> B.Builder+evalToBuilder escape k v = valToBuilder escape $ evalKeyPath k v++-- evaluates the a JS key path against a Value context to a leaf Value+evalKeyPath :: KeyPath -> Value -> Value+evalKeyPath [] x@(String _) = x+evalKeyPath [] x@Null = x+evalKeyPath [] x@(Number _) = x+evalKeyPath [] x@(Bool _) = x+evalKeyPath [] x@(Object _) = x+evalKeyPath (Key ".":[]) x = x+evalKeyPath [] x@(Array _) = x +evalKeyPath (Key key:ks) (Object s) = +    case (HM.lookup key s) of+        Just x          -> evalKeyPath ks x+        Nothing -> Null+evalKeyPath (Index idx:ks) (Array v) = +      let e = (V.!?) v idx+      in case e of +        Just e' -> evalKeyPath ks e'+        Nothing -> Null+evalKeyPath ((Index _):_) _ = Null+evalKeyPath _ _ = Null++valToBuilder :: Bool -> Value -> B.Builder+valToBuilder True (String x) = B.fromText . htmlEscape $ x+valToBuilder False (String x) = B.fromText x+valToBuilder _ Null = B.fromText "null"+valToBuilder _ (Bool True) = B.fromText "true"+valToBuilder _ (Bool False) = B.fromText "false"+valToBuilder _ (Number x) = +    case floatingOrInteger x of+        Left float -> B.realFloat float+        Right int -> B.decimal int+valToBuilder _ (Object x) = B.fromText . T.pack . show $ x++++-- | Escape HTML symbols+-- adapted from Hastache.hs -- thank you+htmlEscape :: T.Text -> T.Text+htmlEscape = T.concatMap proc+  where+    proc '&'  = "&amp;"+    proc '\\' = "&#92;"+    proc '"'  = "&quot;"+    proc '\'' = "&#39;"+    proc '<'  = "&lt;"+    proc '>'  = "&gt;"+    proc h    = T.singleton h+
+ Text/Mustache/Parse.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Text.Mustache.Parse (+    runParse+  , readTemplate+) where+import Text.Mustache.Types+import Text.Parsec+import Data.Functor.Identity+import Control.Applicative hiding (many, (<|>))+import Data.Text (Text)+import qualified Data.Text as T+import Data.Monoid+import System.Directory ++-- Custom delimiters may not contain whitespace or the equals sign.+type DelimiterState = (String, String)  -- left and right delimiters++defDelimiters = ("{{", "}}")++type Parser a = ParsecT String DelimiterState Identity a++readTemplate :: String -> IO [Chunk]+readTemplate = loadPartials . runParse ++-- | preload partials++loadPartials :: [Chunk] -> IO [Chunk]+loadPartials xs = mapM loadPartial xs >>= return . concat++loadPartial :: Chunk -> IO [Chunk]+loadPartial (Partial path) = do+      e <- doesFileExist path+      if e +      then do+        s <- readFile path+        return $ runParse s +      else error $ "Partial file missing: " ++ path+loadPartial (Section k cs sep) = do+      cs' <- mapM loadPartial cs+      return [Section k (concat cs') sep]+loadPartial (InvertedSection k cs) = do+      cs' <- mapM loadPartial cs+      return [InvertedSection k (concat cs')]+loadPartial x = return [x]+++runParse :: String -> [Chunk]+runParse input = +    case (runParserT (many chunk) defDelimiters "" input) of+        Identity (Left x) -> error $ "parser failed: " ++ show x+        Identity (Right xs') -> xs'++delimiters :: Monad m => ParsecT s DelimiterState m DelimiterState+delimiters = getState ++leftDelimiter :: Parser String+leftDelimiter = do +    (x,_) <- delimiters +    string x <* spaces++rightDelimiter = do+    (_,x) <- delimiters +    string x ++inDelimiters p = (between leftDelimiter (spaces >> rightDelimiter) p) <?> "inDelimiters"++varname :: Parser String+varname = (many1 (alphaNum <|> oneOf ".[]0-9_")) <?> "varname"++chunk :: Parser Chunk+chunk = choice [+      try unescapedVar+    , try var+    , try section+    , try invertedSection+    , try setDelimiter+    , try partial+    , plain+    ]++var :: Parser Chunk +var = (Var <$> inDelimiters keyPath) <?> "var"++unescapedVar = +  (UnescapedVar +    <$> (try tripleBraceForm <|> ampersandForm)) <?> "unescapedVar"+  where tripleBraceForm = between (string "{{{" <* spaces)+                                  (spaces *> string "}}}")+                                  keyPath+        ampersandForm = inDelimiters ((char '&' >> spaces) *> keyPath)++-- {{#section}} is a section; optional separator may be designated as {{#section|,}}+section :: Parser Chunk+section = do+    (key, sep) <- inDelimiters ((char '#' >> spaces) *> ((,) <$> keyPath <*> sep))+    xs :: [Chunk] <- manyTill chunk (closeTag key)+    (return  $ Section key xs sep) <?> ("section " ++ show key)++sep :: Parser (Maybe Text)+sep  = do+  spaces +  Just <$> do+    spaces+    notFollowedBy rightDelimiter+    x <- anyChar+    xs <- manyTill anyChar ((eof >> (string "")) <|> bumpClose)+    return . T.pack $ x:xs+  <|> pure Nothing++invertedSection :: Parser Chunk+invertedSection = do+    key <- inDelimiters ((char '^' >> spaces) *> keyPath)+    xs :: [Chunk] <- manyTill chunk (closeTag key)+    (return  $ InvertedSection key xs) <?> ("section " ++ show key)++setDelimiter :: Parser Chunk+setDelimiter = do+  (left, right) <- inDelimiters $ do+      char '='+      left <- many1 (noneOf "= ")+      spaces+      right <- many1 (noneOf "= ")+      char '='+      return (left, right)+  setState (left, right)+  return $ SetDelimiter left right++closeTag :: KeyPath -> Parser String+closeTag k = try (inDelimiters (char '/' *> string k')) +  where k' = keyPathToString k++partial = do+  Partial <$> (inDelimiters ((char '>' >> spaces) *> filename))++filename :: Parser String+filename = (many1 (alphaNum <|> oneOf "/.[]0-9_")) <?> "filename"++plain = do+    -- thanks to http://stackoverflow.com/a/20735868/232417+    notFollowedBy leftDelimiter+    x <- anyChar+    xs <- manyTill anyChar ((eof >> (string "")) <|> bumpOpen)+    return . Plain . T.pack $ x:xs++bumpOpen = (lookAhead $ try leftDelimiter) <?> "bumpOpen"+bumpClose = (lookAhead $ try rightDelimiter) <?> "bumpClose"+++------------------------------------------------------------------------++keyPath :: Parser KeyPath+keyPath = do+  raw <- varname+  let res = parse (sepBy1 pKeyOrIndex (many1 $ oneOf ".[")) "" raw+  spaces+  return +    $ case res of +        Left err -> error $ "Can't parse keypath: " ++ raw+        Right res' -> res'++keyPathToString :: KeyPath -> String+keyPathToString xs = go xs+  where go ((Key x):[]) = T.unpack x +        go ((Key x):xs) = T.unpack x+                  <> "."+                  <> keyPathToString xs+        go ((Index x):xs) = +              "[" <> (show x) +              <> (']':keyPathToString xs)+        go [] = []++pKeyOrIndex = pIndex <|> pKey++pKey = Key . T.pack +    <$> (+        ((:[]) <$> char '.')  -- immediate context key+        <|> (many1 (alphaNum <|> noneOf ".[")) +        )++pIndex = Index . read <$> (many1 digit) <* char ']'+
+ Text/Mustache/Types.hs view
@@ -0,0 +1,18 @@+module Text.Mustache.Types+where+import Data.Text (Text)++data Chunk = Var KeyPath  +         | UnescapedVar KeyPath+         | Section KeyPath [Chunk] (Maybe Text)  -- separator text+         | InvertedSection KeyPath [Chunk]+         | Comment KeyPath+         | SetDelimiter String String -- a stateful operation+         | Plain Text+         | Partial FilePath+         deriving (Show, Read, Eq)++type KeyPath = [Key]+data Key = Key Text | Index Int deriving (Eq, Show, Read)++
+ mustache-haskell.cabal view
@@ -0,0 +1,61 @@+-- Initial text-mustache.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                mustache-haskell +version:             0.1.0.0+synopsis:            straight implementation of mustache templates+-- description:         +homepage:            https://github.com/danchoi/mustache+license:             MIT+license-file:        LICENSE+author:              Daniel Choi+maintainer:          dhchoi@gmail.com+-- copyright:           +category:            Text+build-type:          Simple+-- extra-source-files:  +cabal-version:       >=1.10++library +  default-language: Haskell2010+  hs-source-dirs: .+  exposed-modules:+      Text.Mustache.Types+      Text.Mustache+  other-modules:+      Text.Mustache.Parse+  build-depends:       base >=4.7 && <4.8+                     , aeson+                     , text+                     , parsec+                     , transformers+                     , pretty-show+                     , aeson+                     , unordered-containers+                     , vector+                     , scientific >=0.3.0.0 && <0.4.0.0+                     , directory+                     , bytestring+++executable mus+  main-is:             Main.hs+  -- other-modules:       +  -- other-extensions:    +  build-depends:       base >=4.7 && <4.8+                     , aeson+                     , text+                     , mustache-haskell+                     , parsec+                     , directory+                     , transformers+                     , pretty-show+                     , bytestring+                     , aeson+                     , unordered-containers+                     , vector+                     , scientific+                     , directory+                     , optparse-applicative >=0.11.0 && <0.12.0+  default-language:    Haskell2010+
+ test1.mustache view
@@ -0,0 +1,23 @@+ehllo {{test}}  {{{unesc}}}++{{#section}}+  hello+  {{#name}} hello {{first}} {{/name}}+{{/section}}++{{^section2}}++  inverted+{{/section2}}++{{=<% %>=}}++{{notdelimited}}++<% newdelim %>++<%={{ }}=%>++{{ old_delim }}++<% invalid_delim %>