ginger2 2.1.0.1 → 2.2.0.0
raw patch · 8 files changed
+390/−89 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Language.Ginger.Interpret.Builtins: fnGroupBy :: forall (m :: Type -> Type). Monad m => Procedure m
+ Language.Ginger.StringFormatting: pDoublePercent :: P String
+ Language.Ginger.StringFormatting: pLeader :: P String
+ Language.Ginger.StringFormatting: pPrintfFormat :: P String
+ Language.Ginger.StringFormatting: pPrintfFormats :: P (String, [String])
+ Language.Ginger.StringFormatting: pPrintfPlainChars :: P String
+ Language.Ginger.StringFormatting: pTrailer :: P String
+ Language.Ginger.StringFormatting: printfList :: PrintfArg a => String -> [a] -> String
+ Language.Ginger.StringFormatting: splitPrintfFormat :: String -> (String, [String])
+ Language.Ginger.StringFormatting: type P a = Parsec Void String a
+ Language.Ginger.Value: instance Text.Printf.PrintfArg (Language.Ginger.Value.Value m)
+ Language.Ginger.Value: namespaceProcedureDoc :: ProcedureDoc
Files
- CHANGELOG +11/−0
- ginger2.cabal +2/−1
- src/Language/Ginger/BuiltinsAutodoc.hs +35/−37
- src/Language/Ginger/Interpret/Builtins.hs +73/−1
- src/Language/Ginger/Interpret/Eval.hs +8/−1
- src/Language/Ginger/StringFormatting.hs +83/−0
- src/Language/Ginger/Value.hs +76/−33
- test/Language/Ginger/Interpret/Tests.hs +102/−16
CHANGELOG view
@@ -20,3 +20,14 @@ like in Jinja. - Added an RNG state to the execution environment; this is necessary in order to support the random() function.++2.1.0.1++- Fix build errors on GHC 9.2 through 9.10++2.2.0.0++- Added built-ins:+ - groupby()+- Added printf-style % string formatting+- Fixed some spurious test failures
ginger2.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: ginger2-version: 2.1.0.1+version: 2.2.0.0 synopsis: Jinja templates for Haskell description: Ginger2 approximates Jinja2 (https://jinja.palletsprojects.com/) in Haskell.@@ -39,6 +39,7 @@ , Language.Ginger.Render , Language.Ginger.RuntimeError , Language.Ginger.SourcePosition+ , Language.Ginger.StringFormatting -- other-modules: -- other-extensions: build-depends: base >=4.14.0.0 && <5
src/Language/Ginger/BuiltinsAutodoc.hs view
@@ -34,8 +34,8 @@ markdownToHaddock = Text.replace "'" "\\'" . Text.replace "\"" "\\\"" .- Text.replace "\n" "\n\n" .- Text.replace "`" "@"+ Text.replace "`" "@" .+ Text.replace "```" "@" addHaddockFromFile :: FilePath -> DecsQ addHaddockFromFile path = do@@ -117,16 +117,11 @@ pure [] where goTy :: TypeDoc -> Text- goTy TypeDocAny = "@any@"- goTy TypeDocNone = "@none@"- goTy (TypeDocSingle t) = "@" <> t <> "@"+ goTy TypeDocAny = "any"+ goTy TypeDocNone = "none"+ goTy (TypeDocSingle t) = t goTy (TypeDocAlternatives ts) =- case Vector.unsnoc ts of- Just (ts', t) ->- "@" <> Text.intercalate "@, @" (Vector.toList $ ts') <> "@" <>- ", or @" <> t <> "@"- Nothing ->- "@" <> Text.intercalate "@ or @" (Vector.toList $ ts) <> "@"+ "[" <> Text.intercalate " | " (Vector.toList $ ts) <> "]" goItemHeading :: Maybe Text -> Text -> Text -> [Text] goItemHeading namespaceMay prefix name =@@ -136,47 +131,48 @@ , "=== " <> maybe "" (<> ".") namespaceMay <> name ] + goArgSig :: ArgumentDoc -> Text+ goArgSig arg =+ argumentDocName arg <>+ maybe "" ("=" <>) (argumentDocDefault arg) <>+ maybe "" ((" : " <>) . goTy) (argumentDocType arg) + goArgDesc :: ArgumentDoc -> [Text]+ goArgDesc arg =+ [ "[@" <> argumentDocName arg <> "@]:" <> argumentDocDescription arg ]+ goDocumentedItem :: Maybe Text -> Text -> (Identifier, ProcedureDoc) -> String goDocumentedItem namespaceMay prefix (name, d) = let qualifiedName = maybe "" (<> ".") namespaceMay <> identifierName name in Text.unpack . Text.unlines $- goItemHeading namespaceMay prefix (identifierName name <> "()") +++ goItemHeading namespaceMay prefix+ (identifierName name)+ +++ [ ""+ , "@" <> identifierName name+ <> "(" <> (Text.intercalate ", " . map goArgSig . Vector.toList $ procedureDocArgs d) <> ")"+ <> maybe "" ((" → " <>) . goTy) (procedureDocReturnType d)+ <> "@"+ , ""+ ]+ ++ ( if qualifiedName /= procedureDocName d && identifierName name /= procedureDocName d then [ "Alias for [" <> procedureDocName d <> "](#" <> prefix <> procedureDocName d <> ")" ] else- [ ""- , "__Arguments:__"- ] ++ ( if Vector.null (procedureDocArgs d) then- [ "none"- , ""- ]+ [ ] else- [ "" ] ++- [ "* @" <> argumentDocName arg- <> case argumentDocDefault arg of- Nothing -> ""- Just defval -> "=" <> defval- <> "@"- <> maybe "" ((" : " <>) . goTy) (argumentDocType arg)- <> case argumentDocDefault arg of- Nothing -> " __(required)__"- Just _ -> ""- <> (if argumentDocDescription arg /= "" then- " - " <> markdownToHaddock (argumentDocDescription arg)- else- ""- )- | arg <- Vector.toList (procedureDocArgs d)- ]+ [ ""+ , "==== Arguments"+ , ""+ ] +++ (concatMap goArgDesc . Vector.toList $ procedureDocArgs d) +++ [ "" ] ) ++ [ ""- , "__Return type:__ " <> maybe "n/a" goTy (procedureDocReturnType d)- , "" , markdownToHaddock $ procedureDocDescription d ] )@@ -197,6 +193,8 @@ goItem namespaceMay prefix (name, ProcedureV (NativeProcedure _ (Just d) _)) = goDocumentedItem namespaceMay prefix (name, d)+ goItem namespaceMay prefix (name, ProcedureV NamespaceProcedure) =+ goDocumentedItem namespaceMay prefix (name, namespaceProcedureDoc) goItem namespaceMay prefix (name, FilterV (NativeFilter (Just d) _)) = goDocumentedItem namespaceMay prefix (name, d) goItem namespaceMay prefix (name, TestV (NativeTest (Just d) _)) =
src/Language/Ginger/Interpret/Builtins.hs view
@@ -18,6 +18,7 @@ import Language.Ginger.RuntimeError import Language.Ginger.Value +import Control.Applicative ( (<|>) ) import Control.Monad.Except import Control.Monad.Trans (lift) import qualified Data.Aeson as JSON@@ -155,7 +156,7 @@ , ("float", ProcedureV fnToFloat) -- , ("forceescape", undefined) -- , ("format", undefined)- -- , ("groupby", undefined)+ , ("groupby", ProcedureV fnGroupBy) -- , ("indent", undefined) , ("int", ProcedureV fnToInt) , ("items", ProcedureV fnItems)@@ -1893,6 +1894,77 @@ unit | otherwise = go multiplier units (value `div` multiplier)++fnGroupBy :: forall m. Monad m => Procedure m+fnGroupBy = mkFn4 "groupby"+ (Text.unlines+ [ "Group a list of objects by an attribute."+ , "The attribute can use dot notation for nested access, " <>+ "e.g. `'address.city'`."+ ]+ )+ ( "value"+ , Nothing :: Maybe [Value m]+ , Just $ TypeDocAny+ , ""+ )+ ( "attribute"+ , Nothing+ , Just $ TypeDocAlternatives [ "string", "int" ]+ , "Attribute to group by."+ )+ ( "default"+ , Just Nothing+ , Just $ TypeDocAny+ , "Default value to use if an object doesn't have the " <>+ "requested attribute."+ )+ ( "case_sensitive"+ , Just True+ , Just $ TypeDocSingle "bool"+ , "Use case-sensitive comparisons for grouping objects."+ )+ (Just $ TypeDocSingle "dict")+ $ \value attrib (defValMay :: Maybe (Value m)) caseSensitive -> do+ let getAttribPath :: Value m -> [Text] -> ExceptT RuntimeError m (Maybe (Value m))+ getAttribPath o [] = pure $ Just o+ getAttribPath o (p:ps) = do+ o'May <- eitherExceptM $ getAttrOrItemRaw o (Identifier p)+ case o'May of+ Just o' -> getAttribPath o' ps+ Nothing -> pure $ Nothing++ let attribProjection :: Value m -> ExceptT RuntimeError m (Maybe (Value m))+ attribProjection = fmap (maybe (Just NoneV) Just) . case attrib of+ Left i -> \obj ->+ lift $ getItemRaw obj (IntV i)+ Right t -> \obj ->+ getAttribPath obj $ Text.splitOn "." t++ attribCaseFold :: Scalar -> Scalar+ attribCaseFold (StringScalar t) =+ if caseSensitive then+ StringScalar t+ else+ StringScalar (Text.toCaseFold t)+ attribCaseFold x = x++ append :: Map Scalar [Value m] -> Maybe (Scalar, Value m) -> Map Scalar [Value m]+ append m Nothing = m+ append m (Just (k, v)) =+ Map.insertWith (flip (++)) k [v] m++ prepare :: Value m -> ExceptT RuntimeError m (Maybe (Scalar, Value m))+ prepare v = do+ kMay <- attribProjection v+ skMay <- mapM+ (fmap attribCaseFold . eitherExcept . asScalarVal)+ (kMay <|> defValMay)+ case skMay of+ Nothing -> pure Nothing+ Just k -> pure $ Just (k, v)++ foldl' append mempty <$> mapM prepare value fnBatch :: forall m. Monad m => Procedure m fnBatch = mkFn3 "batch"
src/Language/Ginger/Interpret/Eval.hs view
@@ -39,6 +39,7 @@ import qualified Language.Ginger.Parse as Parse import Language.Ginger.RuntimeError import Language.Ginger.SourcePosition+import Language.Ginger.StringFormatting import Language.Ginger.Value import Control.Monad (foldM, forM, void)@@ -398,7 +399,6 @@ -> GingerT m (Value m) boolBinop f a b = native . pure $ boolFunc2 f a b - valuesEqual :: Monad m => Value m -> Value m@@ -436,6 +436,12 @@ ordering <- compareValues a b pure $ BoolV (f ordering) +printfValues :: Monad m => Text -> Value m -> GingerT m (Value m)+printfValues fmtText (ListV args) = do+ pure . StringV . Text.pack $ printfList (Text.unpack fmtText) (V.toList args)+printfValues fmtText x = do+ pure . StringV . Text.pack $ printfList (Text.unpack fmtText) [x]+ dictsEqual :: forall m. Monad m => Map Scalar (Value m) -> Map Scalar (Value m)@@ -457,6 +463,7 @@ evalBinary BinopMinus a b = numericBinop (-) (-) a b evalBinary BinopDiv a b = floatBinop safeDiv a b evalBinary BinopIntDiv a b = intBinop safeIntDiv a b+evalBinary BinopMod (StringV a) b = printfValues a b evalBinary BinopMod a b = intBinop safeIntMod a b evalBinary BinopMul a b = numericBinop (*) (*) a b evalBinary BinopPower a b = numericBinopCatch safeIntPow (\x y -> Right (x ** y)) a b
+ src/Language/Ginger/StringFormatting.hs view
@@ -0,0 +1,83 @@+module Language.Ginger.StringFormatting+where++import Control.Applicative ( (<|>) )+import qualified Text.Megaparsec as P+import qualified Text.Megaparsec.Char as P+import Text.Printf (printf, PrintfArg)+import Data.Maybe (fromMaybe)+import Data.Void (Void)+import Data.Char (isDigit)++printfList :: PrintfArg a => String -> [a] -> String+printfList fmt args =+ leader ++ concat (zipWith printf fmts args)+ where+ (leader, fmts) = splitPrintfFormat fmt++splitPrintfFormat :: String -> (String, [String])+splitPrintfFormat fmt =+ fromMaybe ("", []) $ P.parseMaybe pPrintfFormats fmt++type P a = P.Parsec Void String a++pPrintfFormats :: P (String, [String])+pPrintfFormats = (,) <$> pLeader <*> P.many pPrintfFormat++pLeader :: P String+pLeader = concat <$> P.many (("%" <$ pDoublePercent) <|> pPrintfPlainChars)++pTrailer :: P String+pTrailer = concat <$> P.many (pDoublePercent <|> pPrintfPlainChars)++pDoublePercent :: P String+pDoublePercent = P.chunk "%%"++pPrintfPlainChars :: P String+pPrintfPlainChars = P.takeWhile1P (Just "plain characters") (/= '%')++pPrintfFormat :: P String+pPrintfFormat = do+ leadingPercent <- P.chunk "%"+ flags <- P.takeWhileP (Just "flags") (`elem` flagChars)+ fieldWidth <- pFieldWidth+ precision <- P.option "" $ (P.chunk "." *> pFieldWidth)+ widthModifier <- P.takeWhileP (Just "width modifier") (`elem` widthChars)+ formatChar <- P.choice+ [ 'd' <$ P.char 'i'+ , 'v' <$ P.satisfy (`elem` stringFormatChars)+ , P.satisfy (`elem` formatChars)+ , pure 'v'+ ]+ remaining <- pTrailer+ pure $ concat+ [ leadingPercent+ , flags+ , fieldWidth+ , precision+ , widthModifier+ , [formatChar]+ , remaining+ ]++ where+ pFieldWidth :: P String+ pFieldWidth = P.chunk "*" <|> pNumericFieldWidth++ pNumericFieldWidth :: P String+ pNumericFieldWidth =+ (++) <$> P.option "" (P.chunk "-")+ <*> P.takeWhileP (Just "numeric field width") isDigit++ flagChars :: [Char]+ flagChars = " +-0#"++ widthChars :: [Char]+ widthChars = "lLhH"++ stringFormatChars :: [Char]+ stringFormatChars = "rsa"++ formatChars :: [Char]+ formatChars = "cdobuxXfFgGeEsv"+
src/Language/Ginger/Value.hs view
@@ -46,6 +46,7 @@ import System.Random (RandomGen (..), SplitGen (..)) import Test.Tasty.QuickCheck (Arbitrary (..)) import qualified Test.Tasty.QuickCheck as QC+import Text.Printf (PrintfArg (..)) import Text.Read (readMaybe) import Language.Ginger.AST@@ -192,6 +193,33 @@ | MutableRefV !RefID deriving (Ord) +pattern NoneV :: Value m+pattern NoneV = ScalarV NoneScalar++pattern BoolV :: Bool -> Value m+pattern BoolV b = ScalarV (BoolScalar b)++pattern TrueV :: Value m+pattern TrueV = BoolV True++pattern FalseV :: Value m+pattern FalseV = BoolV False++pattern StringV :: Text -> Value m+pattern StringV v = ScalarV (StringScalar v)++pattern EncodedV :: Encoded -> Value m+pattern EncodedV v = ScalarV (EncodedScalar v)++pattern BytesV :: ByteString -> Value m+pattern BytesV v = ScalarV (BytesScalar v)++pattern IntV :: Integer -> Value m+pattern IntV v = ScalarV (IntScalar v)++pattern FloatV :: Double -> Value m+pattern FloatV v = ScalarV (FloatScalar v)+ instance FromJSON (Value m) where parseJSON v@JSON.Object {} = DictV <$> parseJSON v parseJSON v@JSON.Array {} = ListV <$> parseJSON v@@ -203,13 +231,6 @@ toJSON (DictV d) = toJSON d toJSON x = toJSON (show x) -traverseValue :: Monoid a => (Value m -> a) -> Value m -> a-traverseValue p v@(ListV xs) =- p v <> fold (fmap (traverseValue p) xs)-traverseValue p v@(DictV m) =- p v <> mconcat (map (traverseValue p . snd) $ Map.toList m)-traverseValue p v = p v- instance Show (Value m) where show (ScalarV s) = show s show (ListV xs) = "ListV " ++ show xs@@ -241,6 +262,21 @@ DictV a == DictV b = a == b _ == _ = False +instance PrintfArg (Value m) where+ formatArg (BoolV b) = formatArg (fromEnum b)+ formatArg (IntV i) = formatArg i+ formatArg (FloatV f) = formatArg f+ formatArg (StringV t) = formatArg t+ formatArg (EncodedV (Encoded t)) = formatArg t+ formatArg ScalarV {} = formatArg ("" :: String)+ formatArg (ListV xs) = \fmt x -> foldr (flip formatArg fmt) x xs+ formatArg (DictV xs) = formatArg . ListV . V.fromList . Map.elems $ xs+ formatArg (NativeV {}) = formatArg ("[[object]]" :: String)+ formatArg (ProcedureV {}) = formatArg ("[[procedure]]" :: String)+ formatArg (FilterV {}) = formatArg ("[[filter]]" :: String)+ formatArg (TestV {}) = formatArg ("[[test]]" :: String)+ formatArg (MutableRefV {}) = formatArg ("[[ref]]" :: String)+ tagNameOf :: Value m -> Text tagNameOf ScalarV {} = "scalar" tagNameOf ListV {} = "list"@@ -251,32 +287,12 @@ tagNameOf FilterV {} = "filter" tagNameOf MutableRefV {} = "mutref" -pattern NoneV :: Value m-pattern NoneV = ScalarV NoneScalar--pattern BoolV :: Bool -> Value m-pattern BoolV b = ScalarV (BoolScalar b)--pattern TrueV :: Value m-pattern TrueV = BoolV True--pattern FalseV :: Value m-pattern FalseV = BoolV False--pattern StringV :: Text -> Value m-pattern StringV v = ScalarV (StringScalar v)--pattern EncodedV :: Encoded -> Value m-pattern EncodedV v = ScalarV (EncodedScalar v)--pattern BytesV :: ByteString -> Value m-pattern BytesV v = ScalarV (BytesScalar v)--pattern IntV :: Integer -> Value m-pattern IntV v = ScalarV (IntScalar v)--pattern FloatV :: Double -> Value m-pattern FloatV v = ScalarV (FloatScalar v)+traverseValue :: Monoid a => (Value m -> a) -> Value m -> a+traverseValue p v@(ListV xs) =+ p v <> fold (fmap (traverseValue p) xs)+traverseValue p v@(DictV m) =+ p v <> mconcat (map (traverseValue p . snd) $ Map.toList m)+traverseValue p v = p v newtype ObjectID = ObjectID { unObjectID :: Text } deriving (Eq, Ord)@@ -348,6 +364,33 @@ ) | GingerProcedure !(Env m) ![(Identifier, Maybe (Value m))] !Expr | NamespaceProcedure++namespaceProcedureDoc :: ProcedureDoc+namespaceProcedureDoc =+ ProcedureDoc+ { procedureDocName = "namespace"+ , procedureDocArgs = mempty+ , procedureDocReturnType = Just $ TypeDocSingle "namespace"+ , procedureDocDescription = Text.unlines+ [ "Create a namespace object."+ , "Namespace objects are mutable dictionary-like objects; the main " <>+ "use case for these is to work around the fact that `{% for %}` " <>+ "loops, macros, and other constructs establish local scopes, " <>+ "which means that any `{% set %}` invocations inside those will " <>+ "not propagate to the containing scope."+ , ""+ , "Using a namespace object, this problem can be solved like in this example:"+ , ""+ , "```"+ , "{% set ns = namespace() %}"+ , "{% for x in items %}"+ , " {{ x.bar }}"+ , " {% set ns.foo = x.foo %}"+ , "{% endfor %}"+ , "{{ ns.foo }}"+ , "```"+ ]+ } instance Ord (Procedure m) where compare NamespaceProcedure NamespaceProcedure = EQ
test/Language/Ginger/Interpret/Tests.hs view
@@ -32,10 +32,11 @@ import Data.Vector (Vector) import qualified Data.Vector as V import Data.Word (Word8, Word16, Word32, Word64)+import System.Random (mkStdGen, splitGen, uniformR) import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.QuickCheck hiding ((.&.))-import System.Random (mkStdGen, splitGen, uniformR)+import Text.Printf (printf) import Language.Ginger.AST import Language.Ginger.Interpret@@ -107,6 +108,34 @@ , testProperty "Integer modulo" (prop_binopCond @Integer Just justNonzero BinopMod mod) , testProperty "Integer power" (prop_binopCond @Integer @Integer justPositive justPositive BinopPower (^)) + , testGroup "Printf formatting operator"+ [ testProperty "single integer arg, %i"+ (prop_binop @Text @Integer @Text BinopMod+ (\fmt val -> Text.pack $ printf (Text.unpack fmt) val)+ "%i"+ )+ , testProperty "single string arg, %s"+ (prop_binop @Text @Text @Text BinopMod+ (\fmt val -> Text.pack $ printf (Text.unpack fmt) val)+ "%s"+ )+ , testProperty "int + string args, %d %s"+ (prop_binop @Text @(Integer, Text) @Text BinopMod+ (\fmt (i, s) -> Text.pack $ printf (Text.unpack fmt) i s)+ "%d %s"+ )+ , testProperty "single string arg, a%s"+ (prop_binop @Text @Text @Text BinopMod+ (\fmt val -> Text.pack $ printf (Text.unpack fmt) val)+ "a%s"+ )+ , testProperty "single string arg, %%%s"+ (prop_binop @Text @Text @Text BinopMod+ (\fmt val -> Text.pack $ printf (Text.unpack fmt) val)+ "%%%s"+ )+ ]+ , testProperty "Double addition" (prop_binop @Double BinopPlus (+)) , testProperty "Double subtraction" (prop_binop @Double BinopMinus (-)) , testProperty "Double multiplication" (prop_binop @Double BinopMul (*))@@ -694,6 +723,46 @@ FilterE (ListE (V.replicate (i * j) NoneE)) (VarE "batch") [IntLitE $ fromIntegral j] []) (\(PositiveInt i, PositiveInt j) -> ListV (V.replicate i (ListV (V.replicate j NoneV))))+ , testProperty "groupby" $+ prop_eval+ (\(Identifier a, Identifier b, itemsA, itemsB) ->+ FilterE+ (ListE $+ V.fromList+ [ DictE+ [ (StringLitE "foo", StringLitE a)+ , (StringLitE "bar", IntLitE val)+ ]+ | val <- itemsA+ ]+ <>+ V.fromList+ [ DictE+ [ (StringLitE "foo", StringLitE b)+ , (StringLitE "bar", IntLitE val)+ ]+ | val <- itemsB+ ]+ )+ (VarE "groupby")+ [StringLitE "foo"]+ [])+ (\(Identifier a, Identifier b, itemsA, itemsB) ->+ DictV $+ Map.fromList $+ ( if null itemsA then+ []+ else+ [ (StringScalar a, ListV $ V.fromList [ DictV $ Map.fromList [ ("foo", StringV a), ("bar", IntV val) ] | val <- itemsA ] )+ ]+ ) +++ ( if null itemsB then+ []+ else+ [ (StringScalar b, ListV $ V.fromList [ DictV $ Map.fromList [ ("foo", StringV b), ("bar", IntV val) ] | val <- itemsB ] )+ ]+ )+ ) , testProperty "batch with fill" $ prop_eval (\(PositiveInt i, PositiveInt j, PositiveInt x, f) ->@@ -849,32 +918,36 @@ (\(NonEmptyText needle, _) -> [StringLitE needle]) (\_ -> TrueV) , testProperty "string split (no sep)" $- prop_method "split"+ prop_methodCond "split"+ (\(NonEmptyText sep, NonEmptyText item) ->+ not (sep `Text.isInfixOf` item)+ ) (\(NonEmptyText _sep, NonEmptyText item) -> StringLitE item ) (\(NonEmptyText sep, NonEmptyText _item) -> [StringLitE sep] )- (\(NonEmptyText sep, NonEmptyText item) ->- if sep `Text.isInfixOf` item then- ListV (fmap StringV . V.fromList $ Text.splitOn sep item)- else+ (\(NonEmptyText _sep, NonEmptyText item) -> ListV $ V.singleton (StringV item) ) , testProperty "string split" $- prop_method "split"+ prop_methodCond "split"+ (\(NonEmptyText sep, NonEmptyText item, PositiveInt _i) ->+ not+ ( sep `Text.isInfixOf` item+ || Text.take 1 sep `Text.isSuffixOf` item+ || Text.takeEnd 1 sep `Text.isPrefixOf` item+ )+ ) (\(NonEmptyText sep, NonEmptyText item, PositiveInt i) -> StringLitE (Text.intercalate sep $ replicate (i + 1) item) ) (\(NonEmptyText sep, NonEmptyText _item, PositiveInt _i) -> [StringLitE sep] )- (\(NonEmptyText sep, NonEmptyText item, PositiveInt i) ->- if sep `Text.isInfixOf` item then- ListV $ V.concat $ replicate (i + 1) (fmap StringV . V.fromList $ Text.splitOn sep item)- else- ListV $ V.replicate (i + 1) (StringV item)+ (\(NonEmptyText _sep, NonEmptyText item, PositiveInt i) ->+ ListV $ V.replicate (i + 1) (StringV item) ) , testProperty "string splitlines" $ prop_method "splitlines"@@ -1393,6 +1466,8 @@ resultG = runGingerIdentityEither 0 (setVar name1 (toValue val1) >> eval expr) in name1 /= name2 ==>+ varOK name1 ==>+ varOK name2 ==> resultG === leftPRE (NotInScopeError (identifierName name2)) prop_nativeNullary :: Identifier -> Integer -> Property@@ -1485,11 +1560,11 @@ in result === BoolV expected -prop_eval :: (Arbitrary a, Show a, Show e, Eval Identity e) => (a -> e) -> (a -> Value Identity) -> a -> Property+prop_eval :: (Arbitrary a, Show a, RenderSyntax e, Eval Identity e) => (a -> e) -> (a -> Value Identity) -> a -> Property prop_eval mkEvaluable mkExpected = prop_evalRNG mkEvaluable (const mkExpected) 0 -prop_evalRNG :: (Arbitrary a, Show a, Show e, Eval Identity e)+prop_evalRNG :: (Arbitrary a, Show a, RenderSyntax e, Eval Identity e) => (a -> e) -> (Int -> a -> Value Identity) -> Int@@ -1500,7 +1575,7 @@ expected = mkExpected seed x result = runGingerIdentity seed $ eval e in- counterexample (show e) $+ counterexample (Text.unpack $ renderSyntaxText e) $ result === expected prop_attr :: (Arbitrary a, Show a, ToValue b Identity)@@ -1520,8 +1595,19 @@ -> (a -> Value Identity) -> a -> Property-prop_method methodName mkSelf mkArgs mkExpected t =+prop_method methodName = prop_methodCond methodName (const True)++prop_methodCond :: (Arbitrary a, Show a)+ => Identifier+ -> (a -> Bool)+ -> (a -> Expr)+ -> (a -> [Expr])+ -> (a -> Value Identity)+ -> a+ -> Property+prop_methodCond methodName valid mkSelf mkArgs mkExpected t = counterexample (show $ mkArgs t) $+ valid t ==> prop_eval (\t' -> CallE (DotE (mkSelf t) methodName) (mkArgs t') []) mkExpected t