packages feed

darcs-2.18.1: src/Darcs/Util/Regex.hs

-- | This module is a subset of the defunct regex-compat-tdfa.
{-# LANGUAGE CPP #-}
module Darcs.Util.Regex
    ( Regex
    , mkRegex
    , mkRegexWithOpts
    , matchRegex
    ) where

import Darcs.Prelude

import Control.Exception ( throw )
#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail
#endif
import Text.Regex.Base
    ( RegexContext(matchM)
    , RegexMaker(makeRegexOptsM)
    , defaultCompOpt
    , defaultExecOpt
    )
import Text.Regex.TDFA ( Regex, caseSensitive, multiline, newSyntax )

-- | The "sane" API for regex ('makeRegexOptM') requires 'MonadFail'
-- but we want a pure one for compatibility with e.g. "Darcs.Patch.Match".
newtype RegexFail a = RegexFail { runRegexFail :: Either String a }
  -- The subtlety here is that only in base-4.13.0 the fail method
  -- in class Monad was removed. For earlier versions, regex-tdfa
  -- calls the fail from class Monad, not the one from class MonadFail.
#if MIN_VERSION_base(4,13,0)
  deriving (Functor, Applicative, Monad)
#else
  deriving (Functor, Applicative)

instance Monad RegexFail where
  RegexFail (Left e) >>= _ = RegexFail (Left e)
  RegexFail (Right r) >>= k = k r
  fail = RegexFail . Left
#endif

instance MonadFail RegexFail where
  fail = RegexFail . Left

-- | Makes a regular expression with the default options (multi-line,
-- case-sensitive).  The syntax of regular expressions is
-- otherwise that of @egrep@ (i.e. POSIX \"extended\" regular
-- expressions).
mkRegex :: String -> Regex
mkRegex s = mkRegexInternal opt s
  where
    opt = defaultCompOpt {newSyntax = True, multiline = True}

-- | Makes a regular expression, where the multi-line and
-- case-sensitive options can be changed from the default settings.
mkRegexWithOpts
   :: String  -- ^ The regular expression to compile
   -> Bool    -- ^ 'True' @\<=>@ @\'^\'@ and @\'$\'@ match the beginning and 
              -- end of individual lines respectively, and @\'.\'@ does /not/
              -- match the newline character.
   -> Bool    -- ^ 'True' @\<=>@ matching is case-sensitive
   -> Regex   -- ^ Returns: the compiled regular expression
mkRegexWithOpts s single_line case_sensitive
  = let opt = defaultCompOpt
                { multiline    = (if single_line then True else False)
                , caseSensitive = (if case_sensitive then True else False)
                , newSyntax     = True }
    in mkRegexInternal opt s

mkRegexInternal :: RegexMaker p compOpt execOpt String => compOpt -> String -> p
mkRegexInternal opt s =
  case runRegexFail (makeRegexOptsM opt defaultExecOpt s) of
    Left e -> throw (userError ("Invalid regular expression:\n" ++ e))
    Right r -> r

-- | Match a regular expression against a string
matchRegex ::
     Regex -- ^ The regular expression
  -> String -- ^ The string to match against
  -> Maybe [String] -- ^ Returns: @'Just' strs@ if the match succeeded
                      -- (and @strs@ is the list of subexpression matches),
                      -- or 'Nothing' otherwise.
matchRegex p str = fmap go (matchM p str)
  where
    go :: (String, String, String, [String]) -> [String]
    go (_, _, _, ss) = ss