packages feed

system-canonicalpath (empty) → 0.1.1.0

raw patch · 7 files changed

+513/−0 lines, 7 filesdep +basedep +basic-preludedep +directorybuild-type:Customsetup-changed

Dependencies added: base, basic-prelude, directory, system-filepath, text

Files

+ Filesystem/CanonicalPath.hs view
@@ -0,0 +1,36 @@+{-|+Module      : Filesystem.CanonicalPath+Copyright   : (c) Boris Buliga, 2014+License     : MIT+Maintainer  : d12frosted@icloud.com+Stability   : stable+Portability : portable++@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.++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.++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.++Oh, and last but not least, @CanonicalPath@ constructor extracts all environment variables. Also it treats @~@ as home directory.++Happy Haskell Hacking!+-}+module Filesystem.CanonicalPath+  (-- * Abstract Type+   CanonicalPath+  ,UnsafePath++  -- * Constructors+  ,canonicalPath+  ,canonicalPathM+  ,canonicalPathE+  ,unsafePath++  -- * Some IO functions+  ,readFile+  ,writeFile+  ,appendFile) where++import Filesystem.CanonicalPath.Internal+import Prelude ()
+ Filesystem/CanonicalPath/Directory.hs view
@@ -0,0 +1,207 @@+{-|+Module      : Filesystem.CanonicalPath.Directory+Copyright   : (c) Boris Buliga, 2014+License     : MIT+Maintainer  : d12frosted@icloud.com+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?++Happy Haskell Hacking!+-}+{-# LANGUAGE OverloadedStrings #-}++module Filesystem.CanonicalPath.Directory where++import           BasicPrelude+import           Data.Text ()+import           Filesystem.CanonicalPath+import           Filesystem.CanonicalPath.Internal+import           Filesystem.Path+import           Prelude ()+import qualified System.Directory as Directory++{-|+@'createDirectory' base dir@ creates new directory @dir@ in existing @base@ directory and returns @'CanonicalPath'@ of created directory.++For more information look for documentation of @'System.Directory.createDirectory'@.++/Since 0.1.1.0/+-}+createDirectory :: CanonicalPath -- ^ base directory+                -> UnsafePath -- ^ name of new directory+                -> IO CanonicalPath -- ^ @'CanonicalPath'@ of created directory+createDirectory cp dir =+  do Directory.createDirectory $ toPrelude path+     return $ CanonicalPath path+  where path = unsafePath cp </> dir++{-|+@'createDirectoryIfMissing' parents dir@ creates a new directory @dir@ in @base@ directory. If the first argument is 'True' the function will also create all parent directories if they are missing. Function returns @'CanonicalPath'@ of created directory.++For more information look for documentation of @'System.Directory.createDirectoryIfMissing'@.++/Since 0.1.1.0/+-}+createDirectoryIfMissing :: Bool -- ^ Create its parents too?+                         -> CanonicalPath -- ^ base directory+                         -> UnsafePath -- ^ name of new directory+                         -> IO CanonicalPath -- ^ @'CanonicalPath'@ of created directory+createDirectoryIfMissing flag cp dir =+  do Directory.createDirectoryIfMissing flag $ toPrelude path+     return $ CanonicalPath path+  where path = unsafePath cp </> dir++{-|+@'removeDirectory' dir@ removes an existing directory /dir/.++For more information look for documentation of @'System.Directory.removeDirectory'@.++/Since 0.1.1.0/+-}+removeDirectory :: CanonicalPath -> IO ()+removeDirectory = preludeMap Directory.removeDirectory++{-|+@'removeDirectoryRecursive' dir@  removes an existing directory /dir/ together with its content and all subdirectories. Be careful, if the directory contains symlinks, the function will follow them.++For more information look for documentation of @'System.Directory.removeDirectoryRecursive'@.++/Since 0.1.1.0/+-}+removeDirectoryRecursive :: CanonicalPath -> IO ()+removeDirectoryRecursive = preludeMap Directory.removeDirectoryRecursive++{-|+@'renameDirectory' old new@ changes the name of an existing directory from /old/ to /new/ and returns @'CanonicalPath'@ of new directory.++For more information look for documentation of @'System.Directory.renameDirectory'@.++/Since 0.1.1.0/+-}+renameDirectory :: CanonicalPath -- ^ old directory+                -> UnsafePath -- ^ new directory (should be just name of directory)+                -> IO CanonicalPath -- ^ @'CanonicalPath'@ of new directory+renameDirectory cp p =+  do newPath <- canonicalPath $ parent p+     Directory.renameDirectory (toPrelude . unsafePath $ cp) (toPrelude p)+     return . CanonicalPath $ unsafePath newPath </> dirname (addSlash p)++{-|+@'getDirectoryContents' dir@ returns a list of /all/ entries in /dir/. If you want to have list of @'CanonicalPath'@ instead use function @'getDirectoryContents''@.++For more information look for documentation of @'System.Directory.getDirectoryContents'@.++/Since 0.1.1.0/+-}+getDirectoryContents :: CanonicalPath -> IO [UnsafePath]+getDirectoryContents cp = liftM (fromPrelude <$>) $ preludeMap Directory.getDirectoryContents cp++{-|+The same as @'getDirectoryContents'@, but returns list of @'CanonicalPath'@ instead of @'UnsafePath'@.++/Since 0.1.1.0/+-}+getDirectoryContents' :: CanonicalPath -> IO [CanonicalPath]+getDirectoryContents' cp = liftM (CanonicalPath . (</> ) up <$>) $ getDirectoryContents cp+  where up = unsafePath cp++{- |If the operating system has a notion of current directories, 'getCurrentDirectory' returns an @'CanonicalPath'@ to the current directory of the calling process.++For more information look for documentation of @'System.Directory.getCurrentDirectory'@.++/Since 0.1.1.0/+-}+getCurrentDirectory :: IO CanonicalPath+getCurrentDirectory = liftM (CanonicalPath . fromPrelude) Directory.getCurrentDirectory++{-|+If the operating system has a notion of current directories, @'setCurrentDirectory' dir@ changes the current directory of the calling process to /dir/.++For more information look for documentation of @'System.Directory.setCurrentDirectory'@.++/Since 0.1.1.0/+-}+setCurrentDirectory :: CanonicalPath -> IO ()+setCurrentDirectory = preludeMap Directory.setCurrentDirectory++{-|+Returns the current user's home directory.++For more information look for documentation of @'System.Directory.getHomeDirectory'@.++/Since 0.1.1.0/+-}+getHomeDirectory :: IO CanonicalPath+getHomeDirectory = liftM (CanonicalPath . fromPrelude) Directory.getHomeDirectory++{-|+Returns the @'CanonicalPath'@ of a directory in which application-specific data for the current user can be stored.  The result of 'getAppUserDataDirectory' for a given application is specific to the current user.++For more information look for documentation of @'System.Directory.getAppUserDataDirectory'@.++/Since 0.1.1.0/+-}+getAppUserDataDirectory :: Text -> IO UnsafePath+getAppUserDataDirectory = liftM fromPrelude . Directory.getAppUserDataDirectory . textToString++{-|+Returns the current user's document directory.++For more information look for documentation of @'System.Directory.getUserDocumentsDirectory'@.++/Since 0.1.1.0/+-}+getUserDocumentsDirectory :: IO CanonicalPath+getUserDocumentsDirectory = liftM (CanonicalPath . fromPrelude) Directory.getUserDocumentsDirectory++{-|+Returns the current directory for temporary files.++For more information look for documentation of @'System.Directory.getUserDocumentsDirectorygetTemporaryDirectory'@.++/Since 0.1.1.0/+-}+getTemporaryDirectory :: IO UnsafePath+getTemporaryDirectory = liftM fromPrelude Directory.getTemporaryDirectory++{-|+'removeFile' /file/ removes the directory entry for an existing file /file/, where /file/ is not itself a directory.++For more information look for documentation of @'System.Directory.removeFile'@.++/Since 0.1.1.0/+-}+removeFile :: CanonicalPath -> IO ()+removeFile = preludeMap Directory.removeFile++{-|+@'renameFile' old new@ changes the name of an existing file system object from /old/ to /new/.++For more information look for documentation of @'System.Directory.renameFile'@.++/Since 0.1.1.0/+-}+renameFile :: CanonicalPath -- ^ @'CanonicalPath'@ of file you want to rename+           -> UnsafePath -- ^ new name of file+           -> IO CanonicalPath -- ^ @'CanonicalPath'@ of /new/ file+renameFile cp p =+  do newPath <- canonicalPath $ parent p+     Directory.renameFile (toPrelude . unsafePath $ cp) (toPrelude p)+     return . CanonicalPath $ unsafePath newPath </> filename p++{-|+@'copyFile' old new@ copies the existing file from /old/ to /new/. If the /new/ file already exists, it is atomically replaced by the /old/ file. Neither path may refer to an existing directory.  The permissions of /old/ are copied to /new/, if possible.++For more information look for documentation of @'System.Directory.copyFile'@.++/Since 0.1.1.0/+-}+copyFile :: CanonicalPath -- ^ @'CanonicalPath'@ of file you want to copy+         -> UnsafePath -- ^ name of new file (actually it can be path relative to directory of /old/+         -> IO CanonicalPath -- ^ @'CanonicalPath'@ of /new/ file+copyFile oldFile newFile =+  do newPath <- canonicalPath $ parent newFile+     Directory.copyFile (toPrelude . unsafePath $ oldFile) (toPrelude newFile)+     return . CanonicalPath $ unsafePath newPath </> filename newFile
+ Filesystem/CanonicalPath/Internal.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE OverloadedStrings #-}++module Filesystem.CanonicalPath.Internal (CanonicalPath(..)+                                         ,canonicalPath+                                         ,canonicalPathM+                                         ,canonicalPathE+                                         ,unsafePath+                                         ,UnsafePath+                                         ,SafePath+                                         ,Filesystem.CanonicalPath.Internal.readFile+                                         ,Filesystem.CanonicalPath.Internal.writeFile+                                         ,Filesystem.CanonicalPath.Internal.appendFile+                                         ,preludeMap+                                         ,pathToText+                                         ,textToPath+                                         ,toPrelude+                                         ,fromPrelude+                                         ,addSlash) where++import           BasicPrelude+import           Control.Applicative as Applicative+import           Control.Arrow (left)+import           Data.Text ()+import qualified Data.Text as Text+import qualified Filesystem.Path.CurrentOS as FilePath+import qualified Prelude+import           System.Directory (getHomeDirectory+                                  ,canonicalizePath+                                  ,doesDirectoryExist+                                  ,doesFileExist)+import qualified System.Environment as SE (getEnv)++data CanonicalPath = CanonicalPath UnsafePath++instance Show CanonicalPath where+  showsPrec d path =+    showParen (d > 15)+              (showString "CanonicalPath " .+               shows (toText path))+    where toText (CanonicalPath p) = pathToText p++{-|+Unsafe constructor of @CanonicalPath@. In case of any problem it will @error@.++Example:++>>> canonicalPath "$HOME"+CanonicalPath "/Users/your-user-name"++>>> canonicalPath "unknown"+*** Exception: Path does not exist (no such file or directory): unknown++/Since 0.1.0.0/+-}+canonicalPath :: UnsafePath -> IO CanonicalPath+canonicalPath path = canonicalize path >>= either (error . textToString) (return . CanonicalPath)++{-|+Constucts @Maybe CanonicalPath@.++>>> canonicalPathM "~"+Just CanonicalPath "Users/your-user-name"++>>> canonicalPathM "unknown"+Nothing++/Since 0.1.0.0/+-}+canonicalPathM :: UnsafePath -> IO (Maybe CanonicalPath)+canonicalPathM path = canonicalize path >>= either (\_ -> return Nothing) (return . Just . CanonicalPath)++{-|+Constructs @Either Text CanonicalPath@.++>>> canonicalPathE "~/"+Right CanonicalPath "/Users/your-user-name"++>>> canonicalPathE "$HOME/this-folder-does-not-exist"+Left "Path does not exist (no such file or directory): /Users/your-user-name/this-folder-does-not-exist"++/Since 0.1.0.0/+-}+canonicalPathE :: UnsafePath -> IO (Either Text CanonicalPath)+canonicalPathE path = canonicalize path >>= either (return . Left) (return . Right . CanonicalPath)++-- | Convert @CanonicalPath@ to @Filesystem.FilePath@.+--+-- /Since 0.1.0.0/+unsafePath :: CanonicalPath -> UnsafePath+unsafePath (CanonicalPath up) = up++-- | Synonym of @FilePath@ from @Filesystem.Path@ module.+type UnsafePath = FilePath.FilePath+type SafePath = Either Text UnsafePath++canonicalize :: UnsafePath -> IO SafePath+canonicalize fp = extractPath fp >>= either (return . Left) canonicalize'++canonicalize' :: UnsafePath -> IO SafePath+canonicalize' fp =+  do exists <- liftM2 (||) (doesFileExist . toPrelude $ fp) (doesDirectoryExist . toPrelude $ fp)+     if exists+        then liftM Right (pathMap canonicalizePath fp)+        else return . Left $ "Path does not exist (no such file or directory): " ++ pathToText fp++extractPath :: UnsafePath -> IO SafePath+extractPath = liftM concatPath . mapM extractAtom . FilePath.splitDirectories++extractAtom :: UnsafePath -> IO SafePath+extractAtom atom = tryEnvPosix <||> tryEnvWindows <||> tryHome <%> atom++-- 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.+--+-- /Since 0.1.1.0/+readFile :: CanonicalPath -> IO Text+readFile = BasicPrelude.readFile . unsafePath++-- | @'writeFile' file txt@ writes /txt/ to the /file/.+--+-- /Since 0.1.1.0/+writeFile :: CanonicalPath -> Text -> IO ()+writeFile = BasicPrelude.writeFile . unsafePath++-- | @'appendFile' file txt@ appends /txt/ to the /file/.+--+-- /Since 0.1.1.0/+appendFile :: CanonicalPath -> Text -> IO ()+appendFile = BasicPrelude.appendFile . unsafePath++-- Parsers and parser combinators++type Parser = UnsafePath -> Maybe (IO SafePath)++tryEnvPosix :: Parser+tryEnvPosix x = when' (hasPrefix "$" x) (Just . getEnv . pathTail $ x)++tryEnvWindows :: Parser+tryEnvWindows x =+  when' (hasPrefix "%" x &&+         hasSuffix "%" x)+        (Just . getEnv . pathTail . pathInit $ x)++tryHome :: Parser+tryHome x = when' (textToPath "~" == x) (Just $ liftM Right homeDirectory)++(<||>) :: Parser -> Parser -> Parser+p1 <||> p2 = \v -> p1 v <|> p2 v++(<%>) :: Parser -> UnsafePath -> IO SafePath+p <%> v = fromMaybe (return . Right $ v) (p v)++-- Utilities++getEnv :: UnsafePath -> IO SafePath+getEnv var = map (left show) tryEnv+  where env = pathMap SE.getEnv+        tryEnv :: IO (Either IOException UnsafePath)+        tryEnv = try . env $ var++homeDirectory :: IO UnsafePath+homeDirectory = fromPrelude <$> getHomeDirectory++when' :: Alternative f => Bool -> f a -> f a+when' b v = if b then v else Applicative.empty++pathMap :: (Prelude.FilePath -> IO Prelude.FilePath) -> UnsafePath -> IO UnsafePath+pathMap f = map fromPrelude . f . toPrelude++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++concatPath :: [SafePath] -> SafePath+concatPath = foldl' (<//>) (Right "")++(<//>) :: SafePath -> SafePath -> SafePath+(<//>) l@(Left _) _ = l+(<//>) _ l@(Left _) = l+(<//>) (Right a) (Right b) = Right $ a </> b++preludeMap :: (Prelude.FilePath -> a) -> CanonicalPath -> a+preludeMap f = f . toPrelude . unsafePath++-- Type conversions++pathToText :: UnsafePath -> Text+pathToText s =+  case FilePath.toText s of+    Left e -> error . textToString $ e+    Right t -> t++textToPath :: Text -> UnsafePath+textToPath = FilePath.fromText++fromPrelude :: Prelude.FilePath -> UnsafePath+fromPrelude = textToPath . Text.pack++toPrelude :: UnsafePath -> Prelude.FilePath+toPrelude = Text.unpack . pathToText
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 Boris Buliga++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ changelog.md view
@@ -0,0 +1,6 @@+0.1.1.0:+    * Filesystem.CanonicalPath.Directory implementation+    * Add documentation++0.1.0.0:+    * Initial implementation of library
+ system-canonicalpath.cabal view
@@ -0,0 +1,30 @@+name:                system-canonicalpath+version:             0.1.1.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.+homepage:            https://github.com/d12frosted/CanonicalPath+license:             MIT+license-file:        LICENSE+author:              Boris Buliga <d12frosted@icloud.com>+maintainer:          Boris Buliga <d12frosted@icloud.com>+copyright:           (c) Boris Buliga, 2014+category:            System+build-type:          Custom+cabal-version:       >=1.10+extra-source-files:  changelog.md++library+  exposed-modules:   Filesystem.CanonicalPath+                   , Filesystem.CanonicalPath.Directory++  other-modules:     Filesystem.CanonicalPath.Internal++  other-extensions:  OverloadedStrings++  build-depends:     base >=4.7 && <4.8+                   , basic-prelude+                   , directory+                   , system-filepath+                   , text++  default-language:  Haskell2010