diff --git a/README.md b/README.md index 410cbdd..e8c6057 100644 --- a/README.md +++ b/README.md @@ -154,15 +154,37 @@ #### `RegexFlags` - type RegexFlags = { unicode :: Boolean, sticky :: Boolean, multiline :: Boolean, ignoreCase :: Boolean, global :: Boolean } + newtype RegexFlags + = RegexFlags { unicode :: Boolean, sticky :: Boolean, multiline :: Boolean, ignoreCase :: Boolean, global :: Boolean } ### Type Class Instances +#### `boolLikeRegexFlags` + + instance boolLikeRegexFlags :: BoolLike RegexFlags + +#### `eqRegexFlags` + + instance eqRegexFlags :: Eq RegexFlags + +#### `semiGroupRegexFlags` + +Example usage: +`regex "Foo" $ global <> ignoreCase` +is equivalent to +`/Foo/ig` + + instance semiGroupRegexFlags :: Semigroup RegexFlags + #### `showRegex` instance showRegex :: Show Regex +#### `showRegexFlags` + + instance showRegexFlags :: Show RegexFlags + ### Values @@ -170,14 +192,46 @@ flags :: Regex -> RegexFlags +#### `global` + +Flags where `global : true` and all others are false + + global :: RegexFlags + +#### `ignoreCase` + +Flags where `ignoreCase : true` and all others are false + + ignoreCase :: RegexFlags + #### `match` match :: Regex -> String -> Maybe [String] +#### `multiline` + +Flags where `multiline : true` and all others are false + + multiline :: RegexFlags + +#### `newRegexFlags` + +Produce a new `RegexFlags` from `Booleans` + + newRegexFlags :: Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> RegexFlags + #### `noFlags` +All flags are set to false. Useful as a base for building flags or a default. + noFlags :: RegexFlags +#### `onRegexFlags` + +Perform a `Boolean` comparison across `RegexFlags` + + onRegexFlags :: (Boolean -> Boolean -> Boolean) -> RegexFlags -> RegexFlags -> RegexFlags + #### `parseFlags` parseFlags :: String -> RegexFlags @@ -198,6 +252,12 @@ replace' :: Regex -> (String -> [String] -> String) -> String -> String +#### `runRegexFlags` + +Unwrap `RegexFlags` type to the underlying record + + runRegexFlags :: RegexFlags -> { unicode :: Boolean, sticky :: Boolean, multiline :: Boolean, ignoreCase :: Boolean, global :: Boolean } + #### `search` search :: Regex -> String -> Number @@ -210,10 +270,22 @@ split :: Regex -> String -> [String] +#### `sticky` + +Flags where `sticky : true` and all others are false + + sticky :: RegexFlags + #### `test` test :: Regex -> String -> Boolean +#### `unicode` + +Flags where `unicode : true` and all others are false + + unicode :: RegexFlags + ## Module Data.String.Unsafe diff --git a/src/Data/String/Regex.purs b/src/Data/String/Regex.purs index 6ad1bce..3d8ff66 100644 --- a/src/Data/String/Regex.purs +++ b/src/Data/String/Regex.purs @@ -13,6 +13,14 @@ module Data.String.Regex , search , split , noFlags + , global + , ignoreCase + , multiline + , sticky + , unicode + , runRegexFlags + , onRegexFlags + , newRegexFlags ) where import Data.Function @@ -31,20 +39,91 @@ foreign import showRegex' instance showRegex :: Show Regex where show = showRegex' -type RegexFlags = - { global :: Boolean +newtype RegexFlags = RegexFlags + { global :: Boolean , ignoreCase :: Boolean - , multiline :: Boolean - , sticky :: Boolean - , unicode :: Boolean - } + , multiline :: Boolean + , sticky :: Boolean + , unicode :: Boolean } -noFlags :: RegexFlags -noFlags = { global : false - , ignoreCase : false - , multiline : false - , sticky : false - , unicode : false } +-- | Unwrap `RegexFlags` type to the underlying record +runRegexFlags :: RegexFlags -> + { global :: Boolean + , ignoreCase :: Boolean + , multiline :: Boolean + , sticky :: Boolean + , unicode :: Boolean } +runRegexFlags (RegexFlags x) = x + +-- | Produce a new `RegexFlags` from `Booleans` +newRegexFlags :: Boolean -> Boolean -> Boolean -> Boolean -> Boolean -> RegexFlags +newRegexFlags g i m s u = RegexFlags + { global : g + , ignoreCase : i + , multiline : m + , sticky : s + , unicode : u } + +-- | All flags are set to false. Useful as a base for building flags or a default. +noFlags :: RegexFlags +noFlags = newRegexFlags false false false false false + +-- | Flags where `global : true` and all others are false +global :: RegexFlags +global = newRegexFlags true false false false false + +-- | Flags where `ignoreCase : true` and all others are false +ignoreCase :: RegexFlags +ignoreCase = newRegexFlags false true false false false + +-- | Flags where `multiline : true` and all others are false +multiline :: RegexFlags +multiline = newRegexFlags false false true false false + +-- | Flags where `sticky : true` and all others are false +sticky :: RegexFlags +sticky = newRegexFlags false false false true false + +-- | Flags where `unicode : true` and all others are false +unicode :: RegexFlags +unicode = newRegexFlags false false false false true + +-- | Perform a `Boolean` comparison across `RegexFlags` +onRegexFlags :: (Boolean -> Boolean -> Boolean) -> RegexFlags -> RegexFlags -> RegexFlags +onRegexFlags f x' y' = RegexFlags + { global : x.global `f` y.global + , ignoreCase : x.ignoreCase `f` y.ignoreCase + , multiline : x.multiline `f` y.multiline + , sticky : x.sticky `f` y.sticky + , unicode : x.unicode `f` y.unicode } + where + x = runRegexFlags x' + y = runRegexFlags y' + +instance showRegexFlags :: Show RegexFlags where + show (RegexFlags rf) = "RegexFlags " ++ + "{ global : " ++ show rf.global ++ "\n" ++ + ", ignoreCase : " ++ show rf.ignoreCase ++ "\n" ++ + ", multiline : " ++ show rf.multiline ++ "\n" ++ + ", sticky : " ++ show rf.sticky ++ "\n" ++ + ", unicode : " ++ show rf.unicode ++ "\n" ++ " }" + +instance eqRegexFlags :: Eq RegexFlags where + (==) x y = case onRegexFlags (==) x y of + RegexFlags rf -> rf.global && rf.ignoreCase && rf.multiline && rf.sticky && rf.unicode + (/=) x y = not (x == y) + +instance boolLikeRegexFlags :: BoolLike RegexFlags where + (&&) = onRegexFlags (&&) + (||) = onRegexFlags (||) + not = onRegexFlags (const not) noFlags + +-- | Example usage: +-- | `regex "Foo" $ global <> ignoreCase` +-- | is equivalent to +-- | `/Foo/ig` +instance semiGroupRegexFlags :: Semigroup RegexFlags where + (<>) = (||) foreign import regex' """ @@ -58,6 +137,7 @@ foreign import regex' regex :: String -> RegexFlags -> Regex regex source flags = regex' source $ renderFlags flags + foreign import source """ function source(r) { @@ -79,7 +159,7 @@ foreign import flags """ :: Regex -> RegexFlags renderFlags :: RegexFlags -> String -renderFlags flags = +renderFlags = runRegexFlags >>> \flags -> (if flags.global then "g" else "") ++ (if flags.ignoreCase then "i" else "") ++ (if flags.multiline then "m" else "") ++ @@ -87,7 +167,7 @@ renderFlags flags = (if flags.unicode then "u" else "") parseFlags :: String -> RegexFlags -parseFlags s = +parseFlags s = RegexFlags { global: indexOf "g" s >= 0 , ignoreCase: indexOf "i" s >= 0 , multiline: indexOf "m" s >= 0