hpath 0.10.2 → 0.11.0
raw patch · 5 files changed
+127/−143 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- HPath: any :: QuasiQuoter
- HPath: class RelC m
- HPath: data Fn
- HPath: fn :: QuasiQuoter
- HPath: instance HPath.RelC HPath.Fn
- HPath: instance HPath.RelC HPath.Rel
- HPath: parseFn :: MonadThrow m => ByteString -> m (Path Fn)
- HPath: withFnPath :: Path Fn -> (ByteString -> IO a) -> IO a
- HPath.Internal: MkPath :: ByteString -> Path b
- HPath.Internal: data Path b
- HPath.Internal: instance Control.DeepSeq.NFData (HPath.Internal.Path b)
- HPath.Internal: instance GHC.Classes.Eq (HPath.Internal.Path b)
- HPath.Internal: instance GHC.Classes.Ord (HPath.Internal.Path b)
- HPath.Internal: instance GHC.Show.Show (HPath.Internal.Path b)
+ HPath: fromAny :: Either (Path Abs) (Path Rel) -> ByteString
+ HPath: getAllComponents :: Path Rel -> [Path Rel]
+ HPath: getAllComponentsAfterRoot :: Path Abs -> [Path Rel]
+ HPath: isRootPath :: Path Abs -> Bool
+ HPath: rootPath :: Path Abs
- HPath: (</>) :: RelC r => Path b -> Path r -> Path b
+ HPath: (</>) :: Path b -> Path Rel -> Path b
- HPath: basename :: MonadThrow m => Path b -> m (Path Fn)
+ HPath: basename :: MonadThrow m => Path b -> m (Path Rel)
- HPath: fromRel :: RelC r => Path r -> ByteString
+ HPath: fromRel :: Path Rel -> ByteString
- HPath: parseAny :: MonadThrow m => ByteString -> m (Path a)
+ HPath: parseAny :: MonadThrow m => ByteString -> m (Either (Path Abs) (Path Rel))
Files
- CHANGELOG +7/−0
- README.md +0/−2
- hpath.cabal +2/−2
- src/HPath.hs +108/−133
- src/HPath/Internal.hs +10/−6
CHANGELOG view
@@ -1,3 +1,10 @@+0.11.0+ * Many API breaking changes+ * Remove RelC and Fn, because they complicate API/break semantics (see #29)+ * Redo 'parseAny'+ * Unexpose HPath.Internal+ * Don't preserve trailing path separators (if you need to pass something to a C function that way, do it manually)+ * Added `rooPath`, `isRootPath`, `getAllComponents`, `getAllComponentsAfterRoot` 0.10.2 * Add `parseAny` and the related QuasiQuoter 0.10.1
README.md view
@@ -28,11 +28,9 @@ ## Differences to 'path' * doesn't attempt to fake IO-related information into the path, so whether a path points to a file or directory is up to your IO-code to decide...-* trailing path separators will be preserved if they exist, no messing with that * uses safe ByteString for filepaths under the hood instead of unsafe String * fixes broken [dirname](https://github.com/chrisdone/path/issues/18) * renames dirname/filename to basename/dirname to match the POSIX shell functions-* introduces a new `Path Fn` for safe filename guarantees and a `RelC` class * allows pattern matching via unidirectional PatternSynonym * uses simple doctest for testing * allows `~/` as relative path, because on posix level `~` is just a regular filename that does _NOT_ point to `$HOME`
hpath.cabal view
@@ -1,5 +1,5 @@ name: hpath-version: 0.10.2+version: 0.11.0 synopsis: Support for well-typed paths description: Support for well-typed paths, utilizing ByteString under the hood. license: BSD3@@ -30,7 +30,7 @@ else ghc-options: -Wall exposed-modules: HPath- HPath.Internal+ other-modules: HPath.Internal build-depends: base >= 4.8 && <5 , bytestring >= 0.10.0.0 , deepseq
src/HPath.hs view
@@ -25,39 +25,39 @@ Abs ,Path ,Rel- ,Fn ,PathParseException ,PathException- ,RelC #if __GLASGOW_HASKELL__ >= 708 -- * PatternSynonyms/ViewPatterns ,pattern Path #endif- -- * Path Parsing+ -- * Path Construction ,parseAbs- ,parseFn ,parseRel ,parseAny+ ,rootPath -- * Path Conversion ,fromAbs ,fromRel ,toFilePath+ ,fromAny -- * Path Operations ,(</>) ,basename ,dirname- ,isParentOf ,getAllParents+ ,getAllComponents+ ,getAllComponentsAfterRoot ,stripDir+ -- * Path Examination+ ,isParentOf+ ,isRootPath -- * Path IO helpers ,withAbsPath ,withRelPath- ,withFnPath -- * Quasiquoters ,abs ,rel- ,fn- ,any ) where @@ -91,14 +91,10 @@ -- | A relative path; one without a root. data Rel deriving (Typeable) --- | A filename, without any '/'.-data Fn deriving (Typeable)- -- | Exception when parsing a location. data PathParseException = InvalidAbs ByteString | InvalidRel ByteString- | InvalidFn ByteString | Couldn'tStripPrefixTPS ByteString ByteString deriving (Show,Typeable) instance Exception PathParseException@@ -107,11 +103,7 @@ deriving (Show,Typeable) instance Exception PathException -class RelC m -instance RelC Rel-instance RelC Fn- -------------------------------------------------------------------------------- -- PatternSynonyms @@ -138,7 +130,7 @@ -- >>> parseAbs "/abc/def" :: Maybe (Path Abs) -- Just "/abc/def" -- >>> parseAbs "/abc/def/.///" :: Maybe (Path Abs)--- Just "/abc/def/"+-- Just "/abc/def" -- >>> parseAbs "abc" :: Maybe (Path Abs) -- Nothing -- >>> parseAbs "" :: Maybe (Path Abs)@@ -151,7 +143,7 @@ if isAbsolute filepath && isValid filepath && not (hasParentDir filepath)- then return (MkPath $ normalise filepath)+ then return (MkPath . dropTrailingPathSeparator . normalise $ filepath) else throwM (InvalidAbs filepath) @@ -166,11 +158,11 @@ -- >>> parseRel "abc" :: Maybe (Path Rel) -- Just "abc" -- >>> parseRel "def/" :: Maybe (Path Rel)--- Just "def/"+-- Just "def" -- >>> parseRel "abc/def" :: Maybe (Path Rel) -- Just "abc/def" -- >>> parseRel "abc/def/." :: Maybe (Path Rel)--- Just "abc/def/"+-- Just "abc/def" -- >>> parseRel "/abc" :: Maybe (Path Rel) -- Nothing -- >>> parseRel "" :: Maybe (Path Rel)@@ -189,46 +181,11 @@ filepath /= BS.pack [_period, _period] && not (hasParentDir filepath) && isValid filepath- then return (MkPath $ normalise filepath)+ then return (MkPath . dropTrailingPathSeparator . normalise $ filepath) else throwM (InvalidRel filepath) --- | Parses a filename. Filenames must not contain slashes.--- Excludes '.' and '..'.------ Throws: 'PathParseException'------ >>> parseFn "abc" :: Maybe (Path Fn)--- Just "abc"--- >>> parseFn "..." :: Maybe (Path Fn)--- Just "..."--- >>> parseFn "def/" :: Maybe (Path Fn)--- Nothing--- >>> parseFn "abc/def" :: Maybe (Path Fn)--- Nothing--- >>> parseFn "abc/def/." :: Maybe (Path Fn)--- Nothing--- >>> parseFn "/abc" :: Maybe (Path Fn)--- Nothing--- >>> parseFn "" :: Maybe (Path Fn)--- Nothing--- >>> parseFn "abc/../foo" :: Maybe (Path Fn)--- Nothing--- >>> parseFn "." :: Maybe (Path Fn)--- Nothing--- >>> parseFn ".." :: Maybe (Path Fn)--- Nothing-parseFn :: MonadThrow m- => ByteString -> m (Path Fn)-parseFn filepath =- if isFileName filepath &&- filepath /= BS.singleton _period &&- filepath /= BS.pack [_period, _period] &&- isValid filepath- then return (MkPath filepath)- else throwM (InvalidFn filepath) - -- | Parses a path, whether it's relative or absolute. Will lose -- information on whether it's relative or absolute. If you need to know, -- reparse it.@@ -238,32 +195,36 @@ -- -- Throws: 'PathParseException' ----- >>> parseAny "/abc" :: Maybe (Path a)--- Just "/abc"--- >>> parseAny "..." :: Maybe (Path a)--- Just "..."--- >>> parseAny "abc/def" :: Maybe (Path a)--- Just "abc/def"--- >>> parseAny "abc/def/." :: Maybe (Path a)--- Just "abc/def/"--- >>> parseAny "/abc" :: Maybe (Path a)--- Just "/abc"--- >>> parseAny "" :: Maybe (Path a)+-- >>> parseAny "/abc" :: Maybe (Either (Path Abs) (Path Rel))+-- Just (Left "/abc")+-- >>> parseAny "..." :: Maybe (Either (Path Abs) (Path Rel))+-- Just (Right "...")+-- >>> parseAny "abc/def" :: Maybe (Either (Path Abs) (Path Rel))+-- Just (Right "abc/def")+-- >>> parseAny "abc/def/." :: Maybe (Either (Path Abs) (Path Rel))+-- Just (Right "abc/def")+-- >>> parseAny "/abc" :: Maybe (Either (Path Abs) (Path Rel))+-- Just (Left "/abc")+-- >>> parseAny "" :: Maybe (Either (Path Abs) (Path Rel)) -- Nothing--- >>> parseAny "abc/../foo" :: Maybe (Path a)+-- >>> parseAny "abc/../foo" :: Maybe (Either (Path Abs) (Path Rel)) -- Nothing--- >>> parseAny "." :: Maybe (Path a)+-- >>> parseAny "." :: Maybe (Either (Path Abs) (Path Rel)) -- Nothing--- >>> parseAny ".." :: Maybe (Path a)+-- >>> parseAny ".." :: Maybe (Either (Path Abs) (Path Rel)) -- Nothing-parseAny :: MonadThrow m => ByteString -> m (Path a)+parseAny :: MonadThrow m => ByteString -> m (Either (Path Abs) (Path Rel)) parseAny filepath = case parseAbs filepath of- Just (MkPath p) -> pure $ (MkPath p)+ Just p -> pure $ Left p Nothing -> case parseRel filepath of- Just (MkPath p) -> pure $ (MkPath p)- Nothing -> throwM (InvalidRel filepath)+ Just p -> pure $ Right p+ Nothing -> throwM (InvalidRel filepath) +rootPath :: Path Abs+rootPath = (MkPath (BS.singleton _slash))++ -------------------------------------------------------------------------------- -- Path Conversion @@ -276,10 +237,13 @@ fromAbs = toFilePath -- | Convert a relative Path to a ByteString type.-fromRel :: RelC r => Path r -> ByteString+fromRel :: Path Rel -> ByteString fromRel = toFilePath +fromAny :: Either (Path Abs) (Path Rel) -> ByteString+fromAny = either toFilePath toFilePath + -------------------------------------------------------------------------------- -- Path Operations @@ -298,15 +262,16 @@ -- "/path/to/file" -- >>> (MkPath "/") </> (MkPath "file/lal" :: Path Rel) -- "/file/lal"--- >>> (MkPath "/") </> (MkPath "file/" :: Path Rel)--- "/file/"-(</>) :: RelC r => Path b -> Path r -> Path b+-- >>> (MkPath "/") </> (MkPath "file" :: Path Rel)+-- "/file"+(</>) :: Path b -> Path Rel -> Path b (</>) (MkPath a) (MkPath b) = MkPath (a' `BS.append` b) where- a' = if BS.last a == pathSeparator- then a- else addTrailingPathSeparator a+ a' = if hasTrailingPathSeparator a+ then a+ else addTrailingPathSeparator a + -- | Strip directory from path, making it relative to that directory. -- Throws 'Couldn'tStripPrefixDir' if directory is not a parent of the path. --@@ -333,27 +298,13 @@ where p' = addTrailingPathSeparator p --- | Is p a parent of the given location? Implemented in terms of--- 'stripDir'. The bases must match.------ >>> (MkPath "/lal/lad") `isParentOf` (MkPath "/lal/lad/fad")--- True--- >>> (MkPath "lal/lad") `isParentOf` (MkPath "lal/lad/fad")--- True--- >>> (MkPath "/") `isParentOf` (MkPath "/")--- False--- >>> (MkPath "/lal/lad/fad") `isParentOf` (MkPath "/lal/lad")--- False--- >>> (MkPath "fad") `isParentOf` (MkPath "fad")--- False-isParentOf :: Path b -> Path b -> Bool-isParentOf p l = isJust (stripDir p l :: Maybe (Path Rel)) - -- |Get all parents of a path. -- -- >>> getAllParents (MkPath "/abs/def/dod") -- ["/abs/def","/abs","/"]+-- >>> getAllParents (MkPath "/foo")+-- ["/"] -- >>> getAllParents (MkPath "/") -- [] getAllParents :: Path Abs -> [Path Abs]@@ -361,9 +312,29 @@ | np == BS.singleton pathSeparator = [] | otherwise = dirname (MkPath np) : getAllParents (dirname $ MkPath np) where- np = dropTrailingPathSeparator . normalise $ p+ np = normalise p +-- | Gets all path components.+--+-- >>> getAllComponents (MkPath "abs/def/dod")+-- ["abs","def","dod"]+-- >>> getAllComponents (MkPath "abs")+-- ["abs"]+getAllComponents :: Path Rel -> [Path Rel]+getAllComponents (MkPath p) = fmap MkPath . splitDirectories $ p+++-- | Gets all path components after the "/" root directory.+--+-- >>> getAllComponentsAfterRoot (MkPath "/abs/def/dod")+-- ["abs","def","dod"]+-- >>> getAllComponentsAfterRoot (MkPath "/abs")+-- ["abs"]+getAllComponentsAfterRoot :: Path Abs -> [Path Rel]+getAllComponentsAfterRoot p = getAllComponents (fromJust $ stripDir rootPath p)++ -- | Extract the directory name of a path. -- -- >>> dirname (MkPath "/abc/def/dod")@@ -371,7 +342,7 @@ -- >>> dirname (MkPath "/") -- "/" dirname :: Path Abs -> Path Abs-dirname (MkPath fp) = MkPath (takeDirectory $ dropTrailingPathSeparator fp)+dirname (MkPath fp) = MkPath (takeDirectory fp) -- | Extract the file part of a path. --@@ -382,21 +353,54 @@ -- -- Throws: `PathException` if given the root path "/" ----- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Fn)+-- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Rel) -- Just "dod"--- >>> basename (MkPath "/abc/def/dod/") :: Maybe (Path Fn)+-- >>> basename (MkPath "abc/def/dod") :: Maybe (Path Rel) -- Just "dod"--- >>> basename (MkPath "/") :: Maybe (Path Fn)+-- >>> basename (MkPath "dod") :: Maybe (Path Rel)+-- Just "dod"+-- >>> basename (MkPath "/") :: Maybe (Path Rel) -- Nothing-basename :: MonadThrow m => Path b -> m (Path Fn)+basename :: MonadThrow m => Path b -> m (Path Rel) basename (MkPath l) | not (isAbsolute rl) = return $ MkPath rl | otherwise = throwM RootDirHasNoBasename where- rl = last . splitPath . dropTrailingPathSeparator $ l+ rl = last . splitPath $ l + --------------------------------------------------------------------------------+-- Path Examination++-- | Is p a parent of the given location? Implemented in terms of+-- 'stripDir'. The bases must match.+--+-- >>> (MkPath "/lal/lad") `isParentOf` (MkPath "/lal/lad/fad")+-- True+-- >>> (MkPath "lal/lad") `isParentOf` (MkPath "lal/lad/fad")+-- True+-- >>> (MkPath "/") `isParentOf` (MkPath "/")+-- False+-- >>> (MkPath "/lal/lad/fad") `isParentOf` (MkPath "/lal/lad")+-- False+-- >>> (MkPath "fad") `isParentOf` (MkPath "fad")+-- False+isParentOf :: Path b -> Path b -> Bool+isParentOf p l = isJust (stripDir p l :: Maybe (Path Rel))+++-- | Check whether the given Path is the root "/" path.+--+-- >>> isRootPath (MkPath "/lal/lad")+-- False+-- >>> isRootPath (MkPath "/")+-- True+isRootPath :: Path Abs -> Bool+isRootPath = (== rootPath)+++-------------------------------------------------------------------------------- -- Path IO helpers @@ -408,10 +412,7 @@ withRelPath (MkPath p) action = action p -withFnPath :: Path Fn -> (ByteString -> IO a) -> IO a-withFnPath (MkPath p) action = action p - ------------------------ -- ByteString helpers @@ -447,12 +448,6 @@ mkRel :: ByteString -> Q Exp mkRel = either (error . show) lift . parseRel -mkFN :: ByteString -> Q Exp-mkFN = either (error . show) lift . parseFn--mkAny :: ByteString -> Q Exp-mkAny = either (error . show) lift . parseAny- -- | Quasiquote an absolute Path. This accepts Unicode Chars and will encode as UTF-8. -- -- >>> [abs|/etc/profile|] :: Path Abs@@ -475,23 +470,3 @@ rel :: QuasiQuoter rel = qq mkRel --- | Quasiquote a file name. This accepts Unicode Chars and will encode as UTF-8.------ >>> [fn|etc|] :: Path Fn--- "etc"--- >>> [fn||] :: Path Fn--- "\239\131\144"-fn :: QuasiQuoter-fn = qq mkFN---- | Quasiquote any path (relative or absolute).--- This accepts Unicode Chars and will encode as UTF-8.------ >>> [any|/etc/profile|] :: Path a--- "/etc/profile"--- >>> [any|etc|] :: Path a--- "etc"--- >>> [any||] :: Path a--- "\239\131\144"-any :: QuasiQuoter-any = qq mkAny
src/HPath/Internal.hs view
@@ -10,15 +10,19 @@ import Data.ByteString (ByteString) import Data.Data --- | Path of some base and type.+-- | The main Path type. ----- Internally is a ByteString. The ByteString can be of two formats only:+-- The type variable 'b' is either: ----- 1. without trailing path separator: @file.txt@, @foo\/bar.txt@, @\/foo\/bar.txt@--- 2. with trailing path separator: @foo\/@, @\/foo\/bar\/@+-- * Abs -- absolute path+-- * Rel -- relative path ----- There are no duplicate--- path separators @\/\/@, no @..@, no @.\/@, no @~\/@, etc.+-- Internally is a ByteString. The path is guaranteed to+-- be normalised and contain no trailing Path separators,+-- except for the '/' root path.+--+-- There are no duplicate path separators+-- @\/\/@, no @..@, no @.\/@, no @~\/@, etc. data Path b = MkPath ByteString deriving (Typeable)