{-| Module : Text.StringRandom Description : generating random string from a regexp Copyright : Copyright (C) 2016- hiratara License : GPL-3 Maintainer : hiratara@cpan.org Stability : experimental Generate a random character string that matches the given regular expression. This library ported String_random.js to Haskell. @ {-# LANGUAGE OverloadedStrings #-} import Text.StringRandom main = do ymd <- stringRandomIO "20\\d\\d-(1[0-2]|0[1-9])-(0[1-9]|1\\d|2[0-8])" print ymd -- "2048-12-08" etc. @ See As with this package, there are in packages that generate random strings, but this module is superior in the following respects. * The format of the string to be generated using regular expressions * You can change the random number generator (e.g. tf-random package) * With pure calculation without using IO monad. -} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Strict #-} module Text.StringRandom ( stringRandomIO , stringRandom , stringRandomWithError ) where import qualified Data.IntMap.Strict as Map import qualified Data.Text as Text import qualified System.Random as Random import qualified Text.StringRandom.Parser as Parser import qualified Control.Monad.Trans.RWS.Strict as RWS -- Int: size, g: generater, IntMap: Record backrefs type GenRWS g = RWS.RWS Int () (g, Map.IntMap Text.Text) {-| The 'stringRandomIO' function generates random strings that match the given regular expression. Regular expression is specified by 'Text' type. This function internally uses the random number generator generated by 'newStdGen'. -} stringRandomIO :: Text.Text -> IO Text.Text stringRandomIO txt = do g <- Random.newStdGen return $ stringRandom g txt {-| The 'stringRandom' function uses a specified random number generator to generate a random string that matches a given regular expression. An exception is raised if the regular expression can not be parsed. -} stringRandom :: Random.RandomGen g => g -> Text.Text -> Text.Text stringRandom g txt = case stringRandomWithError g txt of Left l -> error l Right r -> r {-| The 'stringRandomWithError' function behaves like the 'stringRandom' function, but notifies the error through the Either monad. -} stringRandomWithError :: Random.RandomGen g => g -> Text.Text -> Either String Text.Text stringRandomWithError g txt = do parsed <- Parser.processParse txt -- 10 : max length of a* or a+ let (ret, _) = RWS.evalRWS (str parsed) 10 (g, Map.empty) return ret withGen :: Random.RandomGen g => (g -> (a, g)) -> GenRWS g a withGen f = do (gen, m) <- RWS.get let (a, gen') = f gen RWS.put (gen', m) return a randomRM :: (Random.RandomGen g, Random.Random a) => (a, a) -> GenRWS g a randomRM = withGen . Random.randomR -- randomM :: (Random.RandomGen g, Random.Random a) => GenRWS g a -- randomM = withGen Random.random choice :: Random.RandomGen g => [a] -> GenRWS g a choice xs = do i <- randomRM (0, length xs - 1) return $ xs !! i putGroup :: Int -> Text.Text -> GenRWS g () putGroup n v = do (gen, m) <- RWS.get let m' = Map.insert n v m RWS.put (gen, m') getGroup :: Int -> GenRWS g Text.Text getGroup n = do m <- RWS.gets snd let maybeV = Map.lookup n m case maybeV of Nothing -> return "" Just v -> return v size :: GenRWS g Int size = RWS.ask str :: Random.RandomGen g => Parser.Parsed -> GenRWS g Text.Text str (Parser.PClass cs) = Text.singleton <$> choice cs str (Parser.PRange s me p) = do e <- case me of Just e' -> return e' Nothing -> size n <- randomRM (s, e) Text.concat <$> mapM (const $ str p) [1 .. n] str (Parser.PConcat ps) = Text.concat <$> mapM str ps str (Parser.PSelect ps) = str =<< choice ps str (Parser.PGrouped n p) = do v <- str p putGroup n v return v str (Parser.PBackward n) = getGroup n str (Parser.PIgnored) = return ""