193 line
5.2 KiB
Haskell
193 line
5.2 KiB
Haskell
{-# LANGUAGE TemplateHaskell #-}
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
{-# LANGUAGE DeriveDataTypeable #-}
|
|
{-# LANGUAGE EmptyDataDecls #-}
|
|
|
|
-- | A normalizing well-typed path type.
|
|
|
|
module Path
|
|
(-- * Types
|
|
Path
|
|
,Abs
|
|
,Rel
|
|
,File
|
|
,Dir
|
|
-- * Parsing
|
|
,parseAbsDir
|
|
,parseRelDir
|
|
,parseAbsFile
|
|
,parseRelFile
|
|
,PathParseException
|
|
-- * Constructors
|
|
,mkAbsDir
|
|
,mkRelDir
|
|
,mkAbsFile
|
|
,mkRelFile
|
|
-- * Conversion
|
|
,toFilePath
|
|
)
|
|
where
|
|
|
|
import Control.Exception (Exception)
|
|
import Control.Monad.Catch (MonadThrow(..))
|
|
import Data.Data
|
|
import Data.List
|
|
import Language.Haskell.TH
|
|
import Path.Internal
|
|
import qualified System.FilePath as FilePath
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Types
|
|
|
|
-- | An absolute path.
|
|
data Abs
|
|
|
|
-- | A relative path; one without a root.
|
|
data Rel
|
|
|
|
-- | A file path.
|
|
data File
|
|
|
|
-- | A directory path.
|
|
data Dir
|
|
|
|
-- | Exception when parsing a location.
|
|
data PathParseException
|
|
= InvalidAbsDir FilePath
|
|
| InvalidRelDir FilePath
|
|
| InvalidAbsFile FilePath
|
|
| InvalidRelFile FilePath
|
|
deriving (Show,Typeable)
|
|
instance Exception PathParseException
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Parsers
|
|
|
|
-- | Get a location for an absolute directory. Produces a normalized
|
|
-- path which always ends in a path separator.
|
|
--
|
|
-- Throws: 'PathParseException'
|
|
--
|
|
parseAbsDir :: MonadThrow m
|
|
=> FilePath -> m (Path Abs Dir)
|
|
parseAbsDir filepath =
|
|
if FilePath.isAbsolute filepath &&
|
|
not (null (normalizeDir filepath)) &&
|
|
not (isPrefixOf "~/" filepath)
|
|
then return (Path (normalizeDir filepath))
|
|
else throwM (InvalidAbsDir filepath)
|
|
|
|
-- | Get a location for a relative directory. Produces a normalized
|
|
-- path which always ends in a path separator.
|
|
--
|
|
-- Throws: 'PathParseException'
|
|
--
|
|
parseRelDir :: MonadThrow m
|
|
=> FilePath -> m (Path Rel Dir)
|
|
parseRelDir filepath =
|
|
if not (FilePath.isAbsolute filepath) &&
|
|
not (null filepath) &&
|
|
not (isPrefixOf "~/" filepath) &&
|
|
not (null (normalizeDir filepath))
|
|
then return (Path (normalizeDir filepath))
|
|
else throwM (InvalidRelDir filepath)
|
|
|
|
-- | Get a location for an absolute file. Produces a normalized
|
|
-- path which always ends in a path separator.
|
|
--
|
|
-- Throws: 'PathParseException'
|
|
--
|
|
parseAbsFile :: MonadThrow m
|
|
=> FilePath -> m (Path Abs File)
|
|
parseAbsFile filepath =
|
|
if FilePath.isAbsolute filepath &&
|
|
not (FilePath.hasTrailingPathSeparator filepath) &&
|
|
not (isPrefixOf "~/" filepath) &&
|
|
not (null (normalizeFile filepath))
|
|
then return (Path (normalizeFile filepath))
|
|
else throwM (InvalidAbsFile filepath)
|
|
|
|
-- | Get a location for a relative file. Produces a normalized
|
|
-- path which always ends in a path separator.
|
|
--
|
|
-- Throws: 'PathParseException'
|
|
--
|
|
parseRelFile :: MonadThrow m
|
|
=> FilePath -> m (Path Rel File)
|
|
parseRelFile filepath =
|
|
if not (FilePath.isAbsolute filepath || FilePath.hasTrailingPathSeparator filepath) &&
|
|
not (null filepath) &&
|
|
not (isPrefixOf "~/" filepath) &&
|
|
not (null (normalizeFile filepath))
|
|
then return (Path (normalizeFile filepath))
|
|
else throwM (InvalidRelFile filepath)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Constructors
|
|
|
|
-- | Make a 'Path Abs Dir'.
|
|
--
|
|
-- Remember: due to the nature of absolute paths this (e.g. @\/home\/foo@)
|
|
-- may compile on your platform, but it may not compile on another
|
|
-- platform (Windows).
|
|
mkAbsDir :: FilePath -> Q Exp
|
|
mkAbsDir s =
|
|
case parseAbsDir s of
|
|
Left err -> error (show err)
|
|
Right (Path str) ->
|
|
[|Path $(return (LitE (StringL str))) :: Path Abs Dir|]
|
|
|
|
-- | Make a 'Path Rel Dir'.
|
|
mkRelDir :: FilePath -> Q Exp
|
|
mkRelDir s =
|
|
case parseRelDir s of
|
|
Left err -> error (show err)
|
|
Right (Path str) ->
|
|
[|Path $(return (LitE (StringL str))) :: Path Rel Dir|]
|
|
|
|
-- | Make a 'Path Abs File'.
|
|
--
|
|
-- Remember: due to the nature of absolute paths this (e.g. @\/home\/foo@)
|
|
-- may compile on your platform, but it may not compile on another
|
|
-- platform (Windows).
|
|
mkAbsFile :: FilePath -> Q Exp
|
|
mkAbsFile s =
|
|
case parseAbsFile s of
|
|
Left err -> error (show err)
|
|
Right (Path str) ->
|
|
[|Path $(return (LitE (StringL str))) :: Path Abs File|]
|
|
|
|
-- | Make a 'Path Rel File'.
|
|
mkRelFile :: FilePath -> Q Exp
|
|
mkRelFile s =
|
|
case parseRelFile s of
|
|
Left err -> error (show err)
|
|
Right (Path str) ->
|
|
[|Path $(return (LitE (StringL str))) :: Path Rel File|]
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Conversion
|
|
|
|
-- | Convert to a 'FilePath' type.
|
|
toFilePath :: Path b t -> FilePath
|
|
toFilePath (Path l) = l
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Internal functions
|
|
|
|
-- | Internal use for normalizing a directory.
|
|
normalizeDir :: FilePath -> FilePath
|
|
normalizeDir =
|
|
clean . FilePath.addTrailingPathSeparator . FilePath.normalise
|
|
where clean "./" = ""
|
|
clean ('/':'/':xs) = clean ('/':xs)
|
|
clean x = x
|
|
|
|
-- | Internal use for normalizing a fileectory.
|
|
normalizeFile :: FilePath -> FilePath
|
|
normalizeFile =
|
|
clean . FilePath.normalise
|
|
where clean "./" = ""
|
|
clean ('/':'/':xs) = clean ('/':xs)
|
|
clean x = x
|