mustache 0.3.1.0 → 0.4.0.0
raw patch · 8 files changed
+144/−158 lines, 8 filesdep −conversiondep −conversion-text
Dependencies removed: conversion, conversion-text
Files
- CHANGELOG.md +24/−0
- mustache.cabal +9/−13
- src/lib/Text/Mustache.hs +4/−5
- src/lib/Text/Mustache/Compile.hs +4/−4
- src/lib/Text/Mustache/Parser.hs +23/−25
- src/lib/Text/Mustache/Render.hs +11/−11
- src/lib/Text/Mustache/Types.hs +57/−89
- test/integration/Language.hs +12/−11
+ CHANGELOG.md view
@@ -0,0 +1,24 @@+# Mustache library changelog++## v0.4.0.0rc-4 (current stable version)++- Removed `conversion` and `conversion-text` dependency.+- Subsequently removed any dependency on overlapping instances+- Readded support for ghc version 7.8+- Removed `Char -> Value` instance of `ToMustache` (because of overlap)+- Renamed `AST`++## v0.3.1.0rc-3++- Added infix precedence to conversion operators+- Added `INLINEABLE` pragma to conversion functions++## v0.3.0.1rc-2++Dropped GHC 7.8 support in favor of efficient and easy data conversion.++## v0.3.0.0rc-1++- improved documentation+- fixed a bug with scope+- small interface changes
mustache.cabal view
@@ -1,27 +1,25 @@ name: mustache-version: 0.3.1.0+version: 0.4.0.0 synopsis: A mustache template parser library. description: Allows parsing and rendering template files with mustache markup. See the mustache <http://mustache.github.io/mustache.5.html language reference>.-- Implements the mustache spec version 1.1.3-- This library requires GHC >= 7.10 because it uses the new overlapping instances- to provide efficient and versatile conversion of custom data types into mustache- Values.+ .+ Implements the mustache spec version 1.1.3.+ .+ /Note/: Versions including and beyond 0.4 are compatible with ghc 7.8 again. license: BSD3 license-file: LICENSE author: Justus Adam maintainer: dev@justus.science homepage: https://github.com/JustusAdam/mustache bug-reports: https://github.com/JustusAdam/mustache/issues--- copyright:+copyright: (c) 2015 Justus Adam category: Development build-type: Simple-extra-source-files: README.md+extra-source-files: README.md CHANGELOG.md cabal-version: >=1.10-tested-with: GHC >=7.10 && <= 7.10.2+tested-with: GHC >=7.8 && <= 7.10.2 source-repository head@@ -32,7 +30,7 @@ type: git branch: master location: git://github.com/JustusAdam/mustache.git- tag: v0.3.1.0rc-3+ tag: v0.4.0.0rc-4 @@ -57,8 +55,6 @@ directory, filepath, scientific,- conversion >= 1.2,- conversion-text, base-unicode-symbols, ja-base-extra >= 0.2.1, containers
src/lib/Text/Mustache.hs view
@@ -71,8 +71,7 @@ @ The values to the left of the '~>' operator has to be of type 'Text', hence the-@OverloadedStrings@ can becomes very handy here. Alternatively the '~~>' operator-can be used, which accepts an arbitrary string-like, converting it to text.+@OverloadedStrings@ can becomes very handy here. Values to the right of the '~>' operator must be an instance of the 'ToMustache' typeclass. Alternatively, if your value to the right of the '~>' operator is@@ -121,7 +120,7 @@ ['ToMustache'] A typeclass for converting arbitrary types to 'Value', similar to Data.Aeson.ToJSON but with support for lambdas. -['Template'] Contains the 'AST', the abstract syntax tree, which is basically a+['Template'] Contains the 'STree', the syntax tree, which is basically a list of text blocks and mustache tags. The 'name' of the template and its 'partials' cache. @@ -130,7 +129,7 @@ During the compilation step the template file is located, read, then parsed in a single pass ('compileTemplate'), resulting in a 'Template' with an empty 'partials' section. -Subsequenty the 'AST' of the template is scanned for included partials, any+Subsequenty the 'STree' of the template is scanned for included partials, any present 'TemplateCache' is queried for the partial ('compileTemplateWithCache'), if not found it will be searched for in the @searchSpache@, compiled and inserted into the template's own cache as well as the global cache for the@@ -173,7 +172,7 @@ , substituteValue -- ** Data Conversion- , ToMustache, toMustache, object, (~>), (~=), (~~>), (~~=)+ , ToMustache, toMustache, object, (~>), (~=) ) where
src/lib/Text/Mustache/Compile.hs view
@@ -78,7 +78,7 @@ Just template → return template Nothing → do rawSource ← lift $ getFile searchSpace name'- compiled@(Template { ast = mAST }) ←+ compiled@(Template { ast = mSTree }) ← lift $ hoistEither $ compileTemplate name' rawSource foldM@@ -88,7 +88,7 @@ return (st { partials = HM.insert partialName nt p }) ) compiled- (getPartials mAST)+ (getPartials mSTree) -- | Flatten a list of Templates into a single 'TemplateChache'@@ -103,11 +103,11 @@ {-|- Find the names of all included partials in a mustache AST.+ Find the names of all included partials in a mustache STree. Same as @join . fmap getPartials'@ -}-getPartials ∷ AST → [FilePath]+getPartials ∷ STree → [FilePath] getPartials = join ∘ fmap getPartials'
src/lib/Text/Mustache/Parser.hs view
@@ -38,17 +38,15 @@ import Control.Monad import Control.Monad.Unicode-import Conversion (Conversion, convert)-import Conversion.Text ()-import Data.Char (isAlphaNum, isSpace)-import Data.Functor ((<$>))-import Data.List (nub)-import Data.Monoid.Unicode ((∅), (⊕))-import Data.Text as T (Text, null, pack)-import Prelude as Prel+import Data.Char (isAlphaNum, isSpace)+import Data.Functor ((<$>))+import Data.List (nub)+import Data.Monoid.Unicode ((∅), (⊕))+import Data.Text as T (Text, null, pack)+import Prelude as Prel import Prelude.Unicode import Text.Mustache.Types-import Text.Parsec as P hiding (endOfLine, parse)+import Text.Parsec as P hiding (endOfLine, parse) -- | Initial configuration for the parser@@ -149,16 +147,16 @@ {-| Runs the parser for a mustache template, returning the syntax tree. -}-parse ∷ FilePath → Text → Either ParseError AST+parse ∷ FilePath → Text → Either ParseError STree parse = parseWithConf defaultConf -- | Parse using a custom initial configuration-parseWithConf ∷ MustacheConf → FilePath → Text → Either ParseError AST+parseWithConf ∷ MustacheConf → FilePath → Text → Either ParseError STree parseWithConf = P.runParser parseText ∘ initState -parseText ∷ Parser AST+parseText ∷ Parser STree parseText = do (MustacheState { isBeginngingOfLine }) ← getState if isBeginngingOfLine@@ -166,24 +164,24 @@ else continueLine -appendTextStack ∷ Conversion t Text ⇒ t → Parser ()-appendTextStack t = modifyState (\s → s { textStack = textStack s ⊕ convert t})+appendStringStack ∷ String → Parser ()+appendStringStack t = modifyState (\s → s { textStack = textStack s ⊕ pack t}) -continueLine ∷ Parser AST+continueLine ∷ Parser STree continueLine = do (MustacheState { sDelimiters = ( start@(x:_), _ )}) ← getState let forbidden = x : "\n\r" - many (noneOf forbidden) ≫= appendTextStack+ many (noneOf forbidden) ≫= appendStringStack - (try endOfLine ≫= appendTextStack ≫ setIsBeginning True ≫ parseLine)+ (try endOfLine ≫= appendStringStack ≫ setIsBeginning True ≫ parseLine) <|> (try (string start) ≫ switchOnTag ≫= continueFromTag) <|> (try eof ≫ finishFile)- <|> (anyChar ≫= appendTextStack ≫ continueLine)+ <|> (anyChar ≫= appendStringStack . (:[]) ≫ continueLine) -flushText ∷ Parser AST+flushText ∷ Parser STree flushText = do s@(MustacheState { textStack = text }) ← getState putState $ s { textStack = (∅) }@@ -192,7 +190,7 @@ else [TextBlock text] -finishFile ∷ Parser AST+finishFile ∷ Parser STree finishFile = getState ≫= \case (MustacheState {currentSectionName = Nothing}) → flushText@@ -200,14 +198,14 @@ parserFail $ "Unclosed section " ⊕ show name -parseLine ∷ Parser AST+parseLine ∷ Parser STree parseLine = do (MustacheState { sDelimiters = ( start, _ ) }) ← getState initialWhitespace ← many (oneOf " \t") let handleStandalone = do tag ← switchOnTag let continueNoStandalone = do- appendTextStack initialWhitespace+ appendStringStack initialWhitespace setIsBeginning False continueFromTag tag standaloneEnding = do@@ -224,11 +222,11 @@ continueFromTag tag ) <|> continueNoStandalone (try (string start) ≫ handleStandalone)- <|> (try eof ≫ appendTextStack initialWhitespace ≫ finishFile)- <|> (appendTextStack initialWhitespace ≫ setIsBeginning False ≫ continueLine)+ <|> (try eof ≫ appendStringStack initialWhitespace ≫ finishFile)+ <|> (appendStringStack initialWhitespace ≫ setIsBeginning False ≫ continueLine) -continueFromTag ∷ ParseTagRes → Parser AST+continueFromTag ∷ ParseTagRes → Parser STree continueFromTag (SectionBegin inverted name) = do textNodes ← flushText state@(MustacheState
src/lib/Text/Mustache/Render.hs view
@@ -66,17 +66,17 @@ substitute' _ (TextBlock t) = t -- substituting a whole section (entails a focus shift)- substitute' (Context parents focus@(Array a)) (Section Implicit secAST)+ substitute' (Context parents focus@(Array a)) (Section Implicit secSTree) | V.null a = (∅) | otherwise = flip joinSubstituted a $ \focus' → let newContext = Context (focus:parents) focus' in- joinSubstituted (substitute' newContext) secAST- substitute' context@(Context _ (Object _)) (Section Implicit secAST) =- joinSubstituted (substitute' context) secAST+ joinSubstituted (substitute' newContext) secSTree+ substitute' context@(Context _ (Object _)) (Section Implicit secSTree) =+ joinSubstituted (substitute' context) secSTree substitute' _ (Section Implicit _) = (∅)- substitute' context@(Context parents focus) (Section (NamedData secName) secAST) =+ substitute' context@(Context parents focus) (Section (NamedData secName) secSTree) = case search context secName of Just arr@(Array arrCont) → if V.null arrCont@@ -85,26 +85,26 @@ let newContext = Context (arr:focus:parents) focus' in- joinSubstituted (substitute' newContext) secAST+ joinSubstituted (substitute' newContext) secSTree Just (Bool False) → (∅)- Just (Lambda l) → joinSubstituted (substitute' context) (l context secAST)+ Just (Lambda l) → joinSubstituted (substitute' context) (l context secSTree) Just focus' → let newContext = Context (focus:parents) focus' in- joinSubstituted (substitute' newContext) secAST+ joinSubstituted (substitute' newContext) secSTree Nothing → (∅) -- substituting an inverted section substitute' _ (InvertedSection Implicit _ ) = (∅)- substitute' context (InvertedSection (NamedData secName) invSecAST) =+ substitute' context (InvertedSection (NamedData secName) invSecSTree) = case search context secName of Just (Bool False) → contents Just (Array a) | V.null a → contents Nothing → contents _ → (∅) where- contents = joinSubstituted (substitute' context) invSecAST+ contents = joinSubstituted (substitute' context) invSecSTree -- substituting a variable substitute' (Context _ current) (Variable _ Implicit) = toString current@@ -122,7 +122,7 @@ $ HM.lookup pName cPartials -handleIndent ∷ Maybe Text → AST → AST+handleIndent ∷ Maybe Text → STree → STree handleIndent Nothing ast' = ast' handleIndent (Just indentation) ast' = preface ⊕ content where
src/lib/Text/Mustache/Types.hs view
@@ -15,7 +15,7 @@ module Text.Mustache.Types ( -- * Types for the Parser / Template- AST+ STree , Template(..) , Node(..) , DataIdentifier(..)@@ -24,8 +24,8 @@ , Key -- ** Converting , object- , (~>), (↝), (~=), (⥱), (~~>), (~↝), (~~=), (~⥱)- , ToMustache, toMustache, toTextBlock, mFromJSON+ , (~>), (↝), (~=), (⥱)+ , ToMustache, toMustache, mFromJSON -- ** Representation , Array, Object, Pair , Context(..)@@ -33,8 +33,6 @@ ) where -import Conversion-import Conversion.Text () import qualified Data.Aeson as Aeson import Data.HashMap.Strict as HM import qualified Data.HashSet as HS@@ -45,15 +43,15 @@ import qualified Data.Vector as V import Prelude.Unicode --- | Abstract syntax tree for a mustache template-type AST = [Node Text]+-- | Syntax tree for a mustache template+type STree = [Node Text] --- | Basic values composing the AST+-- | Basic values composing the STree data Node α = TextBlock α- | Section DataIdentifier AST- | InvertedSection DataIdentifier AST+ | Section DataIdentifier STree+ | InvertedSection DataIdentifier STree | Variable Bool DataIdentifier | Partial (Maybe α) FilePath deriving (Show, Eq)@@ -78,13 +76,13 @@ data Context α = Context [α] α deriving (Eq, Show, Ord) --- | Internal value AST+-- | Internal value STree data Value = Object Object | Array Array | Number Scientific | String Text- | Lambda (Context Value → AST → AST)+ | Lambda (Context Value → STree → STree) | Bool Bool | Null @@ -109,15 +107,12 @@ instance ToMustache Value where toMustache = id -instance {-# OVERLAPPING #-} ToMustache [Char] where+instance ToMustache [Char] where toMustache = toMustache ∘ pack instance ToMustache Bool where toMustache = Bool -instance ToMustache Char where- toMustache = String ∘ pack ∘ return- instance ToMustache () where toMustache = const Null @@ -130,65 +125,71 @@ instance ToMustache Scientific where toMustache = Number -instance {-# OVERLAPPABLE #-} ToMustache ω ⇒ ToMustache [ω] where+instance ToMustache ω ⇒ ToMustache [ω] where toMustache = Array ∘ V.fromList ∘ fmap toMustache -instance {-# OVERLAPPING #-} ToMustache (V.Vector Value) where- toMustache = Array- instance ToMustache ω ⇒ ToMustache (V.Vector ω) where toMustache = toMustache ∘ fmap toMustache -instance {-# OVERLAPPING #-} ToMustache (HM.HashMap Text Value) where- toMustache = Object+instance (ToMustache ω) ⇒ ToMustache (Map.Map Text ω) where+ toMustache = mapInstanceHelper id -instance (Conversion θ Text, ToMustache ω) ⇒ ToMustache (Map.Map θ ω) where- toMustache =- toMustache- ∘ Map.foldrWithKey- (\k → HM.insert (convert k ∷ Text) ∘ toMustache)- HM.empty+instance (ToMustache ω) ⇒ ToMustache (Map.Map LT.Text ω) where+ toMustache = mapInstanceHelper LT.toStrict +instance (ToMustache ω) ⇒ ToMustache (Map.Map String ω) where+ toMustache = mapInstanceHelper pack++mapInstanceHelper :: ToMustache v => (a -> Text) -> Map.Map a v -> Value+mapInstanceHelper conv =+ toMustache+ ∘ Map.foldrWithKey+ (\k → HM.insert (conv k) ∘ toMustache)+ HM.empty+ instance ToMustache ω ⇒ ToMustache (HM.HashMap Text ω) where toMustache = toMustache ∘ fmap toMustache -instance {-# OVERLAPPABLE #-} (Conversion θ Text, ToMustache ω) ⇒ ToMustache (HM.HashMap θ ω) where- toMustache =- toMustache- ∘ HM.foldrWithKey- (\k → HM.insert (convert k ∷ Text) ∘ toMustache)- HM.empty+instance ToMustache ω ⇒ ToMustache (HM.HashMap LT.Text ω) where+ toMustache = hashMapInstanceHelper LT.toStrict -instance ToMustache (Context Value → AST → AST) where+instance ToMustache ω ⇒ ToMustache (HM.HashMap String ω) where+ toMustache = hashMapInstanceHelper pack++hashMapInstanceHelper :: ToMustache v => (a -> Text) -> HM.HashMap a v -> Value+hashMapInstanceHelper conv =+ toMustache+ ∘ HM.foldrWithKey+ (\k → HM.insert (conv k) ∘ toMustache)+ HM.empty++instance ToMustache (Context Value → STree → STree) where toMustache = Lambda -instance ToMustache (Context Value → AST → Text) where- toMustache f = toMustache wrapper- where- wrapper ∷ Context Value → AST → AST- wrapper c lAST = return ∘ TextBlock $ f c lAST+instance ToMustache (Context Value → STree → Text) where+ toMustache = lambdaInstanceHelper id -instance {-# OVERLAPPABLE #-} Conversion θ Text- ⇒ ToMustache (Context Value → AST → θ) where- toMustache f = toMustache wrapper- where- wrapper :: Context Value → AST → Text- wrapper c = convert ∘ f c+instance ToMustache (Context Value → STree → LT.Text) where+ toMustache = lambdaInstanceHelper LT.toStrict -instance ToMustache (AST → AST) where- toMustache f = toMustache (const f ∷ Context Value → AST → AST)+instance ToMustache (Context Value → STree → String) where+ toMustache = lambdaInstanceHelper pack -instance ToMustache (AST → Text) where+lambdaInstanceHelper :: (a -> Text) -> (Context Value -> STree -> a) -> Value+lambdaInstanceHelper conv f = Lambda wrapper+ where+ wrapper ∷ Context Value → STree → STree+ wrapper c lSTree = return ∘ TextBlock $ conv $ f c lSTree++instance ToMustache (STree → STree) where+ toMustache f = toMustache (const f ∷ Context Value → STree → STree)++instance ToMustache (STree → Text) where toMustache f = toMustache wrapper where- wrapper ∷ Context Value → AST → AST+ wrapper ∷ Context Value → STree → STree wrapper _ = (return ∘ TextBlock) ∘ f --- TODO Add these back in when you find a way to do so on earlier GHC versions--- or you drop support for GHC < 7.10--- instance {-# OVERLAPPABLE #-} Conversion θ Text ⇒ ToMustache (AST → θ) where--- toMustache f = toMustache (convert ∘ f ∷ AST → Text)- instance ToMustache Aeson.Value where toMustache (Aeson.Object o) = Object $ fmap toMustache o toMustache (Aeson.Array a) = Array $ fmap toMustache a@@ -344,39 +345,6 @@ infixr 8 ⥱ --- | Conceptually similar to '~>' but uses arbitrary String-likes as keys.-(~~>) ∷ (Conversion ζ Text, ToMustache ω) ⇒ ζ → ω → Pair-(~~>) = (~>) ∘ convert-{-# INLINEABLE (~~>) #-}-infixr 8 ~~>----- | Unicde version of '~~>'-(~↝) ∷ (Conversion ζ Text, ToMustache ω) ⇒ ζ → ω → Pair-(~↝) = (~~>)-{-# INLINEABLE (~↝) #-}-infixr 8 ~↝----- | Conceptually similar to '~=' but uses arbitrary String-likes as keys.-(~~=) ∷ (Conversion ζ Text, Aeson.ToJSON ι) ⇒ ζ → ι → Pair-(~~=) = (~=) ∘ convert-{-# INLINEABLE (~~=) #-}-infixr 8 ~~=----- | Unicode version of '~~='-(~⥱) ∷ (Conversion ζ Text, Aeson.ToJSON ι) ⇒ ζ → ι → Pair-(~⥱) = (~~=)-{-# INLINEABLE (~⥱) #-}-infixr 8 ~⥱----- | Converts arbitrary String-likes to Values-toTextBlock ∷ Conversion ζ Text ⇒ ζ → Value-toTextBlock = String ∘ convert-- -- | Converts a value that can be represented as JSON to a Value. mFromJSON ∷ Aeson.ToJSON ι ⇒ ι → Value mFromJSON = toMustache ∘ Aeson.toJSON@@ -393,6 +361,6 @@ -} data Template = Template { name ∷ String- , ast ∷ AST+ , ast ∷ STree , partials ∷ TemplateCache } deriving (Show)
test/integration/Language.hs view
@@ -26,14 +26,14 @@ import Text.Mustache.Parser import Text.Mustache.Types --- langspecDir = "spec-1.1.3"-langspecDir = "andrewthad-spec-786e4ac"-specDir = "specs"-releaseFile = "langspec.tar.gz"--- releaseURL = "https://codeload.github.com/mustache/spec/tar.gz/v1.1.3"-releaseURL = "https://codeload.github.com/andrewthad/spec/legacy.tar.gz/add_list_context_check" +-- (langspecDir, specDir, releaseFile, releaseURL)+langspecs =+ [ ("andrewthad-spec-786e4ac", "specs", "langspec-pull.tar.gz", "https://codeload.github.com/andrewthad/spec/legacy.tar.gz/add_list_context_check")+ , ("spec-1.1.3", "specs", "langspec-off.tar.gz", "https://codeload.github.com/mustache/spec/tar.gz/v1.1.3")+ ] + data LangSpecFile = LangSpecFile { overview :: String , tests :: [LangSpecTest]@@ -72,14 +72,13 @@ (&) = flip ($) -getOfficialSpecRelease ∷ FilePath → IO ()-getOfficialSpecRelease tempdir = do+getOfficialSpecRelease ∷ FilePath -> FilePath -> FilePath -> FilePath → IO ()+getOfficialSpecRelease tempdir langspecDir releaseFile releaseURL = do currentDirectory ← getCurrentDirectory setCurrentDirectory tempdir createDirectory langspecDir callProcess "curl" [releaseURL, "-o", releaseFile] callProcess "tar" ["-xf", releaseFile]- getDirectoryContents "." >>= print setCurrentDirectory currentDirectory @@ -117,6 +116,8 @@ withSystemTempDirectory "mustache-test-resources" $ \tempdir → do- getOfficialSpecRelease tempdir+ for_ langspecs $ \(langspecDir, _, releaseFile, releaseURL) ->+ getOfficialSpecRelease tempdir langspecDir releaseFile releaseURL hspec $- testOfficialLangSpec (tempdir </> langspecDir </> specDir)+ for_ langspecs $ \(langspecDir, specDir, _, _) ->+ testOfficialLangSpec (tempdir </> langspecDir </> specDir)