packages feed

regex-wrapper (empty) → 0.1.0.0

raw patch · 6 files changed

+261/−0 lines, 6 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, case-insensitive, containers, hashable, regex-tdfa, string-conv, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for regex-wrapper++## 0.1.0.0 -- 2019-11-11++* Initial Version released.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, Luke Clifton++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 Luke Clifton 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,34 @@+### Experimental++This library allows you to create types that are guaranteed to contain a+string that matches a given regular expression which is expressed at the+type level.++```haskell+newtype User = User (Matched String "^[a-zA-Z0-9]{4,15}$")++parseUser :: String -> Either (RegexError String) User+parseUser = fmap User . parseMatchedEither++prettyUser :: User -> String+prettyUser (User m) = asString m++main :: IO ()+main = do+    l <- getLine+    case parseUser l of+        Right user -> putStrLn $ "Hello, " ++ prettyUser user ++ "!"+        Left error -> putStrLn $ "Could not parse username: " ++ prettyRegexError  error+```++```+./prog+bad+Could not parse username: The input "bad" did not match the pattern ^[a-zA-Z0-9]{4,15}$+```++```+./prog+good+Hello, good!+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ regex-wrapper.cabal view
@@ -0,0 +1,33 @@+cabal-version:       2.4+name:                regex-wrapper+version:             0.1.0.0+synopsis:            Types that can only be constructed if they match a regular expression+description:         Provides tooling for working with types whose values must+                     match a regular expression provided in the type.+bug-reports:         https://github.com/luke-clifton/regex-wrapper/issues+license:             BSD-3-Clause+license-file:        LICENSE+author:              Luke Clifton+maintainer:          lukec@themk.net+copyright:           (c) 2019 Luke Clifton+category:            Data+extra-source-files:  CHANGELOG.md, README.md++source-repository head+  type: git+  location: https://github.com/luke-clifton/regex-wrapper++library+  exposed-modules: Text.Regex.Wrapper+  build-depends:+    , base ^>=4.12.0.0+    , regex-tdfa+    , hashable+    , string-conv+    , text+    , bytestring+    , aeson+    , containers+    , case-insensitive+  hs-source-dirs:      src+  default-language:    Haskell2010
+ src/Text/Regex/Wrapper.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+module Text.Regex.Wrapper+    ( Matched()+    , parseMatched+    , parseMatchedEither+    , parseMatchedM+    , asStr+    , asText+    , asString+    , asByteString+    , compile+    , parseMatchedWith+    , parseMatchedEitherWith+    , RegexPat()+    ) where++import Prelude hiding (fail)+import Control.Monad hiding (fail)+import Control.Monad.Fail+import Data.ByteString (ByteString)+import Data.Text (Text)+import Data.Proxy+import GHC.TypeLits+import Text.Regex.TDFA as TDFA+import Data.Hashable+import Data.Aeson+import Data.CaseInsensitive as CI++data RegexError str = NoMatch (MatchError str) | CompileError String+    deriving Show++prettyRegexError :: Show str => RegexError str -> String+prettyRegexError (NoMatch err) = prettyMatchError err+prettyRegexError (CompileError err) = "Invalid regular expression: " ++ err++data MatchError str = MatchError+    { matchErrorPattern :: String+    , matchErrorInput :: str+    } deriving Show++prettyMatchError :: Show str => MatchError str -> String+prettyMatchError err+    = "The input "+    ++ show (matchErrorInput err)+    ++ " did not match the pattern "+    ++ matchErrorPattern err++-- | A compiled regular expression that can produce @`Matched` str pat@+-- values.+newtype RegexPat (pat :: Symbol) = RegexPat TDFA.Regex++-- | Create a @`Regex`@ that can be used to construct @`Matched`@ values+-- when used with @`parseMatchedWith`@. This allows you to compile+-- the pattern once, and use it multiple times.+compile :: forall pat m. (KnownSymbol pat) => Either String (RegexPat pat)+compile =+    let+        pat = symbolVal (Proxy :: Proxy pat)+    in+        case RegexPat <$> makeRegexM pat of+            Success a -> Right a+            Error s -> Left s++-- | Use a precompiled @`Regex`@ to create @`Matched`@ values.+parseMatchedWith+    :: RegexLike Regex str+    => RegexPat pat -> str -> Maybe (Matched str pat)+parseMatchedWith (RegexPat pat) str = do+    guard $ matchTest pat str+    pure (Matched str)++-- | Like @`parseMatchedWith`@, but calls returns a useful error message.+parseMatchedEitherWith+    :: forall pat str m.+    ( RegexLike Regex str+    , Show str+    , KnownSymbol pat+    ) => RegexPat pat -> str -> Either (MatchError str) (Matched str pat)+parseMatchedEitherWith reg str = do+    let pat = symbolVal (Proxy :: Proxy pat)+    case parseMatchedWith reg str of+        Nothing -> Left MatchError+            { matchErrorPattern = pat+            , matchErrorInput = str+            }+        Just x -> Right x++-- | A wrapper type that can only be constructed if the underlying string+-- type matches the regular expression in @pat@.+newtype Matched str (pat :: Symbol) = Matched str+    deriving newtype (Show, Eq, Ord, Hashable, FoldCase)++-- | Extract the wrapped @str@ type.+asStr :: Matched str pat -> str+asStr (Matched str) = str++instance (KnownSymbol pat, RegexLike Regex str, Read str)+    => Read (Matched str pat) where+    readsPrec p s = do+        r@(a,s) <- readsPrec p s+        Just m <- pure (parseMatched a)+        pure (m, s)++instance ToJSON str => ToJSON (Matched str pat) where+    toJSON = toJSON . asStr+    toEncoding = toEncoding . asStr++instance (KnownSymbol pat, Show str, FromJSON str, RegexLike Regex str)+    => FromJSON (Matched str pat) where+    parseJSON v = parseJSON v >>= either (fail . prettyRegexError) pure . parseMatchedEither++-- | Same as @`asStr`@ but with a fixed type.+asText :: Matched Text p -> Text+asText = asStr++-- | Same as @`asStr`@ but with a fixed type.+asString :: Matched String p -> String+asString = asStr++-- | Same as @`asStr`@ but with a fixed type.+asByteString :: Matched ByteString p -> ByteString+asByteString = asStr++-- | Convert a @str@ into a @`Matched` str pat@ if possible.+parseMatched+    :: forall str pat. (KnownSymbol pat, RegexLike Regex str)+    => str -> Maybe (Matched str pat)+parseMatched str = case compile of+    Left _ -> Nothing+    Right r -> parseMatchedWith r str++-- | Same as @`parseMatched`@ but provides a slightly helpful error+-- message on failure. This requires that your @str@ type is an instance+-- of @`Show`@.+parseMatchedEither+    :: forall pat str. (Show str, KnownSymbol pat, RegexLike Regex str)+    => str -> Either (RegexError str) (Matched str pat)+parseMatchedEither str = do+    case compile of+        Left e -> Left $ CompileError e+        Right r -> case parseMatchedEitherWith r str of+            Left e -> Left $ NoMatch e+            Right r -> pure r++-- | Like @`parseMatchedEither`@ except that it calls @`fail`@ instead.+parseMatchedM+    :: forall pat str m.+    ( KnownSymbol pat, RegexLike Regex str, MonadFail m, Show str)+    => str -> m (Matched str pat)+parseMatchedM str = case parseMatchedEither str of+    Left e -> fail $ prettyRegexError e+    Right r -> pure r