packages feed

lens-regex-pcre 0.3.1.0 → 1.0.0.0

raw patch · 10 files changed

+1041/−523 lines, 10 filesdep +gaugedep −pcre-lightPVP ok

version bump matches the API change (PVP)

Dependencies added: gauge

Dependencies removed: pcre-light

API changes (from Hackage documentation)

- Control.Lens.Regex: compile :: ByteString -> [PCREOption] -> Regex
- Control.Lens.Regex: compileM :: ByteString -> [PCREOption] -> Either String Regex
- Control.Lens.Regex: data Regex
- Control.Lens.Regex: groups :: Traversal' (Match text) [text]
- Control.Lens.Regex: match :: Monoid text => Traversal' (Match text) text
- Control.Lens.Regex: matchAndGroups :: Monoid text => Getter (Match text) (text, [text])
- Control.Lens.Regex: mkRegexQQ :: [PCREOption] -> QuasiQuoter
- Control.Lens.Regex: regex :: Regex -> IndexedTraversal' Int Text (Match Text)
- Control.Lens.Regex: regexBS :: Regex -> IndexedTraversal' Int ByteString (Match ByteString)
- Control.Lens.Regex: rx :: QuasiQuoter
- Control.Lens.Regex: type Match text = [Either text text]
+ Control.Lens.Regex.ByteString: data Match
+ Control.Lens.Regex.ByteString: data Regex
+ Control.Lens.Regex.ByteString: group :: Int -> IndexedTraversal' ByteString Match ByteString
+ Control.Lens.Regex.ByteString: groups :: IndexedTraversal' ByteString Match [ByteString]
+ Control.Lens.Regex.ByteString: instance (TypeError ...) => GHC.Show.Show Control.Lens.Regex.ByteString.Match
+ Control.Lens.Regex.ByteString: match :: IndexedTraversal' [ByteString] Match ByteString
+ Control.Lens.Regex.ByteString: matchAndGroups :: Getter Match (ByteString, [ByteString])
+ Control.Lens.Regex.ByteString: mkRegexTraversalQQ :: [PCREOption] -> QuasiQuoter
+ Control.Lens.Regex.ByteString: regex :: QuasiQuoter
+ Control.Lens.Regex.ByteString: regexing :: Regex -> IndexedTraversal' Int ByteString Match
+ Control.Lens.Regex.Text: data Match
+ Control.Lens.Regex.Text: data Regex
+ Control.Lens.Regex.Text: group :: Int -> IndexedTraversal' Text Match Text
+ Control.Lens.Regex.Text: groups :: IndexedTraversal' Text Match [Text]
+ Control.Lens.Regex.Text: match :: IndexedTraversal' [Text] Match Text
+ Control.Lens.Regex.Text: matchAndGroups :: Getter Match (Text, [Text])
+ Control.Lens.Regex.Text: mkRegexTraversalQQ :: [PCREOption] -> QuasiQuoter
+ Control.Lens.Regex.Text: regex :: QuasiQuoter
+ Control.Lens.Regex.Text: regexing :: Regex -> IndexedTraversal' Int Text Match

Files

