content: {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  module Test.Hspec.Core.Spec.Monad (
  -- RE-EXPORTED from Test.Hspec.Core.Spec
    Spec
  , SpecWith
  , SpecM (SpecM)
  , runSpecM
  , fromSpecList
  , runIO
  
  , mapSpecForest
  , mapSpecItem
  , mapSpecItem_
  , modifyParams
  
  , modifyConfig
  -- END RE-EXPORTED from Test.Hspec.Core.Spec
  
  , Env(..)
  , withEnv
  ) where
  
  import           Prelude ()
  import           Test.Hspec.Core.Compat
  
  import           Control.Monad.IO.Class (liftIO)
  import           Control.Monad.Trans.Reader
  import           Control.Monad.Trans.Writer
  
  import           Test.Hspec.Core.Example
  import           Test.Hspec.Core.Tree
  
  import           Test.Hspec.Core.Config.Definition (Config)
  
  -- | A `SpecWith` that can be evaluated directly by the
  -- `Test.Hspec.Core.Runner.hspec` function as it does not require any
  -- parameters.
  type Spec = SpecWith ()
  
  -- | A @'SpecWith' a@ represents a test or group of tests that require an @a@
  -- value to run.
  --
  -- In the common case, a 'Spec' is a @'SpecWith' ()@ which requires @()@ and
  -- can thus be executed with `Test.Hspec.Core.Runner.hspec'.
  --
  -- To supply an argument to `SpecWith` tests to turn them into `Spec`, use
  -- functions from "Test.Hspec.Core.Hooks" such as
  -- `Test.Hspec.Core.Hooks.around`, `Test.Hspec.Core.Hooks.before',
  -- `Test.Hspec.Core.Hooks.mapSubject' and similar.
  --
  -- Values of this type are created by `Test.Hspec.Core.Spec.it`,
  -- `Test.Hspec.Core.Spec.describe` and similar.
  type SpecWith a = SpecM a ()
  
  -- |
  -- @since 2.10.0
  modifyConfig :: (Config -> Config) -> SpecWith a
  modifyConfig f = SpecM $ tell (Endo f, mempty)
  
  -- | A writer monad for `SpecTree` forests.
  --
  -- This is used by `Test.Hspec.Core.Spec.describe` and is used
  -- to construct the forest of spec items.
  --
  -- It allows for dynamically generated spec trees, for example, by using data
  -- obtained by performing IO actions with 'runIO'.
  newtype SpecM a r = SpecM { unSpecM :: WriterT (Endo Config, [SpecTree a]) (ReaderT Env IO) r }
    deriving (Functor, Applicative, Monad)
  
  -- | Convert a `Spec` to a forest of `SpecTree`s.
  runSpecM :: SpecWith a -> IO (Endo Config, [SpecTree a])
  runSpecM = flip runReaderT (Env []) . execWriterT . unSpecM
  
  -- | Create a `Spec` from a forest of `SpecTree`s.
  fromSpecForest :: (Endo Config, [SpecTree a]) -> SpecWith a
  fromSpecForest = SpecM . tell
  
  -- | Create a `Spec` from a forest of `SpecTree`s.
  fromSpecList :: [SpecTree a] -> SpecWith a
  fromSpecList = fromSpecForest . (,) mempty
  
  -- | Run an IO action while constructing the spec tree.
  --
  -- `SpecM` is a monad to construct a spec tree, without executing any spec
  -- items itself.  @runIO@ allows you to run IO actions during this construction phase.
  -- The IO action is always run when the spec tree is constructed (e.g. even
  -- when @--dry-run@ is specified).
  -- If you do not need the result of the IO action to construct the spec tree,
  -- `Test.Hspec.Core.Hooks.beforeAll` may be more suitable for your use case.
  runIO :: IO r -> SpecM a r
  runIO = SpecM . liftIO
  
  mapSpecForest :: ([SpecTree a] -> [SpecTree b]) -> SpecM a r -> SpecM b r
  mapSpecForest f (SpecM specs) = SpecM (mapWriterT (fmap (fmap (second f))) specs)
  
  -- {-# DEPRECATED mapSpecItem "Use `mapSpecItem_` instead." #-}
  -- | Deprecated: Use `mapSpecItem_` instead.
  mapSpecItem :: (ActionWith a -> ActionWith b) -> (Item a -> Item b) -> SpecWith a -> SpecWith b
  mapSpecItem _ = mapSpecItem_
  
  mapSpecItem_ :: (Item a -> Item b) -> SpecWith a -> SpecWith b
  mapSpecItem_ = mapSpecForest . bimapForest id
  
  -- | Modifies the `Params` on the spec items to be generated by `SpecWith`.
  modifyParams :: (Params -> Params) -> SpecWith a -> SpecWith a
  modifyParams f = mapSpecItem_ $ \item -> item {itemExample = \p -> (itemExample item) (f p)}
  
  newtype Env = Env {
    -- | The path of _parent_ `Hspec.Core.Spec.describe` labels from innermost to
    -- outermost.
    envSpecDescriptionPath :: [String]
  }
  
  -- | Applies a function to modify the `Env` of items being written by a child
  -- spec writer.
  --
  -- This is used to implement `Hspec.Core.Spec.describe`.
  withEnv :: (Env -> Env) -> SpecM a r -> SpecM a r
  withEnv f = SpecM . WriterT . local f . runWriterT . unSpecM
  