packages feed

matchers 0.4.0.0 → 0.6.0.0

raw patch · 7 files changed

+363/−163 lines, 7 filesdep +parsecdep +timedep −utf8-stringdep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: parsec, time

Dependencies removed: utf8-string

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

- Text.Matchers.CaseSensitive: Insensitive :: CaseSensitive
- Text.Matchers.CaseSensitive: Sensitive :: CaseSensitive
- Text.Matchers.CaseSensitive: data CaseSensitive
- Text.Matchers.CaseSensitive: instance Eq CaseSensitive
- Text.Matchers.CaseSensitive: instance Ord CaseSensitive
- Text.Matchers.CaseSensitive: instance Show CaseSensitive
- Text.Matchers.Regex.PCRE: pcre :: CaseSensitive -> ByteString -> Exceptional String (ByteString -> Bool)
- Text.Matchers.Regex.TDFA: instance Eq a => Eq (StrErr a)
- Text.Matchers.Regex.TDFA: instance Monad StrErr
- Text.Matchers.Regex.TDFA: instance Show a => Show (StrErr a)
- Text.Matchers.Regex.TDFA: tdfa :: CaseSensitive -> String -> Exceptional String (String -> Bool)
- Text.Matchers.String: Insensitive :: CaseSensitive
- Text.Matchers.String: Sensitive :: CaseSensitive
- Text.Matchers.String: data CaseSensitive
- Text.Matchers.String: exact :: CaseSensitive -> String -> String -> Bool
- Text.Matchers.String: pcre :: CaseSensitive -> String -> Exceptional String (String -> Bool)
- Text.Matchers.String: tdfa :: CaseSensitive -> String -> Exceptional String (String -> Bool)
- Text.Matchers.String: within :: CaseSensitive -> String -> String -> Bool
- Text.Matchers.Text: Insensitive :: CaseSensitive
- Text.Matchers.Text: Sensitive :: CaseSensitive
- Text.Matchers.Text: data CaseSensitive
- Text.Matchers.Text: exact :: CaseSensitive -> Text -> Text -> Bool
- Text.Matchers.Text: pcre :: CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Text.Matchers.Text: tdfa :: CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Text.Matchers.Text: within :: CaseSensitive -> Text -> Text -> Bool
+ Text.Matchers: Insensitive :: CaseSensitive
+ Text.Matchers: Matcher :: Text -> Text -> (Text -> Bool) -> Matcher
+ Text.Matchers: Sensitive :: CaseSensitive
+ Text.Matchers: UAfter :: CompUTC
+ Text.Matchers: UBefore :: CompUTC
+ Text.Matchers: UExactly :: CompUTC
+ Text.Matchers: UOnOrAfter :: CompUTC
+ Text.Matchers: UOnOrBefore :: CompUTC
+ Text.Matchers: compUTCtoCmp :: Ord a => CompUTC -> a -> a -> Bool
+ Text.Matchers: data CaseSensitive
+ Text.Matchers: data CompUTC
+ Text.Matchers: data Matcher
+ Text.Matchers: date :: Maybe (CompUTC, UTCTime) -> Matcher
+ Text.Matchers: descUTC :: CompUTC -> UTCTime -> String
+ Text.Matchers: exact :: CaseSensitive -> Text -> Matcher
+ Text.Matchers: instance Eq CaseSensitive
+ Text.Matchers: instance Eq CompUTC
+ Text.Matchers: instance Eq a => Eq (StrErr a)
+ Text.Matchers: instance Monad StrErr
+ Text.Matchers: instance Ord CaseSensitive
+ Text.Matchers: instance Ord CompUTC
+ Text.Matchers: instance Show CaseSensitive
+ Text.Matchers: instance Show CompUTC
+ Text.Matchers: instance Show a => Show (StrErr a)
+ Text.Matchers: match :: Matcher -> Text -> Bool
+ Text.Matchers: matchDesc :: Matcher -> Text
+ Text.Matchers: pcre :: CaseSensitive -> Text -> Exceptional Text Matcher
+ Text.Matchers: shortDesc :: Matcher -> Text
+ Text.Matchers: tdfa :: CaseSensitive -> Text -> Exceptional Text Matcher
+ Text.Matchers: within :: CaseSensitive -> Text -> Matcher

Files

