packages feed

lens-regex-pcre 0.2.0.0 → 0.3.0.0

raw patch · 4 files changed

+119/−86 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Control.Lens.Regex: iregex :: Regex -> IndexedTraversal' Int Text Match
+ Control.Lens.Regex: compile :: ByteString -> [PCREOption] -> Regex
+ Control.Lens.Regex: compileM :: ByteString -> [PCREOption] -> Either String Regex
+ Control.Lens.Regex: data Regex
+ Control.Lens.Regex: mkRegexQQ :: [PCREOption] -> QuasiQuoter
- Control.Lens.Regex: regex :: Regex -> Traversal' Text Match
+ Control.Lens.Regex: regex :: Regex -> IndexedTraversal' Int Text Match

Files

ChangeLog.md view
@@ -1,13 +1,16 @@ # Changelog for lens-regex-pcre -# 2.0.0 +# 0.3.0.0 +Unify `iregex` into `regex` as a single indexed traversal++# 0.2.0.0  Unify `grouped`, `groups`, and `igroups` into just `groups` with optional traversal -# 1.1.0 +# 0.1.1.0  Adds `grouped` and `matchAndGroups` -# 1.0.1 +# 0.1.0.1  Doc fixes -# 1.0.0 +# 0.1.0.0  Initial Release
lens-regex-pcre.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: db3dca892f8e99d8936c709dd07b7d1a9ac11caa9f0cc2182b9367caa96e63c1+-- hash: a626a534d16f0bea96536315430d586eea2fc04b175395c0047b63268c10fbca  name:           lens-regex-pcre-version:        0.2.0.0+version:        0.3.0.0 description:    Please see the README on GitHub at <https://github.com/ChrisPenner/lens-regex-pcre#readme> homepage:       https://github.com/ChrisPenner/lens-regex-pcre#readme bug-reports:    https://github.com/ChrisPenner/lens-regex-pcre/issues
src/Control/Lens/Regex.hs view
@@ -19,23 +19,36 @@     (     -- * Combinators       regex-    , iregex     , match     , groups     , matchAndGroups -    -- * QuasiQuoter+    -- * Compiling regex     , rx+    , mkRegexQQ+    , compile+    , compileM      -- * Types     , Match+    , Regex     ) where  import Data.Text as T hiding (index) import Text.Regex.PCRE.Heavy+import Text.Regex.PCRE.Light (compile) import Control.Lens hiding (re, matching)+import Data.Data (Data)+import Data.Data.Lens (biplate) import Language.Haskell.TH.Quote +-- $setup+-- >>> :set -XQuasiQuotes+-- >>> :set -XOverloadedStrings+-- >>> :set -XTypeApplications+-- >>> import Data.Text.Lens (unpacked)+-- >>> import Data.List (sort)+ -- | Match represents a whole regex match; you can drill into it using 'match' or 'groups' or -- 'matchAndGroups' -- Consider this to be internal; don't depend on its representation.@@ -43,12 +56,6 @@ type MatchRange = (Int, Int) type GroupRanges = [(Int, Int)] --- | 'QuasiQuoter' for compiling regexes.--- This is just 're' re-exported under a different name so as not to conflict with @re@ from--- 'Control.Lens'-rx :: QuasiQuoter-rx = re- -- | Access all groups of a match at once. -- -- Note that you can edit the groups through this traversal,@@ -56,93 +63,104 @@ -- -- Get all matched groups: ----- > > "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups--- > [["raindrops","roses"],["whiskers","kittens"]]+-- >>> "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]] -- -- You can access a specific group by combining with `ix` ----- > > "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups .  ix 1--- > ["roses", "kittens"]+-- >>> "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups .  ix 1+-- ["roses","kittens"] -- -- @groups@ is a traversal; you can mutate matches through it.--- > > "raindrops on roses and whiskers on kittens" & regex [rx|(\w+) on (\w+)|] . groups .  ix 1 %~ T.toUpper--- > "raindrops on ROSES and whiskers on KITTENS" --+-- >>> "raindrops on roses and whiskers on kittens" & regex [rx|(\w+) on (\w+)|] . groups .  ix 1 %~ T.toUpper+-- "raindrops on ROSES and whiskers on KITTENS"+-- -- Editing the list rearranges groups ----- > > "raindrops on roses and whiskers on kittens" & regex [rx|(\w+) on (\w+)|] . groups %~ reverse--- > "roses on raindrops and kittens on whiskers"+-- >>> "raindrops on roses and whiskers on kittens" & regex [rx|(\w+) on (\w+)|] . groups %~ Prelude.reverse+-- "roses on raindrops and kittens on whiskers" -- -- You can traverse the list to flatten out all groups ----- > > "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups . traversed--- > ["raindrops","roses","whiskers","kittens"]+-- >>> "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups . traversed+-- ["raindrops","roses","whiskers","kittens"] groups :: Traversal' Match [T.Text] groups = partsOf (traversed . _Right) --- | Traverse each match as a whole+-- | Traverse each match ----- Use with 'regex' or 'iregex'+--  Get a match if one exists: ----- > > "one _two_ three _four_" ^.. regex [rx|_\w+_|] . match--- > ["_two_","_four_"]+-- >>> "find a needle in a haystack" ^? regex [rx|n..dle|] . match+-- Just "needle" --+--  Collect all matches+--+-- >>> "one _two_ three _four_" ^.. regex [rx|_\w+_|] . match+-- ["_two_","_four_"]+-- -- You can edit the traversal to perform a regex replace/substitution ----- > > "one _two_ three _four_" & regex [rx|_\w+_|] . match %~ T.toUpper--- > "one _TWO_ three _FOUR_"+-- >>> "one _two_ three _four_" & regex [rx|_\w+_|] . match %~ T.toUpper+-- "one _TWO_ three _FOUR_" match :: Traversal' Match T.Text match f grps = (:[]) . Right <$> f (grps ^. traversed . chosen) --- | Indexed version of 'regex'.-iregex :: Regex -> IndexedTraversal' Int T.Text Match-iregex pattern = indexing (regex pattern)- -- | The base combinator for doing regex searches. -- It's a traversal which selects 'Match'es; you can compose it with 'match' or 'groups' -- to get the relevant parts of your match. ----- Getting all matches:+-- >>> txt = "raindrops on roses and whiskers on kittens" :: Text ----- > > "one _two_ three _four_" ^.. regex [rx|_\w+_|] . match--- > ["_two_","_four_"]+-- Search ----- Regex replace/mutation+-- >>> has (regex [rx|whisk|]) txt+-- True ----- > > "one _two_ three _four_" & regex [rx|_\w+_|] . match %~ T.toUpper--- > "one _TWO_ three _FOUR_"+-- Get matches ----- Getting groups with their group index.+-- >>> txt ^.. regex [rx|\br\w+|] . match+-- ["raindrops","roses"] ----- > > "1/2 and 3/4" ^.. regex [rx|(\d+)/(\d+)|] . groups . traversed . withIndex--- > [(0,"1"),(1,"2"),(0,"3"),(1,"4")]+-- Edit matches ----- Check for any matches:+-- >>> txt & regex [rx|\br\w+|] . match %~ T.intersperse '-' . T.toUpper+-- "R-A-I-N-D-R-O-P-S on R-O-S-E-S and whiskers on kittens" ----- > > has (regex [rx|ne+dle|]) "a needle in a haystack"--- > True+-- Get Groups ----- Check for matches which also match a predicate:+-- >>> txt ^.. regex [rx|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]] ----- > > has (regex [rx|\w+|] . match . filtered ((> 7) . T.length)) "one word here is loooooooong"--- > True+-- Edit Groups ----- Get the third match+-- >>> txt & regex [rx|(\w+) on (\w+)|] . groups %~ Prelude.reverse+-- "roses on raindrops and kittens on whiskers" ----- > > "alpha beta charlie delta" ^? (iregex [rx|\w+|] . index 2 . match)--- > Just "charlie"+-- Get the third match ----- Replace the third match+-- >>> txt ^? regex [rx|\w+|] . index 2 . match+-- Just "roses" ----- > > "alpha beta charlie delta" & (iregex [rx|\w+|] . index 2 . match) .~ "GAMMA"--- > "alpha beta GAMMA delta"+-- Match integers, 'Read' them into ints, then sort them in-place+-- dumping them back into the source text afterwards. ----- Match integers, 'Read' them into ints, then sort each match in-place+-- >>> "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf (regex [rx|\d+|] . match . unpacked . _Show @Int) %~ sort+-- "Monday: 3, Tuesday: 29, Wednesday: 99" ----- > > "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf' (iregex [rx|\d+|] . match . unpacked . _Show @Int) %~ sort--- > "Monday: 3, Tuesday: 29, Wednesday: 99"-regex :: Regex -> Traversal' T.Text Match-regex pattern f txt =  collapseMatch <$> apply (fmap splitAgain <$> splitter txt matches)+-- To alter behaviour of the regex you may wish to pass 'PCREOption's when compiling it.+-- The default behaviour may seem strange in certain cases; e.g. it operates in 'single-line'+-- mode. You can 'compile' the 'Regex' separately and add any options you like, then pass the resulting+-- 'Regex' into 'regex';+-- Alternatively can make your own version of the QuasiQuoter with any options you want embedded+-- by using 'mkRegexQQ'.+regex :: Regex -> IndexedTraversal' Int T.Text Match+regex pattern = indexing (regexT pattern)++-- | Base regex traversal. Used only to define 'regex'+regexT :: Regex -> Traversal' T.Text Match+regexT pattern f txt = collapseMatch <$> apply (fmap splitAgain <$> splitter txt matches)   where     matches :: [(MatchRange, GroupRanges)]     matches = scanRanges pattern txt@@ -151,26 +169,32 @@     -- apply :: [Either Text [Either Text Text]] -> _ [Either Text [Either Text Text]]     apply xs = xs & traversed . _Right %%~ f + matchText :: Match -> T.Text matchText m = m ^. traversed . chosen  -- | Collect both the match text AND all the matching groups ----- > > "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . matchAndGroups--- > [ ("raindrops on roses", ["raindrops","roses"])--- > , ("whiskers on kittens", ["whiskers","kittens"])--- > ]+-- >>> "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . matchAndGroups+-- [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])] matchAndGroups :: Getter Match (T.Text, [T.Text]) matchAndGroups = to $ \m -> (matchText m, m ^. groups) +-- | 'QuasiQuoter' for compiling regexes.+-- This is just 're' re-exported under a different name so as not to conflict with @re@ from+-- 'Control.Lens'+rx :: QuasiQuoter+rx = re+ -- | This allows you to "stash" the match text into an index for use later in the traversal. -- This is a slight abuse of indices; but it can sometimes be handy. This allows you to -- have the full match in scope when editing groups using indexed combinators. -- -- If you're viewing or folding you should probably just use 'matchAndGroups'. ----- > > [(["raindrops","roses"],"raindrops on roses"),(["whiskers","kittens"],"whiskers on kittens")]--- > "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . (withGroups <. match) . withIndex+-- >>> "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . (withGroups <. match) . withIndex+-- [(["raindrops","roses"],"raindrops on roses"),(["whiskers","kittens"],"whiskers on kittens")]+-- withMatch :: IndexedTraversal' T.Text Match Match withMatch p mtch = indexed p (matchText mtch) mtch @@ -180,18 +204,24 @@ -- -- If you're viewing or folding you should probably just use 'matchAndGroups'. ----- > > "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . (withMatch <. groups) . withIndex--- > [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]+-- >>> "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . (withMatch <. groups) . withIndex+-- [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])] withGroups :: IndexedTraversal' [T.Text] Match Match withGroups p mtch = indexed p (mtch ^. groups) mtch +-- split up text into matches paired with groups; Left is unmatched text splitter :: Text -> [(MatchRange, GroupRanges)] -> [Either T.Text (T.Text, GroupRanges)]-splitter t [] | T.null t = []-              | otherwise = [Left t]-splitter t (((start, end), grps) : rest) = do+splitter t [] = wrapIfNotEmpty t+splitter t (((start, end), grps) : rest) =     splitOnce t ((start, end), grps)-        <> splitter (T.drop end t) (rest & traversed . beside both (traversed . both) -~ end)+    <> splitter (T.drop end t) (subtractFromAll end rest) +splitOnce :: Text -> (MatchRange, GroupRanges) -> [Either T.Text (T.Text, GroupRanges)]+splitOnce t ((start, end), grps) = do+    let (before, mid) = T.splitAt start t+    let focused = T.take (end - start) mid+    wrapIfNotEmpty before <> [Right (focused, subtractFromAll start grps)]+ splitAgain :: (T.Text, GroupRanges) -> Match splitAgain (t, []) | T.null t = []                    | otherwise = [Left t]@@ -200,14 +230,11 @@     let focused = T.take (end - start) mid     wrapIfNotEmpty before         <> [Right focused]-        <> splitAgain ((T.drop end t), (rest & traversed . both -~ end))+        <> splitAgain ((T.drop end t), (subtractFromAll end rest)) -splitOnce :: Text -> (MatchRange, GroupRanges) -> [Either T.Text (T.Text, GroupRanges)]-splitOnce t ((start, end), grps) = do-    let (before, mid) = T.splitAt start t-    let focused = T.take (end - start) mid-    wrapIfNotEmpty before-      <> [Right (focused, grps & traversed . both -~ start)]+--- helpers+subtractFromAll :: (Data b) => Int -> b -> b+subtractFromAll n = biplate -~ n  wrapIfNotEmpty :: Text -> [Either Text a] wrapIfNotEmpty txt
test/Spec.hs view
@@ -55,22 +55,21 @@                     ("one two three" & regex [rx|two|] . match %~ T.toUpper)                     `shouldBe` "one TWO three" -    describe "iregex" $ do-        describe "match" $ do+        describe "indexed" $ do             it "should allow folding with index" $ do-                ("one two three" ^.. (iregex [rx|\w+|] <. match) . withIndex)+                ("one two three" ^.. (regex [rx|\w+|] <. match) . withIndex)                 `shouldBe` [(0, "one"), (1, "two"), (2, "three")]              it "should allow getting with index" $ do-                ("one two three" ^.. iregex [rx|\w+|] . index 1 . match)+                ("one two three" ^.. regex [rx|\w+|] . index 1 . match)                 `shouldBe` ["two"]              it "should allow setting with index" $ do-                ("one two three" & iregex [rx|\w+|] <. match .@~ T.pack . show)+                ("one two three" & regex [rx|\w+|] <. match .@~ T.pack . show)                 `shouldBe` "0 1 2"              it "should allow mutating with index" $ do-                ("one two three" & iregex [rx|\w+|] <. match %@~ \i s -> (T.pack $ show i) <> ": " <> s)+                ("one two three" & regex [rx|\w+|] <. match %@~ \i s -> (T.pack $ show i) <> ": " <> s)                 `shouldBe` "0: one 1: two 2: three"      describe "groups" $ do@@ -87,6 +86,10 @@                 ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . groups . ix 1)                 `shouldBe` ["two", "four"] +            xit "should handle weird group alternation" $ do+                "1:2 a=b" ^.. regex [rx|(\d):(\d)|(\w)=(\w)|] . match+                `shouldBe` ["not entirely sure what I expect this to be yet"]+         describe "setting" $ do             it "should allow setting groups as a list" $ do                 ("one two three" & regex [rx|(\w+) (\w+)|] . groups .~ ["1", "2"])@@ -118,7 +121,7 @@                 `shouldBe` "0: one 1: two 0: three 1: four"              it "should compose indices with matches" $ do-                ("one two three four" ^.. (iregex [rx|(\w+) (\w+)|] <.> groups . traversed) . withIndex)+                ("one two three four" ^.. (regex [rx|(\w+) (\w+)|] <.> groups . traversed) . withIndex)                 `shouldBe` [((0, 0), "one"), ((0, 1), "two"), ((1, 0), "three"), ((1, 1), "four")]      describe "matchAndGroups" $ do