diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for lens-regex-pcre
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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")]
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/lens-regex-pcre.cabal b/lens-regex-pcre.cabal
new file mode 100644
--- /dev/null
+++ b/lens-regex-pcre.cabal
@@ -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
diff --git a/src/Control/Lens/Regex.hs b/src/Control/Lens/Regex.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Lens/Regex.hs
@@ -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]
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -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")]
