{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {- | Module : Within License : MIT Stability : experimental The Within type, an EnvT comonad specialised to `Path b Dir`. -} module Within ( WithinT(..) , Within , localDir , localDirM , asWithin , within , fromWithin , blinkLocalDir , blinkAndMap , blinkAndMapM , localDirAndMapM ) where import Control.Applicative import Control.Comonad import Control.Comonad.Env import Control.Monad import Control.Monad.Catch import Data.Functor.Identity import Data.Hashable import Data.Typeable import GHC.Generics import Path import Path.Like -- | The Within Type, newtype wrapper around `EnvT` specialised to a `Path b Dir` environment. newtype WithinT b w a = WithinT (EnvT (Path b Dir) w a) deriving (Typeable, Generic, Functor, Foldable, Traversable) type Within b a = WithinT b Identity a instance Comonad w => Comonad (WithinT b w) where extract (WithinT w) = extract w duplicate (WithinT w) = WithinT (extend WithinT w) instance Comonad w => ComonadEnv (Path b Dir) (WithinT b w) where ask (WithinT w) = ask w instance ComonadTrans (WithinT b) where lower (WithinT w) = lower w -- | Change the parent directory of a `Within` value. localDir :: (Path b Dir -> Path c Dir) -> WithinT b w a -> WithinT c w a localDir f (WithinT w) = WithinT (local f w) -- | Change the parent directory of a `Within` value, monadic verison. localDirM :: Monad m => (Path b Dir -> m (Path c Dir)) -> WithinT b w a -> m (WithinT c w a) localDirM f (WithinT (EnvT e wa)) = f e >>= \e' -> return $ WithinT $ EnvT e' wa -- | Treat a `Path` as if it lies within another directory and returns a `Within` value. -- Used infix like -- -- >>> $(mkRelFile "foo/a.txt") `asWithin` $(mkRelDir "foo") -- asWithin :: MonadThrow m => Path a t -> Path a Dir -> m (Within a (Path Rel t)) asWithin x y = stripProperPrefix y x >>= \z -> return (WithinT (EnvT y (Identity z))) -- | Put a value inside a directory. -- -- >>> $(mkRelFile "a.txt") `within` $(mkRelDir "foo") within :: a -> Path b Dir -> Within b a within x y = WithinT (EnvT y (Identity x)) -- | Turns a `Within` containing a `PathLike` into a single `Path`. fromWithin :: PathLike Rel t a => Within b a -> Path b t fromWithin = liftA2 () ask (toPath . extract) instance PathLike Rel t a => PathLike b t (Within b a) where toPath = fromWithin instance FileLike Rel a => FileLike b (Within b a) instance DirLike Rel a => DirLike b (Within b a) instance Eq t => Eq (Within b t) where WithinT (EnvT e (Identity a)) == WithinT (EnvT e' (Identity a')) = e == e' && a == a' instance Hashable t => Hashable (Within b t) where hashWithSalt n (WithinT (EnvT e (Identity a))) = n `hashWithSalt` e `hashWithSalt` a instance Show t => Show (Within b t) where show (WithinT (EnvT e (Identity a))) = show e ++ "/" ++ show a instance Ord t => Ord (Within b t) where compare (WithinT (EnvT e (Identity a))) (WithinT (EnvT e' (Identity a'))) = compare e e' <> compare a a' -- | Switch the outer part of a `Within` value to a new directory, synonym for localDir . const blinkLocalDir :: Path b Dir -> WithinT a w t -> WithinT b w t blinkLocalDir = localDir . const -- | Switch the outer part of a `Within` value to a new directory and map the inner at the same time. blinkAndMap :: Functor w => Path b Dir -> (s -> t) -> WithinT a w s -> WithinT b w t blinkAndMap k g = blinkLocalDir k . fmap g -- | Switch the outer part of a `Within` value to a new directory and mapM the inner at the same time. blinkAndMapM :: (Monad m, Traversable w) => Path b Dir -> (s -> m t) -> WithinT a w s -> m (WithinT b w t) blinkAndMapM k g = mapM g . blinkLocalDir k -- | mapM the outer and inner part of a `Within` value at the same time. localDirAndMapM :: (Monad m, Traversable w) => (Path b Dir -> m (Path c Dir)) -> (s -> m t) -> WithinT b w s -> m (WithinT c w t) localDirAndMapM f g = localDirM f <=< mapM g