possible cabal monad
Phyx
lonetiger at gmail.com
Tue Nov 23 16:38:50 EST 2010
Hi everyone,
I've been wanting to be able to override/redirect the outputs from Cabal and
Cabal-Install, so if I want to use cabal-install to build something via an
IDE I can display accurate feedback to the users. What I came up with is:
Ps. The mailclient is screwing with the indentation somewhat.
Regards,
Tamar
{-# LANGUAGE MultiParamTypeClasses #-}
-- This api is modelled after the GHC Api, The idea is to provide a way for
someone,
-- say IDE writers to wrap calls to cabal-install commands.
-- In particular control where feedback is returned.
{-
In order to catch all feedback every IO a in Cabal instead be Cabal a, and
at the start of it you'd have a runCabal.
A example of how the functions will become is
--
----------------------------------------------------------------------------
-
-- |Build the libraries and executables in this package.
build :: PackageDescription -- ^mostly information from the .cabal file
-> LocalBuildInfo -- ^Configuration information
-> BuildFlags -- ^Flags that the user passed to build
-> [ PPSuffixHandler ] -- ^preprocessors to run before compiling
-> IO ()
build pkg_descr lbi flags suffixes = do
let distPref = fromFlag (buildDistPref flags)
verbosity = fromFlag (buildVerbosity flags)
initialBuildSteps distPref pkg_descr lbi verbosity suffixes
setupMessage verbosity "Building" (packageId pkg_descr)
=========
build :: PackageDescription -- ^mostly information from the .cabal file
-> LocalBuildInfo -- ^Configuration information
-> BuildFlags -- ^Flags that the user passed to build
-> [ PPSuffixHandler ] -- ^preprocessors to run before compiling
-> Cabal ()
build pkg_descr lbi flags suffixes = do
let distPref = fromFlag (buildDistPref flags)
verbosity = fromFlag (buildVerbosity flags)
setVerbosity verbosity
initialBuildSteps distPref pkg_descr lbi suffixes
setupMessage "Building" (packageId pkg_descr)
----------
-}
module Distribution.Simple.Api
( -- * Types
CabalSession(..)
, Logger(..)
, Cabal()
, CabalMonad
-- * Utility functions
, getVerbosity
, setVerbosity
, setLoggers
, getLoggers
-- * Execution functions
, runCabal
, reflectCabal
)
where
-- * Imports
import Control.Monad.Trans
import Distribution.Verbosity as Verbosity
( Verbosity , normal )
import qualified Distribution.Simple.Utils as Utils
import Distribution.Text
( display )
import Distribution.Package
( PackageIdentifier )
import Data.IORef
import System.IO.Error
import System.FilePath ( normalise )
-- * Types
-- | A type synonym incase we ever want to change Message to something other
than string.
-- It also makes it clear that we are printing a Message for someone.
type Message = String
-- | A Session data type which is used to carry information from function
call to function call.
-- It currently only contains the verbosity level and the logger
functions, but more can be added if needed.
-- maintenance becomes easier since now to add extra information that is
accessable by every function all we
-- need to do is extend this datatype.
data CabalSession
= CabalSession { verbosity :: Verbosity
, loggers :: Logger
}
type Session = IORef CabalSession
-- | This data type holds the functions you should redirect in case you want
to do something other than just
-- print an error out, or if you want to change the formatting of the
feedback messages.
data Logger
= Logger { logger_dieWithLocation :: FilePath -> Maybe Int -> Message ->
IO ()
, logger_die :: Message -> IO ()
, logger_warn :: Verbosity -> Message -> IO ()
, logger_notice :: Verbosity -> Message -> IO ()
, logger_info :: Verbosity -> Message -> IO ()
, logger_debug :: Verbosity -> Message -> IO ()
}
-- | the Cabal Monad data type and a few of it's standard definitions
newtype Cabal a = Cabal { unCabal :: Session -> IO a }
instance Functor Cabal where
fmap f m = Cabal $ \s -> f `fmap` unCabal m s
instance Monad Cabal where
return a = Cabal $ \_ -> return a
m >>= g = Cabal $ \s -> unCabal m s >>= \a -> unCabal (g a) s
instance MonadIO Cabal where
liftIO a = Cabal $ \_ -> a
instance CabalMonad Cabal where
getSession = Cabal $ \s -> readIORef s
setSession s = Cabal $ \r -> writeIORef r s
-- End standard definitions
-- | The CabalMonad declaration which provides two standard functions for
manipulating it's state
class (Functor m, MonadIO m) => CabalMonad m where
getSession :: m CabalSession
setSession :: CabalSession -> m ()
-- | These are the feedback functions, which just wrap the functions in the
Logger data type
-- The names and types were mostly choosen to preserve existing
conventions. They also get
-- the current verbosity level and passes it along explicitly.
-- .
-- These are mostly wrappers around functions in Distribution.Simple.Utils
-- .
-- Note: The >> undefined is just so that the return type is fully
polymorphic. The
-- "die" loggers should always abort computation. So it should never
be reached
-- but I can't marshal the IOError type (atleast not easily) which
is why I just
-- require the loggers in Loggers to return a IO ()
dieWithLocation :: CabalMonad m => FilePath -> Maybe Int -> Message -> m a
dieWithLocation f i m = do l <- fetchLogger logger_dieWithLocation
liftIO $ l f i m >> undefined
die :: CabalMonad m => Message -> m a
die m = do l <- fetchLogger logger_die
liftIO $ l m >> undefined
warn :: CabalMonad m => Message -> m ()
warn = executeLogger logger_warn
notice :: CabalMonad m => Message -> m ()
notice = executeLogger logger_notice
info :: CabalMonad m => Message -> m ()
info = executeLogger logger_info
debug :: CabalMonad m => Message -> m ()
debug = executeLogger logger_debug
setupMessage :: CabalMonad m => Message -> PackageIdentifier -> m ()
setupMessage m pkg = notice (m ++ ' ': display pkg ++ "...")
-- | Execute a logger by first fetching it using /fetchLogger/ and then
calling it
-- with as parameters the current verbosity level and the given Message.
executeLogger :: (CabalMonad m) => (Logger -> Verbosity -> Message -> IO a)
-> Message -> m a
executeLogger f m = do l <- fetchLogger f
v <- getVerbosity
liftIO $ l v m
-- | Retreives a logger from the CabalSession and return it.
-- The argument to this function should be one of the record
-- labels of Loggers
fetchLogger :: (CabalMonad m) => (Logger -> c) -> m c
fetchLogger f = fmap (f . loggers) getSession
-- * Verbosity utility functions
getVerbosity :: CabalMonad m => m Verbosity
getVerbosity = fmap verbosity getSession
setVerbosity :: CabalMonad m => Verbosity -> m ()
setVerbosity v = fmap (\cfg -> (cfg{verbosity = v})) getSession >>=
setSession
-- * Loggers utility functions
setLoggers :: CabalMonad m => Logger -> m ()
setLoggers l = do session <- getSession
let newSession = session{loggers = l}
setSession newSession
getLoggers :: CabalMonad m => m Logger
getLoggers = fmap loggers getSession
-- | A standard set of default loggers.
defaultLoggers :: Logger
defaultLoggers =
Logger { logger_dieWithLocation = Utils.dieWithLocation
, logger_die = Utils.die
, logger_warn = Utils.warn
, logger_notice = Utils.notice
, logger_info = Utils.info
, logger_debug = Utils.debug
}
-- | A standard verbosity level
defaultVerbosity :: Verbosity
defaultVerbosity = normal
-- | Create a default session using the default loggers and default
verbosity
defaultSession :: CabalSession
defaultSession =
CabalSession { verbosity = defaultVerbosity
, loggers = defaultLoggers
}
-- | A way to run a Cabal monad
runCabal :: Cabal a -> IO a
runCabal = flip reflectCabal defaultSession
-- | Reflect a Cabal monad using a given CabalSession
reflectCabal :: Cabal a -> CabalSession -> IO a
reflectCabal c s = unCabal c =<< newIORef s
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/cabal-devel/attachments/20101124/c99d9f0b/attachment-0001.html
More information about the cabal-devel
mailing list