system-filepath 0.1 → 0.1.1
raw patch · 5 files changed
+130/−29 lines, 5 files
Files
- System/FilePath.hs +59/−12
- System/FilePath/CurrentOS.hs +14/−2
- System/FilePath/Internal.hs +24/−0
- System/FilePath/Rules.hs +32/−14
- system-filepath.cabal +1/−1
System/FilePath.hs view
@@ -7,6 +7,10 @@ -- Maintainer: jmillikin@gmail.com -- Portability: portable --+-- High-level, byte-based file and directory path manipulations. You probably+-- want to import "System.FilePath.CurrentOS" instead, since it handles+-- detecting which rules to use in the current compilation.+-- ----------------------------------------------------------------------------- module System.FilePath@@ -49,7 +53,7 @@ import Prelude hiding (FilePath, concat, null) import qualified Prelude as P-import Data.Maybe (isNothing, listToMaybe)+import Data.Maybe (isNothing) import qualified Data.Monoid as M import System.FilePath.Internal import qualified Data.ByteString as B@@ -64,47 +68,71 @@ -- Basic properties ------------------------------------------------------------------------------- +-- | @null p == (p == 'empty')@ null :: FilePath -> Bool null = (== empty) +-- | Retrieves the 'FilePath''s root. root :: FilePath -> FilePath root p = empty { pathRoot = pathRoot p } +-- | Retrieves the 'FilePath''s directory. If the path is already a+-- directory, it is returned unchanged. directory :: FilePath -> FilePath directory p = empty { pathRoot = pathRoot p- , pathComponents = if P.null (pathComponents p) && isNothing (pathRoot p)- then [B8.pack "."]- else pathComponents p+ , pathComponents = let+ starts = map (Just . B8.pack) [".", ".."]+ dot = if safeHead (pathComponents p) `elem` starts+ then []+ else if isNothing (pathRoot p)+ then [B8.pack "."]+ else []+ in dot ++ pathComponents p } +-- | Retrieves the 'FilePath''s parent directory. parent :: FilePath -> FilePath parent p = empty { pathRoot = pathRoot p- , pathComponents = if P.null (pathComponents p) && isNothing (pathRoot p)- then [B8.pack "."]- else if null (filename p)+ , pathComponents = let+ starts = map (Just . B8.pack) [".", ".."]+ components = if null (filename p) then safeInit (pathComponents p) else pathComponents p+ + dot = if safeHead components `elem` starts+ then []+ else if isNothing (pathRoot p)+ then [B8.pack "."]+ else []+ + in dot ++ components } +-- | Retrieve the filename component of a 'FilePath'.+-- @filename \"foo/bar.txt\" == \"bar.txt\"@. filename :: FilePath -> FilePath filename p = empty { pathBasename = pathBasename p , pathExtensions = pathExtensions p } +-- | Retrieve the basename component of a 'FilePath'.+-- @filename \"foo/bar.txt\" == \"bar\"@. basename :: FilePath -> FilePath basename p = empty { pathBasename = pathBasename p } +-- | Return whether the path is absolute. absolute :: FilePath -> Bool absolute p = case pathRoot p of Just RootPosix -> True Just (RootWindowsVolume _) -> True _ -> False +-- | Return whether the path is relative. relative :: FilePath -> Bool relative p = case pathRoot p of Just _ -> False@@ -114,6 +142,8 @@ -- Basic operations ------------------------------------------------------------------------------- +-- | Appends two 'FilePath's. If the second path is absolute, it is returned+-- unchanged. append :: FilePath -> FilePath -> FilePath append x y = if absolute y then y else xy where xy = y@@ -123,15 +153,18 @@ components = xComponents ++ pathComponents y xComponents = (pathComponents x ++) $ if null (filename x) then []- else [B.concat $ [maybe (B8.pack "") id (pathBasename x)] ++ pathExtensions x]+ else [filenameBytes x] +-- | An alias for 'append'. (</>) :: FilePath -> FilePath -> FilePath (</>) = append +-- | A fold over 'append'. concat :: [FilePath] -> FilePath concat [] = empty concat ps = foldr1 append ps +-- | Find the greatest common prefix between two 'FilePath's. commonPrefix :: [FilePath] -> FilePath commonPrefix [] = empty commonPrefix ps = foldr1 step ps where@@ -155,48 +188,58 @@ else [] ---------------------------------------------------------------------------------- Basenames----------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Extensions ------------------------------------------------------------------------------- +-- | Get a 'FilePath''s last extension, or 'Nothing' if it has no+-- extensions. extension :: FilePath -> Maybe B.ByteString extension p = case extensions p of [] -> Nothing es -> Just (last es) +-- | Get a 'FilePath''s full extension list. extensions :: FilePath -> [B.ByteString] extensions = pathExtensions +-- | Get whether a 'FilePath''s last extension is the predicate. hasExtension :: FilePath -> B.ByteString -> Bool hasExtension p e = extension p == Just e +-- | Append an extension to the end of a 'FilePath'. addExtension :: FilePath -> B.ByteString -> FilePath addExtension p ext = addExtensions p [ext] +-- | Append many extensions to the end of a 'FilePath'. addExtensions :: FilePath -> [B.ByteString] -> FilePath addExtensions p exts = p { pathExtensions = pathExtensions p ++ exts } +-- | An alias for 'addExtension'. (<.>) :: FilePath -> B.ByteString -> FilePath (<.>) = addExtension +-- | Remove a 'FilePath''s last extension. dropExtension :: FilePath -> FilePath dropExtension p = p { pathExtensions = safeInit (pathExtensions p) } +-- | Remove all extensions from a 'FilePath'. dropExtensions :: FilePath -> FilePath dropExtensions p = p { pathExtensions = [] } +-- | Replace a 'FilePath''s last extension. replaceExtension :: FilePath -> B.ByteString -> FilePath replaceExtension = addExtension . dropExtension +-- | Remove all extensions from a 'FilePath', and replace them with a new+-- list. replaceExtensions :: FilePath -> [B.ByteString] -> FilePath replaceExtensions = addExtensions . dropExtensions +-- | @splitExtension p = ('dropExtension' p, 'extension' p)@ splitExtension :: FilePath -> (FilePath, Maybe B.ByteString) splitExtension p = (dropExtension p, extension p) +-- | @splitExtensions p = ('dropExtensions' p, 'extensions' p)@ splitExtensions :: FilePath -> (FilePath, [B.ByteString]) splitExtensions p = (dropExtensions p, extensions p) @@ -208,3 +251,7 @@ safeInit xs = case xs of [] -> [] _ -> init xs++safeHead :: [a] -> Maybe a+safeHead [] = Nothing+safeHead (x:_) = Just x
System/FilePath/CurrentOS.hs view
@@ -7,9 +7,11 @@ -- Maintainer: jmillikin@gmail.com -- Portability: portable ----- Re-exports contents of "System.FilePath", defaulting to the current OS's--- rules when needed.+-- Re-exports contents of "System.FilePath.Rules", defaulting to the+-- current OS's rules when needed. --+-- Also enables 'Show' and 'S.IsString' instances for 'F.FilePath'.+-- ----------------------------------------------------------------------------- {-# LANGUAGE CPP #-}@@ -54,32 +56,42 @@ showsPrec d path = showParen (d > 10) $ showString "FilePath " . shows (toBytes path) +-- | See 'R.valid' valid :: F.FilePath -> Bool valid = R.valid currentOS +-- | See 'R.normalise' normalise :: F.FilePath -> F.FilePath normalise = R.normalise currentOS +-- | See 'R.equivalent' equivalent :: F.FilePath -> F.FilePath -> Bool equivalent = R.equivalent currentOS +-- | See 'R.toBytes' toBytes :: F.FilePath -> B.ByteString toBytes = R.toBytes currentOS +-- | See 'R.toLazyBytes' toLazyBytes :: F.FilePath -> BL.ByteString toLazyBytes = R.toLazyBytes currentOS +-- | See 'R.toString' toString :: F.FilePath -> String toString = R.toString currentOS +-- | See 'R.fromBytes' fromBytes :: B.ByteString -> F.FilePath fromBytes = R.fromBytes currentOS +-- | See 'R.fromLazyBytes' fromLazyBytes :: BL.ByteString -> F.FilePath fromLazyBytes = R.fromLazyBytes currentOS +-- | See 'R.fromString' fromString :: String -> F.FilePath fromString = R.fromString currentOS +-- | See 'R.splitSearchPath' splitSearchPath :: B.ByteString -> [F.FilePath] splitSearchPath = R.splitSearchPath currentOS
System/FilePath/Internal.hs view
@@ -16,6 +16,7 @@ import Data.Data (Data) import Data.Typeable (Typeable) import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8 ------------------------------------------------------------------------------- -- File Paths@@ -39,9 +40,19 @@ } deriving (Eq, Data, Typeable) +-- | A file path with no root, components, or filename empty :: FilePath empty = FilePath Nothing [] Nothing [] +filenameBytes :: FilePath -> B.ByteString+filenameBytes p = B.append name ext where+ name = case pathBasename p of+ Nothing -> B.empty+ Just name' -> name'+ ext = case pathExtensions p of+ [] -> B.empty+ exts -> B.intercalate (B8.pack ".") (B.empty:exts)+ ------------------------------------------------------------------------------- -- Rules -------------------------------------------------------------------------------@@ -49,10 +60,23 @@ data Rules = Rules { rulesName :: String , toByteChunks :: FilePath -> [B.ByteString]+ + -- | Parse a strict 'B.ByteString', such as those received from+ -- OS libraries, into a 'FilePath'. , fromBytes :: B.ByteString -> FilePath , caseSensitive :: Bool+ + -- | Check if a 'FilePath' is valid; that is, it must not contain+ -- any illegal characters, and must have a root appropriate to the+ -- current 'Rules'. , valid :: FilePath -> Bool+ + -- | Split a search path, such as @$PATH@ or @$PYTHONPATH@, into+ -- a list of 'FilePath's. , splitSearchPath :: B.ByteString -> [FilePath]+ + -- | Remove redundant characters. On case-insensitive platforms,+ -- also lowercases any ASCII uppercase characters. , normalise :: FilePath -> FilePath }
System/FilePath/Rules.hs view
@@ -40,12 +40,16 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BL8 -import System.FilePath+import System.FilePath hiding (root, filename) import System.FilePath.Internal ------------------------------------------------------------------------------- -- Rule-specific path properties -------------------------------------------------------------------------------++-- | Check if two different 'FilePath's refer to the same file. This does+-- not perform any link resolution, so some equivalent files might be+-- missed. equivalent :: Rules -> FilePath -> FilePath -> Bool equivalent r x y = n x == n y where n p = if caseSensitive r@@ -64,18 +68,32 @@ ------------------------------------------------------------------------------- -- Public helpers -------------------------------------------------------------------------------++-- | Convert a 'FilePath' into a strict 'B.ByteString', suitable for passing+-- to OS libraries. toBytes :: Rules -> FilePath -> B.ByteString toBytes r = B.concat . toByteChunks r +-- | Convert a 'FilePath' into a lazy 'BL.ByteString'. toLazyBytes :: Rules -> FilePath -> BL.ByteString toLazyBytes r = BL.fromChunks . toByteChunks r +-- | Parse a lazy 'BL.ByteString' into a 'FilePath'. fromLazyBytes :: Rules -> BL.ByteString -> FilePath fromLazyBytes r = fromBytes r . B.concat . BL.toChunks +-- | Convert a 'FilePath' into a lazy 'String'. This is useful for+-- interoperating with legacy libraries. No decoding is performed; the+-- string's character ordinals are equal to the path's original bytes. If you+-- need to display a 'FilePath' to the user, use 'toLazyBytes' and an+-- appropriate decoding function. toString :: Rules -> FilePath -> String toString r = BL8.unpack . toLazyBytes r +-- | Parse a lazy 'String' into a 'FilePath'. This is useful for+-- interoperating with legacy libraries. No encoding is performed;+-- characters are truncated to 8 bits. If you need to accept a 'FilePath'+-- from the user, use 'fromLazyBytes' and an appropriate encoding function. fromString :: Rules -> String -> FilePath fromString r = fromBytes r . B8.pack @@ -90,13 +108,7 @@ RootWindowsCurrentVolume -> "\\" byteComponents :: FilePath -> [B.ByteString]-byteComponents path = pathComponents path ++ [name] where- name = (`B.append` ext) $ case pathBasename path of- Nothing -> B.empty- Just name' -> name'- ext = case pathExtensions path of- [] -> B.empty- exts -> B.intercalate (B8.pack ".") (B.empty:exts)+byteComponents path = pathComponents path ++ [filenameBytes path] upperBytes :: B.ByteString -> B.ByteString upperBytes bytes = (`B.map` bytes) $ \b -> if b >= 0x41 && b <= 0x5A@@ -138,9 +150,12 @@ then pastRoot else init pastRoot - (name, exts) = case B.split 0x2E (last split') of- [] -> (Nothing, [])- (name':exts') -> (Just name', exts')+ filename = last split'+ (name, exts) = if elem filename [B8.pack ".", B8.pack ".."]+ then (Just filename, [])+ else case B.split 0x2E (last split') of+ [] -> (Nothing, [])+ (name':exts') -> (Just name', exts') posixValid :: FilePath -> Bool posixValid p = validRoot && validComponents where@@ -203,9 +218,12 @@ then pastRoot else init pastRoot - (name, exts) = case B.split 0x2E (last split') of- [] -> (Nothing, [])- (name':exts') -> (Just name', exts')+ filename = last split'+ (name, exts) = if elem filename [B8.pack ".", B8.pack ".."]+ then (Just filename, [])+ else case B.split 0x2E (last split') of+ [] -> (Nothing, [])+ (name':exts') -> (Just name', exts') winValid :: FilePath -> Bool winValid p = validRoot && noReserved && validCharacters where
system-filepath.cabal view
@@ -1,5 +1,5 @@ name: system-filepath-version: 0.1+version: 0.1.1 synopsis: High-level, byte-based file and directory path manipulations license: MIT license-file: license.txt