module System.FriendlyPath where import System.FilePath import System.Directory import Data.List -- | A version of canonicalizePath that works. -- Note that we cannot use canonicalizePath because if used on a non existing directory -- then the file part will be dropped (arguably this is a bug in canonicalizePath) -- eg. @x/y@ can become @x@, and we do not want that. canonicalizePathFix :: FilePath -> IO FilePath canonicalizePathFix f = do de <- doesDirectoryExist (takeDirectory f) if de then canonicalizePath f else makeAbsolute f -- The documentation for 'System.FilePath.makeRelative' is wrong. It says -- There is no corresponding makeAbsolute function, instead use -- System.Directory.canonicalizePath which has the same effect. -- canonicalizePath follows symlinks, and does not work if the directory does not exist. -- | Make a path absolute. makeAbsolute :: FilePath -> IO FilePath makeAbsolute f | isAbsolute' f = return f | otherwise = fmap ( f) getCurrentDirectory -- | Canonicalize a user-friendly path userToCanonPath :: FilePath -> IO String userToCanonPath f = canonicalizePathFix =<< expandTilda f -- | Make a path more user-friendly by replacing the home directory with tilda. recoverTilda :: FilePath -> IO String recoverTilda path = do home <- getHomeDirectory return $ if home `isPrefixOf` path then "~" ++ drop (length home) path else path -- | Turn a path into its canonicalized, user-friendly version. canonicalizePath' :: FilePath -> IO String canonicalizePath' f = recoverTilda =<< canonicalizePathFix f -- | Turn a user-friendly path into a computer-friendly path by expanding the leading tilda. expandTilda :: String -> IO FilePath expandTilda "~" = getHomeDirectory expandTilda s0 = do home <- getHomeDirectory return $ if (['~',pathSeparator] `isPrefixOf` s0) then home drop 2 s0 else s0 -- | Is a user-friendly path absolute? isAbsolute' :: String -> Bool isAbsolute' ('~':_) = True isAbsolute' p = isAbsolute p