{-# OPTIONS_HADDOCK hide #-}
module StrongPath.FilePath
( -- ** Parsers (from 'FilePath' to 'Path')
-- $parsersFilepath
parseRelDir,
parseRelFile,
parseAbsDir,
parseAbsFile,
parseRelDirW,
parseRelFileW,
parseAbsDirW,
parseAbsFileW,
parseRelDirP,
parseRelFileP,
parseAbsDirP,
parseAbsFileP,
-- ** Conversion (from 'Path' to 'FilePath')
-- $conversionFilepath
toFilePath,
fromRelDir,
fromRelFile,
fromAbsDir,
fromAbsFile,
fromRelDirP,
fromRelFileP,
fromAbsDirP,
fromAbsFileP,
fromRelDirW,
fromRelFileW,
fromAbsDirW,
fromAbsFileW,
)
where
import Control.Monad.Catch (MonadThrow)
import Data.List (intercalate)
import qualified Path as P
import qualified Path.Posix as PP
import qualified Path.Windows as PW
import StrongPath.Internal
import StrongPath.Path
import qualified System.FilePath as FP
import qualified System.FilePath.Posix as FPP
import qualified System.FilePath.Windows as FPW
-- $parsersFilepath
-- Path can be constructed from `FilePath`:
--
-- > parse :: MonadThrow m => FilePath -> m ()
--
-- There are 12 parser functions, each of them parsing 'FilePath' into a specific 'Path'
-- type.
-- All of them work in the same fashion and will throw an error (via 'MonadThrow')
-- if given 'FilePath' can't be parsed into the specific 'Path' type.
-- For example, if path is absolute, 'parseRelDir' will throw an error.
--
-- Not all parsers accept all types of separators, for example
-- 'parseRelDirP' parser will fail to parse paths using Windows separators,
-- while 'parseRelDirW' will accept both Windows and Posix separators.
--
-- Below is a table describing, for all the parser functions,
-- which path standard (separators) do they accept as input
-- and to what path standard they parse it.
--
-- +---------------------------+-----------------+----------+
-- | Parsers | From | To |
-- +===========================+=================+==========+
-- | parse[Abs|Rel][Dir|File] | System/Posix | System |
-- +---------------------------+-----------------+----------+
-- | parse[Abs|Rel][Dir|File]W | Win/Posix | Win |
-- +---------------------------+-----------------+----------+
-- | parse[Abs|Rel][Dir|File]P | Posix | Posix |
-- +---------------------------+-----------------+----------+
--
-- NOTE: Root of @parseAbs...@ input always has to match its path standard!
-- e.g., 'parseAbsDirW' can parse @\"C:\\foo\/bar\"@ but it can't parse @\"\/foo\/bar\"@.
--
-- Examples:
--
-- - @parseAbsFile \"C:\\foo\\bar.txt\"@ is valid if system is Windows, and gives the same result as @parseAbsFile \"C:\\foo\/bar.txt\"@.
-- On the other hand, both are invalid if system is Linux.
-- - @parseRelFile \"foo\/bar.txt\"@ is valid independent of the system.
-- - @parseRelFile \"foo\\bar.txt\"@ is valid only if system is Windows.
-- - @parseRelDirW \"foo\\bar\\test\"@ is valid, independent of the system, and gives the same result as @parseRelDirW \"foo\\bar\/test\"@ or @parseRelDirW "foo\/bar\/test\"@.
--
-- Basically, all of the parsers accept their \"native\" standard AND Posix,
-- which enables you to hardcode paths as Posix in the code that will compile
-- and work both on Linux and Windows when using `System` as a standard.
-- So Posix becames a kind of \"universal\" language for hardcoding the paths.
parseRelDir :: MonadThrow m => FilePath -> m (Path System (Rel d1) (Dir d2))
parseRelFile :: MonadThrow m => FilePath -> m (Path System (Rel d) (File f))
parseAbsDir :: MonadThrow m => FilePath -> m (Path System Abs (Dir d))
parseAbsFile :: MonadThrow m => FilePath -> m (Path System Abs (File f))
parseRelDirW :: MonadThrow m => FilePath -> m (Path Windows (Rel d1) (Dir d2))
parseRelFileW :: MonadThrow m => FilePath -> m (Path Windows (Rel d) (File f))
parseAbsDirW :: MonadThrow m => FilePath -> m (Path Windows Abs (Dir d))
parseAbsFileW :: MonadThrow m => FilePath -> m (Path Windows Abs (File f))
parseRelDirP :: MonadThrow m => FilePath -> m (Path Posix (Rel d1) (Dir d2))
parseRelFileP :: MonadThrow m => FilePath -> m (Path Posix (Rel d) (File f))
parseAbsDirP :: MonadThrow m => FilePath -> m (Path Posix Abs (Dir d))
parseAbsFileP :: MonadThrow m => FilePath -> m (Path Posix Abs (File f))
---- System
parseRelDir = parseRelDirFP RelDir [FP.pathSeparator, FPP.pathSeparator] P.parseRelDir
parseRelFile = parseRelFileFP RelFile [FP.pathSeparator, FPP.pathSeparator] P.parseRelFile
parseAbsDir fp = fromPathAbsDir <$> P.parseAbsDir fp
parseAbsFile fp = fromPathAbsFile <$> P.parseAbsFile fp
---- Windows
parseRelDirW = parseRelDirFP RelDirW [FPW.pathSeparator, FPP.pathSeparator] PW.parseRelDir
parseRelFileW = parseRelFileFP RelFileW [FPW.pathSeparator, FPP.pathSeparator] PW.parseRelFile
parseAbsDirW fp = fromPathAbsDirW <$> PW.parseAbsDir fp
parseAbsFileW fp = fromPathAbsFileW <$> PW.parseAbsFile fp
---- Posix
parseRelDirP = parseRelDirFP RelDirP [FPP.pathSeparator] PP.parseRelDir
parseRelFileP = parseRelFileFP RelFileP [FPP.pathSeparator] PP.parseRelFile
parseAbsDirP fp = fromPathAbsDirP <$> PP.parseAbsDir fp
parseAbsFileP fp = fromPathAbsFileP <$> PP.parseAbsFile fp
-- $conversionFilepath
-- 'Path' can be converted into 'FilePath' via polymorphic function 'toFilePath'
-- or via any of the 12 functions that accept specific path type.
--
-- We recommend using specific functions instead of 'toFilePath',
-- because that way you are explicit about which path you expect
-- and if that expectancy is not met, type system will catch it.
toFilePath :: Path s b t -> FilePath
toFilePath sp = case sp of
---- System
RelDir p prefix -> relPathToFilePath P.toFilePath FP.pathSeparator prefix p
RelFile p prefix -> relPathToFilePath P.toFilePath FP.pathSeparator prefix p
AbsDir p -> P.toFilePath p
AbsFile p -> P.toFilePath p
---- Windows
RelDirW p prefix -> relPathToFilePath PW.toFilePath FPW.pathSeparator prefix p
RelFileW p prefix -> relPathToFilePath PW.toFilePath FPW.pathSeparator prefix p
AbsDirW p -> PW.toFilePath p
AbsFileW p -> PW.toFilePath p
---- Posix
RelDirP p prefix -> relPathToFilePath PP.toFilePath FPP.pathSeparator prefix p
RelFileP p prefix -> relPathToFilePath PP.toFilePath FPP.pathSeparator prefix p
AbsDirP p -> PP.toFilePath p
AbsFileP p -> PP.toFilePath p
where
relPathToFilePath pathToFilePath sep prefix path =
combinePrefixWithPath sep (relPathPrefixToFilePath sep prefix) (pathToFilePath path)
relPathPrefixToFilePath :: Char -> RelPathPrefix -> FilePath
relPathPrefixToFilePath _ NoPrefix = ""
relPathPrefixToFilePath sep (ParentDir n) =
intercalate [sep] (replicate n "..") ++ [sep]
-- TODO: This function and helper functions above are somewhat too loose and hard to
-- follow, implement them in better way.
-- Here we are assuming that prefix is of form (../)*, therefore it ends with separator,
-- and it could also be empty.
combinePrefixWithPath :: Char -> String -> FilePath -> FilePath
combinePrefixWithPath sep prefix path
| path `elem` [".", ['.', sep], "./"] && not (null prefix) = prefix
combinePrefixWithPath _ prefix path = prefix ++ path
-- These functions just call toFilePath, but their value is in
-- their type: they allow you to capture expected type of the strong path
-- that you want to convert into FilePath.
fromRelDir :: Path System (Rel r) (Dir d) -> FilePath
fromRelDir = toFilePath
fromRelFile :: Path System (Rel r) (File f) -> FilePath
fromRelFile = toFilePath
fromAbsDir :: Path System Abs (Dir d) -> FilePath
fromAbsDir = toFilePath
fromAbsFile :: Path System Abs (File f) -> FilePath
fromAbsFile = toFilePath
fromRelDirP :: Path Posix (Rel r) (Dir d) -> FilePath
fromRelDirP = toFilePath
fromRelFileP :: Path Posix (Rel r) (File f) -> FilePath
fromRelFileP = toFilePath
fromAbsDirP :: Path Posix Abs (Dir d) -> FilePath
fromAbsDirP = toFilePath
fromAbsFileP :: Path Posix Abs (File f) -> FilePath
fromAbsFileP = toFilePath
fromRelDirW :: Path Windows (Rel r) (Dir d) -> FilePath
fromRelDirW = toFilePath
fromRelFileW :: Path Windows (Rel r) (File f) -> FilePath
fromRelFileW = toFilePath
fromAbsDirW :: Path Windows Abs (Dir d) -> FilePath
fromAbsDirW = toFilePath
fromAbsFileW :: Path Windows Abs (File f) -> FilePath
fromAbsFileW = toFilePath