-- This file is part of Goatee.
--
-- Copyright 2014-2021 Bryan Gardiner
--
-- Goatee is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- Goatee is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with Goatee.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE CPP #-}

-- | Common definitions for a renderer that supports failure.
module Game.Goatee.Lib.Renderer (
  Render,
  runRender,
  rendererOf,
  ) where

#if MIN_VERSION_mtl(2,2,1)
import Control.Monad.Except (Except, catchError, runExcept, throwError)
#else
import Control.Monad.Error (catchError, throwError)
#endif
import Control.Monad.Writer (WriterT, execWriterT)

-- | A monad for accumulating string output with the possibility of failure.
#if MIN_VERSION_mtl(2,2,1)
type Render = WriterT String (Except String)
#else
type Render = WriterT String (Either String)
#endif

-- | Returns either the rendered result on the right, or a message describing a
-- failure on the left.
runRender :: Render a -> Either String String
#if MIN_VERSION_mtl(2,2,1)
runRender :: Render a -> Either String String
runRender = Except String String -> Either String String
forall e a. Except e a -> Either e a
runExcept (Except String String -> Either String String)
-> (Render a -> Except String String)
-> Render a
-> Either String String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Render a -> Except String String
forall (m :: * -> *) w a. Monad m => WriterT w m a -> m w
execWriterT
#else
runRender = execWriterT
#endif

-- | Wraps a renderer in an exception handler that, when the renderer or
-- something it calls fails, will add context about this renderer's invocation
-- to the failure message.
rendererOf :: Show a => String -> (a -> Render ()) -> a -> Render ()
rendererOf :: String -> (a -> Render ()) -> a -> Render ()
rendererOf String
description a -> Render ()
renderer a
value = Render () -> (String -> Render ()) -> Render ()
forall e (m :: * -> *) a.
MonadError e m =>
m a -> (e -> m a) -> m a
catchError (a -> Render ()
renderer a
value) ((String -> Render ()) -> Render ())
-> (String -> Render ()) -> Render ()
forall a b. (a -> b) -> a -> b
$ \String
message ->
  String -> Render ()
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (String -> Render ()) -> String -> Render ()
forall a b. (a -> b) -> a -> b
$
  String
message String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n    while trying to render " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
description String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" from " String -> String -> String
forall a. [a] -> [a] -> [a]
++ a -> String
forall a. Show a => a -> String
show a
value String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"."