module System.Path.Internal
(
Path,
AbsFile,
RelFile,
AbsDir,
RelDir,
AbsRelFile,
AbsRelDir,
AbsFileDir,
RelFileDir,
AbsRelFileDir,
AbsPath, Abs,
RelPath, Rel,
FilePath, File,
DirPath, Dir,
AbsRelPath, AbsRel,
FileDirPath, FileDir,
withAbsRel, withFileDir,
toString,
getPathString,
rootDir,
currentDir,
emptyFile,
maybePath, maybe,
parsePath, parse,
path,
relFile,
relDir,
absFile,
absDir,
relPath, rel,
absPath, abs,
filePath, file,
dirPath, dir,
absRel, fileDir,
idAbsRel, idAbs, idRel,
idFileDir, idFile, idDir,
asPath,
asRelFile,
asRelDir,
asAbsFile,
asAbsDir,
asRelPath,
asAbsPath,
asFilePath,
asDirPath,
mkPathAbsOrRel,
mkPathFileOrDir,
mkAbsPath,
mkAbsPathFromCwd,
(</>),
(<.>),
(<++>),
addExtension,
combine,
dropExtension,
dropExtensions,
dropFileName,
replaceExtension,
replaceBaseName,
replaceDirectory,
replaceFileName,
splitExtension,
splitExtensions,
splitFileName,
splitDirName,
takeBaseName,
takeDirectory,
takeSuperDirectory,
takeExtension,
takeExtensions,
takeFileName,
takeDirName,
mapFileName,
mapFileNameF,
equalFilePath,
joinPath,
normalise,
splitPath,
makeRelative,
makeRelativeMaybe,
makeAbsolute,
makeAbsoluteFromCwd,
dynamicMakeAbsolute,
dynamicMakeAbsoluteFromCwd,
genericMakeAbsolute,
genericMakeAbsoluteFromCwd,
pathMap,
dirFromFile,
fileFromDir,
toFileDir,
fromFileDir,
fileFromFileDir,
dirFromFileDir,
toAbsRel,
fromAbsRel,
isAbsolute,
isRelative,
isAbsoluteString,
isRelativeString,
hasAnExtension,
hasExtension,
System(..),
extSeparator,
searchPathSeparator,
isExtSeparator,
isSearchPathSeparator,
genericAddExtension,
genericDropExtension,
genericDropExtensions,
genericSplitExtension,
genericSplitExtensions,
genericTakeExtension,
genericTakeExtensions,
testAll,
isValid,
)
where
import qualified System.Path.Internal.PartClass as Class
import qualified System.Path.Internal.Part as Part
import System.Path.Internal.PartClass as Class
(WrapFileDir(WrapFileDir), WrapAbsRel(WrapAbsRel), FuncArg(..), fdMap)
import System.Path.Internal.Part
(PathComponent(PathComponent), GenComponent, System(..),
absPC, emptyPC, retagPC, untagPC, pcMap)
import qualified System.Directory as SD
import qualified Control.Monad.Trans.Class as MT
import qualified Control.Monad.Trans.State as MS
import Control.Monad (MonadPlus, guard, liftM2, mplus, mzero)
import Control.Applicative (Const(Const), liftA2, (<$>), (<$))
import Control.DeepSeq (NFData(rnf))
import qualified Data.Monoid.HT as MonHT
import qualified Data.List.HT as ListHT
import Data.Tagged (Tagged(Tagged), untag)
import Data.Functor.Compose (Compose(Compose), getCompose)
import Data.List (isSuffixOf, isPrefixOf, stripPrefix, intersperse)
import Data.String (IsString(fromString))
import Data.Maybe.HT (toMaybe)
import Data.Maybe (fromMaybe, maybeToList)
import Data.Tuple.HT (mapFst, mapSnd)
import Data.Monoid (Monoid(mempty, mappend, mconcat), Endo(Endo), appEndo)
import Data.Semigroup (Semigroup(sconcat, (<>)), )
import Data.Char (isSpace)
import Data.Ord.HT (comparing)
import Data.Eq.HT (equating)
import Text.Show.HT (concatS)
import Text.Printf (printf)
import qualified Test.QuickCheck as QC
import Test.QuickCheck
(Gen, Property, property, Arbitrary(arbitrary), frequency)
import qualified Prelude as P
import Prelude hiding (FilePath, maybe, abs)
data Path os ar fd = Path ar [PathComponent os] fd
instance
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Eq (Path os ar fd) where
(==) = equating inspectPath
instance
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Ord (Path os ar fd) where
compare = comparing inspectPath
inspectPath ::
Path os ar fd -> (WrapAbsRel os ar, [PathComponent os], WrapFileDir os fd)
inspectPath (Path ar pcs fd) = (WrapAbsRel ar, pcs, WrapFileDir fd)
selTag :: Path os ar fd -> Tagged os a -> a
selTag _ = untag
type AbsFile os = Path os Part.Abs Part.File
type RelFile os = Path os Part.Rel Part.File
type AbsDir os = Path os Part.Abs Part.Dir
type RelDir os = Path os Part.Rel Part.Dir
type AbsRelFile os = Path os Part.AbsRel Part.File
type AbsRelDir os = Path os Part.AbsRel Part.Dir
type AbsFileDir os = Path os Part.Abs Part.FileDir
type RelFileDir os = Path os Part.Rel Part.FileDir
type AbsRelFileDir os = Path os Part.AbsRel Part.FileDir
type Abs os fd = Path os Part.Abs fd
type Rel os fd = Path os Part.Rel fd
type File os ar = Path os ar Part.File
type Dir os ar = Path os ar Part.Dir
type AbsRel os fd = Path os Part.AbsRel fd
type FileDir os ar = Path os ar Part.FileDir
type AbsPath os fd = Path os Part.Abs fd
type RelPath os fd = Path os Part.Rel fd
type FilePath os ar = Path os ar Part.File
type DirPath os ar = Path os ar Part.Dir
type AbsRelPath os fd = Path os Part.AbsRel fd
type FileDirPath os ar = Path os ar Part.FileDir
instance (Class.AbsRel ar, Class.FileDir fd) => NFData (Path os ar fd) where
rnf (Path ar pcs fd) =
rnf (Class.withAbsRel rnf () ar, pcs, Class.withFileDir rnf () () fd)
pathMap ::
(Class.FileDir fd) => (String -> String) -> Path os ar fd -> Path os ar fd
pathMap f (Path ar pcs fd) = Path ar (map (pcMap f) pcs) (fdMap f fd)
mapFilePart ::
(GenComponent -> GenComponent) -> FilePath os ar -> FilePath os ar
mapFilePart f (Path ar pcs (Part.File fd)) = Path ar pcs $ Part.File $ f fd
mapFilePartF ::
(Functor f) =>
(GenComponent -> f GenComponent) -> FilePath os ar -> f (FilePath os ar)
mapFilePartF f (Path ar pcs (Part.File fd)) =
Path ar pcs <$> Part.File <$> f fd
splitFilePart ::
(GenComponent -> (GenComponent, a)) -> FilePath os ar -> (FilePath os ar, a)
splitFilePart f (Path ar pcs (Part.File fd)) = mapFst (Path ar pcs . Part.File) $ f fd
mapPathDirs ::
([PathComponent os] -> [PathComponent os]) -> Path os ar fd -> Path os ar fd
mapPathDirs f ~(Path ar pcs fd) = Path ar (f pcs) fd
withAbsRel ::
(Class.AbsRel ar) =>
(AbsPath os fd -> a) -> (RelPath os fd -> a) -> Path os ar fd -> a
withAbsRel fAbs fRel (Path ar pcs fd) =
Class.withAbsRel
(\drive -> fAbs $ Path (Part.Abs (PathComponent drive)) pcs fd)
(fRel $ Path Part.Rel pcs fd)
ar
switchFileDir ::
(Class.FileDir fd) =>
f (FilePath os ar) -> f (DirPath os ar) -> f (FileDirPath os ar) ->
f (Path os ar fd)
switchFileDir f d fd =
getCompose $ Class.switchFileDir (Compose f) (Compose d) (Compose fd)
switchFileOrDir ::
(Class.FileOrDir fd) =>
f (FilePath os ar) -> f (DirPath os ar) -> f (Path os ar fd)
switchFileOrDir f d =
getCompose $ Class.switchFileOrDir (Compose f) (Compose d)
withFileDir ::
(Class.FileOrDir fd) =>
(FilePath os ar -> a) -> (DirPath os ar -> a) -> Path os ar fd -> a
withFileDir f g = runFuncArg $ switchFileOrDir (FuncArg f) (FuncArg g)
eitherFromAbsRel ::
Class.AbsRel ar => Path os ar fd -> Either (AbsPath os fd) (RelPath os fd)
eitherFromAbsRel = withAbsRel Left Right
_eitherFromFileDir ::
Class.FileOrDir fd => Path os ar fd -> Either (FilePath os ar) (DirPath os ar)
_eitherFromFileDir = withFileDir Left Right
instance
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Show (Path os ar fd) where
showsPrec = untag showsPrecTagged
showsPrecTagged ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Tagged os (Int -> Path os ar fd -> ShowS)
showsPrecTagged =
flip fmap rootStringTagged $ \root d x ->
case pathComponents x of
(ar, pcs) ->
showParen (d>5) $ concatS $
intersperse
(showChar ' ' . showString combineOperator . showChar ' ') $
Class.withAbsRel
(\drive ->
if drive == root
then showString rootName
else showsCons absDirName drive)
(showString currentName)
ar :
map (\(PathComponent pc) -> showsCons relPathName pc) pcs
showsCons :: Show a => String -> a -> ShowS
showsCons name arg = showString name . showChar ' ' . showsPrec 11 arg
instance
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Read (Path os ar fd) where
readsPrec d = readParen (d>5) $ untag readsPrecTagged
readsPrecTagged ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Tagged os (ReadS (Path os ar fd))
readsPrecTagged =
flip fmap readsSplitDrive $ \readsSplDrv ->
let go =
handleMismatch
(skipSpaces >> matchString combineOperator)
(return [])
(liftM2 (:) (fmap PathComponent $ readsCons relPathName) go)
in MS.runStateT $ do
skipSpaces
MT.lift . maybeToList =<<
liftM2 maybePathFromComponents readsSplDrv go
skipSpaces :: (Monad m) => MS.StateT String m ()
skipSpaces = MS.modify $ dropWhile isSpace
readsCons :: (Read a) => String -> MS.StateT String [] a
readsCons name = do
skipSpaces
matchString name
MS.StateT $ readsPrec 11
handleMismatch ::
MS.StateT s Maybe () ->
MS.StateT s m a -> MS.StateT s m a -> MS.StateT s m a
handleMismatch act err success =
MS.StateT $ \s0 ->
case MS.execStateT act s0 of
Nothing -> MS.runStateT err s0
Just s1 -> MS.runStateT success s1
matchString :: (MonadPlus m) => String -> MS.StateT String m ()
matchString prefix =
MS.StateT $ P.maybe mzero (return . (,) ()) . stripPrefix prefix
readsSplitDrive ::
(System os, Class.AbsRel ar) => Tagged os (MS.StateT String [] ar)
readsSplitDrive =
flip fmap readsSplitDriveAbs $ \readsSplDrvAbs ->
Class.switchAbsRel
readsSplDrvAbs
readsSplitDriveRel
(mplus
(fmap (\(Part.Abs drive) -> Part.AbsO drive) readsSplDrvAbs)
(fmap (\Part.Rel -> Part.RelO) readsSplitDriveRel))
readsSplitDriveAbs :: (System os) => Tagged os (MS.StateT String [] Part.Abs)
readsSplitDriveAbs =
flip fmap rootStringTagged $ \root ->
fmap absPC $
(root <$ matchString rootName)
`mplus`
readsCons absDirName
readsSplitDriveRel :: (MonadPlus m) => MS.StateT String m Part.Rel
readsSplitDriveRel = matchString currentName >> return Part.Rel
toString ::
(System os, Class.AbsRel ar, Class.FileDir fd) => Path os ar fd -> String
toString = flip toStringS ""
getPathString ::
(System os, Class.AbsRel ar, Class.FileDir fd) => Path os ar fd -> String
getPathString = toString
toStringS ::
(System os, Class.AbsRel ar, Class.FileDir fd) => Path os ar fd -> ShowS
toStringS x =
case pathComponents x of
(ar, []) ->
Class.withAbsRel showString (showString currentDirComponent) ar
(ar, pcs) ->
concatS $
Class.withAbsRel (\drive -> (showString drive :)) id ar $
intersperse (showChar (selTag x pathSeparator)) $
map (\(PathComponent pc) -> showString pc) pcs
prop_asPath_toString :: (System os) => AbsFile os -> Property
prop_asPath_toString p = property $ p == asPath (toString p)
rootDir :: (System os) => AbsDir os
rootDir = untag rootDirTagged
rootDirTagged :: (System os) => Tagged os (AbsDir os)
rootDirTagged = fmap (\root -> Path (absPC root) [] Part.Dir) rootStringTagged
rootStringTagged :: (System os) => Tagged os String
rootStringTagged = fmap (\sep -> [sep]) pathSeparator
currentDir :: (System os) => RelDir os
currentDir = mempty
emptyFile :: (System os) => RelFile os
emptyFile = atomicFile $ Part.File emptyPC
atomicFile :: Part.File -> RelFile os
atomicFile = Path Part.Rel []
rootName :: String
rootName = "rootDir"
currentName :: String
currentName = "currentDir"
currentDirComponent :: String
currentDirComponent = "."
absDirName :: String
absDirName = "absDir"
relPathName :: String
relPathName = "relPath"
maybe, maybePath ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
String -> Maybe (Path os ar fd)
maybe str = do
let (ar0, pcs0, fd0) = untag makePathComponents str
ar <- Class.fromAbsRel ar0
(pcs, fd) <-
case fd0 of
Left Part.FileDir -> arrangeComponents pcs0
Right Part.Dir ->
fmap ((,) pcs0) $
Class.switchFileDir Nothing (Just Part.Dir) (Just Part.FileDir)
return $ Path ar pcs fd
maybePath = maybe
parse, parsePath ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
String -> Either String (Path os ar fd)
parse = pathWithNames arName fdName
parsePath = parse
pathWithNames ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Const String ar -> Const String fd ->
String -> Either String (Path os ar fd)
pathWithNames (Const ar) (Const fd) str =
P.maybe (Left (printf "\"%s\" is not a valid %s%spath" str ar fd)) Right $
maybePath str
arName :: (Class.AbsRel ar) => Const String ar
arName = Class.switchAbsRel (Const "absolute ") (Const "relative ") (Const "")
fdName :: (Class.FileDir fd) => Const String fd
fdName = Class.switchFileDir (Const "file ") (Const "directory ") (Const "")
path ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
String -> Path os ar fd
path = either error id . parsePath
relFile :: (System os) => String -> RelFile os
relFile = path
relDir :: (System os) => String -> RelDir os
relDir = path
absFile :: (System os) => String -> AbsFile os
absFile = path
absDir :: (System os) => String -> AbsDir os
absDir = path
rel :: (System os, Class.FileDir fd) => String -> Rel os fd
rel = path
abs :: (System os, Class.FileDir fd) => String -> Abs os fd
abs = path
absRel :: (System os, Class.FileDir fd) => String -> AbsRel os fd
absRel = path
file :: (System os, Class.AbsRel ar) => String -> File os ar
file = path
dir :: (System os, Class.AbsRel ar) => String -> Dir os ar
dir = path
fileDir :: (System os, Class.AbsRel ar) => String -> FileDir os ar
fileDir = path
relPath :: (System os, Class.FileDir fd) => String -> RelPath os fd
relPath = path
absPath :: (System os, Class.FileDir fd) => String -> AbsPath os fd
absPath = path
filePath :: (System os, Class.AbsRel ar) => String -> FilePath os ar
filePath = path
dirPath :: (System os, Class.AbsRel ar) => String -> DirPath os ar
dirPath = path
idAbsRel :: AbsRelPath os fd -> AbsRelPath os fd
idAbsRel = id
idAbs :: AbsPath os fd -> AbsPath os fd
idAbs = id
idRel :: RelPath os fd -> RelPath os fd
idRel = id
idFileDir :: FileDirPath os fd -> FileDirPath os fd
idFileDir = id
idFile :: FilePath os fd -> FilePath os fd
idFile = id
idDir :: DirPath os fd -> DirPath os fd
idDir = id
asPath ::
(System os, Class.AbsRel ar, Class.FileDir fd) => String -> Path os ar fd
asPath = uncurry mkPathFromComponents . untag mkPathComponents
asRelFile :: (System os) => String -> RelFile os
asRelFile = asPath
asRelDir :: (System os) => String -> RelDir os
asRelDir = asPath
asAbsFile :: (System os) => String -> AbsFile os
asAbsFile = asPath
asAbsDir :: (System os) => String -> AbsDir os
asAbsDir = asPath
asRelPath :: (System os, Class.FileDir fd) => String -> RelPath os fd
asRelPath = asPath
asAbsPath :: (System os, Class.FileDir fd) => String -> AbsPath os fd
asAbsPath = asPath
asFilePath :: (System os, Class.AbsRel ar) => String -> FilePath os ar
asFilePath = asPath
asDirPath :: (System os, Class.AbsRel ar) => String -> DirPath os ar
asDirPath = asPath
instance
(ForbiddenSystem os, ForbiddenAbsRel ar, ForbiddenFileDir fd) =>
IsString (Path os ar fd) where fromString = forbiddenFromString
class System os => ForbiddenSystem os where
forbiddenFromString :: String -> Path os ar fd
class Class.AbsRel ar => ForbiddenAbsRel ar where
class Class.FileDir fd => ForbiddenFileDir fd where
mkPathAbsOrRel, mkPathAbsOrRelPriv ::
(System os, Class.FileDir fd) =>
String -> Either (AbsPath os fd) (RelPath os fd)
mkPathAbsOrRel = mkPathAbsOrRelPriv
mkPathAbsOrRelPriv = eitherFromAbsRel . absRel
mkPathFileOrDir ::
(System os, Class.AbsRel ar) =>
String -> IO (Maybe (Either (FilePath os ar) (DirPath os ar)))
mkPathFileOrDir s = do
isfile <- SD.doesFileExist s
isdir <- SD.doesDirectoryExist s
case (isfile, isdir) of
(False, False) -> return Nothing
(True, False) -> return $ Just $ Left $ path s
(False, True ) -> return $ Just $ Right $ path s
(True, True ) -> ioError $ userError "mkPathFileOrDir - object type changed while checking"
mkAbsPath ::
(System os, Class.FileDir fd) => AbsDir os -> String -> AbsPath os fd
mkAbsPath d = either id (makeAbsolute d) . mkPathAbsOrRelPriv
mkAbsPathFromCwd ::
(System os, Class.FileDir fd) => String -> IO (AbsPath os fd)
mkAbsPathFromCwd = either return makeAbsoluteFromCwd . mkPathAbsOrRelPriv
mkPathFromComponents ::
(Class.FileDir fd) => ar -> [PathComponent os] -> Path os ar fd
mkPathFromComponents ar pcs =
uncurry (Path ar) $
Class.switchFileDir
(mapSnd Part.File $
ListHT.switchR ([], emptyPC) (curry $ mapSnd untagPC) pcs)
(pcs, Part.Dir)
(pcs, Part.FileDir)
maybePathFromComponents ::
(Class.FileDir fd) => ar -> [PathComponent os] -> Maybe (Path os ar fd)
maybePathFromComponents ar pcs =
fmap (uncurry $ Path ar) $ arrangeComponents pcs
arrangeComponents ::
(Class.FileDir fd) => [PathComponent os] -> Maybe ([PathComponent os], fd)
arrangeComponents pcs =
getCompose $
Class.switchFileDir
(Compose $ fmap (mapSnd (Part.File . untagPC)) $ ListHT.viewR pcs)
(Compose $ Just (pcs, Part.Dir))
(Compose $ Just (pcs, Part.FileDir))
mkPathComponents ::
(System os, Class.AbsRel ar) =>
Tagged os (String -> (ar, [PathComponent os]))
mkPathComponents =
liftA2
(\isSep splDriveOS ->
mapSnd (nonEmptyComponents . ListHT.chop isSep)
. MS.runState splDriveOS)
isPathSeparator splitDriveOS
makePathComponents ::
(System os) =>
Tagged os (String -> (Part.AbsRel, [PathComponent os], Either Part.FileDir Part.Dir))
makePathComponents =
liftA2
(\isSep splAbsolute str ->
let (ar, pct) =
mapSnd (ListHT.chop isSep) $
MS.runState splAbsolute str
(pcs1, fd) =
case ListHT.viewR pct of
Nothing -> ([], Right Part.Dir)
Just (pcs, pc) ->
if null pc
then (pcs, Right Part.Dir)
else (pct, Left Part.FileDir)
in (ar, nonEmptyComponents pcs1, fd))
isPathSeparator splitAbsoluteO
nonEmptyComponents :: [String] -> [PathComponent os]
nonEmptyComponents = map PathComponent . filter (not . null)
splitDriveOS ::
(System os, Class.AbsRel ar) => Tagged os (MS.State String ar)
splitDriveOS =
liftA2
(\splDrive splAbsolute ->
Class.switchAbsRel (fmap absPC splDrive) (return Part.Rel) splAbsolute)
splitDriveAbs splitAbsoluteO
splitDriveAbs :: (System os) => Tagged os (MS.State String String)
splitDriveAbs =
liftA2
(\isSep splDrive -> do
drive <- splDrive
xt <- MS.get
case xt of
[] -> return drive
x:xs ->
if isSep x
then MS.put xs >> return (drive++[x])
else return drive)
isPathSeparator splitDrive
splitAbsoluteO :: (System os) => Tagged os (MS.State String Part.AbsRel)
splitAbsoluteO =
fmap (\drive -> if null drive then Part.RelO else Part.AbsO $ PathComponent drive)
<$>
splitAbsolute
pathComponents ::
(Class.FileDir fd) => Path os ar fd -> (ar, [PathComponent os])
pathComponents (Path ar pcs fd) =
(ar, pcs ++ Class.withFileDir ((:[]) . retagPC) [] [] fd)
prop_mkPathFromComponents_pathComponents :: (System os) => AbsDir os -> Property
prop_mkPathFromComponents_pathComponents p =
property $ uncurry mkPathFromComponents (pathComponents p) == p
combineOperator :: String
combineOperator = "</>"
instance (Class.Rel ar, Class.Dir fd) => Semigroup (Path os ar fd) where
Path r pcs0 _dir <> Path _rel pcs1 d = Path r (pcs0 ++ pcs1) d
sconcat paths =
Path Class.relVar
(sconcat $ fmap (\(Path _rel pcs _dir) -> pcs) paths) Class.dirVar
instance (Class.Rel ar, Class.Dir fd) => Monoid (Path os ar fd) where
mempty = Path Class.relVar [] Class.dirVar
mappend (Path r pcs0 _dir) (Path _rel pcs1 d) = Path r (pcs0 ++ pcs1) d
mconcat paths =
Path Class.relVar
(concatMap (\(Path _rel pcs _dir) -> pcs) paths) Class.dirVar
(</>) :: DirPath os ar -> RelPath os fd -> Path os ar fd
Path ar pcs0 Part.Dir </> Path Part.Rel pcs1 fd = Path ar (pcs0 ++ pcs1) fd
infixr 5 </>
(<.>) :: FilePath os ar -> String -> FilePath os ar
p <.> ext = mapFilePart (flip addExtensionPC ext) p
infixl 7 <.>
(<++>) :: FilePath os ar -> String -> FilePath os ar
p <++> str = mapFileName (++str) p
infixl 7 <++>
addExtension :: FilePath os ar -> String -> FilePath os ar
addExtension = (<.>)
combine :: DirPath os ar -> RelPath os fd -> Path os ar fd
combine = (</>)
prop_combine_currentDir :: (System os) => RelDir os -> Property
prop_combine_currentDir p = property $ combine currentDir p == p
dropExtension :: FilePath os ar -> FilePath os ar
dropExtension = fst . splitExtension
dropExtensions :: FilePath os ar -> FilePath os ar
dropExtensions = fst . splitExtensions
dropFileName :: FilePath os ar -> DirPath os ar
dropFileName = fst . splitFileName
replaceExtension :: FilePath os ar -> String -> FilePath os ar
replaceExtension p ext = dropExtension p <.> ext
replaceBaseName :: FilePath os ar -> String -> FilePath os ar
replaceBaseName p bn =
mapFilePart (addExtensionPC (PathComponent bn) . snd . splitExtensionPC) p
replaceDirectory :: FilePath os ar1 -> DirPath os ar2 -> FilePath os ar2
replaceDirectory (Path _ _ fd) (Path ar pcs _) = Path ar pcs fd
replaceFileName :: FilePath os ar -> String -> FilePath os ar
replaceFileName p fn = mapFilePart (const (PathComponent fn)) p
splitExtension :: FilePath os ar -> (FilePath os ar, String)
splitExtension = splitFilePart splitExtensionPC
splitExtensions :: FilePath os ar -> (FilePath os ar, String)
splitExtensions = splitFilePart splitExtensionsPC
prop_split_combineExt :: (System os) => AbsFile os -> Property
prop_split_combineExt p = property $ p == uncurry (<.>) (splitExtension p)
splitFileName :: FilePath os ar -> (DirPath os ar, RelFile os)
splitFileName (Path ar pcs fd) = (Path ar pcs Part.Dir, atomicFile fd)
prop_split_combine :: (System os) => AbsFile os -> Property
prop_split_combine p = property $ uncurry combine (splitFileName p) == p
splitDirName :: DirPath os ar -> Maybe (DirPath os ar, RelDir os)
splitDirName = fmap (mapSnd dirFromFile . splitFileName) . fileFromDir
prop_splitDir_combine :: (System os) => AbsDir os -> Property
prop_splitDir_combine p =
property $
(uncurry combine <$> splitDirName p) == toMaybe (not $ isDrive p) p
takeBaseName :: FilePath os ar -> RelFile os
takeBaseName = takeFileName . dropExtension
takeDirectory :: FilePath os ar -> DirPath os ar
takeDirectory = fst . splitFileName
takeSuperDirectory :: DirPath os ar -> Maybe (DirPath os ar)
takeSuperDirectory = fmap takeDirectory . fileFromDir
takeExtension :: FilePath os ar -> String
takeExtension = snd . splitExtension
takeExtensions :: FilePath os ar -> String
takeExtensions = snd . splitExtensions
takeFileName :: FilePath os ar -> RelFile os
takeFileName (Path _ _ fd) = atomicFile fd
prop_takeFileName_end :: (System os) => AbsFile os -> Property
prop_takeFileName_end p =
property $ toString (takeFileName p) `isSuffixOf` toString p
takeDirName :: DirPath os ar -> Maybe (RelDir os)
takeDirName = fmap snd . splitDirName
prop_takeDirName_end :: (System os) => AbsDir os -> Property
prop_takeDirName_end p =
property $
fmap (\d -> toString d `isSuffixOf` toString p) (takeDirName p)
==
toMaybe (not $ isDrive p) True
mapFileName :: (String -> String) -> FilePath os ar -> FilePath os ar
mapFileName = mapFilePart . pcMap
mapFileNameF ::
(Functor f) =>
(String -> f String) -> FilePath os ar -> f (FilePath os ar)
mapFileNameF = mapFilePartF . Part.pcMapF
equalFilePath :: (System os) => Tagged os (String -> String -> Bool)
equalFilePath = equating <$> mkPathAbsOrRelTagged
mkPathAbsOrRelTagged ::
(System os) =>
Tagged os (String -> Either (AbsFileDir os) (RelFileDir os))
mkPathAbsOrRelTagged = Tagged mkPathAbsOrRelPriv
joinPath :: (Class.FileDir fd) => [String] -> RelPath os fd
joinPath = mkPathFromComponents Part.Rel . map PathComponent
normalise :: (System os) => Path os ar fd -> Path os ar fd
normalise = mapPathDirs (filter (PathComponent currentDirComponent /=))
splitPath ::
(Class.AbsRel ar, Class.FileOrDir fd) =>
Path os ar fd -> (Bool, [RelDir os], Maybe (RelFile os))
splitPath (Path ar pcs fd) =
(Class.isAbsolute ar,
map (\pc -> Path Part.Rel [pc] Part.Dir) pcs,
maybeFileDir fd)
maybeFileDir :: (Class.FileOrDir fd) => fd -> Maybe (RelFile os)
maybeFileDir = Class.withFileOrDir (Just . atomicFile . Part.File) Nothing
makeRelative ::
(System os, Class.FileDir fd) =>
AbsDir os -> AbsPath os fd -> RelPath os fd
makeRelative relTo orig =
fromMaybe
(error $
printf "System.Path can't make (%s) relative to (%s)"
(toString orig) (toString relTo)) $
makeRelativeMaybe relTo orig
makeRelativeMaybe ::
(System os, Class.FileDir fd) =>
AbsDir os -> AbsPath os fd -> Maybe (RelPath os fd)
makeRelativeMaybe relTo orig =
case (inspectPath relTo, inspectPath orig) of
((relToAR, relToPCs, WrapFileDir Part.Dir),
(origAR, origPCs, WrapFileDir fd)) ->
fmap (flip (Path Part.Rel) fd) $
guard (relToAR == origAR) >> stripPrefix relToPCs origPCs
makeAbsolute :: (System os) => AbsDir os -> RelPath os fd -> AbsPath os fd
makeAbsolute = genericMakeAbsolute
makeAbsoluteFromCwd :: (System os) => RelPath os fd -> IO (AbsPath os fd)
makeAbsoluteFromCwd = genericMakeAbsoluteFromCwd
dynamicMakeAbsolute ::
(System os) => AbsDir os -> AbsRelPath os fd -> AbsPath os fd
dynamicMakeAbsolute = genericMakeAbsolute
dynamicMakeAbsoluteFromCwd ::
(System os) => AbsRelPath os fd -> IO (AbsPath os fd)
dynamicMakeAbsoluteFromCwd = genericMakeAbsoluteFromCwd
genericMakeAbsolute ::
(System os, Class.AbsRel ar) => AbsDir os -> Path os ar fd -> AbsPath os fd
genericMakeAbsolute base p = withAbsRel id (base </>) p
genericMakeAbsoluteFromCwd ::
(System os, Class.AbsRel ar) => Path os ar fd -> IO (AbsPath os fd)
genericMakeAbsoluteFromCwd p = do
cwdString <- SD.getCurrentDirectory
return $ genericMakeAbsolute (asAbsDir cwdString) p
prop_makeAbsoluteFromDir_endSame ::
(System os) => AbsDir os -> RelFile os -> Property
prop_makeAbsoluteFromDir_endSame base p =
property $ toString p `isSuffixOf` toString (makeAbsolute base p)
prop_makeAbsoluteFromDir_startSame ::
(System os) => AbsDir os -> RelFile os -> Property
prop_makeAbsoluteFromDir_startSame base p =
property $ toString base `isPrefixOf` toString (makeAbsolute base p)
dirFromFile :: FilePath os ar -> DirPath os ar
dirFromFile p = uncurry Path (pathComponents p) Part.Dir
fileFromDir :: DirPath os ar -> Maybe (FilePath os ar)
fileFromDir = fileFromAny
toFileDir :: (Class.FileDir fd) => Path os ar fd -> FileDirPath os ar
toFileDir p = uncurry Path (pathComponents p) Part.FileDir
fromFileDir ::
(Class.FileDir fd) => FileDirPath os ar -> Maybe (Path os ar fd)
fromFileDir p =
switchFileDir
(fileFromFileDir p)
(Just $ dirFromFileDir p)
(Just p)
fileFromFileDir :: FileDirPath os ar -> Maybe (FilePath os ar)
fileFromFileDir = fileFromAny
fileFromAny :: Path os ar fd -> Maybe (FilePath os ar)
fileFromAny (Path ar pcs _) =
fmap (uncurry (Path ar) . mapSnd (Part.File . untagPC)) $ ListHT.viewR pcs
dirFromFileDir :: FileDirPath os ar -> DirPath os ar
dirFromFileDir (Path ar pcs Part.FileDir) = Path ar pcs Part.Dir
toAbsRel :: (Class.AbsRel ar) => Path os ar fd -> AbsRelPath os fd
toAbsRel (Path ar pcs fd) = Path (Class.toAbsRel ar) pcs fd
fromAbsRel :: (Class.AbsRel ar) => AbsRelPath os fd -> Maybe (Path os ar fd)
fromAbsRel (Path ar0 pcs fd) = (\ar -> Path ar pcs fd) <$> Class.fromAbsRel ar0
isDrive :: AbsDir os -> Bool
isDrive (Path _ pcs _) = null pcs
isAbsolute :: Class.AbsRel ar => Path os ar fd -> Bool
isAbsolute = withAbsRel (const True) (const False)
isRelative :: Class.AbsRel ar => Path os ar fd -> Bool
isRelative = not . isAbsolute
isAbsoluteString :: (System os) => Tagged os (String -> Bool)
isAbsoluteString =
fmap (\split -> not . null . MS.evalState split) splitAbsolute
isRelativeString :: (System os) => Tagged os (String -> Bool)
isRelativeString = (not .) <$> isAbsoluteString
hasAnExtension :: FilePath os ar -> Bool
hasAnExtension = not . null . snd . splitExtension
hasExtension :: String -> FilePath os ar -> Bool
hasExtension ext = (==ext) . snd . splitExtension
extSeparator :: Char
extSeparator = '.'
searchPathSeparator :: Char
searchPathSeparator = ':'
isExtSeparator :: Char -> Bool
isExtSeparator = (== extSeparator)
isSearchPathSeparator :: Char -> Bool
isSearchPathSeparator = (== searchPathSeparator)
genericAddExtension ::
(Class.FileDir fd) => Path os ar fd -> String -> Path os ar fd
genericAddExtension =
flip $ \ext ->
appEndo $ MonHT.when (not $ null ext) $
switchFileDir
(Endo $ flip addExtension ext)
(Endo $ componentsAddExtension ext)
(Endo $ componentsAddExtension ext)
componentsAddExtension :: String -> Path os ar fd -> Path os ar fd
componentsAddExtension ext (Path ar pcs0 fd) =
let pcs = if null pcs0 then [emptyPC] else pcs0
in Path ar (mapLast (flip addExtensionPC ext) pcs) fd
genericDropExtension :: (Class.FileDir fd) => Path os ar fd -> Path os ar fd
genericDropExtension = fst . genericSplitExtension
genericDropExtensions :: (Class.FileDir fd) => Path os ar fd -> Path os ar fd
genericDropExtensions = fst . genericSplitExtensions
genericSplitExtension ::
(Class.FileDir fd) => Path os ar fd -> (Path os ar fd, String)
genericSplitExtension =
runSplitExtension $
switchFileDir
(SplitExtension splitExtension)
(SplitExtension componentsSplitExtension)
(SplitExtension componentsSplitExtension)
componentsSplitExtension :: Path os ar b -> (Path os ar b, String)
componentsSplitExtension (Path ar pcs fd) =
mapFst (flip (Path ar) fd) $
mapLastPair
(error "genericSplitExtension: empty path")
splitExtensionPC pcs
genericSplitExtensions ::
(Class.FileDir fd) => Path os ar fd -> (Path os ar fd, String)
genericSplitExtensions =
runSplitExtension $
switchFileDir
(SplitExtension splitExtensions)
(SplitExtension componentsSplitExtensions)
(SplitExtension componentsSplitExtensions)
componentsSplitExtensions :: Path os ar b -> (Path os ar b, String)
componentsSplitExtensions (Path ar pcs fd) =
mapFst (flip (Path ar) fd) $
mapLastPair
(error "genericSplitExtensions: empty path")
splitExtensionsPC pcs
genericTakeExtension :: (Class.FileDir fd) => Path os ar fd -> String
genericTakeExtension = snd . genericSplitExtension
genericTakeExtensions :: (Class.FileDir fd) => Path os ar fd -> String
genericTakeExtensions = snd . genericSplitExtension
newtype
SplitExtension path =
SplitExtension {runSplitExtension :: path -> (path, String)}
mapLast :: (a -> a) -> [a] -> [a]
mapLast f xs = zipWith id (drop 1 $ map (const id) xs ++ [f]) xs
mapLastPair :: b -> (a -> (a,b)) -> [a] -> ([a], b)
mapLastPair b f =
ListHT.switchR ([], b) (\as a -> mapFst ((as++) . (:[])) $ f a)
mapLastPairFoldr :: b -> (a -> (a,b)) -> [a] -> ([a], b)
mapLastPairFoldr b _ [] = ([], b)
mapLastPairFoldr _ f (x:xs) =
foldr
(\y1 go y0 -> mapFst (y0:) $ go y1)
(\y -> mapFst (:[]) $ f y)
xs x
mapLastPairRec :: b -> (a -> (a,b)) -> [a] -> ([a], b)
mapLastPairRec b _ [] = ([], b)
mapLastPairRec _ f (x:xs) =
let go y [] = mapFst (:[]) $ f y
go y0 (y1:ys) = mapFst (y0:) $ go y1 ys
in go x xs
mapLastPairRev :: b -> (a -> (a,b)) -> [a] -> ([a], b)
mapLastPairRev b0 f xs =
case reverse xs of
[] -> (xs, b0)
y:ys ->
let (a, b) = f y
in (reverse ys ++ [a], b)
_prop_mapLastPair :: String -> Int -> [String] -> Bool
_prop_mapLastPair b n strs =
let f = splitAt n
in all (mapLastPair b f strs ==) $
mapLastPairFoldr b f strs :
mapLastPairRev b f strs :
mapLastPairRec b f strs :
[]
addExtensionPC :: PathComponent os -> String -> PathComponent os
addExtensionPC p "" = p
addExtensionPC (PathComponent pc) ext =
PathComponent $ pc ++
if [extSeparator] `isPrefixOf` ext
then ext
else extSeparator : ext
splitExtensionPC :: PathComponent os -> (PathComponent os, String)
splitExtensionPC (PathComponent s) =
mapFst PathComponent $
P.maybe (s, "") (mapFst concat) $
((\p@(pcs,_) -> toMaybe (not (null pcs)) p) =<<) $ ListHT.viewR $
ListHT.segmentBefore isExtSeparator s
_splitExtensionPC :: PathComponent os -> (PathComponent os, String)
_splitExtensionPC (PathComponent s) =
mapFst PathComponent $
case break isExtSeparator $ reverse s of
(_, "") -> (s, "")
(rext, dot:rstem) -> (reverse rstem, dot : reverse rext)
splitExtensionsPC :: PathComponent os -> (PathComponent os, String)
splitExtensionsPC (PathComponent s) =
mapFst PathComponent $ break isExtSeparator s
isValid ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Path os ar fd -> Bool
isValid = untag isValidTagged
isValidTagged ::
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Tagged os (Path os ar fd -> Bool)
isValidTagged =
fmap
(\isValidPC (Path ar pcs fd) ->
Class.withAbsRel isValidComponent True ar
&&
all isValidPC pcs
&&
Class.withFileDir (isValidPC . retagPC) True True fd)
isValidPathComponent
isValidComponent :: String -> Bool
isValidComponent = not . null
isValidPathComponent ::
(System os) => Tagged os (PathComponent os -> Bool)
isValidPathComponent =
fmap
(\isSep (PathComponent str) ->
isValidComponent str && not (any isSep str))
isPathSeparator
testAll :: (System os) => os -> [(String, IO ())]
testAll os =
("asPath_toString",
quickCheck os prop_asPath_toString) :
("mkPathFromComponents_pathComponents",
quickCheck os prop_mkPathFromComponents_pathComponents) :
("combine_currentDir",
quickCheck os prop_combine_currentDir) :
("makeAbsoluteFromDir_endSame",
quickCheck os prop_makeAbsoluteFromDir_endSame) :
("makeAbsoluteFromDir_startSame",
quickCheck os prop_makeAbsoluteFromDir_startSame) :
("split_combine",
quickCheck os prop_split_combine) :
("splitDir_combine",
quickCheck os prop_splitDir_combine) :
("takeFileName_end",
quickCheck os prop_takeFileName_end) :
("takeDirName_end",
quickCheck os prop_takeDirName_end) :
("split_combineExt",
quickCheck os prop_split_combineExt) :
[]
quickCheck ::
(QC.Testable prop, System os, Class.FileDir fd, Class.AbsRel ar) =>
os -> (Path os ar fd -> prop) -> IO ()
quickCheck _ = QC.quickCheck
qcFileComponent :: Gen (PathComponent os)
qcFileComponent = PathComponent <$> frequency [
(1, return "someFile"),
(1, return "fileWith.ext"),
(1, return "file.with.multiple.exts"),
(1, return "file with spcs")
]
qcDirComponent :: Gen (PathComponent os)
qcDirComponent = PathComponent <$> frequency [
(1, return "someDir"),
(1, return "aDir"),
(1, return "aFolder"),
(1, return "a folder"),
(1, return "directory")
]
qcAbsRel :: (System os, Class.AbsRel ar) => Tagged os (Gen ar)
qcAbsRel =
flip fmap genDrive $ \drive ->
Class.switchAbsRel (fmap absPC drive) (return Part.Rel)
(QC.oneof
[fmap (Part.AbsO . PathComponent) drive, return Part.RelO])
qcGenPath ::
Tagged os (Gen ar) ->
(Gen ar -> Gen (Path os ar fd)) ->
Gen (Path os ar fd)
qcGenPath qcAR gen = gen $ untag qcAR
qcFilePath :: (System os, Class.AbsRel ar) => Gen (FilePath os ar)
qcFilePath = qcGenPath qcAbsRel $ \qcAR -> do
ar <- qcAR
pcs <- QC.listOf qcDirComponent
pc <- qcFileComponent
return $ Path ar pcs $ Part.File pc
qcDirPath :: (System os, Class.AbsRel ar) => fd -> Gen (Path os ar fd)
qcDirPath fd = qcGenPath qcAbsRel $ \qcAR -> do
ar <- qcAR
pcs <- QC.listOf qcDirComponent
return $ Path ar pcs fd
qcPath ::
(System os, Class.AbsRel ar, Class.FileDir fd) => Gen (Path os ar fd)
qcPath =
switchFileDir qcFilePath (qcDirPath Part.Dir) (qcDirPath Part.FileDir)
instance
(System os, Class.AbsRel ar, Class.FileDir fd) =>
Arbitrary (Path os ar fd) where
arbitrary = qcPath