    {-# LANGUAGE CPP #-}
    {-# LANGUAGE ViewPatterns #-}
    -- | Stability: unstable
    module Test.Hspec.Core.Util (
    -- * String functions
      pluralize
    , strip
    , lineBreaksAt
    , stripAnsi

    -- * Working with paths
    , Path
    , joinPath
    , formatRequirement
    , filterPredicate

    -- * Working with exceptions
    , safeTry
    , formatException
    , formatExceptionWith
    ) where

    import           Prelude ()
    import           Test.Hspec.Core.Compat hiding (join)

    import           Data.Char (isSpace)

    #ifndef __MHS__
    import           GHC.IO.Exception
    #else
    import           System.IO.Error
    #endif

    import           Control.Concurrent.Async

    -- |
    -- @pluralize count singular@ pluralizes the given @singular@ word unless given
    -- @count@ is 1.
    --
    -- Examples:
    --
    -- >>> pluralize 0 "example"
    -- "0 examples"
    --
    -- >>> pluralize 1 "example"
    -- "1 example"
    --
    -- >>> pluralize 2 "example"
    -- "2 examples"
    --
    -- @since 2.0.0
    pluralize :: Int -> String -> String
    pluralize :: Int -> String -> String
    pluralize Int
    1 String
    s = String
    "1 " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    s
    pluralize Int
    n String
    s = Int -> String
    forall a. Show a => a -> String
    show Int
    n String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    s String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    "s"

    -- | Strip leading and trailing whitespace
    --
    -- @since 2.0.0
    strip :: String -> String
    strip :: String -> String
    strip = (Char -> Bool) -> String -> String
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile Char -> Bool
    isSpace (String -> String) -> (String -> String) -> String -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> String
    forall a. [a] -> [a]
    reverse (String -> String) -> (String -> String) -> String -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Char -> Bool) -> String -> String
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile Char -> Bool
    isSpace (String -> String) -> (String -> String) -> String -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> String
    forall a. [a] -> [a]
    reverse

    -- |
    -- Ensure that lines are not longer than given `n`, insert line breaks at word
    -- boundaries
    --
    -- @since 2.0.0
    lineBreaksAt :: Int -> String -> [String]
    lineBreaksAt :: Int -> String -> [String]
    lineBreaksAt Int
    n = (String -> [String]) -> [String] -> [String]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap String -> [String]
    f ([String] -> [String])
    -> (String -> [String]) -> String -> [String]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> [String]
    lines
      where
        f :: String -> [String]
    f String
    input = case String -> [String]
    words String
    input of
          []   -> []
          String
    x:[String]
    xs -> (String, [String]) -> [String]
    go (String
    x, [String]
    xs)

        go :: (String, [String]) -> [String]
        go :: (String, [String]) -> [String]
    go (String, [String])
    c = case (String, [String])
    c of
          (String
    s, [])   -> [String
    s]
          (String
    s, String
    y:[String]
    ys) -> let r :: String
    r = String
    s String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    y in
            if String -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length String
    r Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <= Int
    n
              then (String, [String]) -> [String]
    go (String
    r, [String]
    ys)
              else String
    s String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : (String, [String]) -> [String]
    go (String
    y, [String]
    ys)

    -- |
    -- Remove ANSI color escape sequences.
    --
    -- @since 2.11.0
    stripAnsi :: String -> String
    stripAnsi :: String -> String
    stripAnsi = String -> String
    go
      where
        go :: String -> String
        go :: String -> String
    go String
    input = case String
    input of
          Char
    '\ESC' : Char
    '[' : (String -> String
    parameters -> Char
    'm' : String
    xs) -> String -> String
    go String
    xs
          Char
    x : String
    xs -> Char
    x Char -> String -> String
    forall a. a -> [a] -> [a]
    : String -> String
    go String
    xs
          [] -> []

        parameters :: String -> String
        parameters :: String -> String
    parameters = (Char -> Bool) -> String -> String
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile Char -> Bool
    p
          where
            p :: Char -> Bool
            p :: Char -> Bool
    p Char
    c = Char
    c Char -> Char -> Bool
    forall a. Ord a => a -> a -> Bool
    >= Char
    '0' Bool -> Bool -> Bool
    && Char
    c Char -> Char -> Bool
    forall a. Ord a => a -> a -> Bool
    <= Char
    '9' Bool -> Bool -> Bool
    || Char
    c Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == Char
    ';'

    -- |
    -- A `Path` describes the location of a spec item within a spec tree.
    --
    -- It consists of a list of group descriptions and a requirement description.
    --
    -- @since 2.0.0
    type Path = ([String], String)

    -- |
    -- Join a `Path` with slashes.  The result will have a leading and a trailing
    -- slash.
    --
    -- @since 2.5.4
    joinPath :: Path -> String
    joinPath :: Path -> String
    joinPath ([String]
    groups, String
    requirement) = String
    "/" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate String
    "/" ([String]
    groups [String] -> [String] -> [String]
    forall a. [a] -> [a] -> [a]
    ++ [String
    requirement]) String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    "/"

    -- |
    -- Try to create a proper English sentence from a path by applying some
    -- heuristics.
    --
    -- @since 2.0.0
    formatRequirement :: Path -> String
    formatRequirement :: Path -> String
    formatRequirement ([String]
    groups, String
    requirement) = String
    groups_ String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    requirement
      where
        groups_ :: String
    groups_ = case (String -> Bool) -> [String] -> ([String], [String])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break ((Char -> Bool) -> String -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    any Char -> Bool
    isSpace) [String]
    groups of
          ([], [String]
    ys) -> [String] -> String
    join [String]
    ys
          ([String]
    xs, [String]
    ys) -> [String] -> String
    join (String -> [String] -> String
    forall a. [a] -> [[a]] -> [a]
    intercalate String
    "." [String]
    xs String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : [String]
    ys)

        join :: [String] -> String
    join [String]
    xs = case [String]
    xs of
          [String
    x] -> String
    x String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " "
          [String]
    ys  -> (String -> String) -> [String] -> String
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap (String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    ", ") [String]
    ys

    -- | A predicate that can be used to filter a spec tree.
    --
    -- @since 2.0.0
    filterPredicate :: String -> Path -> Bool
    filterPredicate :: String -> Path -> Bool
    filterPredicate String
    pattern_ Path
    path =
         String
    pattern_ String -> String -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    `isInfixOf` String
    plain
      Bool -> Bool -> Bool
    || String
    pattern_ String -> String -> Bool
    forall a. Eq a => [a] -> [a] -> Bool
    `isInfixOf` String
    formatted
      where
        plain :: String
    plain = Path -> String
    joinPath Path
    path
        formatted :: String
    formatted = Path -> String
    formatRequirement Path
    path

    -- | The function `formatException` converts an exception to a string.
    --
    -- This is different from `show`.  The type of the exception is included, e.g.:
    --
    -- >>> formatException (toException DivideByZero)
    -- "ArithException\ndivide by zero"
    --
    -- For `IOException`s the `IOErrorType` is included, as well.
    --
    -- @since 2.0.0
    formatException :: SomeException -> String
    formatException :: SomeException -> String
    formatException = (SomeException -> String) -> SomeException -> String
    formatExceptionWith SomeException -> String
    forall a. Show a => a -> String
    show

    -- | @since 2.11.5
    formatExceptionWith :: (SomeException -> String) -> SomeException -> String
    formatExceptionWith :: (SomeException -> String) -> SomeException -> String
    formatExceptionWith SomeException -> String
    showException SomeException
    err = case SomeException -> Maybe IOException
    forall e. Exception e => SomeException -> Maybe e
    fromException SomeException
    err of
      Maybe IOException
    Nothing  -> SomeException -> String
    showExceptionType SomeException
    err String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    "\n" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ SomeException -> String
    showException SomeException
    err
      Just IOException
    ioe -> IOException -> String
    forall a. Typeable a => a -> String
    showType IOException
    ioe String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " of type " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ IOException -> String
    showIOErrorType IOException
    ioe String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    "\n" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ SomeException -> String
    showException (IOException -> SomeException
    forall e. Exception e => e -> SomeException
    toException IOException
    ioe)
      where
        showIOErrorType :: IOException -> String
        showIOErrorType :: IOException -> String
    showIOErrorType IOException
    ioe = case IOException -> IOErrorType
    ioe_type IOException
    ioe of
          IOErrorType
    AlreadyExists -> String
    "AlreadyExists"
          IOErrorType
    NoSuchThing -> String
    "NoSuchThing"
          IOErrorType
    ResourceBusy -> String
    "ResourceBusy"
          IOErrorType
    ResourceExhausted -> String
    "ResourceExhausted"
          IOErrorType
    EOF -> String
    "EOF"
          IOErrorType
    IllegalOperation -> String
    "IllegalOperation"
          IOErrorType
    PermissionDenied -> String
    "PermissionDenied"
          IOErrorType
    UserError -> String
    "UserError"
          IOErrorType
    UnsatisfiedConstraints -> String
    "UnsatisfiedConstraints"
          IOErrorType
    SystemError -> String
    "SystemError"
          IOErrorType
    ProtocolError -> String
    "ProtocolError"
          IOErrorType
    OtherError -> String
    "OtherError"
          IOErrorType
    InvalidArgument -> String
    "InvalidArgument"
          IOErrorType
    InappropriateType -> String
    "InappropriateType"
          IOErrorType
    HardwareFault -> String
    "HardwareFault"
          IOErrorType
    UnsupportedOperation -> String
    "UnsupportedOperation"
          IOErrorType
    TimeExpired -> String
    "TimeExpired"
          IOErrorType
    ResourceVanished -> String
    "ResourceVanished"
          IOErrorType
    Interrupted -> String
    "Interrupted"

    showExceptionType :: SomeException -> String
    showExceptionType :: SomeException -> String
    showExceptionType (SomeException e
    e) = e -> String
    forall a. Typeable a => a -> String
    showType e
    e

    -- | @safeTry@ evaluates given action and returns its result.  If an exception
    -- occurs, the exception is returned instead.  Unlike `try` it is agnostic to
    -- asynchronous exceptions.
    --
    -- @since 2.0.0
    safeTry :: IO a -> IO (Either SomeException a)
    safeTry :: forall a. IO a -> IO (Either SomeException a)
    safeTry IO a
    action = IO a
    -> (Async a -> IO (Either SomeException a))
    -> IO (Either SomeException a)
    forall a b. IO a -> (Async a -> IO b) -> IO b
    withAsync (IO a
    action IO a -> (a -> IO a) -> IO a
    forall a b. IO a -> (a -> IO b) -> IO b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= a -> IO a
    forall a. a -> IO a
    evaluate) Async a -> IO (Either SomeException a)
    forall a. Async a -> IO (Either SomeException a)
    waitCatch