build-env-1.2.0.0: src/BuildEnv/Path.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE UndecidableInstances #-}
module BuildEnv.Path
( SymbolicPath, RelativePath, AbsolutePath
, FileOrDir(..)
, CWD, Pkg, PkgDb, Project
, Tmp, Logs, Fetch, Prefix, Install
, sameDirectory
, mkSymbolicPath
, mkRelativePath
, mkAbsolutePath
, interpretSymbolicPath
, getSymbolicPath
, getAbsolutePath
, absoluteSymbolicPath
, makeAbsolute
, (<.>), (</>)
)
where
-- base
import Data.Kind
( Type )
-- directory
import qualified System.Directory as Directory
-- filepath
import qualified System.FilePath as FilePath
--------------------------------------------------------------------------------
-- | A type-level symbolic name, to an abstract file or directory
-- (e.g. the Cabal package directory).
data FileOrDir
= -- | A file (with no further information).
File
| -- | The abstract name of a directory or category of directories,
-- e.g. the package directory or a source directory.
Dir Type
-- | Is this symbolic path allowed to be absolute, or must it be relative?
data AllowAbsolute
= -- | The path may be absolute, or it may be relative.
AllowAbsolute
| -- | The path must be relative.
OnlyRelative
-- | A symbolic path, possibly relative to an abstract location specified
-- by the @from@ type parameter.
--
-- They are *symbolic*, which means we cannot perform any 'IO'
-- until we interpret them (using e.g. 'interpretSymbolicPath').
type SymbolicPathX :: AllowAbsolute -> Type -> FileOrDir -> Type
newtype SymbolicPathX allowAbsolute from to = SymbolicPath FilePath
deriving newtype Show
type role SymbolicPathX nominal nominal nominal
-- | A symbolic relative path, relative to an abstract location specified
-- by the @from@ type parameter.
--
-- They are *symbolic*, which means we cannot perform any 'IO'
-- until we interpret them (using e.g. 'interpretSymbolicPath').
type RelativePath = SymbolicPathX 'OnlyRelative
-- | A path which is either absolute or relative to the given abstract*
-- location specified by the @from@ type parameter.
--
-- They are *symbolic*, which means we cannot perform any 'IO'
-- until we interpret them (using e.g. 'interpretSymbolicPath').
type SymbolicPath = SymbolicPathX 'AllowAbsolute
-- | An absolute path, or a reference to a path from the PATH environment variable.
type AbsolutePath :: FileOrDir -> Type
newtype AbsolutePath to = AbsolutePath ( forall from. SymbolicPath from to )
instance Show ( AbsolutePath to ) where
show ( AbsolutePath fp ) = show fp
--------------------------------------------------------------------------------
-- | Abstract directory: current working directory.
data CWD
-- | Abstract directory: project root for 'build-env' commands.
data Project
-- | Abstract directory: package directory (e.g. a directory containing the @.cabal@ file).
data Pkg
-- | Abstract directory: package database directory (e.g. a directory containing a @package.conf@ file).
data PkgDb
-- | Abstract temporary directory.
data Tmp
-- | Abstract directory for logs.
data Logs
-- | Abstract directory: fetched sources directory.
data Fetch
-- | Abstract directory: prefix.
data Prefix
-- | Abstract directory: installation directory.
data Install
--------------------------------------------------------------------------------
mkSymbolicPath :: FilePath -> SymbolicPath from to
mkSymbolicPath = SymbolicPath
mkRelativePath :: FilePath -> RelativePath from to
mkRelativePath = SymbolicPath
mkAbsolutePath :: FilePath -> AbsolutePath to
mkAbsolutePath fp = AbsolutePath ( mkSymbolicPath fp )
sameDirectory :: SymbolicPathX allowAbsolute from to
sameDirectory = SymbolicPath "."
getSymbolicPath :: SymbolicPathX allowAbsolute from to -> FilePath
getSymbolicPath ( SymbolicPath p ) = p
getAbsolutePath :: AbsolutePath to -> FilePath
getAbsolutePath ( AbsolutePath p ) = getSymbolicPath p
absoluteSymbolicPath :: AbsolutePath to -> SymbolicPath from to
absoluteSymbolicPath ( AbsolutePath p ) = p
-- | Interpret a symbolic path with respect to the given directory.
--
-- Use this function before directly interacting with the file system in order
-- to take into account a working directory argument.
interpretSymbolicPath :: SymbolicPath CWD ( Dir dir ) -> SymbolicPathX allowAbsolute dir to -> FilePath
interpretSymbolicPath ( SymbolicPath workDir ) ( SymbolicPath p ) =
if workDir == "."
then p -- NB: this just avoids creating paths of the form "./././blah".
else workDir </> p
-- Note that this properly handles an absolute symbolic path,
-- because if @q@ is absolute, then @p </> q = q@.
-- | Make the given 'SymbolicPath' absolute.
makeAbsolute :: SymbolicPath CWD ( Dir dir ) -> SymbolicPath dir to -> IO ( AbsolutePath to )
makeAbsolute workDir path =
mkAbsolutePath <$> Directory.makeAbsolute ( interpretSymbolicPath workDir path )
-------------------------------------------------------------------------------
-- * Composition
-------------------------------------------------------------------------------
infixr 7 <.>
-- | Types that support 'System.FilePath.<.>'.
class FileLike p where
-- | Like 'System.FilePath.<.>', but also supporting symbolic paths.
(<.>) :: p -> String -> p
instance FileLike FilePath where
(<.>) = (FilePath.<.>)
instance p ~ File => FileLike ( SymbolicPathX allowAbsolute dir p ) where
SymbolicPath p <.> ext = SymbolicPath ( p <.> ext )
instance p ~ File => FileLike ( AbsolutePath p ) where
p <.> ext = mkAbsolutePath ( getAbsolutePath p <.> ext )
infixr 5 </>
-- | Types that support 'System.FilePath.</>'.
class PathLike p q r | q r -> p, p q -> r where
-- | Like 'System.FilePath.</>', but also supporting symbolic paths.
(</>) :: p -> q -> r
instance ( q ~ FilePath ) => PathLike FilePath q FilePath where
(</>) = (FilePath.</>)
-- | This instance ensures we don't accidentally discard a symbolic path
-- in a 'System.FilePath.</>' operation due to the second path being absolute.
--
-- (Recall that @a </> b = b@ whenever @b@ is absolute.)
instance
(b1 ~ 'Dir b2, a3 ~ a1, c2 ~ c3)
=> PathLike
( SymbolicPathX allowAbsolute a1 b1 )
( SymbolicPathX midAbsolute b2 c2 )
( SymbolicPathX allowAbsolute a3 c3 )
where
SymbolicPath p1 </> SymbolicPath p2 =
if p1 == "."
then SymbolicPath p2 -- NB: this just avoids creating paths of the form "./././blah".
else SymbolicPath (p1 </> p2)
instance
( b1 ~ 'Dir b2, c2 ~ c3, midAbsolute ~ OnlyRelative )
=> PathLike
( AbsolutePath b1 )
( SymbolicPathX midAbsolute b2 c2 )
( AbsolutePath c3 )
where
AbsolutePath (SymbolicPath p1) </> SymbolicPath p2 =
mkAbsolutePath (p1 </> p2)