regex-do 2.3 → 2.4
raw patch · 9 files changed
+225/−98 lines, 9 files
Files
- changelog.md +30/−1
- regex-do.cabal +1/−1
- src/Text/Regex/Do/Pcre/Match.hs +0/−11
- src/Text/Regex/Do/Pcre/Option.hs +90/−10
- src/Text/Regex/Do/Pcre/Replace.hs +67/−49
- src/Text/Regex/Do/Trim.hs +3/−1
- src/Text/Regex/Do/Type/Do.hs +0/−10
- src/Text/Regex/Do/Type/Regex_.hs +19/−1
- test/TestRegex/TestReplace.hs +15/−14
changelog.md view
@@ -1,3 +1,6 @@+##### 2.4+ refactor Replace: remove ReplaceCase. Use Once | All hints instead + ##### 2.3 include missing TestReplaceOpen @@ -26,4 +29,30 @@ ##### 1.7 add MatchSame- ++##### 1.6+ rollup all match fns into one class+ add Matchf, Pad++##### 1.5+ rename Match class -> ExplicitMatch++##### 1.4+ *API changes*+ changed namespace ++##### 1.3+ fix bug in replace+ docs+ base version constraint+ + +there is no 1.2 version+ +##### 1.1+ refactor Replace + ! buggy replace++##### 1.0+ initial version + ! buggy replace
regex-do.cabal view
@@ -1,5 +1,5 @@ name: regex-do-version: 2.3+version: 2.4 synopsis: PCRE wrapper description: format, search, replace (String | ByteString) with PCRE regex. Utf8-safe author: Imants Cekusins
src/Text/Regex/Do/Pcre/Match.hs view
@@ -6,8 +6,6 @@ import qualified Text.Regex.Base.RegexLike as R hiding (makeRegex) import Text.Regex.Do.Type.Do-import Text.Regex.Do.Pcre.Option as O-import Text.Regex.Do.Type.Reexport import Text.Regex.Do.Pcre.Matchf as F import Text.Regex.PCRE.Wrap() import Text.Regex.Do.Type.Regex_@@ -72,12 +70,3 @@ instance Rx_ a b => Match a b [[PosLen]] where match = poslen_ ----- | tweak Regex with options-makeRegexOpts::Opt_ a =>- [O.Comp] -> [O.Exec] -> Pattern a -> Regex-makeRegexOpts comp0 exec0 (Pattern pat0) = rx1- where c1 = O.comp comp0- e1 = O.exec exec0- rx1 = R.makeRegexOpts c1 e1 pat0
src/Text/Regex/Do/Pcre/Option.hs view
@@ -9,20 +9,100 @@ import qualified Text.Regex.PCRE.ByteString as B +-- | <http://www.pcre.org/pcre.txt pcre man pages>+ data Comp = Blank -- ^ 'B.compBlank'- | Anchored -- ^ 'B.compAnchored'- | Caseless -- ^ 'B.compCaseless'- | Dotall -- ^ 'B.compDotAll'- | Multiline -- ^ 'B.compMultiline'- | Utf8 -- ^ 'B.compUTF8'- | Ungreedy -- ^ 'B.compUngreedy'- deriving Enum+ | Anchored {- ^ 'B.compAnchored' + the pattern is forced to be "anchored", that is, it+ is constrained to match only at the first matching point in the string+ that is being searched (the "subject string"). + This effect can also be achieved by appropriate constructs in the pattern itself. -}++ | Caseless {- ^ 'B.compCaseless'++ letters in the pattern match both upper and lower case letters.++ It is equivalent to Perl's /i option, and it can be changed within a pattern by a (?i) option setting.++ In UTF-8 mode, PCRE always understands the concept of case for characters whose values are+ less than 128, so caseless matching is always possible. For characters with higher values, the concept of case is supported if PCRE is compiled with Unicode property support.+ If you want to use caseless matching for characters 128 and above, you must ensure+ that PCRE is compiled with Unicode property support as well as with UTF-8 support. -}++ | Dotall {- ^ 'B.compDotAll'++ a dot metacharacter in the pattern matches a character of any value,+ including one that indicates a newline.++ However, it only ever matches one character, even if newlines are coded as CRLF.++ Without this option, a dot does not match when the current position is+ at a newline.++ This option is equivalent to Perl's /s option, and it can be changed within a pattern by a (?s) option setting.++ A negative class such as [^a] always matches newline characters, independent of the setting of this option. -}++ | Multiline {- ^ 'B.compMultiline'++ By default, for the purposes of matching "start of line" and "end of+ line", PCRE treats the subject string as consisting of a single line of+ characters, even if it actually contains newlines. The "start of line"+ metacharacter (^) matches only at the start of the string, and the "end+ of line" metacharacter ($) matches only at the end of the string, or+ before a terminating newline (except when PCRE_DOLLAR_ENDONLY is set).+ Note, however, that unless PCRE_DOTALL is set, the "any character"+ metacharacter (.) does not match at a newline. This behaviour (for ^,+ $, and dot) is the same as Perl.++ When PCRE_MULTILINE it is set, the "start of line" and "end of line"+ constructs match immediately following or immediately before internal+ newlines in the subject string, respectively, as well as at the very+ start and end.++ This is equivalent to Perl's /m option, and it can be+ changed within a pattern by a (?m) option setting.++ If there are no newlines in a subject string, or no occurrences of ^ or $ in a pattern,+ setting PCRE_MULTILINE has no effect. -}++ | Utf8 {- ^ 'B.compUTF8'++ This option causes PCRE to regard both the pattern and the subject as+ strings of UTF-8 characters instead of single-byte strings. However, it+ is available only when PCRE is built to include UTF support. If not,+ the use of this option provokes an error. Details of how this option+ changes the behaviour of PCRE are given in the pcreunicode page. -}++ | Ungreedy {- ^ 'B.compUngreedy'++ This option inverts the "greediness" of the quantifiers so that they+ are not greedy by default, but become greedy if followed by "?".++ It can also be set by a (?U) option setting within the pattern. -}+ deriving (Eq,Ord,Enum)++ data Exec = BlankE -- ^ 'B.execBlank'- | NotEmpty -- ^ 'B.execNotEmpty'- | Partial -- ^ 'B.execPartial'- deriving Enum+ | NotEmpty {- ^ 'B.execNotEmpty'++ An empty string is not considered to be a valid match if this option is+ set. If there are alternatives in the pattern, they are tried. If all+ the alternatives match the empty string, the entire match fails. For+ example, if the pattern++ a?b?++ is applied to a string not beginning with "a" or "b", it matches an+ empty string at the start of the subject. With PCRE_NOTEMPTY set, this+ match is not valid, so PCRE searches further into the string for occurrences of "a" or "b". -}++ | Partial {- ^ 'B.execPartial'++ see PCREPARTIAL(3) in <http://www.pcre.org/pcre.txt pcre man pages> -}+ deriving (Eq,Ord,Enum) compOpt::Comp -> B.CompOption
src/Text/Regex/Do/Pcre/Replace.hs view
@@ -1,12 +1,10 @@-module Text.Regex.Do.Pcre.Replace(- replace,- ReplaceCase_(..)) where+module Text.Regex.Do.Pcre.Replace+ (Replace(..)) where import Text.Regex.Base.RegexLike as R import Prelude as P import Data.ByteString as B import qualified Text.Regex.Do.Pcre.Option as O-import Text.Regex.Do.Pcre.Match as M import Text.Regex.Do.Type.Do import Text.Regex.Do.Type.Reexport import Text.Regex.Do.Pcre.Matchf@@ -14,8 +12,16 @@ import Text.Regex.Do.Convert import Text.Regex.Do.Type.Regex_ import Text.Regex.Do.Type.Extract+import Text.Regex.Do.Type.MatchHint ++type Ro_ rx = (Regex_ rx, Opt_ rx)++rx'::Regex_ rx => Pattern rx -> [O.Comp] -> Pattern Regex+rx' p0 opt0 = Pattern $ ropt_ p0 opt0 []++ {- | == dynamic group replace custom replacer fn returns replacement value. See 'O.defaultReplacer' @@ -28,11 +34,11 @@ 'Once' vs 'All' options - >>> replace [Once,Utf8] (Pattern "\\w=(\\d{1,3})") replacer $ Body "a=101 b=3 12"+ >>> replace (Once[]) (Pattern "\\w=(\\d{1,3})") replacer $ Body "a=101 b=3 12" "a=[сто один] b=3 12" - >>> replace [All,Utf8] (Pattern "\\w=(\\d{1,3})") replacer $ Body "a=101 b=3 12"+ >>> replace (All[]) (Pattern "\\w=(\\d{1,3})") replacer $ Body "a=101 b=3 12" "a=[сто один] b=[three] 12" @@ -41,69 +47,81 @@ for no-regex 'ByteString' replacement see "Text.Regex.Do.Split" - >>> replace [Once,Utf8] (Pattern "менее") (Replacement "более") $ Body "менее менее"+ >>> replace (Once[Utf8]) (Pattern "менее") (Replacement "более") $ Body "менее менее" "более менее" - >>> replace [Once,Utf8] (Pattern "^a\\s") (Replacement "A") $ Body "a bc хол.гор."+ >>> replace (Once[]) (Pattern "^a\\s") (Replacement "A") $ Body "a bc хол.гор." "Abc хол.гор." -} +class Replace hint rx r a where+ replace::hint [O.Comp] -> Pattern rx -> r a -> Body a -> a -replace::(Rx_ a a, ReplaceCase_ r a, Opt_ a) =>- [ReplaceCase] -> Pattern a -> r a -> Body a -> a-replace case0 p0 r0 b0 = replace_ case0 p1 r0 b0- where opt1 = comp case0- p1 = addOpt p0 opt1 +instance Regex_ rx => Replace Once rx Replacement String where+ replace opt0@(Once opt1) p0 r0 b0 =+ let ma1 = marray_ p1 b1::Maybe MatchArray+ b1 = toByteString <$> b0+ r1 = toByteString <$> r0+ isUtf1 = O.Utf8 `P.elem` opt1+ bs1 = O.replace ma1 r1 b1+ p1 = rx' p0 opt1+ in if isUtf1 then toString bs1+ else vanilla_once p1 r0 b0 --- | implementation detail for the curious-class ReplaceCase_ r a where- replace_::[ReplaceCase] -> Pattern Regex -> r a -> Body a -> a -instance ReplaceCase_ Replacement String where- replace_ case0 p0 r0 b0 =- let ma1 = marray_ p0 b1::Maybe MatchArray- ma2 = marray_ p0 b1::[MatchArray]+instance Regex_ rx => Replace All rx Replacement String where+ replace opt0@(All opt1) p0 r0 b0 =+ let ma2 = marray_ p0 b1::[MatchArray] b1 = toByteString <$> b0 r1 = toByteString <$> r0- isAll1 = All `P.elem` case0- isUtf1 = Utf8 `P.elem` case0- bs1 = if isAll1 then O.replace ma2 r1 b1- else O.replace ma1 r1 b1+ isUtf1 = O.Utf8 `P.elem` opt1+ bs1 = O.replace ma2 r1 b1+ p1 = rx' p0 opt1 in if isUtf1 then toString bs1- else vanilla_replace case0 p0 r0 b0+ else vanilla_all p1 r0 b0 -instance ReplaceCase_ Replacement ByteString where- replace_ = vanilla_replace+instance Regex_ rx => Replace Once rx Replacement ByteString where+ replace opt0@(Once opt1) p0 = vanilla_once $ rx' p0 opt1 -instance ReplaceCase_ GroupReplacer ByteString where- replace_ = vanilla_replace+instance Regex_ rx => Replace All rx Replacement ByteString where+ replace opt0@(All opt1) p0 = vanilla_all $ rx' p0 opt1 -instance ReplaceCase_ GroupReplacer String where- replace_ = vanilla_replace+instance Regex_ rx => Replace Once rx GroupReplacer ByteString where+ replace opt0@(Once opt1) p0 = vanilla_once $ rx' p0 opt1 +instance Regex_ rx => Replace All rx GroupReplacer ByteString where+ replace opt0@(All opt1) p0 = vanilla_all $ rx' p0 opt1 -vanilla_replace::(O.ReplaceOpen [] r, O.ReplaceOpen Maybe r,- Extract' a,Rx_ a a,Opt_ a) =>- [ReplaceCase] -> Pattern Regex -> r a -> Body a -> a-vanilla_replace case0 p0 r0 b0 =- let ma1 = marray_ p0 b0::Maybe MatchArray- ma2 = marray_ p0 b0:: [MatchArray]- isAll1 = All ` P.elem ` case0- in if isAll1 then O.replace ma2 r0 b0- else O.replace ma1 r0 b0+instance Regex_ rx => Replace Once rx GroupReplacer String where+ replace opt0@(Once opt1) p0 = vanilla_once $ rx' p0 opt1 +instance Regex_ rx => Replace All rx GroupReplacer String where+ replace opt0@(All opt1) p0 = vanilla_all $ rx' p0 opt1 -addOpt::Opt_ a =>- Pattern a -> [O.Comp] -> Pattern Regex-addOpt pat0 opt0 = Pattern rx1- where rx1 = M.makeRegexOpts opt0 [] pat0 +type Vanilla_ f r a = (O.ReplaceOpen f r, Extract' a, RegexLike Regex a)+type Vanilla_once r a = Vanilla_ Maybe r a+type Vanilla_all r a = Vanilla_ [] r a -comp::[ReplaceCase]-> [O.Comp]-comp = P.map mapFn . P.filter filterFn- where filterFn o1 = o1 `P.elem` [Utf8,Multiline]- mapFn Utf8 = O.Utf8- mapFn Multiline = O.Multiline++vanilla_once::Vanilla_once r a =>+ Pattern Regex ->+ r a ->+ Body a ->+ a+vanilla_once p0 r0 b0 =+ let ma1 = marray_ p0 b0::Maybe MatchArray+ in O.replace ma1 r0 b0+++vanilla_all::Vanilla_all r a =>+ Pattern Regex ->+ r a ->+ Body a ->+ a+vanilla_all p0 r0 b0 =+ let ma2 = marray_ p0 b0:: [MatchArray]+ in O.replace ma2 r0 b0
src/Text/Regex/Do/Trim.hs view
@@ -5,7 +5,9 @@ import qualified Data.ByteString as B import Text.Regex.Do.Convert import Text.Regex.Do.Pcre.Replace+import Text.Regex.Do.Type.MatchHint + {- | removes leading and trailing spaces and tabs -} class Trim a where@@ -13,7 +15,7 @@ instance Trim B.ByteString where- trim bs1 = replace [All] rx1 repl1 $ Body bs1+ trim bs1 = replace (All []) rx1 repl1 $ Body bs1 where repl1 = Replacement B.empty rx1 = rxFn "(^[\\s\\t]+)|([\\s\\t]+$)" rxFn = Pattern . toByteString
src/Text/Regex/Do/Type/Do.hs view
@@ -1,7 +1,6 @@ module Text.Regex.Do.Type.Do where import Text.Regex.Base.RegexLike as R-import Text.Regex.Do.Type.Reexport -- | see "Text.Regex.Do.Pcre.ReplaceOpen" 'defaultReplacer' for example implementation@@ -24,12 +23,3 @@ -- | Offset, Length type PosLen = (MatchOffset, MatchLength)---data ReplaceCase = Once -- ^ may be omitted- | All -- ^ if both Once and All are passed, All prevails- | Utf8- | Multiline deriving Eq---type Opt_ a = R.RegexMaker Regex CompOption ExecOption a
src/Text/Regex/Do/Type/Regex_.hs view
@@ -1,22 +1,40 @@ module Text.Regex.Do.Type.Regex_ where -import Text.Regex.Base.RegexLike as R+import qualified Text.Regex.Base.RegexLike as R import Text.Regex.Do.Type.Reexport import Data.ByteString import Text.Regex.Do.Type.Do+import Text.Regex.Do.Pcre.Option class Regex_ a where r_::Pattern a -> Regex+ ropt_::Pattern a -> [Comp] -> [Exec] -> Regex + instance Regex_ ByteString where r_ (Pattern p0) = R.makeRegex p0+ ropt_ = makeRegexOpts instance Regex_ String where r_ (Pattern p0) = R.makeRegex p0+ ropt_ = makeRegexOpts instance Regex_ Regex where r_ (Pattern p0) = p0+ ropt_ (Pattern p0) _ _ = p0 type Rx_ a b = (Regex_ a, R.Extract b, R.RegexLike Regex b)+type Opt_ a = R.RegexMaker Regex CompOption ExecOption a+++-- | tweak Regex with options+makeRegexOpts::Opt_ a =>+ Pattern a ->+ [Comp] -> [Exec] ->+ Regex+makeRegexOpts (Pattern pat0) comp0 exec0 = rx1+ where c1 = comp comp0+ e1 = exec exec0+ rx1 = R.makeRegexOpts c1 e1 pat0
test/TestRegex/TestReplace.hs view
@@ -9,7 +9,8 @@ import Text.Regex.Do.Convert import Data.ByteString as B import Debug.Trace-+import Text.Regex.Do.Type.MatchHint+import Text.Regex.Do.Pcre.Option main::IO() main = do@@ -23,19 +24,19 @@ doc = hspec $ do describe "Pcre.Replace doc" $ do it "replaceGroup 1" $ do- replace [Once,Utf8] (Pattern "\\w=(\\d{1,3})") replacer (Body "a=101 b=3 12")+ replace (Once []) (Pattern "\\w=(\\d{1,3})") replacer (Body "a=101 b=3 12") `shouldBe` "a=[сто один] b=3 12" it "replaceGroup 2" $ do- replace [All,Utf8] (Pattern "\\w=(\\d{1,3})") replacer (Body "a=101 b=3 12")+ replace (All []) (Pattern "\\w=(\\d{1,3})") replacer (Body "a=101 b=3 12") `shouldBe` "a=[сто один] b=[three] 12" it "replace 3" $ do- replace [Once,Utf8] (Pattern "менее") (Replacement "более") (Body "менее менее")+ replace (Once [Utf8]) (Pattern "менее") (Replacement "более") (Body "менее менее") `shouldBe` "более менее" it "replace 4" $ do- replace [All,Utf8] (Pattern "менее") (Replacement "боле") (Body "менее менее")+ replace (All[Utf8]) (Pattern "менее") (Replacement "боле") (Body "менее менее") `shouldBe` "боле боле" it "replace 5" $ do- replace [Once,Utf8] (Pattern "^a\\s") (Replacement "A") (Body "a bc хол.гор.")+ replace (Once[]) (Pattern "^a\\s") (Replacement "A") (Body "a bc хол.гор.") `shouldBe` "Abc хол.гор." @@ -43,24 +44,24 @@ onceUtf8 = hspec $ do describe "Pcre.Replace Once Utf8" $ do it "^a\\s" $ do- replace [Once,Utf8] (Pattern "^a\\s") (Replacement "A") (Body "a bc хол.гор.") `shouldBe` "Abc хол.гор."+ replace (Once[]) (Pattern "^a\\s") (Replacement "A") (Body "a bc хол.гор.") `shouldBe` "Abc хол.гор." it "^b\\s" $ do- replace [Once,Utf8] (Pattern "^b\\s") (Replacement "A") (Body "a bc хол.гор.") `shouldBe` "a bc хол.гор."+ replace (Once[]) (Pattern "^b\\s") (Replacement "A") (Body "a bc хол.гор.") `shouldBe` "a bc хол.гор." latinOnceAll::IO() latinOnceAll = hspec $ do describe "Pcre.Replace" $ do it "Once" $ do- runFn [Once] `shouldBe` toByteString "a=text1 b=11 12"+ runFn1 Once `shouldBe` toByteString "a=text1 b=11 12" it "All" $ do- runFn [All] `shouldBe` toByteString "a=text1 b=text1 12"- where runFn opts =+ runFn1 All `shouldBe` toByteString "a=text1 b=text1 12"+ where runFn1 hint1 = let rx1 = pattern "(?<==)(\\d{2})" body1 = Body $ toByteString haystack1 haystack1 = "a=10 b=11 12" repl1 = replacement "text1"- in replace opts rx1 repl1 body1+ in replace (hint1 []) rx1 repl1 body1 pattern::String -> Pattern ByteString@@ -75,9 +76,9 @@ groupReplace = hspec $ do describe "Pcre.Replace group" $ do it "Once" $ do- runFn1 [Once,Utf8] `shouldBe` "a=[сто один] b=3 12"+ runFn1 (Once[Utf8]) `shouldBe` "a=[сто один] b=3 12" it "All" $ do- runFn1 [All,Utf8] `shouldBe` "a=[сто один] b=[three] 12"+ runFn1 (All[Utf8]) `shouldBe` "a=[сто один] b=[three] 12" where runFn1 opts1 = let rx1 = Pattern "\\w=(\\d{1,3})" body1 = Body "a=101 b=3 12"