system-canonicalpath 0.2.3.0 → 0.3.0.0
raw patch · 6 files changed
+220/−144 lines, 6 filesdep +chelldep +system-canonicalpathdep ~directorydep ~system-filepathPVP ok
version bump matches the API change (PVP)
Dependencies added: chell, system-canonicalpath
Dependency ranges changed: directory, system-filepath
API changes (from Hackage documentation)
- Filesystem.CanonicalPath: cpathToText :: CanonicalPath -> Text
- Filesystem.CanonicalPath: pathToText :: UnsafePath -> Text
- Filesystem.CanonicalPath: textToPath :: Text -> UnsafePath
- Filesystem.CanonicalPath: type UnsafePath = FilePath
+ Filesystem.CanonicalPath: fromPrelude :: FilePath -> FilePath
+ Filesystem.CanonicalPath: fromText :: Text -> FilePath
+ Filesystem.CanonicalPath: toPrelude :: FilePath -> FilePath
+ Filesystem.CanonicalPath: toText :: FilePath -> Either Text Text
+ Filesystem.CanonicalPath: toText' :: CanonicalPath -> Text
- Filesystem.CanonicalPath: canonicalPath :: MonadIO m => UnsafePath -> m CanonicalPath
+ Filesystem.CanonicalPath: canonicalPath :: MonadIO m => FilePath -> m CanonicalPath
- Filesystem.CanonicalPath: canonicalPathE :: MonadIO m => UnsafePath -> m (Either Text CanonicalPath)
+ Filesystem.CanonicalPath: canonicalPathE :: MonadIO m => FilePath -> m (Either Text CanonicalPath)
- Filesystem.CanonicalPath: canonicalPathM :: MonadIO m => UnsafePath -> m (Maybe CanonicalPath)
+ Filesystem.CanonicalPath: canonicalPathM :: MonadIO m => FilePath -> m (Maybe CanonicalPath)
- Filesystem.CanonicalPath: unsafePath :: CanonicalPath -> UnsafePath
+ Filesystem.CanonicalPath: unsafePath :: CanonicalPath -> FilePath
- Filesystem.CanonicalPath: writeFile' :: MonadIO m => CanonicalPath -> UnsafePath -> Text -> m ()
+ Filesystem.CanonicalPath: writeFile' :: MonadIO m => CanonicalPath -> FilePath -> Text -> m ()
- Filesystem.CanonicalPath.Directory: copyFile :: MonadIO m => CanonicalPath -> UnsafePath -> m CanonicalPath
+ Filesystem.CanonicalPath.Directory: copyFile :: MonadIO m => CanonicalPath -> FilePath -> m CanonicalPath
- Filesystem.CanonicalPath.Directory: copyFile_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()
+ Filesystem.CanonicalPath.Directory: copyFile_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
- Filesystem.CanonicalPath.Directory: createDirectory :: MonadIO m => CanonicalPath -> UnsafePath -> m CanonicalPath
+ Filesystem.CanonicalPath.Directory: createDirectory :: MonadIO m => CanonicalPath -> FilePath -> m CanonicalPath
- Filesystem.CanonicalPath.Directory: createDirectoryIfMissing :: MonadIO m => Bool -> CanonicalPath -> UnsafePath -> m CanonicalPath
+ Filesystem.CanonicalPath.Directory: createDirectoryIfMissing :: MonadIO m => Bool -> CanonicalPath -> FilePath -> m CanonicalPath
- Filesystem.CanonicalPath.Directory: createDirectoryIfMissing_ :: MonadIO m => Bool -> CanonicalPath -> UnsafePath -> m ()
+ Filesystem.CanonicalPath.Directory: createDirectoryIfMissing_ :: MonadIO m => Bool -> CanonicalPath -> FilePath -> m ()
- Filesystem.CanonicalPath.Directory: createDirectory_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()
+ Filesystem.CanonicalPath.Directory: createDirectory_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
- Filesystem.CanonicalPath.Directory: getAppUserDataDirectory :: MonadIO m => Text -> m UnsafePath
+ Filesystem.CanonicalPath.Directory: getAppUserDataDirectory :: MonadIO m => Text -> m FilePath
- Filesystem.CanonicalPath.Directory: getDirectoryContents :: MonadIO m => CanonicalPath -> m [UnsafePath]
+ Filesystem.CanonicalPath.Directory: getDirectoryContents :: MonadIO m => CanonicalPath -> m [FilePath]
- Filesystem.CanonicalPath.Directory: getTemporaryDirectory :: MonadIO m => m UnsafePath
+ Filesystem.CanonicalPath.Directory: getTemporaryDirectory :: MonadIO m => m FilePath
- Filesystem.CanonicalPath.Directory: renameDirectory :: MonadIO m => CanonicalPath -> UnsafePath -> m CanonicalPath
+ Filesystem.CanonicalPath.Directory: renameDirectory :: MonadIO m => CanonicalPath -> FilePath -> m CanonicalPath
- Filesystem.CanonicalPath.Directory: renameDirectory_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()
+ Filesystem.CanonicalPath.Directory: renameDirectory_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
- Filesystem.CanonicalPath.Directory: renameFile :: MonadIO m => CanonicalPath -> UnsafePath -> m CanonicalPath
+ Filesystem.CanonicalPath.Directory: renameFile :: MonadIO m => CanonicalPath -> FilePath -> m CanonicalPath
- Filesystem.CanonicalPath.Directory: renameFile_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()
+ Filesystem.CanonicalPath.Directory: renameFile_ :: MonadIO m => CanonicalPath -> FilePath -> m ()
Files
- Filesystem/CanonicalPath.hs +22/−11
- Filesystem/CanonicalPath/Directory.hs +18/−18
- Filesystem/CanonicalPath/Internal.hs +75/−109
- changelog.md +12/−0
- system-canonicalpath.cabal +23/−6
- tests/CanonicalPathTests.hs +70/−0
Filesystem/CanonicalPath.hs view
@@ -3,23 +3,33 @@ Copyright : (c) Boris Buliga, 2014 License : MIT Maintainer : d12frosted@icloud.com-Stability : stable-Portability : portable+Stability : experimental+Portability : POSIX -@Prelude.FilePath@ is just synonym for @String@, so actually it can be anything - your mothers name or path to file you want to edit. Just look at type signature of function @readFile :: FilePath -> IO String@ which can be converted to @readFile :: String -> IO String@. You can't be sure that it wont break your program in runtime.+'Prelude.FilePath' is very deceptive, because it's just a synonym for 'Prelude.String', so actually it can be anything - your mothers name or path to file you want to edit. Just look at the type signature of function 'Prelude.readFile': -OK, you can use @FilePath@ data type from @Filesystem.Path@ module instead of @Prelude.FilePath@. I really like this data type but it has the same problem as @Prelude.FilePath@ - you can't be sure, that it holds actual path to some file or directory on your system.+@readFile :: FilePath -> IO String@ -So here comes abstract type @CanonicalPath@. It solves main problem - @CanonicalPath@ points to existing file or directory. Well, to be honest, it can't guarantee that this path will refer to existing file or directory always (someone can remove or move it to another path - and it's almost impossible to be aware of such cruelty) - hopefully you can always check if @CanonicalPath@ is not /broken/ (using composition of any constructor and 'unsafePath'). But in most cases @CanonicalPath@ will help you avoid many problems and will make your routine less routine.+You can translate it as follows: -Oh, and last but not least, @CanonicalPath@ constructor extracts all environment variables. Also it treats @~@ as home directory.+@readFile :: String -> IO String@ +Well, it is known that 'IO' actions are dangerous by themselves. And here comes another problem - you need to be sure that the path you pass to function is at least well constructed. For this purpose you can use well known `Filesystem.Path.FilePath` data type. It solves a lot of problems and comes beefed with multiple cool utilities. And also it is build around 'Data.Text' instead of 'Prelude.String'. Awesome!++So why do we need yet another path library? The answer is simple - we want to use paths like @$HOME\/.app.cfg@, @~\/.zshrc@ or @\/full\/path\/to\/existing\/file\/or\/dir@ in our code without any additional overhead. 'CanonicalPath' is named so because it tries to canonicalize given path ('Filesystem.Path.FilePath' or 'Data.Text') using 'System.Directory.canonicalizePath` function. It also will extract any variables it finds in path (like @$VARNAME@, @%VARNAME%@ and special @~\/@). But these steps both may fail. Thats why this library provides functions that return @'Prelude.Maybe' 'CanonicalPath'@ or @'Prelude.Either' 'Data.Text' 'CanonicalPath'@.++'CanonicalPath' also comes with additional useful property. When it is created, it points to real file or directory. Honestly, it can't guarantee that this path will refer to existing file or directory always (someone can remove or move it to another path - and it's almost impossible to be aware of such cruelty), but you can always reconstruct 'CanonicalPath'.++One more thing about path canonicalization. As I mentioned before, under the hood it uses 'System.Directory.canonicalizePath' function. So here are two warnings. Firstly, it behaves differently on different platforms. Sometime too damn differently. So you better watch your steps. Secodly, it's impossible to guarantee that the implication @same file/dir \<=\> same canonicalizedPath@ holds in either direction: this function can make only a best-effort attempt.+ Happy Haskell Hacking! -}++{-# LANGUAGE NoImplicitPrelude #-}+ module Filesystem.CanonicalPath (-- * Abstract Type CanonicalPath- ,UnsafePath -- * Constructors ,canonicalPath@@ -37,9 +47,10 @@ ,appendFile -- * Conversion functions- ,pathToText- ,textToPath- ,cpathToText) where+ ,fromText+ ,toText+ ,toText'+ ,fromPrelude+ ,toPrelude) where import Filesystem.CanonicalPath.Internal-import Prelude ()
Filesystem/CanonicalPath/Directory.hs view
@@ -6,13 +6,14 @@ Stability : experimental Portability : portable -Redefinition of some functions from @System.Directory@ module. Some of them have different signature, because they need to work with @'CanonicalPath'@. For example, we can't create functions @createDirectory :: 'CanonicalPath' -> IO ()@, because it has no sense. How can we create directory that already exists? Instead we have function @createDirectory :: 'CanonicalPath' -> 'UnsafePath' -> IO 'CanonicalPath'@, that creates new directory in base existing directory with provided name. And also it returns @'CanonicalPath'@ of newly created directory. Isn't it nice?+Redefinition of some functions from @System.Directory@ module. Some of them have different signature, because they need to work with @'CanonicalPath'@. For example, we can't create functions @createDirectory :: 'CanonicalPath' -> IO ()@, because it has no sense. How can we create directory that already exists? Instead we have function @createDirectory :: 'CanonicalPath' -> 'FilePath' -> IO 'CanonicalPath'@, that creates new directory in base existing directory with provided name. And also it returns @'CanonicalPath'@ of newly created directory. Isn't it nice? A lot of functions come in two variants: one that returns resulting @'CanonicalPath' and second that ignores result (they end with '_' symbol). Happy Haskell Hacking! -} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE NoImplicitPrelude #-} module Filesystem.CanonicalPath.Directory where @@ -21,7 +22,6 @@ import Filesystem.CanonicalPath import Filesystem.CanonicalPath.Internal import Filesystem.Path-import Prelude () import qualified System.Directory as Directory {-|@@ -33,7 +33,7 @@ -} createDirectory :: MonadIO m => CanonicalPath -- ^ base directory- -> UnsafePath -- ^ name of new directory+ -> FilePath -- ^ name of new directory -> m CanonicalPath -- ^ @'CanonicalPath'@ of created directory createDirectory cp dir = do liftIO . Directory.createDirectory $ toPrelude path@@ -45,7 +45,7 @@ /Since 0.2.2.0/ -}-createDirectory_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()+createDirectory_ :: MonadIO m => CanonicalPath -> FilePath -> m () createDirectory_ cp dir = voidM $ createDirectory cp dir {-|@@ -58,7 +58,7 @@ createDirectoryIfMissing :: MonadIO m => Bool -- ^ Create its parents too? -> CanonicalPath -- ^ base directory- -> UnsafePath -- ^ name of new directory+ -> FilePath -- ^ name of new directory -> m CanonicalPath -- ^ @'CanonicalPath'@ of created directory createDirectoryIfMissing flag cp dir = do liftIO . Directory.createDirectoryIfMissing flag $ toPrelude path@@ -70,7 +70,7 @@ /Since 0.2.2.0/ -}-createDirectoryIfMissing_ :: MonadIO m => Bool -> CanonicalPath -> UnsafePath -> m ()+createDirectoryIfMissing_ :: MonadIO m => Bool -> CanonicalPath -> FilePath -> m () createDirectoryIfMissing_ flag cp dir = voidM $ createDirectoryIfMissing flag cp dir {-|@@ -102,19 +102,19 @@ -} renameDirectory :: MonadIO m => CanonicalPath -- ^ old directory- -> UnsafePath -- ^ new directory (should be just name of directory)+ -> FilePath -- ^ new directory (should be just name of directory) -> m CanonicalPath -- ^ @'CanonicalPath'@ of new directory renameDirectory cp p = do newPath <- canonicalPath $ parent p liftIO $ Directory.renameDirectory (toPrelude . unsafePath $ cp) (toPrelude p)- return . CanonicalPath $ unsafePath newPath </> dirname (addSlash p)+ return . CanonicalPath $ unsafePath newPath </> dirname (p </> "") {-| Variant of @'renameDirectory' that ignores resulting @'CanonicalPath'. /Since 0.2.2.0/ -}-renameDirectory_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()+renameDirectory_ :: MonadIO m => CanonicalPath -> FilePath -> m () renameDirectory_ cp p = voidM $ renameDirectory cp p {-|@@ -124,11 +124,11 @@ /Since 0.1.1.0/ -}-getDirectoryContents :: MonadIO m => CanonicalPath -> m [UnsafePath]+getDirectoryContents :: MonadIO m => CanonicalPath -> m [FilePath] getDirectoryContents cp = liftIO . liftM (fromPrelude <$>) $ preludeMap Directory.getDirectoryContents cp {-|-The same as @'getDirectoryContents'@, but returns list of @'CanonicalPath'@ instead of @'UnsafePath'@.+The same as @'getDirectoryContents'@, but returns list of @'CanonicalPath'@ instead of @'FilePath'@. /Since 0.1.1.0/ -}@@ -137,7 +137,7 @@ where up = unsafePath cp {-|-The same as @'getDirectoryContents'@, but returns list of @'Text'@ instead of @'UnsafePath'@.+The same as @'getDirectoryContents'@, but returns list of @'Text'@ instead of @'FilePath'@. /Since 0.2.2.0/ -}@@ -180,7 +180,7 @@ /Since 0.1.1.0/ -}-getAppUserDataDirectory :: MonadIO m => Text -> m UnsafePath+getAppUserDataDirectory :: MonadIO m => Text -> m FilePath getAppUserDataDirectory = liftIO . liftM fromPrelude . Directory.getAppUserDataDirectory . textToString {-|@@ -200,7 +200,7 @@ /Since 0.1.1.0/ -}-getTemporaryDirectory :: MonadIO m => m UnsafePath+getTemporaryDirectory :: MonadIO m => m FilePath getTemporaryDirectory = liftIO $ liftM fromPrelude Directory.getTemporaryDirectory {-|@@ -222,7 +222,7 @@ -} renameFile :: MonadIO m => CanonicalPath -- ^ @'CanonicalPath'@ of file you want to rename- -> UnsafePath -- ^ new name of file+ -> FilePath -- ^ new name of file -> m CanonicalPath -- ^ @'CanonicalPath'@ of /new/ file renameFile cp p = do newPath <- canonicalPath $ parent p@@ -234,7 +234,7 @@ /Since 0.2.2.0/ -}-renameFile_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()+renameFile_ :: MonadIO m => CanonicalPath -> FilePath -> m () renameFile_ cp p = voidM $ renameFile cp p {-|@@ -246,12 +246,12 @@ -} copyFile :: MonadIO m => CanonicalPath -- ^ @'CanonicalPath'@ of file you want to copy- -> UnsafePath -- ^ name of new file (actually it can be path relative to directory of /old/+ -> FilePath -- ^ name of new file (actually it can be path relative to directory of /old/ -> m CanonicalPath -- ^ @'CanonicalPath'@ of /new/ file copyFile oldFile newFile = do newPath <- canonicalPath $ parent newFile liftIO $ Directory.copyFile (toPrelude . unsafePath $ oldFile) (toPrelude newFile) return . CanonicalPath $ unsafePath newPath </> filename newFile -copyFile_ :: MonadIO m => CanonicalPath -> UnsafePath -> m ()+copyFile_ :: MonadIO m => CanonicalPath -> FilePath -> m () copyFile_ oldFile newFile = voidM $ copyFile oldFile newFile
Filesystem/CanonicalPath/Internal.hs view
@@ -8,45 +8,39 @@ ,canonicalPathE ,canonicalPathE' ,unsafePath- ,UnsafePath- ,SafePath ,Filesystem.CanonicalPath.Internal.readFile ,Filesystem.CanonicalPath.Internal.writeFile ,writeFile' ,Filesystem.CanonicalPath.Internal.appendFile ,preludeMap- ,pathToText- ,textToPath- ,cpathToText+ ,fromText+ ,toText+ ,toText' ,toPrelude ,fromPrelude- ,addSlash ,voidM) where import BasicPrelude import Control.Applicative as Applicative-import Control.Arrow (left)+import Control.Arrow (left, right) import Data.Text () import qualified Data.Text as Text-import qualified Filesystem.Path.CurrentOS as FilePath+import Filesystem.Path.CurrentOS import qualified Prelude import System.Directory (getHomeDirectory- ,canonicalizePath- ,doesDirectoryExist- ,doesFileExist)+ ,canonicalizePath) import qualified System.Environment as SE (getEnv) -newtype CanonicalPath = CanonicalPath UnsafePath+newtype CanonicalPath = CanonicalPath FilePath deriving Eq instance Show CanonicalPath where showsPrec d path = showParen (d > 15) (showString "CanonicalPath " .- shows (toText path))- where toText (CanonicalPath p) = pathToText p+ shows (toText' path)) {-|-Unsafe constructor of @CanonicalPath@. In case of any problem it will @error@.+Unsafe constructor of 'CanonicalPath'. In case of any problems it will 'error'. Example: @@ -58,19 +52,19 @@ /Since 0.1.0.0/ -}-canonicalPath :: MonadIO m => UnsafePath -> m CanonicalPath+canonicalPath :: MonadIO m => FilePath -> m CanonicalPath canonicalPath path = canonicalize path >>= either (error . textToString) (return . CanonicalPath) {-|-Version of @canonicalPath@ that takes @Text@ instead of @UnsafePath@.+Version of 'canonicalPath' that takes 'Data.Text' instead of 'Filesystem.Path.FilePath'. /Since 0.2.1.0/ -} canonicalPath' :: MonadIO m => Text -> m CanonicalPath-canonicalPath' = canonicalPath . textToPath+canonicalPath' = canonicalPath . fromText {-|-Constructs @Maybe CanonicalPath@.+Constructs @'Maybe' 'CanonicalPath'@. >>> canonicalPathM "~" Just CanonicalPath "Users/your-user-name"@@ -80,19 +74,19 @@ /Since 0.1.0.0/ -}-canonicalPathM :: MonadIO m => UnsafePath -> m (Maybe CanonicalPath)+canonicalPathM :: MonadIO m => FilePath -> m (Maybe CanonicalPath) canonicalPathM path = canonicalize path >>= either (\_ -> return Nothing) (return . Just . CanonicalPath) {-|-Version of @canonicalPathM@ that takes @Text@ instead of @UnsafePath@.+Version of 'canonicalPathM' that takes 'Data.Text' instead of 'Filesystem.Path.FilePath'. /Since 0.2.1.0/ -} canonicalPathM' :: MonadIO m => Text -> m (Maybe CanonicalPath)-canonicalPathM' = canonicalPathM . textToPath+canonicalPathM' = canonicalPathM . fromText {-|-Constructs @Either Text CanonicalPath@.+Constructs 'Prelude.Either' 'Data.Text' 'CanonicalPath'. >>> canonicalPathE "~/" Right CanonicalPath "/Users/your-user-name"@@ -102,90 +96,84 @@ /Since 0.1.0.0/ -}-canonicalPathE :: MonadIO m => UnsafePath -> m (Either Text CanonicalPath)+canonicalPathE :: MonadIO m => FilePath -> m (Either Text CanonicalPath) canonicalPathE path = canonicalize path >>= either (return . Left) (return . Right . CanonicalPath) {-|-Version of @canonicalPathE@ that takes @Text@ instead of @UnsafePath@.+Version of 'canonicalPathE' that takes 'Data.Text' instead of 'Filesystem.Path.FilePath'. /Since 0.2.1.0/ -} canonicalPathE' :: MonadIO m => Text -> m (Either Text CanonicalPath)-canonicalPathE' = canonicalPathE . textToPath+canonicalPathE' = canonicalPathE . fromText --- | Convert @CanonicalPath@ to @Filesystem.FilePath@.+-- | Convert 'CanonicalPath' to @Filesystem.FilePath@. -- -- /Since 0.1.0.0/-unsafePath :: CanonicalPath -> UnsafePath+unsafePath :: CanonicalPath -> FilePath unsafePath (CanonicalPath up) = up --- | Synonym of @FilePath@ from @Filesystem.Path@ module.------ /Since 0.1.0.0/-type UnsafePath = FilePath.FilePath-type SafePath = Either Text UnsafePath- -- * Functions used for canonicalization -canonicalize :: MonadIO m => UnsafePath -> m SafePath+canonicalize :: MonadIO m => FilePath -> m (Either Text FilePath) canonicalize fp = extractPath fp >>= either (return . Left) canonicalize' -canonicalize' :: MonadIO m => UnsafePath -> m SafePath-canonicalize' fp =- do exists <- liftIO $ liftM2 (||) (doesFileExist . toPrelude $ fp) (doesDirectoryExist . toPrelude $ fp)- if exists- then liftIO $ liftM Right (pathMap canonicalizePath fp)- else return . Left $ "Path does not exist (no such file or directory): " ++ pathToText fp+-- we do want exceptions from canonicalizePath+-- also canonicalizePath will throw exception+-- when @path does not exist+canonicalize' :: MonadIO m => Text -> m (Either Text FilePath)+canonicalize' path =+ liftIO $ liftM (right fromPrelude) (tryIO . canonicalizePath . textToString $ path) -extractPath :: MonadIO m => UnsafePath -> m SafePath-extractPath = liftM concatPath . mapM extractAtom . FilePath.splitDirectories+extractPath :: MonadIO m => FilePath -> m (Either Text Text)+extractPath = liftM (right concatPath . sequence) . mapM extractAtom . splitPath . toTextUnsafe . collapse -extractAtom :: MonadIO m => UnsafePath -> m SafePath-extractAtom atom = tryEnvPosix <||> tryEnvWindows <||> tryHome <%> atom+extractAtom :: MonadIO m => Text -> m (Either Text Text)+extractAtom atom = tryEnvPosix <||> tryHome <||> tryEnvWindows <%> atom -- * Parsers and parser combinators -type Parser m = UnsafePath -> Maybe (m SafePath)+type Parser m = Text -> Maybe (m (Either Text Text)) tryEnvPosix :: MonadIO m => Parser m-tryEnvPosix x = when' (hasPrefix "$" x) (Just . getEnv . pathTail $ x)+tryEnvPosix x = when' (Text.isPrefixOf "$" x) (Just . getEnv . Text.tail $ x) tryEnvWindows :: MonadIO m => Parser m tryEnvWindows x =- when' (hasPrefix "%" x &&- hasSuffix "%" x)- (Just . getEnv . pathTail . pathInit $ x)+ when' (Text.isPrefixOf "%" x &&+ Text.isSuffixOf "%" x)+ (Just . getEnv . Text.tail . Text.init $ x) tryHome :: MonadIO m => Parser m-tryHome x = when' (textToPath "~" == x) (Just $ liftM Right homeDirectory)+tryHome x = when' ("~" == x) (Just $ liftM Right homeDirectory) (<||>) :: MonadIO m => Parser m -> Parser m -> Parser m p1 <||> p2 = \v -> p1 v <|> p2 v -(<%>) :: MonadIO m => Parser m -> UnsafePath -> m SafePath+(<%>) :: MonadIO m => Parser m -> Text -> m (Either Text Text) p <%> v = fromMaybe (return . Right $ v) (p v) -- * File operations --- | @'readFile' file@ function reads a /file/ and returns the contents of the /file/ as a @'Text'@. The /file/ is read lazily, on demand, as with getContents.+-- | @readFile file@ function reads a /file/ and returns the contents of the /file/ as a 'Data.Text'. The /file/ is read lazily, on demand, as with getContents. -- -- /Since 0.1.1.0/ readFile :: MonadIO m => CanonicalPath -> m Text readFile = liftIO . BasicPrelude.readFile . unsafePath --- | @'writeFile' file txt@ writes /txt/ to the /file/.+-- | @writeFile file txt@ writes /txt/ to the /file/. -- -- /Since 0.1.1.0/ writeFile :: MonadIO m => CanonicalPath -> Text -> m () writeFile p = liftIO . BasicPrelude.writeFile (unsafePath p) --- | @'writeFile'' dir file txt@ writes /txt/ to the /dir\/file/. Useful, when the file isn't created yet or you don't sure if it exists.+-- | @writeFile' dir file txt@ writes /txt/ to the /dir\/file/. Useful, when the file isn't created yet or you don't sure if it exists. -- -- /Since 0.1.2.0/-writeFile' :: MonadIO m => CanonicalPath -> UnsafePath -> Text -> m ()+writeFile' :: MonadIO m => CanonicalPath -> FilePath -> Text -> m () writeFile' cp file = liftIO . BasicPrelude.writeFile (unsafePath cp </> file) --- | @'appendFile' file txt@ appends /txt/ to the /file/.+-- | @appendFile file txt@ appends /txt/ to the /file/. -- -- /Since 0.1.1.0/ appendFile :: MonadIO m => CanonicalPath -> Text -> m ()@@ -193,80 +181,58 @@ -- * Utilities -getEnv :: MonadIO m => UnsafePath -> m SafePath-getEnv var = liftM (left show) tryEnv- where env = pathMap SE.getEnv- tryEnv :: MonadIO m => m (Either IOException UnsafePath)- tryEnv = liftIO . try . env $ var+tryIO :: MonadIO m => IO a -> m (Either Text a)+tryIO a = liftM (left show) (try' a)+ where try' :: MonadIO m => IO a -> m (Either IOException a)+ try' = liftIO . try -homeDirectory :: MonadIO m => m UnsafePath-homeDirectory = liftIO $ fromPrelude <$> getHomeDirectory+getEnv :: MonadIO m => Text -> m (Either Text Text)+getEnv = liftM (right fromString) . tryIO . SE.getEnv . textToString +homeDirectory :: MonadIO m => m Text+homeDirectory = liftIO $ fromString <$> getHomeDirectory+ when' :: Alternative f => Bool -> f a -> f a when' b v = if b then v else Applicative.empty -pathMap :: MonadIO m => (Prelude.FilePath -> m Prelude.FilePath) -> UnsafePath -> m UnsafePath-pathMap f p = liftM fromPrelude (f . toPrelude $ p)--hasPrefix :: Text -> UnsafePath -> Bool-hasPrefix prefix path = prefix `Text.isPrefixOf` pathToText path--hasSuffix :: Text -> UnsafePath -> Bool-hasSuffix suffix path = suffix `Text.isSuffixOf` pathToText path--pathTail :: UnsafePath -> UnsafePath-pathTail = textToPath . Text.tail . pathToText--pathInit :: UnsafePath -> UnsafePath-pathInit = textToPath . Text.init . pathToText--addSlash :: UnsafePath -> UnsafePath-addSlash = textToPath . (++ "/") . pathToText+splitPath :: Text -> [Text]+splitPath = Text.splitOn "/" -concatPath :: [SafePath] -> SafePath-concatPath = foldl' (<//>) (Right "")+concatPath :: [Text] -> Text+concatPath = Text.intercalate "/" -(<//>) :: SafePath -> SafePath -> SafePath-(<//>) l@(Left _) _ = l-(<//>) _ l@(Left _) = l-(<//>) (Right a) (Right b) = Right $ a </> b+-- concatPath :: [Either Text Text] -> Either Text Text+-- concatPath = right BasicPrelude.concat . sequence+-- concatPath = right (Text.intercalate "/") . sequence preludeMap :: (Prelude.FilePath -> a) -> CanonicalPath -> a preludeMap f = f . toPrelude . unsafePath --- * Type conversions---- | @'pathToText' path@ converts 'UnsafePath' /path/ to 'Text'. In case of eny problems it will throw error.+-- | @toText path@ converts 'Filesystem.FilePath' /path/ to 'Data.Text'. In case of any problems it will throw error. -- -- See 'Filesystem.Path.CurrentOS.toText' function for details. ----- /Since 0.1.2.0/-pathToText :: UnsafePath -> Text-pathToText = either (error . textToString) id . FilePath.toText---- | @'textToPath' txt@ converts 'Text' to 'UnsafePath'.------ /Since 0.1.2.0/-textToPath :: Text -> UnsafePath-textToPath = FilePath.fromText+-- /Since 0.3.0.0/+toTextUnsafe :: FilePath -> Text+toTextUnsafe = either (error . textToString) id . toText --- | @'cpathToText' path@ converts 'CanonicalPath' to 'Text'.+-- | @toText' path@ converts 'CanonicalPath' to 'Data.Text'. ----- /Since 0.2.3.0/-cpathToText :: CanonicalPath -> Text-cpathToText = pathToText . unsafePath+-- /Since 0.3.0.0/+toText' :: CanonicalPath -> Text+toText' = toTextUnsafe . unsafePath --- | @'fromPrelude' fp'@ converts 'Prelude.FilePath' to 'UnsafePath'.+-- | @fromPrelude fp@ converts 'Prelude.FilePath' to 'Filesystem.Path.CurrentOS.toText'. -- -- /Since 0.1.0.0/-fromPrelude :: Prelude.FilePath -> UnsafePath-fromPrelude = textToPath . Text.pack+fromPrelude :: Prelude.FilePath -> FilePath+fromPrelude = fromText . Text.pack --- | @'toPrelude' up'@ converts 'UnsafePath' to 'Prelude.FilePath'.+-- | @toPrelude up@ converts 'Filesystem.Path.FilePath' to 'Prelude.FilePath'. -- -- /Since 0.1.0.0/-toPrelude :: UnsafePath -> Prelude.FilePath-toPrelude = Text.unpack . pathToText+toPrelude :: FilePath -> Prelude.FilePath+toPrelude = Text.unpack . toTextUnsafe voidM :: Monad m => m a -> m () voidM a = a >> return ()
changelog.md view
@@ -1,3 +1,15 @@+0.3.0.0:+* remove `UnsafePath` data type. It was synonym to `FilePath` and was confusing me sometimes, so I decided to cut it off+* rename `cpathToText` to `toText'`+* remove `pathToText` and `textToPath`+* export `toText` and `fromText` from `Filesystem.Path.CurrentOS`+* export `fromPrelude` and `toPrelude` functions+* improve performance (path canonicalization now is performed 1.6x faster than before)+* improve `canonicalPath` error messages. Most important - now it respects errors from `System.Directory.canonicalizePath`+* add tests+* add travis support+* update documentation+ 0.2.3.0: * add `cpathToText` that converts `CanonicalPath` to `Text` * update base version constraints
system-canonicalpath.cabal view
@@ -1,7 +1,7 @@ name: system-canonicalpath-version: 0.2.3.0-synopsis: Abstract data type for canonical paths with pretty operations-description: This library provides abstract data type named 'CanonicalPath' and some useful functions for working with it. See every module's description to find out more.+version: 0.3.0.0+synopsis: Abstract data type for canonical paths with some utilities+description: This library provides abstract data type named 'Filesystem.CanonicalPath.CanonicalPath' and some useful functions for working with it. homepage: https://github.com/d12frosted/CanonicalPath license: MIT license-file: LICENSE@@ -21,10 +21,27 @@ other-extensions: OverloadedStrings - build-depends: base >=4.7 && < 4.9+ build-depends: base >= 4.7 && < 4.9 , basic-prelude- , directory- , system-filepath+ , directory >= 1.2+ , system-filepath >= 0.4 , text + ghc-options: -Wall -O2+ default-language: Haskell2010++test-suite canonicalpath_tests+ type: exitcode-stdio-1.0+ main-is: CanonicalPathTests.hs++ hs-source-dirs: tests++ build-depends: base >= 4.7 && < 4.9+ , basic-prelude+ , chell >= 0.4 && < 0.5+ , system-canonicalpath+ , system-filepath++ ghc-options: -Wall -O2+ cc-options: -Wall default-language: Haskell2010
+ tests/CanonicalPathTests.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import BasicPrelude+import qualified Control.Arrow as Arrow(right)+import Filesystem.CanonicalPath+import Filesystem.CanonicalPath.Directory+import Test.Chell++main :: IO ()+main = defaultMain [testCanonicalPath]++testCanonicalPath :: Suite+testCanonicalPath =+ suite "canonical path constructors" $+ [testConstructor]++testConstructor :: Test+testConstructor =+ assertions "canonicalPathE tests" $+ do let left' v = v >>= \v' -> return $ left v'+ equal' v w = do+ v' <- v+ w' <- w+ return $ equal v' w'+ check p1 p2 = equal' (canonicalize' p1) (liftM Right $ inCurrentDir p2)++ $expect $ left' (canonicalize' "diro/")+ $expect $ left' (canonicalize' "dir.txt")+ $expect $ left' (canonicalize' "dir/ab/../../dir/ab/./../file1.tx")+ $expect $ left' (canonicalize' deepFile)++ $expect $ check "dir/" "dir"+ $expect $ check "dir" "dir"+ $expect $ check "file" "file"+ -- check "file/" "file" will fail on linux+ -- but will not fail on os x+ $expect $ check "dir/file1.txt" "dir/file1.txt"+ $expect $ check "dir/ab/" "dir/ab"+ $expect $ check "dir/file" "dir/file"+ $expect $ check "dir/ab/../file" "dir/file"+ $expect $ check "dir/ab/../ab/" "dir/ab"+ $expect $ check "dir/ab/./file2.txt" "dir/ab/file2.txt"+ $expect $ check "dir/ab/../../dir/ab/./../file1.txt" "dir/file1.txt"++ $expect $ equal' (canonicalize "$HOME") (liftM Right getHomeDirectory)+ $expect $ equal' (canonicalize "$HOME/") (liftM Right getHomeDirectory)+ $expect $ equal' (canonicalize "~/") (liftM Right getHomeDirectory)++-- Helper functions++canonicalize :: FilePath -> IO (Either Text CanonicalPath)+canonicalize = canonicalPathE++canonicalize' :: FilePath -> IO (Either Text FilePath)+canonicalize' p = inCurrentDir p >>= liftM (Arrow.right unsafePath) . canonicalize++currentDir :: IO FilePath+currentDir = liftM ((</> "tests") . unsafePath) getCurrentDirectory++inCurrentDir :: FilePath -> IO FilePath+inCurrentDir p =+ do h <- currentDir+ return $ h </> p++deepFile :: FilePath+deepFile = concat $ replicate 500 "deep-hell"