Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit 79cea1e

Browse files
committed
Add a (limited) fromRegex provider
1 parent 2835cf0 commit 79cea1e

3 files changed

Lines changed: 79 additions & 7 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ a field within the JSON object.
2929
- replicate(number, expr)
3030
- object(key, value [, ...])
3131
- fromFile(fileName)
32+
- fromRegex(pattern)
3233

3334

3435
## Installation

package.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ dependencies:
3131
- transformers
3232
- vector
3333
- unordered-containers
34+
- containers
35+
- regex-tdfa
3436

3537
library:
3638
source-dirs: src

src/Fake.hs

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,35 @@ module Fake (
66
Env(..),
77
newEnv,
88
eval,
9+
runExpr
910
) where
1011

1112
import qualified Aeson as A
12-
import Control.Monad (forM)
13+
import Control.Monad (forM, replicateM)
1314
import Control.Monad.IO.Class (liftIO)
1415
import Control.Monad.Trans.State.Strict (StateT)
1516
import qualified Control.Monad.Trans.State.Strict as State
1617
import Data.Aeson (Value (..), object)
1718
import qualified Data.ByteString.Char8 as BS
1819
import qualified Data.HashMap.Strict as M
20+
import Data.Maybe (fromMaybe)
1921
import qualified Data.Scientific as S
22+
import qualified Data.Set as Set
2023
import qualified Data.Text as T
2124
import qualified Data.Text.Encoding as T
2225
import qualified Data.UUID as UUID
2326
import qualified Data.UUID.V1 as UUID1
2427
import qualified Data.Vector as V
2528
import Expr (Expr (..))
2629
import Prelude hiding (lines, replicate)
27-
import System.Random (StdGen, newStdGen, random,
28-
randomR)
30+
import System.Random (StdGen, mkStdGen, newStdGen,
31+
random, randomR)
32+
import qualified Text.Regex.TDFA.Pattern as R
33+
import qualified Text.Regex.TDFA.ReadRegex as R
2934

3035

3136
-- $setup
3237
-- >>> :set -XOverloadedStrings
33-
-- >>> import System.Random (mkStdGen)
3438
-- >>> let g = Env (mkStdGen 1) M.empty
3539
-- >>> let exec expr = State.evalStateT (eval expr) g
3640

@@ -42,6 +46,12 @@ data Env = Env
4246
, envFileCache :: M.HashMap T.Text (V.Vector Value) }
4347

4448

49+
runExpr :: Int -> Expr -> IO Value
50+
runExpr seed expr = State.evalStateT (eval expr) env
51+
where
52+
env = Env (mkStdGen seed) M.empty
53+
54+
4555
newEnv :: IO Env
4656
newEnv = do
4757
stdGen <- newStdGen
@@ -111,9 +121,7 @@ oneOfArray arr = do
111121
-- >>> exec "oneOf(37, 42, 21)"
112122
-- Number 21.0
113123
oneOfArgs :: [Expr] -> State Value
114-
oneOfArgs args = do
115-
idx <- withStdGen $ randomR (0, length args - 1)
116-
eval (args !! idx)
124+
oneOfArgs args = rndListItem args >>= eval
117125

118126

119127
-- | Create an array with `num` items
@@ -146,6 +154,66 @@ objectFromArgs args = do
146154
pure $ object pairs
147155

148156

157+
rndListItem :: [a] -> State a
158+
rndListItem xs = do
159+
idx <- withStdGen $ randomR (0, length xs - 1)
160+
pure $ xs !! idx
161+
162+
163+
rndSetItem :: Set.Set a -> State a
164+
rndSetItem xs = do
165+
idx <- withStdGen $ randomR (0, Set.size xs - 1)
166+
pure $ Set.elemAt idx xs
167+
168+
169+
allPossibleChars :: Set.Set Char
170+
allPossibleChars = Set.fromList [minBound..maxBound]
171+
172+
173+
-- | Create random data that would be matched by the given regex
174+
--
175+
-- >>> exec "fromRegex('\\d-\\d{1,3}-FOO')"
176+
-- String "6-78-FOO"
177+
--
178+
-- >>> exec "fromRegex('[a-z]{3}')"
179+
-- String "vjy"
180+
--
181+
-- >>> exec "fromRegex('[^0-9][0-9]B')"
182+
-- String "\27960\&5B"
183+
fromRegex :: T.Text -> State Value
184+
fromRegex input =
185+
case R.parseRegex input' of
186+
(Left err) -> error $ show err
187+
(Right (pattern, _)) -> String <$> generateText pattern
188+
where
189+
input' = T.unpack input
190+
defaultUpper = 10
191+
replicatePattern lower upper pattern = do
192+
numChars <- withStdGen $ randomR (lower, upper)
193+
T.concat <$> replicateM numChars (generateText pattern)
194+
generateText p = case p of
195+
(R.POr patterns) -> rndListItem patterns >>= generateText
196+
(R.PConcat patterns) -> T.concat <$> mapM generateText patterns
197+
(R.PPlus pattern) -> replicatePattern 1 defaultUpper pattern
198+
(R.PStar _ pattern) -> replicatePattern 0 defaultUpper pattern
199+
(R.PBound lower mUpper pattern) -> do
200+
replicatePattern lower (fromMaybe defaultUpper mUpper) pattern
201+
(R.PAny _ patternSet) -> fromPatternSet patternSet
202+
(R.PAnyNot _ ps@(R.PatternSet mChars _ _ _)) -> case mChars of
203+
(Just notAllowedChars) ->
204+
charToText <$> rndSetItem (Set.difference allPossibleChars notAllowedChars)
205+
Nothing -> error $ "Can't generate data from regex pattern" <> show ps
206+
(R.PEscape _ 'd') -> do
207+
T.pack . show <$> (withStdGen $ randomR (0, 9 :: Int))
208+
(R.PChar _ char) -> pure $ charToText char
209+
_ -> error $ "Can't generate data from regex pattern" <> show p
210+
fromPatternSet ps@(R.PatternSet mCharSet _ _ _) =
211+
case mCharSet of
212+
(Just charSet) -> charToText <$> rndSetItem charSet
213+
Nothing -> error $ "Can't generate data from regex pattern" <> show ps
214+
charToText c = T.pack [c]
215+
216+
149217
fromFile :: Expr -> State Value
150218
fromFile fileName = do
151219
fileName' <- A.asText <$> eval fileName
@@ -183,4 +251,5 @@ eval (FunctionCall "oneOf" args) = oneOfArgs args
183251
eval (FunctionCall "replicate" [num, expr]) = replicate num expr
184252
eval (FunctionCall "object" args) = objectFromArgs args
185253
eval (FunctionCall "fromFile" [fileName]) = fromFile fileName
254+
eval (FunctionCall "fromRegex" [pattern]) = eval pattern >>= fromRegex . A.asText
186255
eval (FunctionCall name _) = error $ "No random generator for " <> T.unpack name

0 commit comments

Comments
 (0)