ChangeLog.md view
@@ -1,20 +1,30 @@ # Changelog for lens-regex-pcre +# 1.0.0.0+- Add `regexing` and `makeRegexTraversalQQ`+- Replace `regex` traversal maker with `regex` QuasiQuoter+- Split Control.Lens.Regex into Control.Lens.Regex.Text and Control.Lens.Regex.ByteString+- Move regexBS to `Control.Lens.Regex.ByteString.regex`+- Change whole implementation to use ByteString Builders for a massive speedup+- Monomorphise `Match text` -> `Match`+- Add groups to index of `match` and match to index of `groups` & `group`+- Add `group = groups . ix n` for accessing a single group.+ # 0.3.1.0 -Match -> Match text-Added regexBS to run regex on ByteStrings directly+- Match -> Match text+- Added regexBS to run regex on ByteStrings directly  # 0.3.0.0 -Unify `iregex` into `regex` as a single indexed traversal+- Unify `iregex` into `regex` as a single indexed traversal  # 0.2.0.0 -Unify `grouped`, `groups`, and `igroups` into just `groups` with optional traversal+- Unify `grouped`, `groups`, and `igroups` into just `groups` with optional traversal  # 0.1.1.0 -Adds `grouped` and `matchAndGroups`+- Adds `grouped` and `matchAndGroups`  # 0.1.0.1 -Doc fixes+- Doc fixes  # 0.1.0.0 -Initial Release+- Initial Release
README.md view
@@ -2,179 +2,146 @@  [Hackage and Docs](http://hackage.haskell.org/package/lens-regex-pcre) -* NOTE: I don't promise that this is __fast__ yet, nor do I have any benchmarks;-* NOTE: currently only supports `Text` but should be generalizable to more string-likes; open an issue if you need it+Based on `pcre-heavy`; so it should support any regexes or options which it supports. -Based on `pcre-heavy`; so it should support any regexes which it supports.-I'll likely add a way to pass settings in soon; make an issue if you need this :)+Performance is [equal, sometimes **better**](#performance) than that of `pcre-heavy` alone. +Which module should you use?++If you need unicode support, use `Control.Lens.Regex.Text`, if not then `Control.Lens.Regex.ByteString` is faster.+ Working with Regexes in Haskell kinda sucks; it's tough to figure out which libs-to use, and even after you pick one it's tough to figure out how to use it.+to use, and even after you pick one it's tough to figure out how to use it; `lens-regex-pcre` hopes to replace most other solutions by being fast, easy to set up, more adaptable with a more consistent interface. +It helps that there are already HUNDREDS of combinators which interop with lenses :smile:.+ As it turns out; regexes are a very lens-like tool; Traversals allow you to select and alter zero or more matches; traversals can even carry indexes so you know which match or group you're working on. -Note that all traversals in this library are not techically lawful, as the semantics of regular expressions don't allow for it;-They break the 'multi-set' idempotence law of traversals (same one broken by `filtered` from `lens`); in reality this isn't usually a problem (I've literally never encountered an issue with it); but consider yourself warned. Test your code.--Here are a few examples:+# Examples  ```haskell txt :: Text txt = "raindrops on roses and whiskers on kittens"  -- Search-λ> has (regex [rx|whisk|]) txt+>>> has [regex|whisk|] . match txt True  -- Get matches-λ> txt ^.. regex [rx|\br\w+|] . match+>>> txt ^.. [regex|\br\w+|] . match ["raindrops","roses"]  -- Edit matches-λ> txt & regex [rx|\br\w+|] . match %~ T.intersperse '-' . T.toUpper+>>> txt & [regex|\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"  -- Get Groups-λ> txt ^.. regex [rx|(\w+) on (\w+)|] . groups+>>> txt ^.. [regex|(\w+) on (\w+)|] . groups [["raindrops","roses"],["whiskers","kittens"]]  -- Edit Groups-λ> txt & regex [rx|(\w+) on (\w+)|] . groups %~ reverse+>>> txt & [regex|(\w+) on (\w+)|] . groups %~ reverse "roses on raindrops and kittens on whiskers"  -- Get the third match-λ> txt ^? regex [rx|\w+|] . index 2 . match+>>> txt ^? [regex|\w+|] . index 2 . match Just "roses"  -- Match integers, 'Read' them into ints, then sort them in-place -- dumping them back into the source text afterwards.-λ> "Monday: 29, Tuesday: 99, Wednesday: 3" -   & partsOf (regex [rx|\d+|] . match . unpacked . _Show @Int) %~ sort+>>> "Monday: 29, Tuesday: 99, Wednesday: 3" +   & partsOf ([regex|\d+|] . match . unpacked . _Show @Int) %~ sort "Monday: 3, Tuesday: 29, Wednesday: 99"  ```  Basically anything you want to do is possible somehow. -Expected behaviour (and examples) can be found in the test suite:--```haskell-describe "regex" $ do-    describe "match" $ do-        describe "getting" $ do-            it "should find one match" $ do-                "abc" ^.. regex [rx|b|] . match-                `shouldBe` ["b"]--            it "should find many matches" $ do-                "a b c" ^.. regex [rx|\w|] . match-                `shouldBe` ["a", "b", "c"]--            it "should fold" $ do-                "a b c" ^. regex [rx|\w|] . match-                `shouldBe` "abc"--            it "should match with a group" $ do-                "a b c" ^.. regex [rx|(\w)|] . match-                `shouldBe` ["a", "b", "c"]--            it "should match with many groups" $ do-                "a b c" ^.. regex [rx|(\w) (\w)|] . match-                `shouldBe` ["a b"]--            it "should be greedy when overlapping" $ do-                "abc" ^.. regex [rx|\w+|] . match-                `shouldBe`["abc"]--            it "should respect lazy modifiers" $ do-                "abc" ^.. regex [rx|\w+?|] . match-                `shouldBe`["a", "b", "c"]+# Performance -            it "should allow folding with index" $ do-                ("one two three" ^.. (regex [rx|\w+|] <. match) . withIndex)-                `shouldBe` [(0, "one"), (1, "two"), (2, "three")]+See the [benchmarks](./bench/Bench.hs). -            it "should allow getting with index" $ do-                ("one two three" ^.. regex [rx|\w+|] . index 1 . match)-                `shouldBe` ["two"]+## Summary -        describe "setting" $ do-            it "should allow setting" $ do-                ("one two three" & regex [rx|two|] . match .~ "new")-                `shouldBe` "one new three"+Caveat: I'm by no means a benchmarking expert; if you have tips on how to do this better I'm all ears! -            it "should allow setting many" $ do-                ("one <two> three" & regex [rx|\w+|] . match .~ "new")-                `shouldBe` "new <new> new"+* **Search** `lens-regex-pcre` is *marginally* slower than `pcre-heavy`, but well within acceptable margins (within 0.6%)+* **Replace** `lens-regex-pcre` beats `pcre-heavy` by ~10%+* **Modify** `pcre-heavy` doesn't support this operation at all, so I guess `lens-regex-pcre` wins here :) -            it "should allow mutating" $ do-                ("one two three" & regex [rx|two|] . match %~ (<> "!!"). T.toUpper)-                `shouldBe` "one TWO!! three"+How can it possibly be **faster** if it's based on `pcre-heavy`? `lens-regex-pcre` only uses `pcre-heavy` for **finding** the matches, not substitution/replacement. After that it splits the text into chunks and traverses over them with whichever operation you've chosen. The nature of this implementation makes it a lot easier to understand than imperative implementations of the same thing. This means it's pretty easy to make edits, and is also the reason we can support arbitrary traversals/actions. It was easy enough, so I went ahead and made the whole thing use ByteString Builders, which sped it up a lot. I suspect that `pcre-heavy` can benefit from the same optimization if anyone feels like back-porting it; it could be (almost) as nicely using simple `traverse` without any lenses. The whole thing is only about 25 LOC. -            it "should allow mutating many" $ do-                ("one two three" & regex [rx|two|] . match %~ T.toUpper)-                `shouldBe` "one TWO three"+I'm neither a benchmarks nor stats person, so please open an issue if anything here seems fishy. -            it "should allow setting with index" $ do-                ("one two three" & regex [rx|\w+|] <. match .@~ T.pack . show)-                `shouldBe` "0 1 2"+Without `pcre-light` and `pcre-heavy` this library wouldn't be possible, so huge thanks to all contributors! -            it "should allow mutating with index" $ do-                ("one two three" & regex [rx|\w+|] <. match %@~ \i s -> (T.pack $ show i) <> ": " <> s)-                `shouldBe` "0: one 1: two 2: three"+Here are the benchmarks on my 2013 Macbook (2.6 Ghz i5) -describe "groups" $ do-    describe "getting" $ do-        it "should get groups" $ do-            "a b c" ^.. regex [rx|(\w)|] . groups-            `shouldBe` [["a"], ["b"], ["c"]]+```haskell+benchmarking static pattern search/pcre-heavy ... took 20.78 s, total 56 iterations+benchmarked static pattern search/pcre-heavy+time                 375.3 ms   (372.0 ms .. 378.5 ms)+                     1.000 R²   (0.999 R² .. 1.000 R²)+mean                 378.1 ms   (376.4 ms .. 380.8 ms)+std dev              3.747 ms   (922.3 μs .. 5.609 ms) -        it "should get multiple groups" $ do-            "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups-            `shouldBe` [["raindrops","roses"],["whiskers","kittens"]]+benchmarking static pattern search/lens-regex-pcre ... took 20.79 s, total 56 iterations+benchmarked static pattern search/lens-regex-pcre+time                 379.5 ms   (376.2 ms .. 382.4 ms)+                     1.000 R²   (1.000 R² .. 1.000 R²)+mean                 377.3 ms   (376.5 ms .. 378.4 ms)+std dev              1.667 ms   (1.075 ms .. 2.461 ms) -        it "should allow getting a specific index" $ do-            ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . groups . ix 1)-            `shouldBe` ["two", "four"]+benchmarking complex pattern search/pcre-heavy ... took 95.95 s, total 56 iterations+benchmarked complex pattern search/pcre-heavy+time                 1.741 s    (1.737 s .. 1.746 s)+                     1.000 R²   (1.000 R² .. 1.000 R²)+mean                 1.746 s    (1.744 s .. 1.749 s)+std dev              4.499 ms   (3.186 ms .. 6.080 ms) -    describe "setting" $ do-        it "should allow setting groups as a list" $ do-            ("one two three" & regex [rx|(\w+) (\w+)|] . groups .~ ["1", "2"])-            `shouldBe` "1 2 three"+benchmarking complex pattern search/lens-regex-pcre ... took 97.26 s, total 56 iterations+benchmarked complex pattern search/lens-regex-pcre+time                 1.809 s    (1.736 s .. 1.908 s)+                     0.996 R²   (0.991 R² .. 1.000 R²)+mean                 1.757 s    (1.742 s .. 1.810 s)+std dev              42.83 ms   (11.51 ms .. 70.69 ms) -        it "should allow editing when result list is the same length" $ do-            ("raindrops on roses and whiskers on kittens" & regex [rx|(\w+) on (\w+)|] . groups %~ reverse)-            `shouldBe` "roses on raindrops and kittens on whiskers"+benchmarking simple replacement/pcre-heavy ... took 23.32 s, total 56 iterations+benchmarked simple replacement/pcre-heavy+time                 423.8 ms   (422.4 ms .. 425.3 ms)+                     1.000 R²   (1.000 R² .. 1.000 R²)+mean                 424.0 ms   (422.9 ms .. 426.2 ms)+std dev              2.684 ms   (1.239 ms .. 4.270 ms) -    describe "traversed" $ do-        it "should allow setting all group matches" $ do-            ("one two three" & regex [rx|(\w+) (\w+)|] . groups . traversed .~ "new")-            `shouldBe` "new new three"+benchmarking simple replacement/lens-regex-pcre ... took 20.84 s, total 56 iterations+benchmarked simple replacement/lens-regex-pcre+time                 382.8 ms   (374.3 ms .. 391.5 ms)+                     0.999 R²   (0.999 R² .. 1.000 R²)+mean                 378.2 ms   (376.3 ms .. 381.0 ms)+std dev              3.794 ms   (2.577 ms .. 5.418 ms) -        it "should allow mutating" $ do-            ("one two three four" & regex [rx|one (two) (three)|] . groups . traversed %~ (<> "!!") . T.toUpper)-            `shouldBe` "one TWO!! THREE!! four"+benchmarking complex replacement/pcre-heavy ... took 24.77 s, total 56 iterations+benchmarked complex replacement/pcre-heavy+time                 448.1 ms   (444.7 ms .. 450.0 ms)+                     1.000 R²   (1.000 R² .. 1.000 R²)+mean                 450.8 ms   (449.5 ms .. 453.9 ms)+std dev              3.129 ms   (947.0 μs .. 4.841 ms) -        it "should allow folding with index" $ do-            ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . groups . traversed . withIndex)-            `shouldBe` [(0, "one"), (1, "two"), (0, "three"), (1, "four")]+benchmarking complex replacement/lens-regex-pcre ... took 21.99 s, total 56 iterations+benchmarked complex replacement/lens-regex-pcre+time                 399.9 ms   (398.4 ms .. 402.2 ms)+                     1.000 R²   (1.000 R² .. 1.000 R²)+mean                 399.6 ms   (399.0 ms .. 400.4 ms)+std dev              1.135 ms   (826.2 μs .. 1.604 ms) -        it "should allow setting with index" $ do-            ("one two three four" & regex [rx|(\w+) (\w+)|] . groups . traversed .@~ T.pack . show)-            `shouldBe` "0 1 0 1"+Benchmark lens-regex-pcre-bench: FINISH+``` -        it "should allow mutating with index" $ do-            ("one two three four" & regex [rx|(\w+) (\w+)|] . groups . traversed %@~ \i s -> (T.pack $ show i) <> ": " <> s)-            `shouldBe` "0: one 1: two 0: three 1: four"+# Behaviour -        it "should compose indices with matches" $ do-            ("one two three four" ^.. (regex [rx|(\w+) (\w+)|] <.> groups . traversed) . withIndex)-            `shouldBe` [((0, 0), "one"), ((0, 1), "two"), ((1, 0), "three"), ((1, 1), "four")]+Precise Expected behaviour (and examples) can be found in the test suites: -describe "matchAndGroups" $ do-    it "should get match and groups" $ do-        "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . matchAndGroups-        `shouldBe` [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]-```+* [ByteString tests](./test/ByteString.hs)+* [Text tests](./test/Text.hs)
+ bench/Bench.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}++import Gauge.Benchmark+import Gauge.Main+import Data.ByteString as BS+import qualified Text.Regex.PCRE.Heavy as PCRE+import Control.Lens+import Control.Lens.Regex.ByteString++main :: IO ()+main = do+    srcFile <- BS.readFile "./bench/data/small-bible.txt"+    defaultMain+      [ bgroup "static pattern search"+        [ bench "pcre-heavy" $ nf (heavySearch [PCRE.re|Moses|]) srcFile+        , bench "lens-regex-pcre" $ nf (lensSearch [PCRE.re|Moses|]) srcFile+        ]+      , bgroup "complex pattern search"+        [ bench "pcre-heavy" $ nf (heavySearch [PCRE.re|l\w+e|]) srcFile+        , bench "lens-regex-pcre" $ nf (lensSearch [PCRE.re|l\w+e|]) srcFile+        ]+      , bgroup "simple replacement"+        [ bench "pcre-heavy" $ nf (heavyReplace [PCRE.re|Moses|] "Jarvis") srcFile+        , bench "lens-regex-pcre" $ nf (lensReplace [PCRE.re|Moses|] "Jarvis") srcFile+        ]+      , bgroup "complex replacement"+        [ bench "pcre-heavy" $ nf (heavyReplace [PCRE.re|M\w*s\w*s|] "Jarvis") srcFile+        , bench "lens-regex-pcre" $ nf (lensReplace [PCRE.re|M\w*s\w*s|] "Jarvis") srcFile+        ]+      ]++heavySearch :: Regex -> BS.ByteString -> [BS.ByteString]+heavySearch pat src = fst <$> PCRE.scan pat src++lensSearch :: Regex -> BS.ByteString -> [BS.ByteString]+lensSearch pat src = src ^.. regexing pat . match++heavyReplace :: Regex -> BS.ByteString -> BS.ByteString -> BS.ByteString+heavyReplace pat replacement src = PCRE.gsub pat replacement src++lensReplace :: Regex -> BS.ByteString -> BS.ByteString -> BS.ByteString+lensReplace pat replacement src = src & regexing pat . match .~ replacement
lens-regex-pcre.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.1.+-- This file has been generated from package.yaml by hpack version 0.31.2. -- -- see: https://github.com/sol/hpack ----- hash: c1baf100f3d2d1c413b88695a96d578b0a8d18aace0929759a5428dcfa30b1f1+-- hash: 19bd65f2de279f3777dc4f946f7d0efb2805098d91aa727f525e3a4375b09351  name:           lens-regex-pcre-version:        0.3.1.0+version:        1.0.0.0 synopsis:       A lensy interface to regular expressions description:    Please see the README on GitHub at <https://github.com/ChrisPenner/lens-regex-pcre#readme> category:       Regex@@ -29,7 +29,8 @@  library   exposed-modules:-      Control.Lens.Regex+      Control.Lens.Regex.ByteString+      Control.Lens.Regex.Text   other-modules:       Paths_lens_regex_pcre   hs-source-dirs:@@ -37,29 +38,49 @@   ghc-options: -Wall   build-depends:       base >=4.7 && <5+    , bytestring     , lens     , pcre-heavy-    , pcre-light     , template-haskell     , text-    , bytestring   default-language: Haskell2010  test-suite lens-regex-pcre-test   type: exitcode-stdio-1.0   main-is: Spec.hs   other-modules:+      ByteString+      Text       Paths_lens_regex_pcre   hs-source-dirs:       test   ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N   build-depends:       base >=4.7 && <5+    , bytestring     , hspec     , lens     , lens-regex-pcre     , pcre-heavy-    , pcre-light+    , template-haskell+    , text+  default-language: Haskell2010++benchmark lens-regex-pcre-bench+  type: exitcode-stdio-1.0+  main-is: Bench.hs+  other-modules:+      Paths_lens_regex_pcre+  hs-source-dirs:+      bench+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , bytestring+    , gauge+    , lens+    , lens-regex-pcre+    , pcre-heavy     , template-haskell     , text   default-language: Haskell2010
− src/Control/Lens/Regex.hs
@@ -1,257 +0,0 @@-{-|-Module      : Control.Lens.Regex-Description : PCRE regex combinators for interop with lens-Copyright   : (c) Chris Penner, 2019-License     : BSD3--}--{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PartialTypeSignatures #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TemplateHaskell #-}--module Control.Lens.Regex-    (-    -- * Combinators-      regex-    , regexBS-    , match-    , groups-    , matchAndGroups--    -- * Compiling regex-    , rx-    , mkRegexQQ-    , compile-    , compileM--    -- * Types-    , Match-    , Regex-    ) where--import qualified Data.Text as T hiding (index)-import qualified Data.Text.Encoding as T-import qualified Data.Text.Encoding.Error as T-import qualified Data.ByteString as BS-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.Text (Text)--- >>> import Data.List (sort)---- | Match represents a whole regex match; you can drill into it using 'match' or 'groups' or 'matchAndGroups'------ @text@ is either "Text" or "ByteString" depending on whether you use 'regex' or 'regexBS'------ Consider this to be internal; don't depend on its representation.-type Match text = [Either text text]-type MatchRange = (Int, Int)-type GroupRanges = [(Int, Int)]---- | Access all groups of a match at once.------ Note that you can edit the groups through this traversal,--- Changing the length of the list has behaviour similar to 'partsOf'.------ Get all matched groups:------ >>> "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"]------ @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"------ Editing the list rearranges groups------ >>> "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"]-groups :: Traversal' (Match text) [text]-groups = partsOf (traversed . _Right)---- | Traverse each match------  Get a match if one exists:------ >>> "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_"-match :: Monoid text => Traversal' (Match text) text-match f grps = (:[]) . Right <$> f (grps ^. traversed . chosen)---- | 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.------ >>> txt = "raindrops on roses and whiskers on kittens" :: Text------ Search------ >>> has (regex [rx|whisk|]) txt--- True------ Get matches------ >>> txt ^.. regex [rx|\br\w+|] . match--- ["raindrops","roses"]------ Edit 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"------ Get Groups------ >>> txt ^.. regex [rx|(\w+) on (\w+)|] . groups--- [["raindrops","roses"],["whiskers","kittens"]]------ Edit Groups------ >>> txt & regex [rx|(\w+) on (\w+)|] . groups %~ Prelude.reverse--- "roses on raindrops and kittens on whiskers"------ Get the third match------ >>> txt ^? regex [rx|\w+|] . index 2 . match--- Just "roses"------ Match integers, 'Read' them into ints, then sort them in-place--- dumping them back into the source text afterwards.------ >>> "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf (regex [rx|\d+|] . match . unpacked . _Show @Int) %~ sort--- "Monday: 3, Tuesday: 29, Wednesday: 99"------ 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 T.Text)-regex pattern = utf8 . regexBS pattern . matchBsText-  where-    utf8 :: Iso' T.Text BS.ByteString-    utf8 = iso T.encodeUtf8 (T.decodeUtf8With T.lenientDecode)-    matchBsText :: Iso' [Either BS.ByteString BS.ByteString] (Match T.Text)-    matchBsText = iso (traversed . chosen %~ T.decodeUtf8With T.lenientDecode) (traversed . chosen %~ T.encodeUtf8)---- | A version of 'regex' which operates directly on 'BS.ByteString's.--- This is more efficient than using 'regex' as it avoids converting back and forth--- between 'BS.ByteString' and 'T.Text'.-regexBS :: Regex -> IndexedTraversal' Int BS.ByteString (Match BS.ByteString)-regexBS pattern = indexing (regexT pattern)---- | Base regex traversal. Used only to define 'regex' traversals-regexT :: Regex -> Traversal' BS.ByteString [Either BS.ByteString BS.ByteString]-regexT pattern f txt = collapseMatch <$> apply (fmap splitAgain <$> splitter txt matches)-  where-    matches :: [(MatchRange, GroupRanges)]-    matches = scanRanges pattern txt-    collapseMatch :: [Either BS.ByteString [Either BS.ByteString BS.ByteString]] -> BS.ByteString-    collapseMatch xs = xs ^. folded . beside id (traversed . chosen)-    -- apply :: [Either Text [Either Text Text]] -> _ [Either Text [Either Text Text]]-    apply xs = xs & traversed . _Right %%~ f----- | Get the full match text from a match-matchText :: Monoid text => Match text -> 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"])]-matchAndGroups :: Monoid text => Getter (Match text) (text, [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 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 :: Monoid text => IndexedTraversal' text (Match text) (Match text)-withMatch p mtch = indexed p (matchText mtch) mtch---- | 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 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' [text] (Match text) (Match text)-withGroups p mtch = indexed p (mtch ^. groups) mtch---- split up text into matches paired with groups; Left is unmatched text-splitter :: BS.ByteString -> [(MatchRange, GroupRanges)] -> [Either BS.ByteString (BS.ByteString, GroupRanges)]-splitter t [] = wrapIfNotEmpty t-splitter t (((start, end), grps) : rest) =-    splitOnce t ((start, end), grps)-    <> splitter (BS.drop end t) (subtractFromAll end rest)--splitOnce :: BS.ByteString -> (MatchRange, GroupRanges) -> [Either BS.ByteString (BS.ByteString, GroupRanges)]-splitOnce t ((start, end), grps) = do-    let (before, mid) = BS.splitAt start t-    let focused = BS.take (end - start) mid-    wrapIfNotEmpty before <> [Right (focused, subtractFromAll start grps)]--splitAgain :: (BS.ByteString, GroupRanges) -> [Either BS.ByteString BS.ByteString]-splitAgain (t, []) | BS.null t = []-                   | otherwise = [Left t]-splitAgain (t, (start, end) : rest) = do-    let (before, mid) = BS.splitAt start t-    let focused = BS.take (end - start) mid-    wrapIfNotEmpty before-        <> [Right focused]-        <> splitAgain ((BS.drop end t), (subtractFromAll end rest))----- helpers-subtractFromAll :: (Data b) => Int -> b -> b-subtractFromAll n = biplate -~ n--wrapIfNotEmpty :: BS.ByteString -> [Either BS.ByteString a]-wrapIfNotEmpty txt-    | BS.null txt = []-    | otherwise = [Left txt]
+ src/Control/Lens/Regex/ByteString.hs view
@@ -0,0 +1,319 @@+{-|+Module      : Control.Lens.Regex.ByteString+Description : ByteString PCRE Regex library with a lensy interface.+Copyright   : (c) Chris Penner, 2019+License     : BSD3+-}++{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeOperators #-}++module Control.Lens.Regex.ByteString+    (+    -- * Basics+      regex+    , match+    , groups+    , group+    , matchAndGroups++    -- * Compiling regexes to Traversals+    , regexing+    , mkRegexTraversalQQ++    -- * Types+    , Match+    , PCRE.Regex+    ) where++import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Builder as BS+import qualified Text.Regex.PCRE.Heavy as PCRE+import Control.Lens hiding (re)+import Data.Bifunctor+import qualified Language.Haskell.TH.Quote as TH+import qualified Language.Haskell.TH.Syntax as TH+import qualified Language.Haskell.TH as TH+import GHC.TypeLits++-- $setup+-- >>> :set -XQuasiQuotes+-- >>> :set -XOverloadedStrings+-- >>> :set -XTypeApplications+-- >>> import qualified Data.ByteString.Char8 as Char8+-- >>> import Data.Char+-- >>> import Data.List hiding (group)+-- >>> import Data.ByteString.Lens++type MatchRange = (Int, Int)+type GroupRanges = [(Int, Int)]++-- | Match represents an opaque regex match.+-- You can drill into it using 'match', 'groups', 'group' or 'matchAndGroups'+newtype Match = Match [Either BS.Builder BS.Builder]++instance TypeError+  ('Text "You're trying to 'show' a raw 'Match' object."+   ':$$: 'Text "You likely missed adding a 'match' or 'groups' or 'group' call after your 'regex' call :)")+  => Show Match where+  show _ = "This is a raw Match object, did you miss a 'match' or 'groups' or 'group' call after your 'regex'?"++chunks :: Iso' Match [Either BS.Builder BS.Builder]+chunks = coerced++unBuilder :: BS.Builder -> BS.ByteString+unBuilder = BL.toStrict . BS.toLazyByteString++building :: Iso' BS.Builder BS.ByteString+building = iso unBuilder BS.byteString++-- | Access all groups of a match as a list. Stashes the full match text as the index in case+-- you need it.+--+-- Note that you can edit the groups through this traversal,+-- Changing the length of the list has behaviour similar to 'partsOf'.+--+-- Get all matched groups:+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]]+--+-- You can access a specific group combining with 'ix', or just use 'group' instead+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . groups .  ix 1+-- ["roses","kittens"]+--+-- Editing groups:+--+-- >>> "raindrops on roses and whiskers on kittens" & [regex|(\w+) on (\w+)|] . groups .  ix 1 %~ Char8.map toUpper+-- "raindrops on ROSES and whiskers on KITTENS"+--+-- Editing the list rearranges groups+--+-- >>> "raindrops on roses and whiskers on kittens" & [regex|(\w+) on (\w+)|] . groups %~ 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|(\w+) on (\w+)|] . groups . traversed+-- ["raindrops","roses","whiskers","kittens"]+--+-- Use indexed helpers to access the full match when operating on a group.+--+-- This replaces each group with the full match text wrapped in parens:+--+-- >>> "one-two" & [regex|(\w+)-(\w+)|] . groups <. traversed %@~ \mtch grp -> grp <> ":(" <> mtch <> ")"+-- "one:(one-two)-two:(one-two)"+groups :: IndexedTraversal' BS.ByteString Match [BS.ByteString]+groups = conjoined groupsT (reindexed (view match) selfIndex <. groupsT)+    where+      groupsT :: Traversal' Match [BS.ByteString]+      groupsT = chunks . partsOf (traversed . _Right . building)++-- | Access a specific group of a match. Numbering starts at 0.+--+-- Stashes the full match text as the index in case you need it.+--+-- See 'groups' for more info on grouping+--+-- >>> "key:value, a:b" ^.. [regex|(\w+):(\w+)|] . group 0+-- ["key","a"]+--+-- >>> "key:value, a:b" ^.. [regex|(\w+):(\w+)|] . group 1+-- ["value","b"]+--+-- >>> "key:value, a:b" & [regex|(\w+):(\w+)|] . group 1 %~ Char8.map toUpper+-- "key:VALUE, a:B"+--+-- Replace the first capture group with the full match:+--+-- >>> "a, b" & [regex|(\w+), (\w+)|] . group 0 .@~ \i -> "(" <> i <> ")"+-- "(a, b), b"+group :: Int -> IndexedTraversal' BS.ByteString Match BS.ByteString+group n = groups <. ix n++-- | Traverse each match+--+-- Stashes any matched groups into the index in case you need them.+--+--  Get a match if one exists:+--+-- >>> "find a needle in a haystack" ^? [regex|n..dle|] . match+-- Just "needle"+--+--  Collect all matches+--+-- >>> "one _two_ three _four_" ^.. [regex|_\w+_|] . match+-- ["_two_","_four_"]+--+-- You can edit the traversal to perform a regex replace/substitution+--+-- >>> "one _two_ three _four_" & [regex|_\w+_|] . match %~ Char8.map toUpper+-- "one _TWO_ three _FOUR_"+--+-- Here we use the group matches stored in the index to form key-value pairs, replacing the entire match.+--+-- >>> "abc-def, ghi-jkl" & [regex|(\w+)-(\w+)|] . match %@~ \[k, v] _ -> "{" <> k <> ":" <> v <> "}"+-- "{abc:def}, {ghi:jkl}"+match :: IndexedTraversal' [BS.ByteString] Match BS.ByteString+match = conjoined matchBS (reindexed (view groups) selfIndex <. matchBS)+  where+    matchBS :: Traversal' Match BS.ByteString+    matchBS = chunks . matchT . building+    matchT :: Traversal' [Either BS.Builder BS.Builder] BS.Builder+    matchT f grps =+        (:[]) . Right <$> f (grps ^. folded . chosen)++-- | Build a traversal from the provided 'PCRE.Regex', this is handy if you're QuasiQuoter+-- averse, or if you already have a 'PCRE.Regex' object floating around.+--+-- Also see 'mkRegexTraversalQQ'+regexing :: PCRE.Regex -> IndexedTraversal' Int BS.ByteString Match+regexing pattern = conjoined (regexT pattern) (indexing (regexT pattern)) . from chunks++-- | Base regex traversal helper+regexT :: PCRE.Regex -> Traversal' BS.ByteString [Either BS.Builder BS.Builder]+regexT pattern f txt = unBuilder . collapseMatch <$> apply (splitAll txt matches)+  where+    matches :: [(MatchRange, GroupRanges)]+    matches = PCRE.scanRanges pattern txt+    collapseMatch :: [Either BS.Builder [Either BS.Builder BS.Builder]] -> BS.Builder+    collapseMatch xs = xs ^. folded . beside id (traversed . chosen)+    apply xs = xs & traversed . _Right %%~ f++-- | Collect both the match text AND all the matching groups+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . matchAndGroups+-- [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]+matchAndGroups :: Getter Match (BS.ByteString, [BS.ByteString])+matchAndGroups = to $ \m -> (m ^. match, m ^. groups)++-- | Builds a traversal over text using a Regex pattern+--+-- It's a 'QuasiQuoter' which creates a Traversal out of the given regex string.+-- It's equivalent to calling 'regexing' on a 'Regex' created using the+-- 're' QuasiQuoter.+--+-- The "real" type is:+--+-- > regex :: Regex -> IndexedTraversal' Int BS.ByteString Match+--+-- It's a traversal which selects 'Match'es; compose it with 'match' or 'groups'+-- to get the relevant parts of your match.+--+-- >>> txt = "raindrops on roses and whiskers on kittens"+--+-- Search+--+-- >>> has ([regex|whisk|]) txt+-- True+--+-- Get matches+--+-- >>> txt ^.. [regex|\br\w+|] . match+-- ["raindrops","roses"]+--+-- Edit matches+--+-- >>> txt & [regex|\br\w+|] . match %~ Char8.intersperse '-' . Char8.map toUpper+-- "R-A-I-N-D-R-O-P-S on R-O-S-E-S and whiskers on kittens"+--+-- Get Groups+--+-- >>> txt ^.. [regex|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]]+--+-- Edit Groups+--+-- >>> txt & [regex|(\w+) on (\w+)|] . groups %~ reverse+-- "roses on raindrops and kittens on whiskers"+--+-- Get the third match+--+-- >>> txt ^? [regex|\w+|] . index 2 . match+--Just "roses"+--+-- Edit matches+--+-- >>> txt & [regex|\br\w+|] . match %~ Char8.intersperse '-' . Char8.map toUpper+-- "R-A-I-N-D-R-O-P-S on R-O-S-E-S and whiskers on kittens"+--+-- Get Groups+--+-- >>> txt ^.. [regex|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]]+--+-- Edit Groups+--+-- >>> txt & [regex|(\w+) on (\w+)|] . groups %~ reverse+-- "roses on raindrops and kittens on whiskers"+--+-- Get the third match+--+-- >>> txt ^? [regex|\w+|] . index 2 . match+-- Just "roses"+--+-- Match integers, 'Read' them into ints, then sort them in-place+-- dumping them back into the source text afterwards.+--+-- >>> "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf ([regex|\d+|] . match . from packedChars . _Show @Int) %~ sort+-- "Monday: 3, Tuesday: 29, Wednesday: 99"+--+-- 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 :: TH.QuasiQuoter+regex = PCRE.re{TH.quoteExp=quoter}+  where+    quoter str = do+        rgx <- TH.quoteExp PCRE.re str+        regexExpr <- TH.varE 'regexing+        return $ TH.AppE regexExpr rgx++-- | Build a QuasiQuoter just like 'regex' but with the provided 'PCRE.PCREOption' overrides.+mkRegexTraversalQQ :: [PCRE.PCREOption] -> TH.QuasiQuoter+mkRegexTraversalQQ opts = (PCRE.mkRegexQQ opts){TH.quoteExp=quoter}+  where+    quoter str = do+        rgx <- TH.quoteExp (PCRE.mkRegexQQ opts) str+        regexExpr <- TH.varE 'regexing+        return $ TH.AppE regexExpr rgx++---------------------------------------------------------------------------------------------++splitAll :: BS.ByteString -> [(MatchRange, GroupRanges)] -> [Either BS.Builder [Either BS.Builder BS.Builder]]+splitAll txt matches = fmap (second (\(txt', (start,_), grps) -> groupSplit txt' start grps)) splitUp+  where+    splitUp = splits txt 0 matches++groupSplit :: BS.ByteString -> Int -> GroupRanges -> [Either BS.Builder BS.Builder]+groupSplit txt _ [] = [Left $ BS.byteString txt]+groupSplit txt offset ((grpStart, grpEnd) : rest) | offset == grpStart =+    let (prefix, suffix) = BS.splitAt (grpEnd - offset) txt+     in Right (BS.byteString prefix) : groupSplit suffix grpEnd rest+groupSplit txt offset ((grpStart, grpEnd) : rest) =+    let (prefix, suffix) = BS.splitAt (grpStart - offset) txt+     in Left (BS.byteString prefix) : groupSplit suffix grpStart ((grpStart, grpEnd) : rest)++splits :: BS.ByteString -> Int -> [(MatchRange, GroupRanges)] -> [Either BS.Builder (BS.ByteString, MatchRange, GroupRanges)]+-- No more matches left+splits txt _ [] = [Left $ BS.byteString txt]+-- We're positioned at a match+splits txt offset (((start, end), grps) : rest) | offset == start =+    let (prefix, suffix) = BS.splitAt (end - offset) txt+     in (Right (prefix, (start, end), grps)) : splits suffix end rest+-- jump to the next match+splits txt offset matches@(((start, _), _) : _) =+    let (prefix, suffix) = BS.splitAt (start - offset) txt+     in (Left $ BS.byteString prefix) : splits suffix start matches
+ src/Control/Lens/Regex/Text.hs view
@@ -0,0 +1,251 @@+{-|+Module      : Control.Lens.Regex.Text+Description : Text PCRE Regex library with a lensy interface.+Copyright   : (c) Chris Penner, 2019+License     : BSD3+-}++{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}++module Control.Lens.Regex.Text+    (+    -- * Basics+      regex+    , match+    , groups+    , group+    , matchAndGroups++    -- * Compiling regexes to Traversals+    , regexing+    , mkRegexTraversalQQ++    -- * Types+    , RBS.Match+    , PCRE.Regex+    ) where++import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Encoding.Error as T+import qualified Data.ByteString as BS+import qualified Text.Regex.PCRE.Heavy as PCRE+import Control.Lens hiding (re, matching)+import qualified Language.Haskell.TH as TH+import qualified Language.Haskell.TH.Quote as TH++import qualified Control.Lens.Regex.ByteString as RBS++-- $setup+-- >>> :set -XQuasiQuotes+-- >>> :set -XOverloadedStrings+-- >>> :set -XTypeApplications+-- >>> import Data.Text.Lens (unpacked)+-- >>> import qualified Data.Text as T+-- >>> import Data.List (sort)++utf8 :: Iso' T.Text BS.ByteString+utf8 = iso T.encodeUtf8 (T.decodeUtf8With T.lenientDecode)++-- | Builds a traversal over text using a Regex pattern+--+-- It's a 'QuasiQuoter' which creates a Traversal out of the given regex string.+-- It's equivalent to calling 'regexing' on a 'Regex' created using the+-- 're' QuasiQuoter.+--+-- The "real" type is:+--+-- > regex :: Regex -> IndexedTraversal' Int T.Text Match+--+-- It's a traversal which selects 'Match'es; compose it with 'match' or 'groups'+-- to get the relevant parts of your match.+--+-- >>> txt = "raindrops on roses and whiskers on kittens"+--+-- Search+--+-- >>> has ([regex|whisk|]) txt+-- True+--+-- Get matches+--+-- >>> txt ^.. [regex|\br\w+|] . match+-- ["raindrops","roses"]+--+-- Edit matches+--+-- >>> txt & [regex|\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"+--+-- Get Groups+--+-- >>> txt ^.. [regex|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]]+--+-- Edit Groups+--+-- >>> txt & [regex|(\w+) on (\w+)|] . groups %~ reverse+-- "roses on raindrops and kittens on whiskers"+--+-- Get the third match+--+-- >>> txt ^? [regex|\w+|] . index 2 . match+--Just "roses"+--+-- Edit matches+--+-- >>> txt & [regex|\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"+--+-- Get Groups+--+-- >>> txt ^.. [regex|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]]+--+-- Edit Groups+--+-- >>> txt & [regex|(\w+) on (\w+)|] . groups %~ reverse+-- "roses on raindrops and kittens on whiskers"+--+-- Get the third match+--+-- >>> txt ^? [regex|\w+|] . index 2 . match+-- Just "roses"+--+-- Match integers, 'Read' them into ints, then sort them in-place+-- dumping them back into the source text afterwards.+--+-- >>> "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf ([regex|\d+|] . match . unpacked . _Show @Int) %~ sort+-- "Monday: 3, Tuesday: 29, Wednesday: 99"+--+-- 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 RBS.Match+regex :: TH.QuasiQuoter+regex = PCRE.re{TH.quoteExp=quoter}+  where+    quoter str = do+        rgx <- TH.quoteExp PCRE.re str+        regexExpr <- TH.varE 'regexing+        return $ TH.AppE regexExpr rgx++-- | Build a QuasiQuoter just like 'regex' but with the provided 'PCRE.PCREOption' overrides.+mkRegexTraversalQQ :: [PCRE.PCREOption] -> TH.QuasiQuoter+mkRegexTraversalQQ opts = (PCRE.mkRegexQQ opts){TH.quoteExp=quoter}+  where+    quoter str = do+        rgx <- TH.quoteExp (PCRE.mkRegexQQ opts) str+        regexExpr <- TH.varE 'regexing+        return $ TH.AppE regexExpr rgx++-- | Build a traversal from the provided 'PCRE.Regex', this is handy if you're QuasiQuoter+-- averse, or if you already have a 'PCRE.Regex' object floating around.+--+-- Also see 'mkRegexTraversalQQ'+regexing :: PCRE.Regex -> IndexedTraversal' Int T.Text RBS.Match+regexing pat = utf8 . RBS.regexing pat++-- | Access all groups of a match as a list. Also keeps full match text as the index in case+-- you need it.+--+-- Note that you can edit the groups through this traversal,+-- Changing the length of the list has behaviour similar to 'partsOf'.+--+-- Get all matched groups:+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . groups+-- [["raindrops","roses"],["whiskers","kittens"]]+--+-- You can access a specific group combining with 'ix', or just use 'group' instead+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . groups .  ix 1+-- ["roses","kittens"]+--+-- Editing groups:+--+-- >>> "raindrops on roses and whiskers on kittens" & [regex|(\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|(\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|(\w+) on (\w+)|] . groups . traversed+-- ["raindrops","roses","whiskers","kittens"]+--+-- This replaces each group with the full match text wrapped in parens:+--+-- >>> "one-two" & [regex|(\w+)-(\w+)|] . groups <. traversed %@~ \mtch grp -> grp <> ":(" <> mtch <> ")"+-- "one:(one-two)-two:(one-two)"+groups :: IndexedTraversal' T.Text RBS.Match [T.Text]+groups = reindexed (view $ from utf8) RBS.groups <. mapping (from utf8)++-- | Access a specific group of a match. Numbering starts at 0.+--+-- Stashes the full match text as the index in case you need it.+--+-- See 'groups' for more info on grouping+--+-- >>> "key:value, a:b" ^.. [regex|(\w+):(\w+)|] . group 0+-- ["key","a"]+--+-- >>> "key:value, a:b" ^.. [regex|(\w+):(\w+)|] . group 1+-- ["value","b"]+--+-- >>> "key:value, a:b" & [regex|(\w+):(\w+)|] . group 1 %~ T.toUpper+-- "key:VALUE, a:B"+--+-- >>> "key:value, a:b" & [regex|(\w+):(\w+)|] . group 1 %~ T.toUpper+-- "key:VALUE, a:B"+--+-- Replace the first capture group with the full match:+--+-- >>> "a, b" & [regex|(\w+), (\w+)|] . group 0 .@~ \i -> "(" <> i <> ")"+-- "(a, b), b"+group :: Int -> IndexedTraversal' T.Text RBS.Match T.Text+group n = groups <. ix n++-- | Traverse each match+--+-- Stashes any matched groups into the index in case you need them.+--+--  Get a match if one exists:+--+-- >>> "find a needle in a haystack" ^? [regex|n..dle|] . match+-- Just "needle"+--+--  Collect all matches+--+-- >>> "one _two_ three _four_" ^.. [regex|_\w+_|] . match+-- ["_two_","_four_"]+--+-- You can edit the traversal to perform a regex replace/substitution+--+-- >>> "one _two_ three _four_" & [regex|_\w+_|] . match %~ T.toUpper+-- "one _TWO_ three _FOUR_"+--+-- Here we use the group matches stored in the index to form key-value pairs, replacing the entire match.+--+-- >>> "abc-def, ghi-jkl" & [regex|(\w+)-(\w+)|] . match %@~ \[k, v] _ -> "{" <> k <> ":" <> v <> "}"+-- "{abc:def}, {ghi:jkl}"+match :: IndexedTraversal' [T.Text] RBS.Match T.Text+match = reindexed (fmap (view $ from utf8)) RBS.match <. from utf8++-- | Collect both the match text AND all the matching groups+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . matchAndGroups+-- [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]+matchAndGroups :: Getter RBS.Match (T.Text, [T.Text])+matchAndGroups = to $ \m -> (m ^. match, m ^. groups)
+ test/ByteString.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+module ByteString where++import Control.Lens+import Control.Lens.Regex.ByteString+import qualified Data.ByteString.Char8 as C8 hiding (index)+import Data.Char+import Test.Hspec++spec :: Spec+spec = do+    describe "regex" $ do+        describe "match" $ do+            describe "getting" $ do+                it "should find one match" $ do+                    "abc" ^.. [regex|b|] . match+                    `shouldBe` ["b"]++                it "should find many matches" $ do+                    "a b c" ^.. [regex|\w|] . match+                    `shouldBe` ["a", "b", "c"]++                it "should fold" $ do+                    "a b c" ^. [regex|\w|] . match+                    `shouldBe` "abc"++                it "should match with a group" $ do+                    "a b c" ^.. [regex|(\w)|] . match+                    `shouldBe` ["a", "b", "c"]++                it "should match with many groups" $ do+                    "a b c" ^.. [regex|(\w) (\w)|] . match+                    `shouldBe` ["a b"]++                it "should be greedy when overlapping" $ do+                    "abc" ^.. [regex|\w+|] . match+                    `shouldBe`["abc"]++                it "should respect lazy modifiers" $ do+                    "abc" ^.. [regex|\w+?|] . match+                    `shouldBe`["a", "b", "c"]++            describe "setting" $ do+                it "should allow setting" $ do+                    ("one two three" & [regex|two|] . match .~ "new")+                    `shouldBe` "one new three"++                it "should allow setting many" $ do+                    ("one <two> three" & [regex|\w+|] . match .~ "new")+                    `shouldBe` "new <new> new"++                it "should allow mutating" $ do+                    ("one two three" & [regex|two|] . match %~ (<> "!!"). C8.map toUpper)+                    `shouldBe` "one TWO!! three"++                it "should allow mutating many" $ do+                    ("one two three" & [regex|two|] . match %~ C8.map toUpper)+                    `shouldBe` "one TWO three"++        describe "indexed" $ do+            it "should allow folding with index" $ do+                ("one two three" ^.. ([regex|\w+|] <. match) . withIndex)+                `shouldBe` [(0, "one"), (1, "two"), (2, "three")]++            it "should allow getting with index" $ do+                ("one two three" ^.. [regex|\w+|] . index 1 . match)+                `shouldBe` ["two"]++            it "should allow setting with index" $ do+                ("one two three" & [regex|\w+|] <. match .@~ C8.pack . show)+                `shouldBe` "0 1 2"++            it "should allow mutating with index" $ do+                ("one two three" & [regex|\w+|] <. match %@~ \i s -> (C8.pack $ show i) <> ": " <> s)+                `shouldBe` "0: one 1: two 2: three"++    describe "groups" $ do+        describe "getting" $ do+            it "should get groups" $ do+                "a b c" ^.. [regex|(\w)|] . groups+                `shouldBe` [["a"], ["b"], ["c"]]++            it "should get multiple groups" $ do+                "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . groups+                `shouldBe` [["raindrops","roses"],["whiskers","kittens"]]++            it "should allow getting a specific index" $ do+                ("one two three four" ^.. [regex|(\w+) (\w+)|] . groups . ix 1)+                `shouldBe` ["two", "four"]++            xit "should handle weird group alternation" $ do+                "1:2 a=b" ^.. [regex|(\d):(\d)|(\w)=(\w)|] . groups+                `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|(\w+) (\w+)|] . groups .~ ["1", "2"])+                `shouldBe` "1 2 three"++            it "should allow editing when result list is the same length" $ do+                ("raindrops on roses and whiskers on kittens" & [regex|(\w+) on (\w+)|] . groups %~ reverse)+                `shouldBe` "roses on raindrops and kittens on whiskers"++    describe "group" $ do+        it "should get a single group" $ do+                "a:b c:d" ^.. [regex|(\w):(\w)|] . group 1+                `shouldBe` ["b", "d"]++        it "should set a single group" $ do+                "a:b c:d" & [regex|(\w):(\w)|] . group 1 %~ C8.map toUpper+                `shouldBe` "a:B c:D"++        describe "traversed" $ do+            it "should allow setting all group matches" $ do+                ("one two three" & [regex|(\w+) (\w+)|] . groups . traversed .~ "new")+                `shouldBe` "new new three"++            it "should allow mutating" $ do+                ("one two three four" & [regex|one (two) (three)|] . groups . traversed %~ (<> "!!") . C8.map toUpper)+                `shouldBe` "one TWO!! THREE!! four"++            it "should allow folding with index" $ do+                ("one two three four" ^.. [regex|(\w+) (\w+)|] . groups . traversed . withIndex)+                `shouldBe` [(0, "one"), (1, "two"), (0, "three"), (1, "four")]++            it "should allow setting with index" $ do+                ("one two three four" & [regex|(\w+) (\w+)|] . groups . traversed .@~ C8.pack . show)+                `shouldBe` "0 1 0 1"++            it "should allow mutating with index" $ do+                ("one two three four" & [regex|(\w+) (\w+)|] . groups . traversed %@~ \i s -> (C8.pack $ show i) <> ": " <> s)+                `shouldBe` "0: one 1: two 0: three 1: four"++            it "should compose indices with matches" $ do+                ("one two three four" ^.. ([regex|(\w+) (\w+)|] <.> groups . traversed) . withIndex)+                `shouldBe` [((0, 0), "one"), ((0, 1), "two"), ((1, 0), "three"), ((1, 1), "four")]++    describe "matchAndGroups" $ do+        it "should get match and groups" $ do+            "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . matchAndGroups+            `shouldBe` [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]+
test/Spec.hs view
@@ -1,141 +1,10 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-}-import Control.Lens-import Control.Lens.Regex-import qualified Data.Text as T import Test.Hspec+import Text+import ByteString  main :: IO () main = hspec $ do-    describe "regex" $ do-        describe "match" $ do-            describe "getting" $ do-                it "should find one match" $ do-                    "abc" ^.. regex [rx|b|] . match-                    `shouldBe` ["b"]--                it "should find many matches" $ do-                    "a b c" ^.. regex [rx|\w|] . match-                    `shouldBe` ["a", "b", "c"]--                it "should fold" $ do-                    "a b c" ^. regex [rx|\w|] . match-                    `shouldBe` "abc"--                it "should match with a group" $ do-                    "a b c" ^.. regex [rx|(\w)|] . match-                    `shouldBe` ["a", "b", "c"]--                it "should match with many groups" $ do-                    "a b c" ^.. regex [rx|(\w) (\w)|] . match-                    `shouldBe` ["a b"]--                it "should be greedy when overlapping" $ do-                    "abc" ^.. regex [rx|\w+|] . match-                    `shouldBe`["abc"]--                it "should respect lazy modifiers" $ do-                    "abc" ^.. regex [rx|\w+?|] . match-                    `shouldBe`["a", "b", "c"]--                it "should handle unicode in source text properly" $ do-                    "🍕 test 🍔" ^. regex [rx|test|] . match-                        `shouldBe` "test"-                    ("🍕 test 🍔" & regex [rx|🍔|] . match .~ "👻🙈")-                        `shouldBe` "🍕 test 👻🙈"--                it "should handle unicode in patterns properly" $ do-                    "*🍕 test 🍔*" ^. regex [rx|🍕 \w+ 🍔|] . match-                    `shouldBe` "🍕 test 🍔"--            describe "setting" $ do-                it "should allow setting" $ do-                    ("one two three" & regex [rx|two|] . match .~ "new")-                    `shouldBe` "one new three"--                it "should allow setting many" $ do-                    ("one <two> three" & regex [rx|\w+|] . match .~ "new")-                    `shouldBe` "new <new> new"--                it "should allow mutating" $ do-                    ("one two three" & regex [rx|two|] . match %~ (<> "!!"). T.toUpper)-                    `shouldBe` "one TWO!! three"--                it "should allow mutating many" $ do-                    ("one two three" & regex [rx|two|] . match %~ T.toUpper)-                    `shouldBe` "one TWO three"--        describe "indexed" $ do-            it "should allow folding with index" $ do-                ("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" ^.. regex [rx|\w+|] . index 1 . match)-                `shouldBe` ["two"]--            it "should allow setting with index" $ do-                ("one two three" & regex [rx|\w+|] <. match .@~ T.pack . show)-                `shouldBe` "0 1 2"--            it "should allow mutating with index" $ do-                ("one two three" & regex [rx|\w+|] <. match %@~ \i s -> (T.pack $ show i) <> ": " <> s)-                `shouldBe` "0: one 1: two 2: three"--    describe "groups" $ do-        describe "getting" $ do-            it "should get groups" $ do-                "a b c" ^.. regex [rx|(\w)|] . groups-                `shouldBe` [["a"], ["b"], ["c"]]--            it "should get multiple groups" $ do-                "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . groups-                `shouldBe` [["raindrops","roses"],["whiskers","kittens"]]--            it "should allow getting a specific index" $ do-                ("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)|] . groups-                `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"])-                `shouldBe` "1 2 three"--            it "should allow editing when result list is the same length" $ do-                ("raindrops on roses and whiskers on kittens" & regex [rx|(\w+) on (\w+)|] . groups %~ reverse)-                `shouldBe` "roses on raindrops and kittens on whiskers"--        describe "traversed" $ do-            it "should allow setting all group matches" $ do-                ("one two three" & regex [rx|(\w+) (\w+)|] . groups . traversed .~ "new")-                `shouldBe` "new new three"--            it "should allow mutating" $ do-                ("one two three four" & regex [rx|one (two) (three)|] . groups . traversed %~ (<> "!!") . T.toUpper)-                `shouldBe` "one TWO!! THREE!! four"--            it "should allow folding with index" $ do-                ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . groups . traversed . withIndex)-                `shouldBe` [(0, "one"), (1, "two"), (0, "three"), (1, "four")]--            it "should allow setting with index" $ do-                ("one two three four" & regex [rx|(\w+) (\w+)|] . groups . traversed .@~ T.pack . show)-                `shouldBe` "0 1 0 1"--            it "should allow mutating with index" $ do-                ("one two three four" & regex [rx|(\w+) (\w+)|] . groups . traversed %@~ \i s -> (T.pack $ show i) <> ": " <> s)-                `shouldBe` "0: one 1: two 0: three 1: four"--            it "should compose indices with matches" $ do-                ("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-        it "should get match and groups" $ do-            "raindrops on roses and whiskers on kittens" ^.. regex [rx|(\w+) on (\w+)|] . matchAndGroups-            `shouldBe` [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]-+    describe "text" Text.spec+    describe "bytestring" ByteString.spec
+ test/Text.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+module Text where++import Control.Lens+import Control.Lens.Regex.Text+import qualified Data.Text as T+import Test.Hspec++spec :: Spec+spec = do+    describe "regex" $ do+        describe "match" $ do+            describe "getting" $ do+                it "should find one match" $ do+                    "abc" ^.. [regex|b|] . match+                    `shouldBe` ["b"]++                it "should find many matches" $ do+                    "a b c" ^.. [regex|\w|] . match+                    `shouldBe` ["a", "b", "c"]++                it "should fold" $ do+                    "a b c" ^. [regex|\w|] . match+                    `shouldBe` "abc"++                it "should match with a group" $ do+                    "a b c" ^.. [regex|(\w)|] . match+                    `shouldBe` ["a", "b", "c"]++                it "should match with many groups" $ do+                    "a b c" ^.. [regex|(\w) (\w)|] . match+                    `shouldBe` ["a b"]++                it "should be greedy when overlapping" $ do+                    "abc" ^.. [regex|\w+|] . match+                    `shouldBe`["abc"]++                it "should respect lazy modifiers" $ do+                    "abc" ^.. [regex|\w+?|] . match+                    `shouldBe`["a", "b", "c"]++                it "should handle unicode in source text properly" $ do+                    "🍕 test 🍔" ^. [regex|test|] . match+                        `shouldBe` "test"+                    ("🍕 test 🍔" & [regex|🍔|] . match .~ "👻🙈")+                        `shouldBe` "🍕 test 👻🙈"++                it "should handle unicode in patterns properly" $ do+                    "*🍕 test 🍔*" ^. [regex|🍕 \w+ 🍔|] . match+                    `shouldBe` "🍕 test 🍔"++            describe "setting" $ do+                it "should allow setting" $ do+                    ("one two three" & [regex|two|] . match .~ "new")+                    `shouldBe` "one new three"++                it "should allow setting many" $ do+                    ("one <two> three" & [regex|\w+|] . match .~ "new")+                    `shouldBe` "new <new> new"++                it "should allow mutating" $ do+                    ("one two three" & [regex|two|] . match %~ (<> "!!"). T.toUpper)+                    `shouldBe` "one TWO!! three"++                it "should allow mutating many" $ do+                    ("one two three" & [regex|two|] . match %~ T.toUpper)+                    `shouldBe` "one TWO three"++        describe "indexed" $ do+            it "should allow folding with index" $ do+                ("one two three" ^.. ([regex|\w+|] <. match) . withIndex)+                `shouldBe` [(0, "one"), (1, "two"), (2, "three")]++            it "should allow getting with index" $ do+                ("one two three" ^.. [regex|\w+|] . index 1 . match)+                `shouldBe` ["two"]++            it "should allow setting with index" $ do+                ("one two three" & [regex|\w+|] <. match .@~ T.pack . show)+                `shouldBe` "0 1 2"++            it "should allow mutating with index" $ do+                ("one two three" & [regex|\w+|] <. match %@~ \i s -> (T.pack $ show i) <> ": " <> s)+                `shouldBe` "0: one 1: two 2: three"++    describe "groups" $ do+        describe "getting" $ do+            it "should get groups" $ do+                "a b c" ^.. [regex|(\w)|] . groups+                `shouldBe` [["a"], ["b"], ["c"]]++            it "should get multiple groups" $ do+                "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . groups+                `shouldBe` [["raindrops","roses"],["whiskers","kittens"]]++            it "should allow getting a specific index" $ do+                ("one two three four" ^.. [regex|(\w+) (\w+)|] . groups . ix 1)+                `shouldBe` ["two", "four"]++            xit "should handle weird group alternation" $ do+                "1:2 a=b" ^.. [regex|(\d):(\d)|(\w)=(\w)|] . groups+                `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|(\w+) (\w+)|] . groups .~ ["1", "2"])+                `shouldBe` "1 2 three"++            it "should allow editing when result list is the same length" $ do+                ("raindrops on roses and whiskers on kittens" & [regex|(\w+) on (\w+)|] . groups %~ reverse)+                `shouldBe` "roses on raindrops and kittens on whiskers"++    describe "group" $ do+        it "should get a single group" $ do+                "a:b c:d" ^.. [regex|(\w):(\w)|] . group 1+                `shouldBe` ["b", "d"]++        it "should set a single group" $ do+                "a:b c:d" & [regex|(\w):(\w)|] . group 1 %~ T.toUpper+                `shouldBe` "a:B c:D"++        describe "traversed" $ do+            it "should allow setting all group matches" $ do+                ("one two three" & [regex|(\w+) (\w+)|] . groups . traversed .~ "new")+                `shouldBe` "new new three"++            it "should allow mutating" $ do+                ("one two three four" & [regex|one (two) (three)|] . groups . traversed %~ (<> "!!") . T.toUpper)+                `shouldBe` "one TWO!! THREE!! four"++            it "should allow folding with index" $ do+                ("one two three four" ^.. [regex|(\w+) (\w+)|] . groups . traversed . withIndex)+                `shouldBe` [(0, "one"), (1, "two"), (0, "three"), (1, "four")]++            it "should allow setting with index" $ do+                ("one two three four" & [regex|(\w+) (\w+)|] . groups . traversed .@~ T.pack . show)+                `shouldBe` "0 1 0 1"++            it "should allow mutating with index" $ do+                ("one two three four" & [regex|(\w+) (\w+)|] . groups . traversed %@~ \i s -> (T.pack $ show i) <> ": " <> s)+                `shouldBe` "0: one 1: two 0: three 1: four"++            it "should compose indices with matches" $ do+                ("one two three four" ^.. ([regex|(\w+) (\w+)|] <.> groups . traversed) . withIndex)+                `shouldBe` [((0, 0), "one"), ((0, 1), "two"), ((1, 0), "three"), ((1, 1), "four")]++    describe "matchAndGroups" $ do+        it "should get match and groups" $ do+            "raindrops on roses and whiskers on kittens" ^.. [regex|(\w+) on (\w+)|] . matchAndGroups+            `shouldBe` [("raindrops on roses",["raindrops","roses"]),("whiskers on kittens",["whiskers","kittens"])]+