Text/Matchers.hs view
@@ -1,3 +1,352 @@-module Text.Matchers (module Text.Matchers.String) where+module Text.Matchers+  ( Matcher(..)+  , CaseSensitive(..)+  , tdfa+  , pcre+  , within+  , exact+  , CompUTC(..)+  , descUTC+  , compUTCtoCmp+  , date+  ) where -import Text.Matchers.String+import Control.Applicative ((<$>), (<*>), (<*), (<$), optional, (<|>))+import Control.Monad (replicateM, mzero)+import Control.Monad.Exception.Synchronous+  ( Exceptional (Exception, Success))+import qualified Data.ByteString as BS+import Data.Fixed (Pico)+import Data.Maybe (fromMaybe)+import Data.Text (Text, pack, unpack, toCaseFold, isInfixOf)+import Data.Text.Encoding (encodeUtf8)+import qualified Text.Regex.TDFA as TDFA+import qualified Text.Regex.Base.RegexLike as RL+import qualified Text.Regex.PCRE.Light as PCRE+import Text.Parsec (many, satisfy)+import qualified Text.Parsec as P+import Text.Parsec.Text (Parser)+import qualified Data.Time as Time++data CaseSensitive = Sensitive | Insensitive deriving (Eq, Ord, Show)++data Matcher = Matcher+  { shortDesc :: Text+    -- ^ Short description of this matcher, e.g. @PCRE@ or @Exact@.++  , matchDesc :: Text+    -- ^ Description of a successful match, e.g.+    -- @Matches the PCRE pattern abc@, or+    -- @a valid date with optional time@.++  , match :: Text -> Bool+    -- ^ Function to carry out the match+    }++-- | Uses the regular expression matcher from the regex-tdfa+-- package. This is a POSIX extended regular expression. It should+-- work correctly with Unicode.+tdfa+  :: CaseSensitive++  -> Text+  -- ^ The pattern++  -> Exceptional Text Matcher+  -- ^ The Matcher if the pattern is good; if the pattern is bad,+  -- returns an error message. The error message has a trailing+  -- newline.++tdfa c t = case tdfaPrim c (unpack t) of+  Exception e -> Exception $ pack e+  Success f ->+    let sDesc = pack "POSIX-like regular expression (TDFA)"+        mrDesc = pack $ "matches the POSIX regular expression \""+          ++ unpack t ++ "\"" ++ descSensitive c+        mr = f . unpack+    in return $ Matcher sDesc mrDesc mr++descSensitive :: CaseSensitive -> String+descSensitive c = case c of+  Sensitive -> " (case sensitive)"+  Insensitive -> " (case insensitive)"++-- | Uses the PCRE regular expression engine. Currently the pcre-light+-- package is used, as it has a simpler interface than the+-- regex-pcre-builtin. It should work correctly with Unicode.+pcre+  :: CaseSensitive++  -> Text+  -- ^ Pattern++  -> Exceptional Text Matcher+  -- ^ The Matcher if the pattern is good; if the pattern is bad,+  -- returns an error message.++pcre c t = case pcrePrim c (encodeUtf8 t) of+  Exception e -> Exception $ pack e+  Success f ->+    let sDesc = pack "Perl-compatible regular expression"+        mrDesc = pack $ "matches the PCRE pattern \""+          ++ unpack t ++ "\"" ++ descSensitive c+        mr = f . encodeUtf8+    in return $ Matcher sDesc mrDesc mr++-- | Matcher that succeeds if the pattern text is found anywhere+-- within the subject.+within+  :: CaseSensitive++  -> Text+  -- ^ The pattern++  -> Matcher+within cs t = Matcher sDesc mrDesc mr+  where+    sDesc = pack "within"+    mrDesc = pack $ "contains the text \"" ++ unpack t+             ++ "\"" ++ descSensitive cs+    mr = txtMatch isInfixOf cs t++-- | Matcher that succeeds if the pattern text exactly matches the+-- subject (with case sensitivity as appropriate.)+exact :: CaseSensitive -> Text -> Matcher+exact cs t = Matcher sDesc mrDesc mr+  where+    sDesc = pack "exact"+    mrDesc = pack $ "matches the text \"" ++ unpack t+             ++ "\"" ++ descSensitive cs+    mr = txtMatch (==) cs t++txtMatch :: (Text -> Text -> Bool)+            -> CaseSensitive+            -> Text+            -> Text -> Bool+txtMatch f c p t = pat `f` txt where+  txt = flipCase t+  pat = flipCase p+  flipCase = case c of+    Sensitive -> id+    Insensitive -> toCaseFold++-- | Matcher that succeeds if the subject represents a valid date with+-- an optional time.+date+  :: Maybe (CompUTC, Time.UTCTime)+  -- ^ If Nothing, any valid date and time will succeed as a match;+  -- the matcher will return False if the subject is not a valid+  -- date. If Just, the subject must be a valid date and must fit+  -- within the range indicated.+  -> Matcher+date mayPair = Matcher (pack "date") md mr+  where+    md = case mayPair of+      Nothing -> pack "any valid date with optional time"+      Just (c, t) -> pack $ "valid date and optional time, "+        ++ descUTC c t+    mr x = fromMaybe False $ do+      subjDT <- case P.parse dateTime "" x of+        Left _ -> mzero+        Right g -> return g+      case mayPair of+        Nothing -> return True+        Just (c, t) ->+          let cmp = compUTCtoCmp c+          in return $ subjDT `cmp` t++------------------------------------------------------------+-- StrErr monad+------------------------------------------------------------++-- | This monad exists because using the mtl Monad instance of (Either+-- String) causes problems due to orphan instances.+--+-- See http://www.haskell.org/pipermail/haskell-cafe/2011-December/098079.html++data StrErr a = Good a+              | Bad String+              deriving (Show, Eq)++instance Monad StrErr where+  return = Good+  (Good a) >>= f = f a+  (Bad s) >>= _ = Bad s+  fail s = Bad s++------------------------------------------------------------+-- TDFA primitives+------------------------------------------------------------++tdfaPrim+  :: CaseSensitive -> String -> Exceptional String (String -> Bool)+tdfaPrim c regexStr = case RL.makeRegexOptsM comp exec regexStr of+  (Bad s) -> Exception s+  (Good rx) -> return (RL.matchTest rx)+  where+    comp = RL.defaultCompOpt { TDFA.caseSensitive = case c of+                                  Sensitive -> True+                                  Insensitive -> False+                             , TDFA.newSyntax = True+                             , TDFA.lastStarGreedy = True }+    exec = RL.defaultExecOpt { TDFA.captureGroups = False }++------------------------------------------------------------+-- PCRE primitives+------------------------------------------------------------++pcrePrim :: CaseSensitive+        -> BS.ByteString+        -> Exceptional String (BS.ByteString -> Bool)+pcrePrim c bs = let+  u8 = [PCRE.utf8]+  opts = case c of+    Sensitive -> u8+    Insensitive -> PCRE.caseless:u8 in+  case PCRE.compileM bs opts of+    (Left err) -> Exception err+    (Right rx) -> Success $ \s ->+      case PCRE.match rx s [] of+        (Just _) -> True+        Nothing -> False++------------------------------------------------------------+-- Date parsers+------------------------------------------------------------+year :: Parser Integer+year = read <$> replicateM 4 P.digit++month :: Parser Int+month = read <$> replicateM 2 P.digit++day :: Parser Int+day = read <$> replicateM 2 P.digit++pDate :: Parser Time.Day+pDate = p >>= failOnErr+  where+    p = Time.fromGregorianValid+        <$> year  <* satisfy dateSep+        <*> month <* satisfy dateSep+        <*> day+    failOnErr = maybe (fail "could not parse date") return++dateSep :: Char -> Bool+dateSep c = c == '/' || c == '-'++digit :: Char -> Bool+digit c = c >= '0' && c <= '9'++colon :: Char -> Bool+colon = (== ':')++hours :: Parser Int+hours = p >>= (maybe (fail "could not parse hours") return)+  where+    p = f <$> satisfy digit <*> satisfy digit+    f d1 d2 =+      let r = read [d1,d2]+      in if r < 0 || r > 23+         then Nothing+         else Just r+++minutes :: Parser Int+minutes = p >>= maybe (fail "could not parse minutes") return+  where+    p = f <$ satisfy colon <*> satisfy digit <*> satisfy digit+    f d1 d2 =+      let r = read [d1, d2]+      in if r < 0 || r > 59+         then Nothing+         else Just r++seconds :: Parser Pico+seconds = p >>= maybe (fail "could not parse seconds") return+  where+    p = f <$ satisfy colon <*> satisfy digit <*> satisfy digit+    f d1 d2 =+      let r = read [d1, d2] :: Int+      in if r < 0 || r > 59+         then Nothing+         else Just . fromIntegral $ r++time :: Parser Time.TimeOfDay+time = f <$> hours <*> minutes <*> optional seconds+  where+    f h m ms = Time.TimeOfDay h m (fromMaybe 0 ms)++tzSign :: Parser (Int -> Int)+tzSign = (id <$ satisfy plus) <|> (negate <$ satisfy minus)+  where+    plus = (== '+')+    minus = (== '-')++tzNumber :: Parser Int+tzNumber = read <$> replicateM 4 (satisfy digit)++timeZone :: Parser Time.TimeZone+timeZone = p >>= maybe (fail "could not parse time zone") return+  where+    p = f <$> tzSign <*> tzNumber+    f s = minsToOffset . s+    minsToOffset m = if abs m > 840+                     then Nothing+                     else Just (Time.TimeZone m False "")++white :: Char -> Bool+white c = c == ' ' || c == '\t'++timeWithZone :: Parser (Time.TimeOfDay, Maybe Time.TimeZone)+timeWithZone =+  (,) <$> time <* many (satisfy white) <*> optional timeZone+++dateTime :: Parser Time.UTCTime+dateTime =+  f <$> pDate <* many (satisfy white) <*> optional timeWithZone+  where+    f d mayTwithZ = Time.zonedTimeToUTC zt+      where+        zt = Time.ZonedTime lt tz+        lt = Time.LocalTime d tod+        (tod, tz) = case mayTwithZ of+          Nothing -> (Time.midnight, Time.utc)+          Just (t, mayZ) -> case mayZ of+            Nothing -> (t, Time.utc)+            Just z -> (t, z)++------------------------------------------------------------+-- Other date things+------------------------------------------------------------++data CompUTC+  = UAfter+  | UOnOrAfter+  | UExactly+  | UBefore+  | UOnOrBefore+  deriving (Eq, Show, Ord)++descUTC :: CompUTC -> Time.UTCTime -> String+descUTC c u = "date is " ++ co ++ " " ++ dt+  where+    co = case c of+      UAfter -> "after"+      UOnOrAfter -> "on or after"+      UExactly -> "on"+      UBefore -> "before"+      UOnOrBefore -> "on or before"+    dt = show dy ++ " " ++ hs ++ ":" ++ ms ++ ":" ++ ss ++ " UTC"+    Time.UTCTime dy difft = u+    Time.TimeOfDay h m s = Time.timeToTimeOfDay difft+    (hs, ms, ss) = (show h, show m, show (round s :: Int))++compUTCtoCmp :: Ord a => CompUTC -> a -> a -> Bool+compUTCtoCmp c = case c of+  UAfter -> (>)+  UOnOrAfter -> (>=)+  UExactly -> (==)+  UBefore -> (<)+  UOnOrBefore -> (<=)+
− Text/Matchers/CaseSensitive.hs
@@ -1,3 +0,0 @@-module Text.Matchers.CaseSensitive where--data CaseSensitive = Sensitive | Insensitive deriving (Eq, Ord, Show)
− Text/Matchers/Regex/PCRE.hs
@@ -1,24 +0,0 @@-module Text.Matchers.Regex.PCRE (pcre) where--import Control.Monad.Exception.Synchronous-  ( Exceptional (Exception, Success))-import qualified Data.ByteString as BS-import qualified Text.Regex.PCRE.Light as PCRE--import Text.Matchers.CaseSensitive-  (CaseSensitive(Sensitive, Insensitive))--pcre :: CaseSensitive-        -> BS.ByteString-        -> Exceptional String (BS.ByteString -> Bool)-pcre c bs = let-  u8 = [PCRE.utf8]-  opts = case c of-    Sensitive -> u8-    Insensitive -> PCRE.caseless:u8 in-  case PCRE.compileM bs opts of-    (Left err) -> Exception err-    (Right rx) -> Success $ \s ->-      case PCRE.match rx s [] of-        (Just _) -> True-        Nothing -> False
− Text/Matchers/Regex/TDFA.hs
@@ -1,32 +0,0 @@-module Text.Matchers.Regex.TDFA (tdfa) where--import Control.Monad.Exception.Synchronous-  ( Exceptional (Exception))-import qualified Text.Regex.TDFA as TDFA-import qualified Text.Regex.Base.RegexLike as RL--import Text.Matchers.CaseSensitive-  (CaseSensitive(Sensitive, Insensitive))--data StrErr a = Good a-              | Bad String-              deriving (Show, Eq)--instance Monad StrErr where-  return = Good-  (Good a) >>= f = f a-  (Bad s) >>= _ = Bad s-  fail s = Bad s--tdfa :: CaseSensitive -> String -> Exceptional String (String -> Bool)-tdfa c regexStr = case RL.makeRegexOptsM comp exec regexStr of-  (Bad s) -> Exception s-  (Good rx) -> return (RL.matchTest rx)-  where-    comp = RL.defaultCompOpt { TDFA.caseSensitive = case c of-                                  Sensitive -> True-                                  Insensitive -> False-                             , TDFA.newSyntax = True-                             , TDFA.lastStarGreedy = True }-    exec = RL.defaultExecOpt { TDFA.captureGroups = False }-
− Text/Matchers/String.hs
@@ -1,43 +0,0 @@-module Text.Matchers.String (-  tdfa, pcre, within, exact,-  CaseSensitive(Sensitive, Insensitive)) where--import Control.Monad.Exception.Synchronous-  ( Exceptional (Exception, Success))-import Data.Char (toLower)-import Data.List (isInfixOf)-import Data.String.UTF8 (fromString, toRep)--import qualified Text.Matchers.Regex.TDFA as TDFA-import qualified Text.Matchers.Regex.PCRE as PCRE-import Text.Matchers.CaseSensitive-  (CaseSensitive(Sensitive, Insensitive))--tdfa :: CaseSensitive-        -> String-        -> Exceptional String (String -> Bool)-tdfa = TDFA.tdfa--pcre :: CaseSensitive-        -> String-        -> Exceptional String (String -> Bool)-pcre c s = case PCRE.pcre c (toRep . fromString $ s) of-  (Success f) -> return (f . toRep . fromString)-  (Exception e) -> Exception e--within :: CaseSensitive -> String -> String -> Bool-within = strMatch isInfixOf--exact :: CaseSensitive -> String -> String -> Bool-exact = strMatch (==)--strMatch :: (String -> String -> Bool)-            -> CaseSensitive-            -> String-            -> String -> Bool-strMatch f c s t = pat `f` str where-  str = flipCase t-  pat = flipCase s-  flipCase = case c of-    Sensitive -> id-    Insensitive -> map toLower
− Text/Matchers/Text.hs
@@ -1,44 +0,0 @@-module Text.Matchers.Text (-  tdfa, pcre, within, exact,-  CaseSensitive(Sensitive, Insensitive)) where--import Control.Monad.Exception.Synchronous-  ( Exceptional (Exception, Success))-import Data.Text (Text, pack, unpack, toCaseFold, isInfixOf)-import Data.Text.Encoding (encodeUtf8)--import qualified Text.Matchers.Regex.PCRE as PCRE-import qualified Text.Matchers.Regex.TDFA as TDFA-import Text.Matchers.CaseSensitive-  (CaseSensitive(Sensitive, Insensitive))--tdfa :: CaseSensitive-        -> Text-        -> Exceptional Text (Text -> Bool)-tdfa c t = case TDFA.tdfa c (unpack t) of-  (Exception e) -> Exception (pack e)-  (Success f) -> return (f . unpack)--pcre :: CaseSensitive-        -> Text-        -> Exceptional Text (Text -> Bool)-pcre c t = case PCRE.pcre c (encodeUtf8 t) of-  (Exception e) -> Exception (pack e)-  (Success f) -> return (f . encodeUtf8)--within :: CaseSensitive -> Text -> Text -> Bool-within = txtMatch isInfixOf--exact :: CaseSensitive -> Text -> Text -> Bool-exact = txtMatch (==)--txtMatch :: (Text -> Text -> Bool)-            -> CaseSensitive-            -> Text-            -> Text -> Bool-txtMatch f c p t = pat `f` txt where-  txt = flipCase t-  pat = flipCase p-  flipCase = case c of-    Sensitive -> id-    Insensitive -> toCaseFold
matchers.cabal view
@@ -1,5 +1,5 @@ Name: matchers-Version: 0.4.0.0+Version: 0.6.0.0 Cabal-version: >=1.8 Build-Type: Simple License: BSD3@@ -21,21 +21,18 @@  Library     Build-depends:-        base ==4.*,-        bytestring ==0.9.*,-        explicit-exception ==0.1.*,-        regex-base ==0.93.*,-        regex-tdfa ==1.1.*,-        text ==0.11.*,-        utf8-string ==0.3.*,-        pcre-light ==0.4.*+          base ==4.*+        , bytestring ==0.10.*+        , explicit-exception ==0.1.*+        , parsec >= 3.1.2 && < 3.2+        , pcre-light ==0.4.*+        , regex-base ==0.93.*+        , regex-tdfa ==1.1.*+        , text ==0.11.*+        , time ==1.4.* +     Exposed-modules:-        Text.Matchers,-        Text.Matchers.CaseSensitive,-        Text.Matchers.Regex.PCRE,-        Text.Matchers.Regex.TDFA,-        Text.Matchers.String,-        Text.Matchers.Text+        Text.Matchers      ghc-options: -Wall