packages feed

ginger 0.2.5.0 → 0.2.6.0

raw patch · 7 files changed

+205/−48 lines, 7 filesdep +tasty-quickcheckPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: tasty-quickcheck

API changes (from Hackage documentation)

- Text.Ginger.GVal: instance Data.Aeson.Types.Class.ToJSON (Text.Ginger.GVal.GVal m)
+ Text.Ginger.GVal: infixr 8 ~>
+ Text.Ginger.GVal: instance Data.Aeson.Types.ToJSON.ToJSON (Text.Ginger.GVal.GVal m)
+ Text.Ginger.Optimizer: class Optimizable a
+ Text.Ginger.Optimizer: instance Text.Ginger.Optimizer.Optimizable Text.Ginger.AST.Block
+ Text.Ginger.Optimizer: instance Text.Ginger.Optimizer.Optimizable Text.Ginger.AST.Macro
+ Text.Ginger.Optimizer: instance Text.Ginger.Optimizer.Optimizable Text.Ginger.AST.Statement
+ Text.Ginger.Optimizer: instance Text.Ginger.Optimizer.Optimizable Text.Ginger.AST.Template
+ Text.Ginger.Optimizer: optimize :: Optimizable a => a -> a
+ Text.Ginger.Run: extractArgs :: [Text] -> [(Maybe Text, a)] -> (HashMap Text a, [a], HashMap Text a, [Text])
+ Text.Ginger.Run: extractArgsDefL :: [(Text, a)] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) [a]
+ Text.Ginger.Run: extractArgsL :: [Text] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) [Maybe a]
+ Text.Ginger.Run: extractArgsT :: ([Maybe a] -> b) -> [Text] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) b

Files

LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2015 Tobias Dammers+Copyright (c) 2015-2016 Tobias Dammers  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
ginger.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                ginger-version:             0.2.5.0+version:             0.2.6.0 synopsis:            An implementation of the Jinja2 template language in Haskell description:         Ginger is Jinja, minus the most blatant pythonisms. Wants                      to be feature complete, but isn't quite there yet.@@ -22,6 +22,7 @@                  , Text.Ginger.AST                  , Text.Ginger.GVal                  , Text.Ginger.Html+                 , Text.Ginger.Optimizer                  , Text.Ginger.Parse                  , Text.Ginger.Run                  , Text.PrintfA@@ -71,6 +72,8 @@                  , mtl                  , tasty                  , tasty-hunit+                 , tasty-quickcheck                  , text                  , transformers+                 , unordered-containers >= 0.2.5                  , utf8-string
src/Text/Ginger.hs view
@@ -277,11 +277,14 @@ -- | The data structures used to represent templates, statements and -- expressions internally. , module Text.Ginger.AST++-- *** Optimizer+-- | An optimizing AST rewriter+, module Text.Ginger.Optimizer ) where import Text.Ginger.Parse+import Text.Ginger.Optimizer import Text.Ginger.AST import Text.Ginger.Run import Text.Ginger.GVal--
src/Text/Ginger/GVal.hs view
@@ -126,7 +126,7 @@ -- Note that the default conversions will never return booleans unless 'asJSON' -- explicitly does this, because 'asText' will always return *something*. instance JSON.ToJSON (GVal m) where-    toJSON g = +    toJSON g =         if isNull g             then JSON.Null             else (fromMaybe (JSON.toJSON $ asText g) $
+ src/Text/Ginger/Optimizer.hs view
@@ -0,0 +1,69 @@+-- | A syntax tree optimizer+module Text.Ginger.Optimizer+( Optimizable (..) )+where++import Text.Ginger.AST+import Data.Monoid++class Optimizable a where+    optimize :: a -> a++instance Optimizable Template where+    optimize = optimizeTemplate++instance Optimizable Statement where+    optimize = optimizeStatement++instance Optimizable Block where+    optimize = optimizeBlock++instance Optimizable Macro where+    optimize = optimizeMacro++optimizeTemplate t =+    t { templateBody = optimize $ templateBody t+      , templateBlocks = fmap optimize $ templateBlocks t+      , templateParent = fmap optimize $ templateParent t+      }++{-+    = MultiS [Statement] -- ^ A sequence of multiple statements+    | ScopedS Statement -- ^ Run wrapped statement in a local scope+    | LiteralS Html -- ^ Literal output (anything outside of any tag)+    | InterpolationS Expression -- ^ {{ expression }}+    | IfS Expression Statement Statement -- ^ {% if expression %}statement{% else %}statement{% endif %}+    | ForS (Maybe VarName) VarName Expression Statement -- ^ {% for index, varname in expression %}statement{% endfor %}+    | SetVarS VarName Expression -- ^ {% set varname = expr %}+    | DefMacroS VarName Macro -- ^ {% macro varname %}statements{% endmacro %}+    | BlockRefS VarName+    | PreprocessedIncludeS Template -- ^ {% include "template" %}+    | NullS -- ^ The do-nothing statement (NOP)+-}+optimizeStatement (MultiS items) =+    case optimizeStatementList items of+        [] -> NullS+        [x] -> x+        xs -> MultiS xs+optimizeStatement s = s++optimizeBlock (Block b) = Block $ optimize b++optimizeMacro (Macro args body) = Macro args (optimize body)++optimizeStatementList =+    mergeLiterals .+    cullNulls .+    fmap optimize++cullNulls :: [Statement] -> [Statement]+cullNulls = filter (not . isNullS)+    where+        isNullS NullS = True+        isNullS _ = False++mergeLiterals :: [Statement] -> [Statement]+mergeLiterals [] = []+mergeLiterals [x] = [x]+mergeLiterals (x@(LiteralS a):y@(LiteralS b):xs) = mergeLiterals $ (LiteralS $ a <> b):xs+mergeLiterals (x:xs) = x:mergeLiterals xs
src/Text/Ginger/Run.hs view
@@ -31,6 +31,7 @@ , makeContextText , makeContextTextM , Run, liftRun, liftRun2+, extractArgs, extractArgsT, extractArgsL, extractArgsDefL ) where @@ -51,6 +52,8 @@                , seq                , fst, snd                , maybe+               , Either (..)+               , id                ) import qualified Prelude import Data.Maybe (fromMaybe, isJust)@@ -80,6 +83,8 @@ import Safe (readMay, lastDef, headMay) import Network.HTTP.Types (urlEncode) import Debug.Trace (trace)+import Data.Maybe (isNothing)+import Data.List (lookup, zipWith, unzip)  -- | Execution context. Determines how to look up variables from the -- environment, and how to write out template output.@@ -132,6 +137,68 @@         args' :: [Text]         args' = Prelude.map (asText . snd) args +-- | Match args according to a given arg spec, Python style.+-- The return value is a triple of @(matched, args, kwargs, unmatchedNames)@,+-- where @matches@ is a hash map of named captured arguments, args is a list of+-- remaining unmatched positional arguments, kwargs is a list of remaining+-- unmatched named arguments, and @unmatchedNames@ contains the argument names+-- that haven't been matched.+extractArgs :: [Text] -> [(Maybe Text, a)] -> (HashMap Text a, [a], HashMap Text a, [Text])+extractArgs argNames args =+    let (matchedPositional, argNames', args') = matchPositionalArgs argNames args+        (matchedKeyword, argNames'', args'') = matchKeywordArgs argNames' args'+        unmatchedPositional = [ a | (Nothing, a) <- args'' ]+        unmatchedKeyword = HashMap.fromList [ (k, v) | (Just k, v) <- args'' ]+    in ( HashMap.fromList (matchedPositional ++ matchedKeyword)+       , unmatchedPositional+       , unmatchedKeyword+       , argNames''+       )+    where+        matchPositionalArgs :: [Text] -> [(Maybe Text, a)] -> ([(Text, a)], [Text], [(Maybe Text, a)])+        matchPositionalArgs [] args = ([], [], args)+        matchPositionalArgs names [] = ([], names, [])+        matchPositionalArgs names@(n:ns) allArgs@((anm, arg):args)+            | Just n == anm || isNothing anm =+                let (matched, ns', args') = matchPositionalArgs ns args+                in ((n, arg):matched, ns', args')+            | otherwise = ([], names, allArgs)++        matchKeywordArgs :: [Text] -> [(Maybe Text, a)] -> ([(Text, a)], [Text], [(Maybe Text, a)])+        matchKeywordArgs [] args = ([], [], args)+        matchKeywordArgs names allArgs@((Nothing, arg):args) =+            let (matched, ns', args') = matchKeywordArgs names args+            in (matched, ns', (Nothing, arg):args')+        matchKeywordArgs names@(n:ns) args =+            case (lookup (Just n) args) of+                Nothing ->+                    let (matched, ns', args') = matchKeywordArgs ns args+                    in (matched, n:ns', args')+                Just v ->+                    let args' = [ (k,v) | (k,v) <- args, k /= Just n ]+                        (matched, ns', args'') = matchKeywordArgs ns args'+                    in ((n,v):matched, ns', args'')++-- | Parse argument list into type-safe argument structure.+extractArgsT :: ([Maybe a] -> b) -> [Text] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) b+extractArgsT f argNames args =+    let (matchedMap, freeArgs, freeKwargs, unmatched) = extractArgs argNames args+    in if List.null freeArgs && HashMap.null freeKwargs+        then Right (f $ fmap (\name -> HashMap.lookup name matchedMap) argNames)+        else Left (freeArgs, freeKwargs, unmatched)++-- | Parse argument list into flat list of matched arguments.+extractArgsL :: [Text] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) [Maybe a]+extractArgsL = extractArgsT id++extractArgsDefL :: [(Text, a)] -> [(Maybe Text, a)] -> Either ([a], HashMap Text a, [Text]) [a]+extractArgsDefL argSpec args =+    let (names, defs) = unzip argSpec+    in injectDefaults defs <$> extractArgsL names args++injectDefaults :: [a] -> [Maybe a] -> [a]+injectDefaults = zipWith fromMaybe+ defRunState :: forall m h. (Monoid h, Monad m) => Template -> RunState m h defRunState tpl =     RunState@@ -148,42 +215,43 @@             , ("any", fromFunction gfnAny)             , ("all", fromFunction gfnAll)             -- TODO: batch-            , ("ceil", fromFunction . unaryNumericFunc 0 $ Prelude.fromIntegral . Prelude.ceiling)             , ("capitalize", fromFunction . variadicStringFunc $ mconcat . Prelude.map capitalize)+            , ("ceil", fromFunction . unaryNumericFunc 0 $ Prelude.fromIntegral . Prelude.ceiling)             , ("center", fromFunction gfnCenter)             , ("concat", fromFunction . variadicStringFunc $ mconcat)             , ("contains", fromFunction gfnContains)-            , ("default", fromFunction gfnDefault)             , ("d", fromFunction gfnDefault)+            , ("default", fromFunction gfnDefault)             , ("difference", fromFunction . variadicNumericFunc 0 $ difference)-            , ("escape", fromFunction gfnEscape)             , ("e", fromFunction gfnEscape)+            , ("equals", fromFunction gfnEquals)+            , ("escape", fromFunction gfnEscape)             , ("filesizeformat", fromFunction gfnFileSizeFormat)             , ("filter", fromFunction gfnFilter)-            , ("equals", fromFunction gfnEquals)-            , ("nequals", fromFunction gfnNEquals)-            , ("greaterEquals", fromFunction gfnGreaterEquals)-            , ("lessEquals", fromFunction gfnLessEquals)-            , ("greater", fromFunction gfnGreater)-            , ("less", fromFunction gfnLess)             , ("floor", fromFunction . unaryNumericFunc 0 $ Prelude.fromIntegral . Prelude.floor)+            , ("greater", fromFunction gfnGreater)+            , ("greaterEquals", fromFunction gfnGreaterEquals)             , ("int", fromFunction . unaryFunc $ toGVal . (fmap (Prelude.truncate :: Scientific -> Int)) . asNumber)             , ("int_ratio", fromFunction . variadicNumericFunc 1 $ fromIntegral . intRatio . Prelude.map Prelude.floor)             , ("iterable", fromFunction . unaryFunc $ toGVal . (\x -> isList x || isDict x))             , ("length", fromFunction . unaryFunc $ toGVal . length)+            , ("less", fromFunction gfnLess)+            , ("lessEquals", fromFunction gfnLessEquals)             , ("modulo", fromFunction . variadicNumericFunc 1 $ fromIntegral . modulo . Prelude.map Prelude.floor)+            , ("nequals", fromFunction gfnNEquals)             , ("num", fromFunction . unaryFunc $ toGVal . asNumber)-            , ("product", fromFunction . variadicNumericFunc 1 $ Prelude.product)             , ("printf", fromFunction gfnPrintf)+            , ("product", fromFunction . variadicNumericFunc 1 $ Prelude.product)             , ("ratio", fromFunction . variadicNumericFunc 1 $ Scientific.fromFloatDigits . ratio . Prelude.map Scientific.toRealFloat)+            , ("replace", fromFunction $ gfnReplace)             , ("round", fromFunction . unaryNumericFunc 0 $ Prelude.fromIntegral . Prelude.round)             , ("show", fromFunction . unaryFunc $ fromString . show)             , ("slice", fromFunction $ gfnSlice)+            , ("sort", fromFunction $ gfnSort)             , ("str", fromFunction . unaryFunc $ toGVal . asText)             , ("sum", fromFunction . variadicNumericFunc 0 $ Prelude.sum)             , ("truncate", fromFunction . unaryNumericFunc 0 $ Prelude.fromIntegral . Prelude.truncate)             , ("urlencode", fromFunction $ gfnUrlEncode)-            , ("sort", fromFunction $ gfnSort)             ]          gfnRawHtml :: Function (Run m h)@@ -315,41 +383,53 @@             return . toGVal $ center (asText s) (fromMaybe 80 $ Prelude.truncate <$> asNumber w) (asText pad)          gfnSlice :: Function (Run m h)-        gfnSlice [] = return def-        gfnSlice [(Nothing, slicee)] = return slicee-        gfnSlice [(Nothing, slicee), (Nothing, startPos)] =-            gfnSlice [(Nothing, slicee), (Nothing, startPos), (Nothing, def)]-        gfnSlice [(Nothing, slicee), (Nothing, startPos), (Nothing, length)] = do-            case asDictItems slicee of-                Just items -> do-                    let slicedItems = slice items startInt lengthInt-                    return $ dict slicedItems-                Nothing -> do-                    let items = fromMaybe [] $ asList slicee-                        slicedItems = slice items startInt lengthInt-                    return $ toGVal slicedItems-            where-                startInt :: Int-                startInt = fromMaybe 0 . fmap Prelude.round . asNumber $ startPos+        gfnSlice args =+            let argValues =+                    extractArgsDefL+                        [ ("slicee", def)+                        , ("start", def)+                        , ("length", def)+                        ]+                        args+            in case argValues of+                Right (slicee:startPos:length:[]) -> do+                    let startInt :: Int+                        startInt = fromMaybe 0 . fmap Prelude.round . asNumber $ startPos -                lengthInt :: Maybe Int-                lengthInt = fmap Prelude.round . asNumber $ length+                        lengthInt :: Maybe Int+                        lengthInt = fmap Prelude.round . asNumber $ length -                slice :: [a] -> Int -> Maybe Int -> [a]-                slice xs start Nothing =-                    Prelude.drop start $ xs-                slice xs start (Just length) =-                    Prelude.take length . Prelude.drop start $ xs-        gfnSlice ((Nothing, slicee):(Nothing, startPos):args) =-            let length = fromMaybe def $ List.lookup (Just "length") args-            in gfnSlice [(Nothing, slicee), (Nothing, startPos), (Nothing, length)]-        gfnSlice ((Nothing, slicee):args) =-            let startPos = fromMaybe def $ List.lookup (Just "start") args-            in gfnSlice ((Nothing, slicee):(Nothing, startPos):args)-        gfnSlice args =-            let slicee = fromMaybe def $ List.lookup (Just "slicee") args-            in gfnSlice ((Nothing, slicee):args)+                        slice :: [a] -> Int -> Maybe Int -> [a]+                        slice xs start Nothing =+                            Prelude.drop start $ xs+                        slice xs start (Just length) =+                            Prelude.take length . Prelude.drop start $ xs+                    case asDictItems slicee of+                        Just items -> do+                            let slicedItems = slice items startInt lengthInt+                            return $ dict slicedItems+                        Nothing -> do+                            let items = fromMaybe [] $ asList slicee+                                slicedItems = slice items startInt lengthInt+                            return $ toGVal slicedItems+                _ -> fail "Invalid arguments to 'slice'" +        gfnReplace :: Function (Run m h)+        gfnReplace args =+            let argValues =+                    extractArgsDefL+                        [ ("str", def)+                        , ("search", def)+                        , ("replace", def)+                        ]+                        args+            in case argValues of+                Right (strG:searchG:replaceG:[]) -> do+                    let str = asText strG+                        search = asText searchG+                        replace = asText replaceG+                    return . toGVal $ Text.replace search replace str+                _ -> fail "Invalid arguments to 'replace'"          gfnSort :: Function (Run m h)         gfnSort [] = return def
test/Spec.hs view
@@ -3,6 +3,7 @@ import Test.Tasty  import Text.Ginger.SimulationTests (simulationTests)+import Text.Ginger.PropertyTests (propertyTests)  main = defaultMain allTests @@ -10,4 +11,5 @@ allTests =     testGroup "All Tests"         [ simulationTests+        , propertyTests         ]