packages feed

lens-regex-pcre (empty) → 0.1.0.0

raw patch · 7 files changed

+547/−0 lines, 7 filesdep +basedep +hspecdep +lenssetup-changed

Dependencies added: base, hspec, lens, lens-regex-pcre, pcre-heavy, pcre-light, template-haskell, text

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for lens-regex-pcre++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2019++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,174 @@+# lens-regex-pcre++* NOTE: I don't promise that this is __fast__ yet;+* NOTE: currently only supports `Text` but should be generalizable to more string-likes; open an issue if you need it++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.++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.++Here are a few examples:++```haskell+-- Getting all matches:+> "one _two_ three _four_" ^.. regex [rx|_\w+_|] . match+["_two_","_four_"]++-- Regex replace/mutation+> "one _two_ three _four_" & regex [rx|_\w+_|] . match %~ T.toUpper+"one _TWO_ three _FOUR_"++-- Getting groups with their index.+> "1/2 and 3/4" ^.. regex [rx|(\d+)/(\d+)|] . igroups . withIndex+[(0,"1"),(1,"2"),(0,"3"),(1,"4")]++-- Check for any matches:+> has (regex [rx|ne+dle|]) "a needle in a haystack"+True++-- Check for matches which also match a predicate:+> has (regex [rx|\w+|] . match . filtered ((> 7) . T.length)) "one word here is loooooooong"+True++-- Get the third match+>  "alpha beta charlie delta" ^? (iregex [rx|\w+|] . index 2 . match)+Just "charlie"++-- Replace the third match+> "alpha beta charlie delta" & (iregex [rx|\w+|] . index 2 . match) .~ "GAMMA"+"alpha beta GAMMA delta"++-- Sort all matches alphabetically in place+> "*charlie* beta = _alpha_ delta" & partsOf (iregex [rx|[a-z]+|] . match) %~ sort+"*alpha* beta = _charlie_ delta"++-- Match integers, 'Read' them into ints, then sort each match in-place+> "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf' (iregex [rx|\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+import Control.Lens+import Control.Lens.Regex++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"]++        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 "groups" $ do+        describe "getting" $ do+            it "should get a group" $ do+                "a b c" ^.. regex [rx|(\w)|] . groups+                `shouldBe` ["a", "b", "c"]++            it "should get many groups" $ do+                "one two three" ^.. regex [rx|(\w+) (\w+)|] . groups+                `shouldBe` ["one", "two"]++        describe "setting" $ do+            it "should allow setting" $ do+                ("one two three" & regex [rx|(\w+) (\w+)|] . groups .~ "new")+                `shouldBe` "new new three"++            it "should allow setting many" $ do+                ("one two three four" & regex [rx|(\w+) (\w+)|] . groups .~ "new")+                `shouldBe` "new new new new"++            it "should allow mutating" $ do+                ("one two three four" & regex [rx|one (two) three|] . groups %~ (<> "!!") . T.toUpper)+                `shouldBe` "one TWO!! three four"++            it "should allow mutating" $ do+                ("one two three four" & regex [rx|one (two) (three)|] . groups %~ (<> "!!") . T.toUpper)+                `shouldBe` "one TWO!! THREE!! four"++describe "iregex" $ do+    describe "match" $ do+        it "should allow folding with index" $ do+            ("one two three" ^.. (iregex [rx|\w+|] <. match) . withIndex)+            `shouldBe` [(0, "one"), (1, "two"), (2, "three")]++        it "should allow getting with index" $ do+            ("one two three" ^.. iregex [rx|\w+|] . index 1 . match)+            `shouldBe` ["two"]++        it "should allow setting with index" $ do+            ("one two three" & iregex [rx|\w+|] <. match .@~ pack . show)+            `shouldBe` "0 1 2"++        it "should allow mutating with index" $ do+            ("one two three" & iregex [rx|\w+|] <. match %@~ \i s -> (pack $ show i) <> ": " <> s)+            `shouldBe` "0: one 1: two 2: three"++describe "igroups" $ do+    it "should allow folding with index" $ do+        ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . igroups . withIndex)+        `shouldBe` [(0, "one"), (1, "two"), (0, "three"), (1, "four")]++    it "should allow getting a specific index" $ do+        ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . igroups . index 1)+        `shouldBe` ["two", "four"]++    it "should allow setting with index" $ do+        ("one two three four" & regex [rx|(\w+) (\w+)|] . igroups .@~ pack . show)+        `shouldBe` "0 1 0 1"++    it "should allow mutating with index" $ do+        ("one two three four" & regex [rx|(\w+) (\w+)|] . igroups %@~ \i s -> (pack $ show i) <> ": " <> s)+        `shouldBe` "0: one 1: two 0: three 1: four"++    it "should compose indices with matches" $ do+        ("one two three four" ^.. (iregex [rx|(\w+) (\w+)|] <.> igroups) . withIndex)+        `shouldBe` [((0, 0), "one"), ((0, 1), "two"), ((1, 0), "three"), ((1, 1), "four")]+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lens-regex-pcre.cabal view
@@ -0,0 +1,62 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.1.+--+-- see: https://github.com/sol/hpack+--+-- hash: 557358c3de76ad43d14cac12d85009470a62f4bbfd0352c727d2d4e89bf80458++name:           lens-regex-pcre+version:        0.1.0.0+description:    Please see the README on GitHub at <https://github.com/ChrisPenner/lens-regex-pcre#readme>+homepage:       https://github.com/ChrisPenner/lens-regex-pcre#readme+bug-reports:    https://github.com/ChrisPenner/lens-regex-pcre/issues+author:         Chris Penner+maintainer:     example@example.com+copyright:      2019 Chris Penner+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/ChrisPenner/lens-regex-pcre++library+  exposed-modules:+      Control.Lens.Regex+  other-modules:+      Paths_lens_regex_pcre+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      base >=4.7 && <5+    , lens+    , pcre-heavy+    , pcre-light+    , template-haskell+    , text+  default-language: Haskell2010++test-suite lens-regex-pcre-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_lens_regex_pcre+  hs-source-dirs:+      test+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , hspec+    , lens+    , lens-regex-pcre+    , pcre-heavy+    , pcre-light+    , template-haskell+    , text+  default-language: Haskell2010
+ src/Control/Lens/Regex.hs view
@@ -0,0 +1,154 @@+{-|+Module      : Control.Lens.Regex+Description : PCRE regex combinators for interop with lens+Copyright   : (c) Chris Penner, 2019+License     : BSD3++Note that all traversals in this library are not techically lawful; the break the 'multi-set'+idempotence law; in reality this isn't usually a problem; but consider yourself warned. Test your code.+-}++{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}++module Control.Lens.Regex+    ( regex+    , iregex+    , match+    , groups+    , igroups++    -- * QuasiQuoter+    , rx+    , Match+    ) where++import Data.Text as T hiding (index)+import Text.Regex.PCRE.Heavy+import Control.Lens hiding (re, matching)+import Language.Haskell.TH.Quote++-- | Match represents a whole regex match; you can drill into it using 'match' or 'groups'+type Match = [Either Text Text]+type MatchRange = (Int, Int)+type GroupRanges = [(Int, Int)]++-- | 'QuasiQuoter' for compiling regexes.+-- This is just 're' re-exported under a different name so as not to conflict with @re@ from+-- 'Control.Lens'+rx :: QuasiQuoter+rx = re++-- | 'groups' but indexed by the group number. If you traverse over many matches you will+-- encounter duplicate indices.+-- E.g.+--+-- > > "a 1 b 2" ^.. regex [rx|(\w) (\d)|] . igroups . withIndex+-- > [(0,"a"),(1,"1"),(0,"b"),(1,"2")]+--+-- If you want only a specific group; combine this with `index`+-- E.g.+--+-- > > "a 1 b 2" ^.. regex [rx|(\w) (\d)|] . igroups . index 0+-- > ["a","b"]+igroups :: IndexedTraversal' Int Match T.Text+igroups = indexing groups++-- | traverse each group within a match. See 'igroups' for selecting specific groups.+groups :: Traversal' Match T.Text+groups = traversed . _Right++-- | Traverse each match as a whole+--+-- Use with 'regex' or 'iregex'+--+-- > > "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 :: Traversal' Match T.Text+match f grps = (:[]) . Right <$> f (grps ^. traversed . chosen)++-- | Indexed version of 'regex'.+iregex :: Regex -> IndexedTraversal' Int T.Text Match+iregex pattern = indexing (regex pattern)++-- | The base combinator for doing regex searches.+-- It's a traversal which selects 'Match'es; you can compose it with 'match' or 'groups'+-- to get the relevant parts of your match.+--+-- Getting all matches:+-- > > "one _two_ three _four_" ^.. regex [rx|_\w+_|] . match+-- > ["_two_","_four_"]+--+-- Regex replace/mutation+-- > > "one _two_ three _four_" & regex [rx|_\w+_|] . match %~ T.toUpper+-- > "one _TWO_ three _FOUR_"+--+-- Getting groups with their group index.+-- > > "1/2 and 3/4" ^.. regex [rx|(\d+)/(\d+)|] . igroups . withIndex+-- > [(0,"1"),(1,"2"),(0,"3"),(1,"4")]+--+-- Check for any matches:+-- > > has (regex [rx|ne+dle|]) "a needle in a haystack"+-- > True+--+-- Check for matches which also match a predicate:+-- > > has (regex [rx|\w+|] . match . filtered ((> 7) . T.length)) "one word here is loooooooong"+-- > True+--+-- Get the third match+-- > >  "alpha beta charlie delta" ^? (iregex [rx|\w+|] . index 2 . match)+-- > Just "charlie"+--+-- Replace the third match+-- > > "alpha beta charlie delta" & (iregex [rx|\w+|] . index 2 . match) .~ "GAMMA"+-- > "alpha beta GAMMA delta"+--+-- Match integers, 'Read' them into ints, then sort each match in-place+-- > > "Monday: 29, Tuesday: 99, Wednesday: 3" & partsOf' (iregex [rx|\d+|] . match . unpacked . _Show @Int) %~ sort+-- > "Monday: 3, Tuesday: 29, Wednesday: 99"+regex :: Regex -> Traversal' T.Text Match+regex pattern f txt =  collapse <$> apply (fmap splitAgain <$> splitter txt matches)+  where+    matches :: [(MatchRange, GroupRanges)]+    matches = scanRanges pattern txt+    collapse :: [Either Text [Either Text Text]] -> Text+    collapse xs = xs ^. folded . beside id (traversed . chosen)+    -- apply :: [Either Text [Either Text Text]] -> _ [Either Text [Either Text Text]]+    apply xs = xs & traversed . _Right %%~ f++splitter :: Text -> [(MatchRange, GroupRanges)] -> [Either T.Text (T.Text, GroupRanges)]+splitter t [] | T.null t = []+              | otherwise = [Left t]+splitter t (((start, end), grps) : rest) = do+    splitOnce t ((start, end), grps)+        <> splitter (T.drop end t) (rest & traversed . beside both (traversed . both) -~ end)++splitAgain :: (T.Text, GroupRanges) -> Match+splitAgain (t, []) | T.null t = []+                   | otherwise = [Left t]+splitAgain (t, (start, end) : rest) = do+    let (before, mid) = T.splitAt start t+    let focused = T.take (end - start) mid+    wrapIfNotEmpty before+        <> [Right focused]+        <> splitAgain ((T.drop end t), (rest & traversed . both -~ end))++splitOnce :: Text -> (MatchRange, GroupRanges) -> [Either T.Text (T.Text, GroupRanges)]+splitOnce t ((start, end), grps) = do+    let (before, mid) = T.splitAt start t+    let focused = T.take (end - start) mid+    wrapIfNotEmpty before+      <> [Right (focused, grps & traversed . both -~ start)]++wrapIfNotEmpty :: Text -> [Either Text a]+wrapIfNotEmpty txt+    | T.null txt = []+    | otherwise = [Left txt]
+ test/Spec.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+import Control.Lens+import Control.Lens.Regex+import Data.Text as T hiding (index)+import Test.Hspec++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"]++            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 "groups" $ do+            describe "getting" $ do+                it "should get a group" $ do+                    "a b c" ^.. regex [rx|(\w)|] . groups+                    `shouldBe` ["a", "b", "c"]++                it "should get many groups" $ do+                    "one two three" ^.. regex [rx|(\w+) (\w+)|] . groups+                    `shouldBe` ["one", "two"]++            describe "setting" $ do+                it "should allow setting" $ do+                    ("one two three" & regex [rx|(\w+) (\w+)|] . groups .~ "new")+                    `shouldBe` "new new three"++                it "should allow setting many" $ do+                    ("one two three four" & regex [rx|(\w+) (\w+)|] . groups .~ "new")+                    `shouldBe` "new new new new"++                it "should allow mutating" $ do+                    ("one two three four" & regex [rx|one (two) three|] . groups %~ (<> "!!") . T.toUpper)+                    `shouldBe` "one TWO!! three four"++                it "should allow mutating" $ do+                    ("one two three four" & regex [rx|one (two) (three)|] . groups %~ (<> "!!") . T.toUpper)+                    `shouldBe` "one TWO!! THREE!! four"++    describe "iregex" $ do+        describe "match" $ do+            it "should allow folding with index" $ do+                ("one two three" ^.. (iregex [rx|\w+|] <. match) . withIndex)+                `shouldBe` [(0, "one"), (1, "two"), (2, "three")]++            it "should allow getting with index" $ do+                ("one two three" ^.. iregex [rx|\w+|] . index 1 . match)+                `shouldBe` ["two"]++            it "should allow setting with index" $ do+                ("one two three" & iregex [rx|\w+|] <. match .@~ pack . show)+                `shouldBe` "0 1 2"++            it "should allow mutating with index" $ do+                ("one two three" & iregex [rx|\w+|] <. match %@~ \i s -> (pack $ show i) <> ": " <> s)+                `shouldBe` "0: one 1: two 2: three"++    describe "igroups" $ do+        it "should allow folding with index" $ do+            ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . igroups . withIndex)+            `shouldBe` [(0, "one"), (1, "two"), (0, "three"), (1, "four")]++        it "should allow getting a specific index" $ do+            ("one two three four" ^.. regex [rx|(\w+) (\w+)|] . igroups . index 1)+            `shouldBe` ["two", "four"]++        it "should allow setting with index" $ do+            ("one two three four" & regex [rx|(\w+) (\w+)|] . igroups .@~ pack . show)+            `shouldBe` "0 1 0 1"++        it "should allow mutating with index" $ do+            ("one two three four" & regex [rx|(\w+) (\w+)|] . igroups %@~ \i s -> (pack $ show i) <> ": " <> s)+            `shouldBe` "0: one 1: two 0: three 1: four"++        it "should compose indices with matches" $ do+            ("one two three four" ^.. (iregex [rx|(\w+) (\w+)|] <.> igroups) . withIndex)+            `shouldBe` [((0, 0), "one"), ((0, 1), "two"), ((1, 0), "three"), ((1, 1), "four")]