packages feed

lens-regex-pcre 1.0.0.1 → 1.1.0.0

raw patch · 7 files changed

+290/−37 lines, 7 filesdep +containersdep +pcre-lightPVP ok

version bump matches the API change (PVP)

Dependencies added: containers, pcre-light

API changes (from Hackage documentation)

+ Control.Lens.Regex.ByteString: namedGroup :: ByteString -> IndexedTraversal' ByteString Match ByteString
+ Control.Lens.Regex.ByteString: namedGroups :: IndexedLens' ByteString Match (Map ByteString ByteString)
+ Control.Lens.Regex.Text: namedGroup :: Text -> IndexedTraversal' Text Match Text
+ Control.Lens.Regex.Text: namedGroups :: IndexedLens' Text Match (Map Text Text)
- Control.Lens.Regex.ByteString: groups :: IndexedTraversal' ByteString Match [ByteString]
+ Control.Lens.Regex.ByteString: groups :: IndexedLens' ByteString Match [ByteString]
- Control.Lens.Regex.Text: groups :: IndexedTraversal' Text Match [Text]
+ Control.Lens.Regex.Text: groups :: IndexedLens' Text Match [Text]

Files

ChangeLog.md view
@@ -1,5 +1,16 @@ # Changelog for lens-regex-pcre +# 1.0.1.0++### *BREAKING CHANGES*+This release fixes a pretty major bugs surrounding the behaviour of optional groups. It's **unlikely** but still possible that the change to grouping behaviour has changed the behaviour of your application, but most likely it just fixed some bugs you didn't know you had yet...++- Handle optional or alternated groups like `pcre-heavy`. This may change group behaviour on regular expressions which had groups with optional groups. E.g.:+    - `A(x)?(B)`+    - `(A)|(B)|(C)`+- Switch `groups` from `IndexedTraversal'` to `IndexedLens'`. Since all lenses are valid traversals this shouldn't cause any breakages.+- Add `namedGroups` and `namedGroup`+ # 1.0.0.0 - Add `regexing` and `makeRegexTraversalQQ` - Replace `regex` traversal maker with `regex` QuasiQuoter
lens-regex-pcre.cabal view
@@ -4,17 +4,17 @@ -- -- see: https://github.com/sol/hpack ----- hash: 6af08c0ccc1056deeb2c3dd469eca9c04a2fd667f31e9039181fc2acc991f398+-- hash: d1c8fcf51e5b49738911f4885c246d4f3a9e709234fad2feefcd25246e284ac2  name:           lens-regex-pcre-version:        1.0.0.1+version:        1.1.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 homepage:       https://github.com/ChrisPenner/lens-regex-pcre#readme bug-reports:    https://github.com/ChrisPenner/lens-regex-pcre/issues author:         Chris Penner-maintainer:     example@example.com+maintainer:     christopher.penner@gmail.com copyright:      2019 Chris Penner license:        BSD3 license-file:   LICENSE@@ -39,8 +39,10 @@   build-depends:       base >=4.7 && <5     , bytestring+    , containers     , lens     , pcre-heavy+    , pcre-light >=0.4.1.0     , template-haskell     , text   default-language: Haskell2010@@ -58,10 +60,12 @@   build-depends:       base >=4.7 && <5     , bytestring+    , containers     , hspec     , lens     , lens-regex-pcre     , pcre-heavy+    , pcre-light >=0.4.1.0     , template-haskell     , text   default-language: Haskell2010@@ -77,10 +81,12 @@   build-depends:       base >=4.7 && <5     , bytestring+    , containers     , gauge     , lens     , lens-regex-pcre     , pcre-heavy+    , pcre-light >=0.4.1.0     , template-haskell     , text   default-language: Haskell2010
src/Control/Lens/Regex/ByteString.hs view
@@ -14,6 +14,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TupleSections #-}  module Control.Lens.Regex.ByteString     (@@ -22,6 +23,8 @@     , match     , groups     , group+    , namedGroups+    , namedGroup     , matchAndGroups      -- * Compiling regexes to Traversals@@ -37,12 +40,15 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Builder as BS import qualified Text.Regex.PCRE.Heavy as PCRE+import qualified Text.Regex.PCRE.Light 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+import qualified Data.Map as M+import Data.Tuple (swap)  -- $setup -- >>> :set -XQuasiQuotes@@ -57,8 +63,12 @@ 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]+-- You can drill into it using 'match', 'groups', 'group', 'namedGroup', 'namedGroups' or 'matchAndGroups'+data Match =+    Match { _chunks    :: [Either BS.Builder BS.Builder]+          , _matchRegex :: PCRE.Regex+          }+makeLensesFor [("_chunks", "chunks")] ''Match  instance TypeError   ('Text "You're trying to 'show' a raw 'Match' object."@@ -66,9 +76,6 @@   => 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 @@ -78,7 +85,6 @@ -- | 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:@@ -112,10 +118,10 @@ -- -- >>> "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 :: IndexedLens' BS.ByteString Match [BS.ByteString] groups = conjoined groupsT (reindexed (view match) selfIndex <. groupsT)     where-      groupsT :: Traversal' Match [BS.ByteString]+      groupsT :: Lens' Match [BS.ByteString]       groupsT = chunks . partsOf (traversed . _Right . building)  -- | Access a specific group of a match. Numbering starts at 0.@@ -140,6 +146,87 @@ group :: Int -> IndexedTraversal' BS.ByteString Match BS.ByteString group n = groups <. ix n +-- | Access all the named groups of a match as a 'M.Map'. Stashes the full match text as the index in case+-- you need it.+--+-- Note that you can edit the groups through this lens, but the behaviour is undefined when editing inner elements of __nested__ groups.+-- Behaviour is undefined if groups are removed from the map (so don't do that).+--+-- NOTE: There's currently some strange behaviour in pcre-heavy where trailing unmatched optional groups are omitted, I'm looking into getting that patched, but for now, note the difference in behaviour:+--+-- >>> "A" ^? [regex|(?<a>A)|(?<b>B)|] . namedGroups+-- Just (fromList [("a","A")])+--+-- >>> "B" ^? [regex|(?<a>A)|(?<b>B)|] . namedGroups+-- Just (fromList [("a",""),("b","B")])+--+-- Get all matched groups:+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(?<first>\w+) on (?<second>\w+)|] . namedGroups+-- [fromList [("first","raindrops"),("second","roses")],fromList [("first","whiskers"),("second","kittens")]]+--+-- You can access a specific group combining with 'ix', or just use 'namedGroup' instead+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(?<first>\w+) on (?<second>\w+)|] . namedGroups .  ix "second"+-- ["roses","kittens"]+--+-- Editing groups:+--+-- >>> "raindrops on roses and whiskers on kittens" & [regex|(?<first>\w+) on (?<second>\w+)|] . namedGroups . ix "second" %~ Char8.map toUpper+-- "raindrops on ROSES and whiskers on KITTENS"+--+-- Use indexed helpers to access the full match when operating on a group.+--+-- This replaces the "first" group with the full match text wrapped in parens:+--+-- >>> "one-two" & [regex|(?<first>\w+)-(\w+)|] . namedGroups <. ix "first" %@~ \mtch grp -> grp <> ":(" <> mtch <> ")"+-- "one:(one-two)-two"+namedGroups :: IndexedLens' BS.ByteString Match (M.Map BS.ByteString BS.ByteString)+namedGroups = conjoined stepOne (reindexed (view match) selfIndex <. stepOne)+    where+      -- stepOne :: Traversal' Match (M.Map BS.ByteString BS.ByteString)+      stepOne :: Lens' Match (M.Map BS.ByteString BS.ByteString)+      stepOne f m = m & (groups . zipT . converterT (_matchRegex m) . partsOf (traversed . _Right) . mapL) %%~ f+      zipT :: Iso' [a]  [(Int, a)]+      zipT = iso (zip [0..]) (fmap snd)+      converterT :: PCRE.Regex -> Lens' [(Int, BS.ByteString)] [Either (Int, BS.ByteString) (BS.ByteString, BS.ByteString)]+      converterT pattern f xs =+          f (converter pattern xs) <&> itraversed %@~ \i l -> either id ((i,) . snd) l+      converter :: PCRE.Regex -> [(Int, BS.ByteString)] -> [Either (Int, BS.ByteString) (BS.ByteString, BS.ByteString)]+      converter pattern = fmap $ \(i, s) ->+          case M.lookup i (names pattern) of+              Nothing -> Left (i, s)+              Just n -> Right (n, s)+      mapL :: Lens' [(BS.ByteString, BS.ByteString)] (M.Map BS.ByteString BS.ByteString)+      mapL = lens M.fromList setter+        where+          setter :: [(BS.ByteString, BS.ByteString)] -> M.Map BS.ByteString BS.ByteString -> [(BS.ByteString, BS.ByteString)]+          setter xs m = xs <&> \(k, _) -> (k, M.findWithDefault "" k m)+      names :: PCRE.Regex -> M.Map Int BS.ByteString+      names pattern = M.fromList . fmap swap $ PCRE.captureNames pattern++-- | Access a specific named group of a match+--+-- See 'namedGroups' for caveats and more info.+--+-- Stashes the full match text as the index in case you need it.+--+-- >>> "key:value, a:b" ^.. [regex|(?<first>\w+):(?<second>\w+)|] . namedGroup "first"+-- ["key","a"]+--+-- >>> "key:value, a:b" ^.. [regex|(?<first>\w+):(?<second>\w+)|] . namedGroup "second"+-- ["value","b"]+--+-- >>> "key:value, a:b" & [regex|(?<first>\w+):(?<second>\w+)|] . namedGroup "second" %~ Char8.map toUpper+-- "key:VALUE, a:B"+--+-- Replace the first capture group with the full match:+--+-- >>> "a, b" & [regex|(?<first>\w+), (?<second>\w+)|] . namedGroup "first" .@~ \i -> "(" <> i <> ")"+-- "(a, b), b"+namedGroup :: BS.ByteString -> IndexedTraversal' BS.ByteString Match BS.ByteString+namedGroup name = namedGroups <. ix name+ -- | Traverse each match -- -- Stashes any matched groups into the index in case you need them.@@ -177,7 +264,13 @@ -- -- Also see 'mkRegexTraversalQQ' regexing :: PCRE.Regex -> IndexedTraversal' Int BS.ByteString Match-regexing pattern = conjoined (regexT pattern) (indexing (regexT pattern)) . from chunks+regexing pattern = conjoined (regexT pattern) (indexing (regexT pattern)) . asMatch+  where+    -- Unlawful iso, but since the Regex field of Match isn't exported it's fine.+    asMatch :: Iso' [Either BS.Builder BS.Builder] Match+    asMatch = iso to' from'+    to' xs = Match xs pattern+    from' (Match xs _) = xs  -- | Base regex traversal helper regexT :: PCRE.Regex -> Traversal' BS.ByteString [Either BS.Builder BS.Builder]@@ -198,9 +291,9 @@  -- | 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.+-- It's a 'TH.QuasiQuoter' which creates a Traversal out of the given regex string.+-- It's equivalent to calling 'regexing' on a 'PCRE.Regex' created using the+-- 'PCRE.re' QuasiQuoter. -- -- The "real" type is: --@@ -267,12 +360,12 @@ -- >>> "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.+-- To alter behaviour of the regex you may wish to pass 'PCRE.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';+-- mode. You can 'PCRE.compile' the 'PCRE.Regex' separately and add any options you like, then pass the resulting+-- 'PCRE.Regex' into 'regex'; -- Alternatively can make your own version of the QuasiQuoter with any options you want embedded--- by using 'mkRegexQQ'.+-- by using 'PCRE.mkRegexQQ'. regex :: TH.QuasiQuoter regex = PCRE.re{TH.quoteExp=quoter}   where@@ -299,6 +392,7 @@  groupSplit :: BS.ByteString -> Int -> GroupRanges -> [Either BS.Builder BS.Builder] groupSplit txt _ [] = [Left $ BS.byteString txt]+groupSplit txt offset ((-1, -1) : rest) = Right "" : groupSplit txt offset rest 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
src/Control/Lens/Regex/Text.hs view
@@ -19,6 +19,8 @@     , match     , groups     , group+    , namedGroups+    , namedGroup     , matchAndGroups      -- * Compiling regexes to Traversals@@ -35,6 +37,7 @@ import qualified Data.Text.Encoding.Error as T import qualified Data.ByteString as BS import qualified Text.Regex.PCRE.Heavy as PCRE+import qualified Data.Map as M import Control.Lens hiding (re, matching) import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH.Quote as TH@@ -54,15 +57,15 @@  -- | 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.+-- It's a 'TH.QuasiQuoter' which creates a Traversal out of the given regex string.+-- It's equivalent to calling 'regexing' on a 'PCRE.Regex' created using the+-- 'PCRE.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'+-- It's a traversal which selects 'RBS.Match'es; compose it with 'match' or 'groups' -- to get the relevant parts of your match. -- -- >>> txt = "raindrops on roses and whiskers on kittens"@@ -123,12 +126,12 @@ -- >>> "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.+-- To alter behaviour of the regex you may wish to pass 'PCRE.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';+-- mode. You can 'PCRE.compile' the 'PCRE.Regex' separately and add any options you like, then pass the resulting+-- 'PCRE.Regex' into 'regex'; -- Alternatively can make your own version of the QuasiQuoter with any options you want embedded--- by using 'mkRegexQQ'.+-- by using 'PCRE.mkRegexQQ'. -- regex :: Regex -> IndexedTraversal' Int T.Text RBS.Match regex :: TH.QuasiQuoter regex = PCRE.re{TH.quoteExp=quoter}@@ -157,7 +160,7 @@ -- | 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,+-- Note that you can edit the groups through this lens, -- Changing the length of the list has behaviour similar to 'partsOf'. -- -- Get all matched groups:@@ -189,7 +192,7 @@ -- -- >>> "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 :: IndexedLens' 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.@@ -216,6 +219,69 @@ -- "(a, b), b" group :: Int -> IndexedTraversal' T.Text RBS.Match T.Text group n = groups <. ix n++-- | Access all the named groups of a match as a 'M.Map'. Stashes the full match text as the index in case+-- you need it.+--+-- Note that you can edit the groups through this lens, but the behaviour is undefined when editing inner elements of __nested__ groups.+-- Behaviour is undefined if groups are removed from the map (so don't do that).+--+-- NOTE: There's currently some strange behaviour in pcre-heavy where trailing unmatched optional groups are omitted, I'm looking into getting that patched, but for now, note the difference in behaviour:+--+-- >>> "A" ^? [regex|(?<a>A)|(?<b>B)|] . namedGroups+-- Just (fromList [("a","A")])+--+-- >>> "B" ^? [regex|(?<a>A)|(?<b>B)|] . namedGroups+-- Just (fromList [("a",""),("b","B")])+--+-- Get all matched groups:+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(?<first>\w+) on (?<second>\w+)|] . namedGroups+-- [fromList [("first","raindrops"),("second","roses")],fromList [("first","whiskers"),("second","kittens")]]+--+-- You can access a specific group combining with 'ix', or just use 'namedGroup' instead+--+-- >>> "raindrops on roses and whiskers on kittens" ^.. [regex|(?<first>\w+) on (?<second>\w+)|] . namedGroups .  ix "second"+-- ["roses","kittens"]+--+-- Editing groups:+--+-- >>> "raindrops on roses and whiskers on kittens" & [regex|(?<first>\w+) on (?<second>\w+)|] . namedGroups . ix "second" %~ T.toUpper+-- "raindrops on ROSES and whiskers on KITTENS"+--+-- Use indexed helpers to access the full match when operating on a group.+--+-- This replaces the "first" group with the full match text wrapped in parens:+--+-- >>> "one-two" & [regex|(?<first>\w+)-(\w+)|] . namedGroups <. ix "first" %@~ \mtch grp -> grp <> ":(" <> mtch <> ")"+-- "one:(one-two)-two"+namedGroups :: IndexedLens' T.Text RBS.Match (M.Map T.Text T.Text)+namedGroups = reindexed (view $ from utf8) RBS.namedGroups <. mapAsTxt+  where+    mapAsTxt :: Iso' (M.Map BS.ByteString BS.ByteString) (M.Map T.Text T.Text)+    mapAsTxt = iso (M.mapKeys (review utf8)) (M.mapKeys (view utf8)) . mapping (from utf8)++-- | Access a specific named group of a match+--+-- See 'namedGroups' for caveats and more info.+--+-- Stashes the full match text as the index in case you need it.+--+-- >>> "key:value, a:b" ^.. [regex|(?<first>\w+):(?<second>\w+)|] . namedGroup "first"+-- ["key","a"]+--+-- >>> "key:value, a:b" ^.. [regex|(?<first>\w+):(?<second>\w+)|] . namedGroup "second"+-- ["value","b"]+--+-- >>> "key:value, a:b" & [regex|(?<first>\w+):(?<second>\w+)|] . namedGroup "second" %~ T.toUpper+-- "key:VALUE, a:B"+--+-- Replace the first capture group with the full match:+--+-- >>> "a, b" & [regex|(?<first>\w+), (?<second>\w+)|] . namedGroup "first" .@~ \i -> "(" <> i <> ")"+-- "(a, b), b"+namedGroup :: T.Text -> IndexedTraversal' T.Text RBS.Match T.Text+namedGroup name = namedGroups <. ix name  -- | Traverse each match --
test/ByteString.hs view
@@ -6,11 +6,16 @@ import Control.Lens.Regex.ByteString import qualified Data.ByteString.Char8 as C8 hiding (index) import Data.Char+import qualified Data.Map as M import Test.Hspec  spec :: Spec spec = do     describe "regex" $ do+        xdescribe "pcre-heavy-compat" $ do+            it "should handle crazy nested groups" $ do+                "abcdefhijklm" ^? [regex|^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$|] . matchAndGroups+                    `shouldBe` Just ("abcdefhijklm", ["bc", "c", "ef", "f", "ij", "j", "lm", "m"])         describe "match" $ do             describe "getting" $ do                 it "should find one match" $ do@@ -89,9 +94,11 @@                 ("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" ]]+            it "should handle weird group alternation" $ do+                ("AB" ^.. [regex|A(x)?(B)|] . groups `shouldBe` [["", "B"]])+                ("B" ^.. [regex|(A)|(B)|] . groups `shouldBe` [["", "B"]])+                -- This behaviour is consistent with pcre-heavy+                ("A" ^.. [regex|(A)|(B)|] . groups `shouldBe` [["A"]])          describe "setting" $ do             it "should allow setting groups as a list" $ do@@ -135,6 +142,40 @@             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 "namedGroups" $ do+        describe "getting" $ do+            it "should get named groups" $ do+                "a b c" ^.. [regex|(?<mygroup>\w)|] . namedGroups+                `shouldBe` [M.fromList [("mygroup", "a")], M.fromList [("mygroup", "b")], M.fromList [("mygroup", "c")]]++            it "should get multiple named groups" $ do+                "raindrops on roses and whiskers on kittens" ^.. [regex|(?<one>\w+) on (?<two>\w+)|] . namedGroups+                `shouldBe` [M.fromList [("one", "raindrops"), ("two", "roses")], M.fromList [("one", "whiskers"), ("two", "kittens")]]++            it "should allow getting a specific named group" $ do+                ("raindrops on roses and whiskers on kittens" ^.. [regex|(?<one>\w+) on (?<two>\w+)|] . namedGroups . ix "two")+                `shouldBe` ["roses", "kittens"]++            it "should handle weird group alternation" $ do+                ("AB" ^.. [regex|A(?<opt>x)?(?<always>B)|] . namedGroups `shouldBe` [M.fromList [("opt", ""), ("always", "B")]])+                ("B" ^.. [regex|(?<a>A)|(?<b>B)|] . namedGroups `shouldBe` [M.fromList [("a", ""), ("b", "B")]])+                -- This is the behaviour of pcre-heavy, it's a bit unfortunate+                ("A" ^.. [regex|(?<a>A)|(?<b>B)|] . namedGroups `shouldBe` [M.fromList [("a", "A")]])++        describe "setting" $ do+            it "should allow setting groups as a map" $ do+                ("one two three" & [regex|(?<a>\w+) (?<b>\w+)|] . namedGroups .~ M.fromList [("a", "1"), ("b", "2")])+                `shouldBe` "1 2 three"++    describe "namedGroup" $ do+        it "should get a single named group" $ do+                "a:b c:d" ^.. [regex|(?<before>\w):(?<after>\w)|] . namedGroup "after"+                `shouldBe` ["b", "d"]++        it "should set a single group" $ do+                "a:b c:d" & [regex|(\w):(?<after>\w)|] . namedGroup "after" %~ C8.map toUpper+                `shouldBe` "a:B c:D"      describe "matchAndGroups" $ do         it "should get match and groups" $ do
test/Spec.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE OverloadedStrings #-} import Test.Hspec import Text import ByteString
test/Text.hs view
@@ -5,6 +5,7 @@ import Control.Lens import Control.Lens.Regex.Text import qualified Data.Text as T+import qualified Data.Map as M import Test.Hspec  spec :: Spec@@ -98,9 +99,11 @@                 ("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" ]]+            it "should handle weird group alternation" $ do+                ("AB" ^.. [regex|A(x)?(B)|] . groups `shouldBe` [["", "B"]])+                ("B" ^.. [regex|(A)|(B)|] . groups `shouldBe` [["", "B"]])+                -- This behaviour is consistent with pcre-heavy+                ("A" ^.. [regex|(A)|(B)|] . groups `shouldBe` [["A"]])          describe "setting" $ do             it "should allow setting groups as a list" $ do@@ -144,6 +147,40 @@             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 "namedGroups" $ do+        describe "getting" $ do+            it "should get named groups" $ do+                "a b c" ^.. [regex|(?<mygroup>\w)|] . namedGroups+                `shouldBe` [M.fromList [("mygroup", "a")], M.fromList [("mygroup", "b")], M.fromList [("mygroup", "c")]]++            it "should get multiple named groups" $ do+                "raindrops on roses and whiskers on kittens" ^.. [regex|(?<one>\w+) on (?<two>\w+)|] . namedGroups+                `shouldBe` [M.fromList [("one", "raindrops"), ("two", "roses")], M.fromList [("one", "whiskers"), ("two", "kittens")]]++            it "should allow getting a specific named group" $ do+                ("raindrops on roses and whiskers on kittens" ^.. [regex|(?<one>\w+) on (?<two>\w+)|] . namedGroups . ix "two")+                `shouldBe` ["roses", "kittens"]++            it "should handle weird group alternation" $ do+                ("AB" ^.. [regex|A(?<opt>x)?(?<always>B)|] . namedGroups `shouldBe` [M.fromList [("opt", ""), ("always", "B")]])+                ("B" ^.. [regex|(?<a>A)|(?<b>B)|] . namedGroups `shouldBe` [M.fromList [("a", ""), ("b", "B")]])+                -- This is the behaviour of pcre-heavy, it's a bit unfortunate+                ("A" ^.. [regex|(?<a>A)|(?<b>B)|] . namedGroups `shouldBe` [M.fromList [("a", "A")]])++        describe "setting" $ do+            it "should allow setting groups as a map" $ do+                ("one two three" & [regex|(?<a>\w+) (?<b>\w+)|] . namedGroups .~ M.fromList [("a", "1"), ("b", "2")])+                `shouldBe` "1 2 three"++    describe "namedGroup" $ do+        it "should get a single named group" $ do+                "a:b c:d" ^.. [regex|(?<before>\w):(?<after>\w)|] . namedGroup "after"+                `shouldBe` ["b", "d"]++        it "should set a single group" $ do+                "a:b c:d" & [regex|(\w):(?<after>\w)|] . namedGroup "after" %~ T.toUpper+                `shouldBe` "a:B c:D"      describe "matchAndGroups" $ do         it "should get match and groups" $ do