stache 1.0.0 → 1.1.0
raw patch · 6 files changed
+119/−30 lines, 6 files
Files
- CHANGELOG.md +7/−0
- Text/Mustache/Render.hs +59/−25
- Text/Mustache/Type.hs +26/−1
- stache.cabal +1/−1
- tests/Text/Mustache/RenderSpec.hs +19/−3
- tests/Text/Mustache/TypeSpec.hs +7/−0
CHANGELOG.md view
@@ -1,3 +1,10 @@+## Stache 1.1.0++* Added `mustacheRenderW` that allows to render a `Template` collecting+ warnings in the process. Also added the `MusthacheWarning` type+ representing the warnings themselves and `displayMustacheWarning` to+ pretty-print the warnings.+ ## Stache 1.0.0 * Improved metadata and documentation.
Text/Mustache/Render.hs view
@@ -15,29 +15,29 @@ {-# LANGUAGE OverloadedStrings #-} module Text.Mustache.Render- ( renderMustache )+ ( renderMustache+ , renderMustacheW ) where import Control.Monad.Reader-import Control.Monad.Writer.Strict+import Control.Monad.State.Strict (State, modify', execState) import Data.Aeson import Data.Foldable (asum) import Data.List (tails) import Data.List.NonEmpty (NonEmpty (..))-import Data.Maybe (fromMaybe)+import Data.Semigroup ((<>)) import Data.Text (Text) import Text.Megaparsec.Pos (Pos, unPos) import Text.Mustache.Type-import qualified Data.ByteString.Lazy as B-import qualified Data.HashMap.Strict as H-import qualified Data.List.NonEmpty as NE-import qualified Data.Map as M-import qualified Data.Semigroup as S-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as B-import qualified Data.Vector as V+import qualified Data.HashMap.Strict as H+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import qualified Data.Semigroup as S+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as B+import qualified Data.Text.Lazy.Encoding as TL+import qualified Data.Vector as V #if !MIN_VERSION_base(4,8,0) import Control.Applicative@@ -50,8 +50,10 @@ -- 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+type Render a = ReaderT RenderContext (State S) a +data S = S ([MustacheWarning] -> [MustacheWarning]) B.Builder+ -- | The render monad context. data RenderContext = RenderContext@@ -69,7 +71,14 @@ -- for interpolation. renderMustache :: Template -> Value -> TL.Text-renderMustache t =+renderMustache t = snd . renderMustacheW t++-- | Like 'renderMustache', but also returns a collection of warnings.+--+-- @since 1.1.0++renderMustacheW :: Template -> Value -> ([MustacheWarning], TL.Text)+renderMustacheW t = runRender (renderPartial (templateActual t) Nothing renderNode) t -- | Render a single 'Node'.@@ -77,9 +86,9 @@ renderNode :: Node -> Render () renderNode (TextBlock txt) = outputIndented txt renderNode (EscapedVar k) =- lookupKey k >>= outputRaw . escapeHtml . renderValue+ lookupKey k >>= renderValue k >>= outputRaw . escapeHtml renderNode (UnescapedVar k) =- lookupKey k >>= outputRaw . renderValue+ lookupKey k >>= renderValue k >>= outputRaw renderNode (Section k ns) = do val <- lookupKey k enterSection k $@@ -103,9 +112,10 @@ -- | Run 'Render' monad given template to render and a 'Value' to take -- values from. -runRender :: Render a -> Template -> Value -> TL.Text-runRender m t v = (B.toLazyText . execWriter) (runReaderT m rc)+runRender :: Render a -> Template -> Value -> ([MustacheWarning], TL.Text)+runRender m t v = (ws [], B.toLazyText b) where+ S ws b = execState (runReaderT m rc) (S id mempty) rc = RenderContext { rcIndent = Nothing , rcContext = v :| []@@ -117,7 +127,7 @@ -- | Output a piece of strict 'Text'. outputRaw :: Text -> Render ()-outputRaw = tell . B.fromText+outputRaw = tellBuilder . B.fromText {-# INLINE outputRaw #-} -- | Output indentation consisting of appropriate number of spaces.@@ -185,7 +195,11 @@ v <- asks rcContext p <- asks rcPrefix let f x = asum (simpleLookup False (x <> k) <$> v)- (return . fromMaybe Null . asum) (fmap (f . Key) . reverse . tails $ unKey p)+ case asum (fmap (f . Key) . reverse . tails $ unKey p) of+ Nothing ->+ Null <$ tellWarning (MustacheVariableNotFound (p <> k))+ Just r ->+ return r -- | Lookup a 'Value' by traversing another 'Value' using given 'Key' as -- “path”.@@ -225,6 +239,16 @@ ---------------------------------------------------------------------------- -- Helpers +-- | Register a warning.++tellWarning :: MustacheWarning -> Render ()+tellWarning w = modify' $ \(S ws b) -> S (ws . (w:)) b++-- | Register a piece of output.++tellBuilder :: B.Builder -> Render ()+tellBuilder b' = modify' $ \(S ws b) -> S ws (b <> b')+ -- | Add two @'Maybe' 'Pos'@ values together. addIndents :: Maybe Pos -> Maybe Pos -> Maybe Pos@@ -254,10 +278,20 @@ -- | Render Aeson's 'Value' /without/ HTML escaping. -renderValue :: Value -> Text-renderValue Null = ""-renderValue (String str) = str-renderValue value = (T.decodeUtf8 . B.toStrict . encode) value+renderValue :: Key -> Value -> Render Text+renderValue k v =+ case v of+ Null -> return ""+ String str -> return str+ Object _ -> do+ tellWarning (MustacheDirectlyRenderedValue k)+ render v+ Array _ -> do+ tellWarning (MustacheDirectlyRenderedValue k)+ render v+ _ -> render v+ where+ render = return . TL.toStrict . TL.decodeUtf8 . encode {-# INLINE renderValue #-} -- | Escape HTML represented as strict 'Text'.
Text/Mustache/Type.hs view
@@ -23,7 +23,9 @@ , Key (..) , showKey , PName (..)- , MustacheException (..) )+ , MustacheException (..)+ , MustacheWarning (..)+ , displayMustacheWarning ) where import Control.DeepSeq@@ -123,3 +125,26 @@ #else instance Exception MustacheException #endif++-- | Warning that may be generated during rendering of a 'Template'.+--+-- @since 1.1.0++data MustacheWarning+ = MustacheVariableNotFound Key+ -- ^ The template contained a variable for which there was no data+ -- counterpart in the current context.+ | MustacheDirectlyRenderedValue Key+ -- ^ A complex value such as an 'Object' or 'Array' was directly+ -- rendered into the template.+ deriving (Eq, Show, Typeable, Generic)++-- | Pretty-print a 'MustacheWarning'.+--+-- @since 1.1.0++displayMustacheWarning :: MustacheWarning -> String+displayMustacheWarning (MustacheVariableNotFound key) =+ "Referenced value was not provided, key: " ++ T.unpack (showKey key)+displayMustacheWarning (MustacheDirectlyRenderedValue key) =+ "Complex value rendered as such, key: " ++ T.unpack (showKey key)
stache.cabal view
@@ -1,5 +1,5 @@ name: stache-version: 1.0.0+version: 1.1.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
tests/Text/Mustache/RenderSpec.hs view
@@ -23,8 +23,11 @@ spec :: Spec spec = describe "renderMustache" $ do- let r ns value =+ let w ns value = let template = Template "test" (M.singleton "test" ns)+ in fst (renderMustacheW template value)+ r ns value =+ let template = Template "test" (M.singleton "test" ns) in renderMustache template value key = Key . pure it "leaves text block “as is”" $@@ -37,11 +40,22 @@ r [UnescapedVar (key "foo")] (object ["foo" .= ("<html>&\"something\"</html>" :: Text)]) `shouldBe` "<html>&\"something\"</html>"+ context "when rendering a variable" $ do+ context "when variable does not exist" $+ it "generates correct warning" $+ w [EscapedVar (key "foo")] (object []) `shouldBe`+ [MustacheVariableNotFound (key "foo")]+ context "when variable is not non-scalar" $+ it "generates correct warning" $+ w [EscapedVar (key "foo")] (object ["foo" .= object []]) `shouldBe`+ [MustacheDirectlyRenderedValue (key "foo")] context "when rendering a section" $ do let nodes = [Section (key "foo") [UnescapedVar (key "bar"), TextBlock "*"]]- context "when the key is not present" $+ context "when the key is not present" $ do it "renders nothing" $ r nodes (object []) `shouldBe` ""+ it "generates correct warning" $+ w nodes (object []) `shouldBe` [MustacheVariableNotFound (key "foo")] context "when the key is present" $ do context "when the key is a “false” value" $ do it "skips the Null value" $@@ -96,9 +110,11 @@ `shouldBe` "x" context "when rendering an inverted section" $ do let nodes = [InvertedSection (key "foo") [TextBlock "Here!"]]- context "when the key is not present" $+ context "when the key is not present" $ do it "renders the inverse section" $ r nodes (object []) `shouldBe` "Here!"+ it "generates correct warning" $+ w nodes (object []) `shouldBe` [MustacheVariableNotFound (key "foo")] context "when the key is present" $ do context "when the key is a “false” value" $ do it "renders with Null value" $
tests/Text/Mustache/TypeSpec.hs view
@@ -35,6 +35,13 @@ showKey (Key ["boo"]) `shouldBe` "boo" showKey (Key ["foo","bar"]) `shouldBe` "foo.bar" showKey (Key ["baz","baz","quux"]) `shouldBe` "baz.baz.quux"+ describe "displayMustacheWarning" $ do+ it "renders “not found” warning correctly" $+ displayMustacheWarning (MustacheVariableNotFound (Key ["foo"]))+ `shouldBe` "Referenced value was not provided, key: foo"+ it "renders “directly rendered value” warning correctly" $+ displayMustacheWarning (MustacheDirectlyRenderedValue (Key ["foo"]))+ `shouldBe` "Complex value rendered as such, key: foo" templateA :: Template templateA = Template "a" $ M.fromList