packages feed

ginger 0.8.2.0 → 0.8.3.0

raw patch · 6 files changed

+116/−3 lines, 6 filesdep +regex-tdfaPVP ok

version bump matches the API change (PVP)

Dependencies added: regex-tdfa

API changes (from Hackage documentation)

+ Text.Ginger.Run.Builtins: fnReMatch :: Monad m => (Regex -> String -> a) -> [(Maybe Text, GVal (Run p m h))] -> Run p m h a
+ Text.Ginger.Run.Builtins: gfnReMatch :: forall p m h. Monad m => Function (Run p m h)
+ Text.Ginger.Run.Builtins: gfnReMatchOne :: forall p m h. Monad m => Function (Run p m h)
+ Text.Ginger.Run.Builtins: gfnReTest :: Monad m => Function (Run p m h)
+ Text.Ginger.Run.Builtins: gfoRegex :: Monad m => GVal (Run p m h)
+ Text.Ginger.Run.Builtins: matchTextToGVal :: Monad m => MatchText String -> GVal m
+ Text.Ginger.Run.Builtins: parseCompOpts :: Monad m => GVal (Run p m h) -> Run p m h CompOption

Files

CHANGELOG.md view
@@ -1,3 +1,16 @@+## 0.8.3.0++- Added builtin `regex` module for POSIX regular expressions support++## 0.8.2.0++- Expose some internals of the `Run` type and the default implementations of+  built-in functions / filters++## 0.8.1.0++- Added built-ins: `partial`, `zip`, `zipwith`, `compose`+ ## 0.8.0.0  - Now compiles on GHC 8.4
ginger.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                ginger-version:             0.8.2.0+version:             0.8.3.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.@@ -44,6 +44,7 @@                , http-types >= 0.8 && (< 0.11 || >= 0.12)                , mtl >= 2.2                , parsec >= 3.0+               , regex-tdfa >=1.2.3.1 && <=1.3                , safe >= 0.3                , scientific >= 0.3                , text
src/Text/Ginger/GVal.hs view
@@ -133,6 +133,7 @@         (JSON.Null, b) -> Just $ b         (a, JSON.Null) -> Just $ a         _ -> Nothing -- If JSON tags mismatch, use default toJSON impl+    , length = (+) <$> length a <*> length b     }  -- | Marshal a GVal between carrier monads.
src/Text/Ginger/Run.hs view
@@ -201,6 +201,8 @@     , ("in", fromFunction gfnIn)     , ("escaped", fromFunction gfnEscaped) +    , ("regex", gfoRegex)+     -- TODO: sameas (predicate)     -- NOTE that this test doesn't make sense in a host language where pointers     -- are transparent - in Haskell, we simply don't care whether two values
src/Text/Ginger/Run/Builtins.hs view
@@ -34,6 +34,8 @@                , Either (..)                , id                , flip+               , const+               , either                ) import qualified Prelude import Data.Maybe (fromMaybe, isJust, isNothing)@@ -82,9 +84,10 @@                  , TimeLocale (..)                  , TimeZone (..)                  )-import Data.Foldable (asum)+import Data.Foldable (asum, toList) import qualified Data.Aeson as JSON import qualified Data.Aeson.Encode.Pretty as JSON+import qualified Text.Regex.TDFA as RE  tshow :: Show a => a -> Text tshow = Text.pack . show@@ -771,3 +774,62 @@ gfnOdd :: Monad m => Function (Run p m h) gfnOdd = unaryFunc $ toGVal . fmap Prelude.odd . toInteger +gfoRegex :: Monad m => GVal (Run p m h)+gfoRegex =+  dict+    [ ("match", fromFunction gfnReMatchOne)+    , ("matches", fromFunction gfnReMatch)+    , ("test", fromFunction gfnReTest)+    ]++gfnReMatchOne :: forall p m h. Monad m => Function (Run p m h)+gfnReMatchOne args =+  toGVal . fmap (\(_, m, _) -> matchTextToGVal m :: GVal (Run p m h)) <$> fnReMatch RE.matchOnceText args++gfnReMatch :: forall p m h. Monad m => Function (Run p m h)+gfnReMatch args =+  toGVal . fmap (matchTextToGVal :: RE.MatchText String -> GVal (Run p m h)) <$> fnReMatch RE.matchAllText args++gfnReTest :: Monad m => Function (Run p m h)+gfnReTest args =+  toGVal <$> fnReMatch RE.matchTest args++matchTextToGVal :: Monad m => RE.MatchText String -> GVal m+matchTextToGVal matchArr =+  let base = toGVal . toList . fmap (Text.pack . fst) $ matchArr+      textRepr = fromMaybe "" . fmap (Text.pack . fst) . headMay . toList $ matchArr+  in base+      { asText = textRepr+      , asHtml = html textRepr+      }++fnReMatch matchFunc args =+  let gArgs = extractArgsL ["re", "haystack", "opts"] args+  in either (const barf) go gArgs+  where+    go = \case+      [r, h, Nothing] ->+        go [r, h, Just def]+      [Just reG, Just haystackG, Just optsG] -> do+        opts <- parseCompOpts optsG+        let re = RE.makeRegexOpts opts RE.defaultExecOpt (Text.unpack . asText $ reG)+            haystack = Text.unpack . asText $ haystackG+        return $ matchFunc re haystack+      _ -> barf+    barf = do+      throwHere $ ArgumentsError (Just "re.match") "expected: regex, haystack, [opts]"++parseCompOpts :: Monad m => GVal (Run p m h) -> Run p m h RE.CompOption+parseCompOpts g = do+  let str = Text.unpack . asText $ g+  foldM+    (\x ->+      \case+        'i' -> return x { RE.caseSensitive = False }+        'm' -> return x { RE.multiline = True }+        c -> do+          warn $ ArgumentsError Nothing $ "unexpected regex modifier: " <> tshow c+          return x+    )+    RE.blankCompOpt+    str
test/Text/Ginger/SimulationTests.hs view
@@ -5,6 +5,7 @@  import Text.Ginger import Text.Ginger.Html+import Text.Ginger.Run.Type (GingerContext (..)) import Test.Tasty import Test.Tasty.HUnit import Data.Default (def)@@ -1424,6 +1425,38 @@                 "{% if 1 + 1 is lt(2) %}true{% else %}false{% endif %}"                 "true"           ]+    , testGroup "regex module"+          [ testGroup "regex.test"+            [ testCase "simple match" $ do+                mkTestText [] []+                  "{{ regex.test('[a-z]', 'abcde') ? 'yes' : 'no' }}"+                  "yes"+            , testCase "simple match" $ do+                mkTestText [] []+                  "{{ regex.test('[a-z]', '12345') ? 'yes' : 'no' }}"+                  "no"+            ]+          , testGroup "regex.match"+            [ testCase "simple match" $ do+                mkTestText [] []+                  "{{ regex.match('[a-z]', 'abcde') }}"+                  "a"+            , testCase "string representation of multiple matches returns only whole match" $ do+                mkTestText [] []+                  "{{ regex.match('(foo)(bar)', 'foobar') }}"+                  "foobar"+            , testCase "html representation of multiple matches returns only whole match" $ do+                mkTestHtml [] []+                  "{{ regex.match('(foo)(bar)', 'foobar') }}"+                  "foobar"+            ]+          , testGroup "regex.matches"+            [ testCase "simple match" $ do+                mkTestText [] []+                  "{{ regex.matches('[a-z]', 'abc')|json(pretty=false) }}"+                  "[[\"a\"],[\"b\"],[\"c\"]]"+            ]+          ]     ]  angleBracketDelimiters :: Delimiters@@ -1501,9 +1534,10 @@       template <- either throw return =<< parseGinger' options src       output <- newIORef mempty       let write h = modifyIORef output (<> h)-      let context = mContext+      let context' = mContext                       (\key -> return $ fromMaybe def (lookup key contextDict))                       write+          context = context' { contextWarn = liftRun2 $ print }       runGingerT context (optimize template)       actual <- valToText <$> readIORef output       assertEqual "" expected actual