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 +3/−0
- LICENSE +30/−0
- README.md +174/−0
- Setup.hs +2/−0
- lens-regex-pcre.cabal +62/−0
- src/Control/Lens/Regex.hs +154/−0
- test/Spec.hs +122/−0
+ 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")]