packages feed

microstache 1 → 1.0.1

raw patch · 9 files changed

+126/−68 lines, 9 filesdep ~transformers

Dependency ranges changed: transformers

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+## microstache 1.0.1++- Add `renderMustacheW`+ ## microstache 1  Initial release, corresponding to `stache-0.2.2`.
microstache.cabal view
@@ -1,5 +1,5 @@ name:                 microstache-version:              1+version:              1.0.1 cabal-version:        >= 1.10 license:              BSD3 license-file:         LICENSE@@ -65,6 +65,7 @@     Text.Microstache.Render     Text.Microstache.Type   hs-source-dirs:     src+  ghc-options:        -Wall   default-language:   Haskell2010  test-suite tests
mustache-spec/Spec.hs view
@@ -71,7 +71,7 @@     return (specData' bytes)   where     specData' bytes = describe aspect $ do-      let handleError = expectationFailure . show +      let handleError = expectationFailure . show       case eitherDecode bytes of         Left err ->           it "should load YAML specs first" $@@ -88,5 +88,6 @@                       Left perr -> handleError perr >> undefined                       Right ns  -> return (pname, ns)                   let ps2 = M.fromList ps1 `M.union` templateCache-                  renderMustache (Template templateActual ps2) testData-                    `shouldBe` testExpected+                  let (_ws, t) = renderMustacheW (Template templateActual ps2) testData+                  -- _ <- traverse print _ws+                  t `shouldBe` testExpected
src/Text/Microstache.hs view
@@ -78,12 +78,17 @@   , Key (..)   , PName (..)   , MustacheException (..)+  , displayMustacheException+  , MustacheWarning (..)+  , displayMustacheWarning     -- * Compiling   , compileMustacheDir   , compileMustacheFile   , compileMustacheText     -- * Rendering-  , renderMustache )+  , renderMustache+  , renderMustacheW+  ) where  import Text.Microstache.Compile
src/Text/Microstache/Compile.hs view
@@ -115,8 +115,8 @@ withException = either (throwIO . MustacheParserException) return  makeAbsolute' :: FilePath -> IO FilePath-makeAbsolute' path =-    fmap (matchTrailingSeparator path . F.normalise) (prependCurrentDirectory path)+makeAbsolute' path0 =+    fmap (matchTrailingSeparator path0 . F.normalise) (prependCurrentDirectory path0)   where     prependCurrentDirectory :: FilePath -> IO FilePath     prependCurrentDirectory path =
src/Text/Microstache/Parser.hs view
@@ -23,7 +23,7 @@ import Data.Maybe (catMaybes) import Data.Text.Lazy (Text) import Text.Parsec hiding ((<|>))-import Text.Parsec.Char+import Text.Parsec.Char () import Data.Word (Word) import Text.Microstache.Type import qualified Data.Text             as T
src/Text/Microstache/Render.hs view
@@ -11,19 +11,17 @@ -- import the module, because "Text.Microstache" re-exports everything you may -- need, import that module instead. -{-# LANGUAGE CPP               #-}-{-# LANGUAGE OverloadedStrings #-}-+{-# LANGUAGE CPP                #-}+{-# LANGUAGE OverloadedStrings  #-} module Text.Microstache.Render-  ( renderMustache )+  ( renderMustache, renderMustacheW ) where -import Control.Exception (throw) import Control.Monad (when, forM_, unless)-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Writer.Lazy+import Control.Monad.Trans.Reader (ReaderT (..), asks, local)+import Control.Monad.Trans.State.Strict (State, modify', execState) import Control.Monad.Trans.Class (lift)-import Data.Aeson+import Data.Aeson (Value (..), encode) import Data.Monoid (mempty) import Data.Semigroup ((<>)) import Data.Foldable (asum)@@ -32,13 +30,10 @@ import Data.Text (Text) import Data.Word (Word) import Text.Microstache.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.Encoding     as TL import qualified Data.Text.Lazy.Builder as B@@ -55,12 +50,21 @@ -- and accumulate the result as 'B.Builder' data which is then turned into -- lazy 'TL.Text'. -type Render a = ReaderT RenderContext (Writer B.Builder) a+type Render a = ReaderT RenderContext (State S) a --- | The render monad context.+data S = S ([MustacheWarning] -> [MustacheWarning]) B.Builder +tellWarning :: MustacheWarning -> Render ()+tellWarning w = lift (modify' f) where+    f (S ws b) = S (ws . (w:)) b++tellBuilder :: B.Builder -> Render ()+tellBuilder b' = lift (modify' f) where+    f (S ws b) = S ws (b <> b')++-- | The render monad context. data RenderContext = RenderContext-  { rcIndent   :: Maybe Word      -- ^ Actual indentation level+  { rcIndent   :: Maybe Word     -- ^ Actual indentation level   , rcContext  :: NonEmpty Value -- ^ The context stack   , rcPrefix   :: Key            -- ^ Prefix accumulated by entering sections   , rcTemplate :: Template       -- ^ The template to render@@ -72,14 +76,14 @@  -- | 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 =+renderMustache t = snd . renderMustacheW t++-- | Like 'renderMustache' but also return a list of warnings.+--+-- @since 1.0.1+renderMustacheW :: Template -> Value -> ([MustacheWarning], TL.Text)+renderMustacheW t =   runRender (renderPartial (templateActual t) Nothing renderNode) t  -- | Render a single 'Node'.@@ -87,9 +91,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 $@@ -113,21 +117,23 @@ -- | 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 = case execState (runReaderT m rc) (S id mempty) of+    S ws b -> (ws [], B.toLazyText b)   where     rc = RenderContext       { rcIndent   = Nothing       , rcContext  = v :| []       , rcPrefix   = mempty       , rcTemplate = t-      , rcLastNode = True }+      , rcLastNode = True+      } {-# INLINE runRender #-}  -- | Output a piece of strict 'Text'.  outputRaw :: Text -> Render ()-outputRaw = lift . tell . B.fromText+outputRaw = tellBuilder . B.fromText {-# INLINE outputRaw #-}  -- | Output indentation consisting of appropriate number of spaces.@@ -194,12 +200,12 @@ lookupKey k = do   v     <- asks rcContext   p     <- asks rcPrefix-  pname <- asks (templateActual . rcTemplate)   let f x = asum (simpleLookup False (x <> k) <$> v)   case asum (fmap (f . Key) . reverse . tails $ unKey p) of-    -- Context Misses: Failed context lookups should be considered falsey.-    -- throw (MustacheRenderException pname (p <> k))-    Nothing -> return (String "")+    Nothing -> do+        -- Context Misses: Failed context lookups should be considered falsey.+        tellWarning $ MustacheVariableNotFound (p <> k)+        return (String "")     Just  r -> return r  -- | Lookup a 'Value' by traversing another 'Value' using given 'Key' as@@ -269,10 +275,19 @@  -- | Render Aeson's 'Value' /without/ HTML escaping. -renderValue :: Value -> Text-renderValue Null         = ""-renderValue (String str) = str-renderValue value        = (TL.toStrict . TL.decodeUtf8 . 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'.
src/Text/Microstache/Type.hs view
@@ -23,7 +23,11 @@   , Key (..)   , showKey   , PName (..)-  , MustacheException (..) )+  , MustacheException (..)+  , displayMustacheException+  , MustacheWarning (..)+  , displayMustacheWarning+  ) where  import Data.Word (Word)@@ -109,20 +113,42 @@ data MustacheException   = MustacheParserException ParseError     -- ^ 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'.   deriving (Eq, Show, Typeable, Generic) -#if MIN_VERSION_base(4,8,0)-instance Exception MustacheException where-  displayException (MustacheParserException e) = show e-  displayException (MustacheRenderException pname key) =+{-# DEPRECATED MustacheRenderException "Not thrown anymore, will be removed in the next major version of microstache" #-}++-- | @since 1.0.1+displayMustacheException :: MustacheException -> String+displayMustacheException (MustacheParserException e) = show e+displayMustacheException (MustacheRenderException pname key) =     "Referenced value was not provided in partial \"" ++ T.unpack (unPName pname) ++     "\", key: " ++ T.unpack (showKey key)-#else-instance Exception MustacheException++instance Exception MustacheException where+#if MIN_VERSION_base(4,8,0)+    displayException = displayMustacheException+#endif++-- | @since 1.0.1+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)++-- | @since 1.0.1+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)++instance Exception MustacheWarning where+#if MIN_VERSION_base(4,8,0)+    displayException = displayMustacheWarning #endif
tests/Text/Microstache/RenderSpec.hs view
@@ -24,10 +24,14 @@  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+      key = Key . return+   it "leaves text block “as is”" $     r [TextBlock "a text block"] Null `shouldBe` "a text block"   it "renders escaped variables correctly" $@@ -38,19 +42,23 @@     r [UnescapedVar (key "foo")]       (object ["foo" .= ("<html>&\"something\"</html>" :: Text)])       `shouldBe` "<html>&\"something\"</html>"+  context "when rendering a variable" $ do+    it "warns when variable doesn't exist" $+      w [EscapedVar (key "foo")] (object []) `shouldBe`+        [MustacheVariableNotFound (key "foo")]+    it "warns when variable is non-scalar" $+      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 "*"]]-    {--    Not correct according to spec     context "when the key is not present" $-      it "throws the correct exception" $-        evaluate (r nodes (object [])) `shouldThrow`-          (== MustacheRenderException "test" (key "foo"))+      it "warns with the correct warning" $+        w nodes (object []) `shouldBe`+          [MustacheVariableNotFound (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 "warns with the correct warning" $+        w nodes (object ["foo" .= ([1] :: [Int])]) `shouldBe`+          [MustacheVariableNotFound (Key ["foo","bar"])]     context "when the key is present" $ do       context "when the key is a “false” value" $ do         it "skips the Null value" $@@ -105,12 +113,10 @@             `shouldBe` "x"   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 "warns the 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" $