stache 0.2.2 → 1.0.0
raw patch · 16 files changed
+213/−239 lines, 16 filesdep +voiddep −exceptionsdep ~hspec-megaparsecdep ~megaparsecdep ~stache
Dependencies added: void
Dependencies removed: exceptions
Dependency ranges changed: hspec-megaparsec, megaparsec, stache, template-haskell
Files
- CHANGELOG.md +20/−0
- README.md +16/−9
- Text/Mustache.hs +10/−10
- Text/Mustache/Compile.hs +28/−25
- Text/Mustache/Compile/TH.hs +12/−14
- Text/Mustache/Parser.hs +40/−33
- Text/Mustache/Render.hs +10/−18
- Text/Mustache/Type.hs +11/−17
- bench/Main.hs +1/−1
- mustache-spec/Spec.hs +4/−4
- specification/interpolation.yml +15/−15
- specification/inverted.yml +5/−5
- specification/sections.yml +5/−5
- stache.cabal +21/−51
- tests/Text/Mustache/ParserSpec.hs +10/−20
- tests/Text/Mustache/RenderSpec.hs +5/−12
CHANGELOG.md view
@@ -1,3 +1,23 @@+## Stache 1.0.0++* Improved metadata and documentation.++* Breaking change: the `renderMustache` function does not throw exceptions+ when referenced key is not provided as per the spec. This is the behaviour+ we had before 0.2.0, and it played better with the rest of Mustache.+ Correspondingly, `MustacheRenderException` was removed.++* Stache now uses Megaparsec 6 for parsing.++* `MustacheException` now includes original input as `Text`.++* `compileMustacheText` and `parseMustache` now accept strict `Text` instead+ of lazy `Text`.++* Minor improvement in rendering speed.++* Stop depending on `exceptions` and drop `MonadThrow` constraints.+ ## Stache 0.2.2 * Add the `getMustacheFilesInDir` function.
README.md view
@@ -8,23 +8,23 @@ [](https://coveralls.io/github/stackbuilders/stache?branch=master) This is a Haskell implementation of Mustache templates. The implementation-conforms to the version 1.1.3 of official [Mustache specification]-(https://github.com/mustache/spec). It is extremely simple and-straightforward to use with minimal but complete API — three functions to-compile templates (from directory, from file, and from lazy text) and one to-render them.+conforms to the version 1.1.3 of the+official [Mustache specification](https://github.com/mustache/spec). It is+extremely simple and straightforward to use with minimal but complete+API—three functions to compile templates (from directory, from file, and+from lazy text) and one to render them. The implementation uses the Megaparsec parsing library to parse the templates which results in superior quality of error messages. For rendering you only need to create Aeson's `Value` where you put the data to interpolate. Since the library re-uses Aeson's instances and most data-types in Haskell ecosystem are instances of classes like-`Data.Aeson.ToJSON`, the whole process is very simple for end user.+types in the Haskell ecosystem are instances of classes like+`Data.Aeson.ToJSON`, the whole process is very simple for the end user. Template Haskell helpers for compilation of templates at compile time are-available in the `Text.Mustache.Compile.TH` module. The helpers are-currently available only for GHC 8 users though.+available in the `Text.Mustache.Compile.TH` module. The helpers currently+work only with GHC 8 and later. One feature that is not currently supported is lambdas. The feature is marked as optional in the spec and can be emulated via processing of parsed@@ -74,6 +74,13 @@ * The manual: https://mustache.github.io/mustache.5.html * The specification: https://github.com/mustache/spec * Stack Builders Stache tutorial: https://www.stackbuilders.com/tutorials/haskell/mustache-templates/++## Contribution++Issues, bugs, and questions may be reported in [the GitHub issue tracker for+this project](https://github.com/stackbuilders/stache/issues).++Pull requests are also welcome and will be reviewed quickly. ## License
Text/Mustache.hs view
@@ -3,29 +3,29 @@ -- Copyright : © 2016–2017 Stack Builders -- License : BSD 3 clause ----- Maintainer : Mark Karpov <markkarpov@openmailbox.org>+-- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable -- -- This is a Haskell implementation of Mustache templates. The -- implementation conforms to the version 1.1.3 of official Mustache -- specification <https://github.com/mustache/spec>. It is extremely simple--- and straightforward to use with minimal but complete API — three--- functions to compile templates (from directory, from file, and from lazy--- text) and one to render them.+-- and straightforward to use with minimal but complete API—three functions+-- to compile templates (from directory, from file, and from lazy text) and+-- one to render them. ----- The implementation uses the Megaparsec parsing library to parse the--- templates which results in superior quality of error messages.+-- The implementation uses Megaparsec parsing library to parse the templates+-- which results in superior quality of error messages. -- -- For rendering you only need to create Aeson's 'Data.Aeson.Value' where -- you put the data to interpolate. Since the library re-uses Aeson's--- instances and most data types in Haskell ecosystem are instances of+-- instances and most data types in the Haskell ecosystem are instances of -- classes like 'Data.Aeson.ToJSON', the whole process is very simple for--- end user.+-- the end user. -- -- Template Haskell helpers for compilation of templates at compile time are--- available in the "Text.Mustache.Compile.TH" module. The helpers are--- currently available only for GHC 8 users though.+-- available in the "Text.Mustache.Compile.TH" module. The helpers currently+-- work only with GHC 8 and later. -- -- One feature that is not currently supported is lambdas. The feature is -- marked as optional in the spec and can be emulated via processing of
Text/Mustache/Compile.hs view
@@ -3,7 +3,7 @@ -- Copyright : © 2016–2017 Stack Builders -- License : BSD 3 clause ----- Maintainer : Mark Karpov <markkarpov@openmailbox.org>+-- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable --@@ -20,30 +20,31 @@ , compileMustacheText ) where -import Control.Monad.Catch (MonadThrow (..))+import Control.Exception import Control.Monad.Except-import Data.Text.Lazy (Text)+import Data.Text (Text)+import Data.Void import System.Directory import Text.Megaparsec import Text.Mustache.Parser import Text.Mustache.Type-import qualified Data.Map as M-import qualified Data.Text as T-import qualified Data.Text.Lazy.IO as TL-import qualified System.FilePath as F+import qualified Data.Map as M+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified System.FilePath as F #if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))+import Control.Applicative #endif -- | Compile all templates in specified directory and select one. Template--- files should have extension @mustache@, (e.g. @foo.mustache@) to be+-- files should have the extension @mustache@, (e.g. @foo.mustache@) to be -- recognized. This function /does not/ scan the directory recursively. -- -- The action can throw the same exceptions as 'getDirectoryContents', and -- 'T.readFile'. -compileMustacheDir :: (MonadIO m, MonadThrow m)+compileMustacheDir :: MonadIO m => PName -- ^ Which template to select after compiling -> FilePath -- ^ Directory with templates -> m Template -- ^ The resulting template@@ -64,20 +65,21 @@ getMustacheFilesInDir :: MonadIO m => FilePath -- ^ Directory with templates -> m [FilePath]-getMustacheFilesInDir path =- liftIO (getDirectoryContents path) >>=+getMustacheFilesInDir path = liftIO $+ getDirectoryContents path >>= filterM isMustacheFile . fmap (F.combine path) >>=- mapM (liftIO . makeAbsolute)+ mapM makeAbsolute --- | Compile single Mustache template and select it.+-- | Compile a single Mustache template and select it. -- -- The action can throw the same exceptions as 'T.readFile'. -compileMustacheFile :: (MonadIO m, MonadThrow m)+compileMustacheFile :: MonadIO m => FilePath -- ^ Location of the file -> m Template-compileMustacheFile path =- liftIO (TL.readFile path) >>= withException . compile+compileMustacheFile path = liftIO $ do+ input <- T.readFile path+ withException input (compile input) where pname = pathToPName path compile = fmap (Template pname . M.singleton pname) . parseMustache path@@ -88,18 +90,18 @@ compileMustacheText :: PName -- ^ How to name the template? -> Text -- ^ The template to compile- -> Either (ParseError Char Dec) Template -- ^ The result+ -> Either (ParseError Char Void) Template -- ^ The result compileMustacheText pname txt = Template pname . M.singleton pname <$> parseMustache "" txt ---------------------------------------------------------------------------- -- Helpers --- | Check if given 'FilePath' points to a mustache file.+-- | Check if a given 'FilePath' points to a mustache file. -isMustacheFile :: MonadIO m => FilePath -> m Bool+isMustacheFile :: FilePath -> IO Bool isMustacheFile path = do- exists <- liftIO (doesFileExist path)+ exists <- doesFileExist path let rightExtension = F.takeExtension path == ".mustache" return (exists && rightExtension) @@ -111,7 +113,8 @@ -- | Throw 'MustacheException' if argument is 'Left' or return the result -- inside 'Right'. -withException :: MonadThrow m- => Either (ParseError Char Dec) Template -- ^ Value to process- -> m Template -- ^ The result-withException = either (throwM . MustacheParserException) return+withException+ :: Text -- ^ Original input+ -> Either (ParseError Char Void) Template -- ^ Value to process+ -> IO Template -- ^ The result+withException input = either (throwIO . MustacheParserException input) return
Text/Mustache/Compile/TH.hs view
@@ -3,7 +3,7 @@ -- Copyright : © 2016–2017 Stack Builders -- License : BSD 3 clause ----- Maintainer : Mark Karpov <markkarpov@openmailbox.org>+-- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable --@@ -26,9 +26,8 @@ , mustache ) where -import Control.Exception (Exception(..))-import Control.Monad.Catch (try)-import Data.Text.Lazy (Text)+import Control.Exception+import Data.Text (Text) import Data.Typeable (cast) import Language.Haskell.TH hiding (Dec) import Language.Haskell.TH.Quote (QuasiQuoter (..))@@ -36,7 +35,6 @@ import System.Directory import Text.Mustache.Type import qualified Data.Text as T-import qualified Data.Text.Lazy as TL import qualified Text.Mustache.Compile as C #if !MIN_VERSION_base(4,8,0)@@ -52,7 +50,7 @@ #endif -- | Compile all templates in specified directory and select one. Template--- files should have extension @mustache@, (e.g. @foo.mustache@) to be+-- files should have the extension @mustache@, (e.g. @foo.mustache@) to be -- recognized. This function /does not/ scan the directory recursively. -- -- This version compiles the templates at compile time.@@ -86,7 +84,7 @@ -> Text -- ^ The template to compile -> Q Exp compileMustacheText pname text =- (handleEither . either (Left . MustacheParserException) Right)+ (handleEither . either (Left . MustacheParserException text) Right) (C.compileMustacheText pname text) -- | Compile Mustache using QuasiQuoter. Usage:@@ -98,14 +96,14 @@ -- > foo = [mustache|This is my inline {{ template }}.|] -- -- Name of created partial is set to @"quasi-quoted"@. You can extend cache--- of 'Template' created this way using 'mappend' and so work with partials--- as usual.+-- of 'Template' created this way using @('Data.Semigroup.<>')@ and so work+-- with partials as usual. -- -- @since 0.1.7 mustache :: QuasiQuoter mustache = QuasiQuoter- { quoteExp = compileMustacheText "quasi-quoted" . TL.pack+ { quoteExp = compileMustacheText "quasi-quoted" . T.pack , quotePat = undefined , quoteType = undefined , quoteDec = undefined }@@ -124,10 +122,10 @@ #endif Right template -> dataToExpQ (fmap liftText . cast) template where- -- NOTE Since the feature requires GHC 8 anyway, we follow indentation- -- style of that version of compiler. This makes it look consistent with- -- other error messages and allows Emacs and similar tools to parse the- -- errors correctly.+ -- NOTE Since the feature requires GHC 8 anyway, we follow the+ -- indentation style of that version of compiler. This makes it look+ -- consistent with other error messages and allows Emacs and similar+ -- tools to parse the errors correctly. indentNicely x' = case lines x' of [] -> ""
Text/Mustache/Parser.hs view
@@ -3,7 +3,7 @@ -- Copyright : © 2016–2017 Stack Builders -- License : BSD 3 clause ----- Maintainer : Mark Karpov <markkarpov@openmailbox.org>+-- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable --@@ -11,6 +11,8 @@ -- import the module, because "Text.Mustache" re-exports everything you may -- need, import that module instead. +{-# LANGUAGE OverloadedStrings #-}+ module Text.Mustache.Parser ( parseMustache ) where@@ -18,26 +20,28 @@ import Control.Applicative import Control.Monad import Control.Monad.State.Strict-import Data.Char (isSpace)-import Data.List (intercalate)+import Data.Char (isSpace, isAlphaNum) import Data.Maybe (catMaybes)-import Data.Text.Lazy (Text)+import Data.Semigroup ((<>))+import Data.Text (Text)+import Data.Void import Text.Megaparsec+import Text.Megaparsec.Char import Text.Mustache.Type-import qualified Data.Text as T-import qualified Text.Megaparsec.Lexer as L+import qualified Data.Text as T+import qualified Text.Megaparsec.Char.Lexer as L ---------------------------------------------------------------------------- -- Parser --- | Parse given Mustache template.+-- | Parse a given Mustache template. parseMustache :: FilePath- -- ^ Location of file to parse+ -- ^ Location of the file to parse -> Text -- ^ File contents (Mustache template)- -> Either (ParseError Char Dec) [Node]+ -> Either (ParseError Char Void) [Node] -- ^ Parsed nodes or parse error parseMustache = parse $ evalStateT (pMustache eof) (Delimiters "{{" "}}")@@ -77,11 +81,11 @@ pUnescapedSpecial = do start <- gets openingDel end <- gets closingDel- between (symbol $ start ++ "{") (string $ "}" ++ end) $+ between (symbol $ start <> "{") (string $ "}" <> end) $ UnescapedVar <$> pKey {-# INLINE pUnescapedSpecial #-} -pSection :: String -> (Key -> [Node] -> Node) -> Parser Node+pSection :: Text -> (Key -> [Node] -> Node) -> Parser Node pSection suffix f = do key <- withStandalone (pTag suffix) nodes <- (pMustache . withStandalone . pClosingTag) key@@ -100,7 +104,7 @@ pComment = void $ do start <- gets openingDel end <- gets closingDel- (void . symbol) (start ++ "!")+ (void . symbol) (start <> "!") manyTill anyChar (string end) {-# INLINE pComment #-} @@ -108,10 +112,10 @@ pSetDelimiters = void $ do start <- gets openingDel end <- gets closingDel- (void . symbol) (start ++ "=")+ (void . symbol) (start <> "=") start' <- pDelimiter <* scn end' <- pDelimiter <* scn- (void . string) ("=" ++ end)+ (void . string) ("=" <> end) put (Delimiters start' end') {-# INLINE pSetDelimiters #-} @@ -127,38 +131,39 @@ pStandalone p = pBol *> try (between sc (sc <* (void eol <|> eof)) p) {-# INLINE pStandalone #-} -pTag :: String -> Parser Key+pTag :: Text -> Parser Key pTag suffix = do start <- gets openingDel end <- gets closingDel- between (symbol $ start ++ suffix) (string end) pKey+ between (symbol $ start <> suffix) (string end) pKey {-# INLINE pTag #-} pClosingTag :: Key -> Parser () pClosingTag key = do start <- gets openingDel end <- gets closingDel- let str = keyToString key- void $ between (symbol $ start ++ "/") (string end) (symbol str)+ let str = keyToText key+ void $ between (symbol $ start <> "/") (string end) (symbol str) {-# INLINE pClosingTag #-} pKey :: Parser Key pKey = (fmap Key . lexeme . label "key") (implicit <|> other) where implicit = [] <$ char '.'- other = sepBy1 (T.pack <$> some ch) (char '.')- ch = alphaNumChar <|> oneOf "-_"+ other = sepBy1 (takeWhile1P (Just lbl) f) (char '.')+ lbl = "alphanumeric char or '-' or '_'"+ f x = isAlphaNum x || x == '-' || x == '_' {-# INLINE pKey #-} -pDelimiter :: Parser String-pDelimiter = some (satisfy delChar) <?> "delimiter"+pDelimiter :: Parser Text+pDelimiter = takeWhile1P (Just "delimiter char") delChar <?> "delimiter" where delChar x = not (isSpace x) && x /= '=' {-# INLINE pDelimiter #-} pBol :: Parser () pBol = do level <- L.indentLevel- unless (level == unsafePos 1) empty+ unless (level == pos1) empty {-# INLINE pBol #-} ----------------------------------------------------------------------------@@ -166,35 +171,37 @@ -- | Type of Mustache parser monad stack. -type Parser = StateT Delimiters (Parsec Dec Text)+type Parser = StateT Delimiters (Parsec Void Text) -- | State used in Mustache parser. It includes currently set opening and -- closing delimiters. data Delimiters = Delimiters- { openingDel :: String- , closingDel :: String }+ { openingDel :: Text+ , closingDel :: Text } ---------------------------------------------------------------------------- -- Lexer helpers and other scn :: Parser ()-scn = L.space (void spaceChar) empty empty+scn = L.space space1 empty empty {-# INLINE scn #-} sc :: Parser ()-sc = L.space (void $ oneOf " \t") empty empty+sc = L.space (void $ takeWhile1P Nothing f) empty empty+ where+ f x = x == ' ' || x == '\t' {-# INLINE sc #-} lexeme :: Parser a -> Parser a lexeme = L.lexeme scn {-# INLINE lexeme #-} -symbol :: String -> Parser String+symbol :: Text -> Parser Text symbol = L.symbol scn {-# INLINE symbol #-} -keyToString :: Key -> String-keyToString (Key []) = "."-keyToString (Key ks) = intercalate "." (T.unpack <$> ks)-{-# INLINE keyToString #-}+keyToText :: Key -> Text+keyToText (Key []) = "."+keyToText (Key ks) = T.intercalate "." ks+{-# INLINE keyToText #-}
Text/Mustache/Render.hs view
@@ -3,7 +3,7 @@ -- Copyright : © 2016–2017 Stack Builders -- License : BSD 3 clause ----- Maintainer : Mark Karpov <markkarpov@openmailbox.org>+-- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable --@@ -18,13 +18,13 @@ ( renderMustache ) where -import Control.Exception (throw) import Control.Monad.Reader-import Control.Monad.Writer.Lazy+import Control.Monad.Writer.Strict import Data.Aeson import Data.Foldable (asum) import Data.List (tails) import Data.List.NonEmpty (NonEmpty (..))+import Data.Maybe (fromMaybe) import Data.Text (Text) import Text.Megaparsec.Pos (Pos, unPos) import Text.Mustache.Type@@ -47,7 +47,7 @@ -- The rendering monad -- | Synonym for the monad we use for rendering. It allows to share context--- and accumulate the result as 'B.Builder' data which is then turned into+-- and accumulate the result as 'B.Builder' data which is then turned into a -- lazy 'TL.Text'. type Render a = ReaderT RenderContext (Writer B.Builder) a@@ -67,11 +67,6 @@ -- | Render a Mustache 'Template' using Aeson's 'Value' to get actual values -- for interpolation.------ As of version 0.2.0, if referenced values are missing (which almost--- always indicates some sort of mistake), 'MustacheRenderException' will be--- thrown. The included 'Key' will indicate full path to missing value and--- 'PName' will contain the name of active partial. renderMustache :: Template -> Value -> TL.Text renderMustache t =@@ -187,13 +182,10 @@ lookupKey :: Key -> Render Value lookupKey (Key []) = NE.head <$> asks rcContext lookupKey k = do- v <- asks rcContext- p <- asks rcPrefix- pname <- asks (templateActual . rcTemplate)+ v <- asks rcContext+ p <- asks rcPrefix let f x = asum (simpleLookup False (x <> k) <$> v)- case asum (fmap (f . Key) . reverse . tails $ unKey p) of- Nothing -> throw (MustacheRenderException pname (p <> k))- Just r -> return r+ (return . fromMaybe Null . asum) (fmap (f . Key) . reverse . tails $ unKey p) -- | Lookup a 'Value' by traversing another 'Value' using given 'Key' as -- “path”.@@ -202,7 +194,7 @@ :: Bool -- ^ At least one part of the path matched, in this case we are -- “committed” to this lookup and cannot say “there is nothing, try- -- other level”. This is necessary to pass the “Dotted Names — Context+ -- other level”. This is necessary to pass the “Dotted Names—Context -- Precedence” test from the “interpolation.yml” spec. -> Key -- ^ The key to lookup -> Value -- ^ Source value@@ -233,7 +225,7 @@ ---------------------------------------------------------------------------- -- Helpers --- | Add two 'Maybe' 'Pos' values together.+-- | Add two @'Maybe' 'Pos'@ values together. addIndents :: Maybe Pos -> Maybe Pos -> Maybe Pos addIndents Nothing Nothing = Nothing@@ -242,7 +234,7 @@ addIndents (Just x) (Just y) = Just (x S.<> y) {-# INLINE addIndents #-} --- | Build intentation of specified length by repeating the space character.+-- | Build indentation of specified length by repeating the space character. buildIndent :: Maybe Pos -> Text buildIndent Nothing = ""
Text/Mustache/Type.hs view
@@ -3,11 +3,11 @@ -- Copyright : © 2016–2017 Stack Buliders -- License : BSD 3 clause ----- Maintainer : Mark Karpov <markkarpov@openmailbox.org>+-- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable ----- Types used by the package. You don't usually need to import the module,+-- Types used in the package. You don't usually need to import the module, -- because "Text.Mustache" re-exports everything you may need, import that -- module instead. @@ -34,6 +34,7 @@ import Data.String (IsString (..)) import Data.Text (Text) import Data.Typeable (Typeable)+import Data.Void import GHC.Generics import Text.Megaparsec import qualified Data.Map as M@@ -75,9 +76,9 @@ -- -- The representation is the following: ----- * @[]@ — empty list means implicit iterators;--- * @[text]@ — single key is a normal identifier;--- * @[text1, text2]@ — multiple keys represent dotted names.+-- * @[]@—empty list means implicit iterators;+-- * @[text]@—single key is a normal identifier;+-- * @[text1, text2]@—multiple keys represent dotted names. newtype Key = Key { unKey :: [Text] } deriving (Eq, Ord, Show, Semigroup, Monoid, Data, Typeable, Generic)@@ -104,28 +105,21 @@ instance NFData PName --- | Exception that is thrown when parsing of a template has failed or--- referenced values were not provided.+-- | Exception that is thrown when parsing of a template fails or referenced+-- values are not provided. data MustacheException- = MustacheParserException (ParseError Char Dec)+ = MustacheParserException Text (ParseError Char Void) -- ^ Template parser has failed. This contains the parse error. -- -- /Before version 0.2.0 it was called 'MustacheException'./- | MustacheRenderException PName Key- -- ^ A referenced value was not provided. The exception provides info- -- about partial in which the issue happened 'PName' and name of the- -- missing key 'Key'. --- -- @since 0.2.0+ -- /The 'Text' field was added in version 1.0.0./ deriving (Eq, Show, Typeable, Generic) #if MIN_VERSION_base(4,8,0) instance Exception MustacheException where- displayException (MustacheParserException e) = parseErrorPretty e- displayException (MustacheRenderException pname key) =- "Referenced value was not provided in partial \"" ++ T.unpack (unPName pname) ++- "\", key: " ++ T.unpack (showKey key)+ displayException (MustacheParserException s e) = parseErrorPretty' s e #else instance Exception MustacheException #endif
bench/Main.hs view
@@ -13,7 +13,7 @@ import Text.Mustache.Parser import Text.Mustache.Render import Text.Mustache.Type-import qualified Data.Text.Lazy.IO as T+import qualified Data.Text.IO as T ---------------------------------------------------------------------------- -- Benchmarks
mustache-spec/Spec.hs view
@@ -38,9 +38,9 @@ { testName :: String , testDesc :: String , testData :: Value- , testTemplate :: TL.Text- , testExpected :: TL.Text- , testPartials :: Map Text TL.Text+ , testTemplate :: Text+ , testExpected :: Text+ , testPartials :: Map Text Text } instance FromJSON Test where@@ -84,5 +84,5 @@ Left perr -> handleError perr >> undefined Right ns -> return (pname, ns) let ps2 = M.fromList ps1 `M.union` templateCache- renderMustache (Template templateActual ps2) testData+ TL.toStrict (renderMustache (Template templateActual ps2) testData) `shouldBe` testExpected
specification/interpolation.yml view
@@ -103,23 +103,23 @@ # Context Misses - # - name: Basic Context Miss Interpolation- # desc: Failed context lookups should default to empty strings.- # data: { }- # template: "I ({{cannot}}) be seen!"- # expected: "I () be seen!"+ - name: Basic Context Miss Interpolation+ desc: Failed context lookups should default to empty strings.+ data: { }+ template: "I ({{cannot}}) be seen!"+ expected: "I () be seen!" - # - name: Triple Mustache Context Miss Interpolation- # desc: Failed context lookups should default to empty strings.- # data: { }- # template: "I ({{{cannot}}}) be seen!"- # expected: "I () be seen!"+ - name: Triple Mustache Context Miss Interpolation+ desc: Failed context lookups should default to empty strings.+ data: { }+ template: "I ({{{cannot}}}) be seen!"+ expected: "I () be seen!" - # - name: Ampersand Context Miss Interpolation- # desc: Failed context lookups should default to empty strings.- # data: { }- # template: "I ({{&cannot}}) be seen!"- # expected: "I () be seen!"+ - name: Ampersand Context Miss Interpolation+ desc: Failed context lookups should default to empty strings.+ data: { }+ template: "I ({{&cannot}}) be seen!"+ expected: "I () be seen!" # Dotted Names
specification/inverted.yml view
@@ -92,11 +92,11 @@ template: "| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |" expected: "| A E |" - # - name: Context Misses- # desc: Failed context lookups should be considered falsey.- # data: { }- # template: "[{{^missing}}Cannot find key 'missing'!{{/missing}}]"- # expected: "[Cannot find key 'missing'!]"+ - name: Context Misses+ desc: Failed context lookups should be considered falsey.+ data: { }+ template: "[{{^missing}}Cannot find key 'missing'!{{/missing}}]"+ expected: "[Cannot find key 'missing'!]" # Dotted Names
specification/sections.yml view
@@ -132,11 +132,11 @@ template: "| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |" expected: "| A E |" - # - name: Context Misses- # desc: Failed context lookups should be considered falsey.- # data: { }- # template: "[{{#missing}}Found key 'missing'!{{/missing}}]"- # expected: "[]"+ - name: Context Misses+ desc: Failed context lookups should be considered falsey.+ data: { }+ template: "[{{#missing}}Found key 'missing'!{{/missing}}]"+ expected: "[]" # Implicit Iterators
stache.cabal view
@@ -1,42 +1,11 @@------ Cabal configuration for ‘stache’ package.------ Copyright © 2016–2017 Stack Builders------ 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 Mark Karpov nor the names of 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 “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 HOLDERS 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.- name: stache-version: 0.2.2-cabal-version: >= 1.10+version: 1.0.0+cabal-version: >= 1.18+tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.1 license: BSD3 license-file: LICENSE.md-author: Mark Karpov <markkarpov@openmailbox.org>-maintainer: Mark Karpov <markkarpov@openmailbox.org>+author: Mark Karpov <markkarpov92@gmail.com>+maintainer: Mark Karpov <markkarpov92@gmail.com> homepage: https://github.com/stackbuilders/stache bug-reports: https://github.com/stackbuilders/stache/issues category: Text@@ -59,20 +28,21 @@ default: False library- build-depends: aeson >= 0.11 && < 1.2+ build-depends: aeson >= 0.11 && < 1.3 , base >= 4.7 && < 5.0 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6 , deepseq >= 1.4 && < 1.5 , directory >= 1.2 && < 1.4- , exceptions >= 0.8 && < 0.9 , filepath >= 1.2 && < 1.5- , megaparsec >= 5.0 && < 6.0+ , megaparsec >= 6.0 && < 7.0 , mtl >= 2.1 && < 3.0- , template-haskell >= 2.10 && < 2.12+ , template-haskell >= 2.10 && < 2.13 , text >= 1.2 && < 1.3 , unordered-containers >= 0.2.5 && < 0.3 , vector >= 0.11 && < 0.13+ if !impl(ghc >= 7.10)+ build-depends: void == 0.7.* if !impl(ghc >= 8.0) build-depends: semigroups == 0.18.* exposed-modules: Text.Mustache@@ -91,13 +61,13 @@ main-is: Spec.hs hs-source-dirs: tests type: exitcode-stdio-1.0- build-depends: aeson >= 0.11 && < 1.2+ build-depends: aeson >= 0.11 && < 1.3 , base >= 4.7 && < 5.0 , containers >= 0.5 && < 0.6 , hspec >= 2.0 && < 3.0- , hspec-megaparsec >= 0.2 && < 0.4- , megaparsec >= 5.0 && < 6.0- , stache >= 0.2.2+ , hspec-megaparsec >= 1.0 && < 2.0+ , megaparsec >= 6.0 && < 7.0+ , stache , text >= 1.2 && < 1.3 other-modules: Text.Mustache.Compile.THSpec , Text.Mustache.ParserSpec@@ -115,14 +85,14 @@ main-is: Spec.hs hs-source-dirs: mustache-spec type: exitcode-stdio-1.0- build-depends: aeson >= 0.11 && < 1.2+ build-depends: aeson >= 0.11 && < 1.3 , base >= 4.7 && < 5.0 , bytestring >= 0.10 && < 0.11 , containers >= 0.5 && < 0.6 , file-embed , hspec >= 2.0 && < 3.0- , megaparsec >= 5.0 && < 6.0- , stache >= 0.2.2+ , megaparsec >= 6.0 && < 7.0+ , stache , text >= 1.2 && < 1.3 , yaml >= 0.8 && < 0.9 if flag(dev)@@ -135,12 +105,12 @@ main-is: Main.hs hs-source-dirs: bench type: exitcode-stdio-1.0- build-depends: aeson >= 0.11 && < 1.2+ build-depends: aeson >= 0.11 && < 1.3 , base >= 4.7 && < 5.0- , criterion >= 0.6.2.1 && < 1.2+ , criterion >= 0.6.2.1 && < 1.3 , deepseq >= 1.4 && < 1.5- , megaparsec >= 5.0 && < 6.0- , stache >= 0.2.2+ , megaparsec >= 6.0 && < 7.0+ , stache , text >= 1.2 && < 1.3 if flag(dev) ghc-options: -O2 -Wall -Werror
tests/Text/Mustache/ParserSpec.hs view
@@ -6,14 +6,12 @@ , spec ) where -import Data.List.NonEmpty (NonEmpty (..))+import Data.Semigroup ((<>)) import Test.Hspec import Test.Hspec.Megaparsec import Text.Megaparsec import Text.Mustache.Parser import Text.Mustache.Type-import qualified Data.List.NonEmpty as NE-import qualified Data.Set as S #if !MIN_VERSION_base(4,8,0) import Control.Applicative (pure)@@ -67,13 +65,13 @@ context "when parsing a partial" $ do it "parses a partial with white space" $ p "{{> that-s_my-partial }}" `shouldParse`- [Partial "that-s_my-partial" (Just $ unsafePos 1)]+ [Partial "that-s_my-partial" (Just $ mkPos 1)] it "parses a partial without white space" $ p "{{>that-s_my-partial}}" `shouldParse`- [Partial "that-s_my-partial" (Just $ unsafePos 1)]+ [Partial "that-s_my-partial" (Just $ mkPos 1)] it "handles indented partial correctly" $ p " {{> next_one }}" `shouldParse`- [Partial "next_one" (Just $ unsafePos 4)]+ [Partial "next_one" (Just $ mkPos 4)] context "when running into delimiter change" $ do it "has effect" $ p "{{=<< >>=}}<<var>>{{var}}" `shouldParse`@@ -91,17 +89,9 @@ p "{{#section}}{{=<< >>=}}<</section>><<var>>" `shouldParse` [Section (key "section") [], EscapedVar (key "var")] context "when given malformed input" $ do- let pos l c = SourcePos "" (unsafePos l) (unsafePos c) :| []- ne = NE.fromList- it "rejects unclosed tags" $- p "{{ name " `shouldFailWith` ParseError- { errorPos = pos 1 9- , errorUnexpected = S.singleton EndOfInput- , errorExpected = S.singleton (Tokens $ ne "}}")- , errorCustom = S.empty }- it "rejects unknown tags" $- p "{{? boo }}" `shouldFailWith` ParseError- { errorPos = pos 1 3- , errorUnexpected = S.singleton (Tokens $ ne "?")- , errorExpected = S.singleton (Label $ ne "key")- , errorCustom = S.empty }+ it "rejects unclosed tags" $ do+ let s = "{{ name "+ p s `shouldFailWith` err (posN 8 s) (ueof <> etoks "}}")+ it "rejects unknown tags" $ do+ let s = "{{? boo }}"+ p s `shouldFailWith` err (posN 2 s) (utoks "?" <> elabel "key")
tests/Text/Mustache/RenderSpec.hs view
@@ -6,7 +6,6 @@ , spec ) where -import Control.Exception (evaluate) import Data.Aeson (object, KeyValue (..), Value (..)) import Data.Text (Text) import Test.Hspec@@ -41,13 +40,8 @@ context "when rendering a section" $ do let nodes = [Section (key "foo") [UnescapedVar (key "bar"), TextBlock "*"]] context "when the key is not present" $- it "throws the correct exception" $- evaluate (r nodes (object [])) `shouldThrow`- (== MustacheRenderException "test" (key "foo"))- context "when the key is not present inside a section" $- it "throws the correct exception" $- evaluate (r nodes (object ["foo" .= ([1] :: [Int])])) `shouldThrow`- (== MustacheRenderException "test" (Key ["foo","bar"]))+ it "renders nothing" $+ r nodes (object []) `shouldBe` "" context "when the key is present" $ do context "when the key is a “false” value" $ do it "skips the Null value" $@@ -103,9 +97,8 @@ context "when rendering an inverted section" $ do let nodes = [InvertedSection (key "foo") [TextBlock "Here!"]] context "when the key is not present" $- it "throws the correct exception" $- evaluate (r nodes (object [])) `shouldThrow`- (== MustacheRenderException "test" (key "foo"))+ it "renders the inverse section" $+ r nodes (object []) `shouldBe` "Here!" context "when the key is present" $ do context "when the key is a “false” value" $ do it "renders with Null value" $@@ -124,7 +117,7 @@ it "skips non-empty list" $ r nodes (object ["foo" .= [True]]) `shouldBe` "" context "when rendering a partial" $ do- let nodes = [ Partial "partial" (Just $ unsafePos 4)+ let nodes = [ Partial "partial" (Just $ mkPos 4) , TextBlock "*" ] it "skips missing partial" $ r nodes Null `shouldBe` " *"