11-- |
2- -- Module : Streamly.Coreutils.RealPath
2+ -- Module : Streamly.Coreutils.ResolvePath
33-- Copyright : (c) 2022 Composewell Technologies
44-- License : BSD-3-Clause
55-- Maintainer : streamly@composewell.com
1010-- @.@ and @..@ segments, and follow every symbolic link along the way.
1111-- Corresponds to the shell @realpath@ command.
1212--
13- -- Call 'realPath ' with @id@ for the default behavior, or compose
13+ -- Call 'resolvePath ' with @id@ for the default behavior, or compose
1414-- modifiers with @(.)@ to customize:
1515--
1616-- * 'requireExistence' - control which path components must exist on
1717-- disk: 'AllParents' (default, GNU @-E@), 'EntirePath' (GNU @-e@),
1818-- or 'DontRequire' (GNU @-m@).
19- -- * 'resolveSymlinks ' - control when (and whether) symbolic links
19+ -- * 'resolutionMode ' - control when (and whether) symbolic links
2020-- are expanded: 'UseTargetParents' (default, GNU @-P@), 'UseOriginalParents'
21- -- (GNU @-L@), or 'DontResolve ' (GNU @-s@).
21+ -- (GNU @-L@), or 'DontResolveSymlinks ' (GNU @-s@).
2222-- * 'relativeTo' - produce a path relative to a given base directory
2323-- (GNU @realpath --relative-to=DIR@).
2424-- * 'relativeIfWithin' - produce a relative path only if it's under
3838-- * 'relativeTo' falls back to returning the canonicalized absolute
3939-- path unchanged when no common prefix exists with the base
4040-- (e.g. different drives on Windows).
41- -- * 'resolveSymlinks ' 'DontResolve ' combined with 'requireExistence'
41+ -- * 'resolutionMode ' 'DontResolveSymlinks ' combined with 'requireExistence'
4242-- 'DontRequire' is the only configuration that performs no
4343-- filesystem access on the path components (only
4444-- 'System.Directory.getCurrentDirectory' when the path is
4545-- relative). All other configurations involve some filesystem IO.
46- -- * Because 'DontResolve ' is lexical, it can give a different result
46+ -- * Because 'DontResolveSymlinks ' is lexical, it can give a different result
4747-- than the default mode when the path traverses through a symlink
4848-- via @..@: @\/link\/..@ lexically resolves to @\/@, but physically
4949-- resolves to the parent of the symlink's target.
5050
51- module Streamly.Coreutils.RealPath
52- ( RealPathOptions
51+ module Streamly.Coreutils.ResolvePath
52+ ( ResolvePathOptions
5353 , ExistenceCheck (.. )
54- , SymlinkResolution (.. )
54+ , ResolutionMode (.. )
5555 , requireExistence
56- , resolveSymlinks
56+ , resolutionMode
5757 , relativeTo
5858 , relativeIfWithin
59- , realPath
59+ , resolvePath
6060 )
6161where
6262
@@ -77,10 +77,10 @@ import System.FilePath
7777--
7878-- * Thin wrapper over 'System.Directory.canonicalizePath' for the
7979-- default 'UseTargetParents' (physical) mode; 'UseOriginalParents' and
80- -- 'DontResolve ' use 'makeAbsolute' plus a custom @..@-collapsing
80+ -- 'DontResolveSymlinks ' use 'makeAbsolute' plus a custom @..@-collapsing
8181-- walker ('lexicalCollapse').
8282--
83- -- * Why a single 'SymlinkResolution ' enum instead of two flags.
83+ -- * Why a single 'ResolutionMode ' enum instead of two flags.
8484-- Symlink expansion is a three-way choice, not two orthogonal
8585-- booleans. An earlier iteration exposed 'logical' and 'noSymlinks'
8686-- as separate modifiers, which required a precedence rule for
@@ -135,7 +135,7 @@ import System.FilePath
135135-- @realpath -e -s@ and @realpath -e -L@.
136136--
137137-- * 'relativeTo' always canonicalizes the base physically (following
138- -- symlinks) regardless of the 'SymlinkResolution ' mode. Otherwise
138+ -- symlinks) regardless of the 'ResolutionMode ' mode. Otherwise
139139-- @relativeTo "foo/../bar"@ with a lexical base would give
140140-- surprising results. If a future use case needs a lexical base,
141141-- add a separate modifier rather than overloading this one.
@@ -152,7 +152,7 @@ import System.FilePath
152152-- failure is an exceptional condition, not a lookup miss - matches
153153-- the error-handling guidance in the package design notes.
154154
155- -- | Which components of a path must exist on disk for 'realPath ' to
155+ -- | Which components of a path must exist on disk for 'resolvePath ' to
156156-- succeed.
157157--
158158-- * 'EntirePath': every component - including the leaf - must exist.
@@ -184,37 +184,37 @@ data ExistenceCheck
184184-- regardless of whether that segment was a symlink. Remaining
185185-- symlinks in the surviving path are still expanded. Matches GNU
186186-- @realpath -L@ / @--logical@.
187- -- * 'DontResolve ': no symlinks are expanded anywhere in the path.
187+ -- * 'DontResolveSymlinks ': no symlinks are expanded anywhere in the path.
188188-- @..@ is lexical (same as 'UseOriginalParents'), and symlinks in
189189-- other components are preserved as-is. Matches GNU @realpath -s@
190190-- / @--no-symlinks@.
191191--
192192-- The three modes produce the same result on paths that contain no
193193-- symlinks. 'UseTargetParents' and 'UseOriginalParents' diverge when a
194- -- symlink is followed by @..@; 'DontResolve ' diverges from both
194+ -- symlink is followed by @..@; 'DontResolveSymlinks ' diverges from both
195195-- whenever the path contains any symlink.
196- data SymlinkResolution
196+ data ResolutionMode
197197 = UseTargetParents
198198 | UseOriginalParents
199- | DontResolve
199+ | DontResolveSymlinks
200200
201- -- | Options for 'realPath '. Users don't construct 'RealPathOptions '
201+ -- | Options for 'resolvePath '. Users don't construct 'ResolvePathOptions '
202202-- directly - instead, pass @id@ for the default behavior, or a
203- -- modifier (or composition of modifiers with @(.)@) to 'realPath '.
204- data RealPathOptions = RealPathOptions
203+ -- modifier (or composition of modifiers with @(.)@) to 'resolvePath '.
204+ data ResolvePathOptions = ResolvePathOptions
205205 { _existenceCheck :: ExistenceCheck
206- , _symlinkResolution :: SymlinkResolution
206+ , _resolutionMode :: ResolutionMode
207207 , _relativeTo :: Maybe FilePath
208208 , _relativeIfWithin :: Maybe FilePath
209209 }
210210
211211-- Default configuration: the seed value that modifiers are composed
212212-- onto. Users supply @id@ (or a modifier chain) at the call site
213213-- rather than referring to this directly.
214- defaultConfig :: RealPathOptions
215- defaultConfig = RealPathOptions
214+ defaultConfig :: ResolvePathOptions
215+ defaultConfig = ResolvePathOptions
216216 { _existenceCheck = AllParents
217- , _symlinkResolution = UseTargetParents
217+ , _resolutionMode = UseTargetParents
218218 , _relativeTo = Nothing
219219 , _relativeIfWithin = Nothing
220220 }
@@ -229,12 +229,12 @@ defaultConfig = RealPathOptions
229229-- 'EntirePath' rejects a path whose leaf does not exist:
230230--
231231-- >>> cwd <- getCurrentDirectory
232- -- >>> r1 <- realPath (requireExistence EntirePath) cwd
233- -- >>> r2 <- realPath (requireExistence EntirePath) r1
232+ -- >>> r1 <- resolvePath (requireExistence EntirePath) cwd
233+ -- >>> r2 <- resolvePath (requireExistence EntirePath) r1
234234-- >>> r1 == r2
235235-- True
236236--
237- -- >>> result <- try (realPath (requireExistence EntirePath) "/definitely/does/not/exist/xyzzy") :: IO (Either SomeException FilePath)
237+ -- >>> result <- try (resolvePath (requireExistence EntirePath) "/definitely/does/not/exist/xyzzy") :: IO (Either SomeException FilePath)
238238-- >>> either (const True) (const False) result
239239-- True
240240--
@@ -244,27 +244,28 @@ defaultConfig = RealPathOptions
244244-- the existing prefix):
245245--
246246-- >>> tmp <- getTemporaryDirectory
247- -- >>> r1 <- realPath id (tmp </> "missing-leaf")
247+ -- >>> r1 <- resolvePath id (tmp </> "missing-leaf")
248248-- >>> r2 <- canonicalizePath (tmp </> "missing-leaf")
249249-- >>> r1 == r2
250250-- True
251251--
252252-- 'AllParents' rejects a path whose parent does not exist:
253253--
254- -- >>> result <- try (realPath id "/definitely/does/not/exist/child") :: IO (Either SomeException FilePath)
254+ -- >>> result <- try (resolvePath id "/definitely/does/not/exist/child") :: IO (Either SomeException FilePath)
255255-- >>> either (const True) (const False) result
256256-- True
257257--
258258-- 'DontRequire' accepts any path, existent or not:
259259--
260- -- >>> r <- realPath (requireExistence DontRequire) "/definitely/does/not/exist/child"
260+ -- >>> r <- resolvePath (requireExistence DontRequire) "/definitely/does/not/exist/child"
261261-- >>> null r
262262-- False
263- requireExistence :: ExistenceCheck -> RealPathOptions -> RealPathOptions
263+ requireExistence :: ExistenceCheck -> ResolvePathOptions -> ResolvePathOptions
264264requireExistence check opts = opts { _existenceCheck = check }
265265
266- -- | Choose how @..@ and symbolic links interact. See
267- -- 'SymlinkResolution' for the three modes and a full explanation.
266+ -- | Set the resolution mode - how @..@ segments and symbolic links
267+ -- are handled. See 'ResolutionMode' for the three modes and a full
268+ -- explanation.
268269--
269270-- Default (without this modifier): 'UseTargetParents' - @..@ ascends
270271-- from the symlink's target (GNU @realpath@'s physical mode, @-P@).
@@ -277,24 +278,24 @@ requireExistence check opts = opts { _existenceCheck = check }
277278-- base):
278279--
279280-- >>> tmp <- getTemporaryDirectory
280- -- >>> let opts m = resolveSymlinks m . requireExistence DontRequire
281- -- >>> r1 <- realPath (opts UseOriginalParents) (tmp </> "a" </> ".." </> "b")
282- -- >>> r2 <- realPath (requireExistence DontRequire) (tmp </> "b")
281+ -- >>> let opts m = resolutionMode m . requireExistence DontRequire
282+ -- >>> r1 <- resolvePath (opts UseOriginalParents) (tmp </> "a" </> ".." </> "b")
283+ -- >>> r2 <- resolvePath (requireExistence DontRequire) (tmp </> "b")
283284-- >>> r1 == r2
284285-- True
285286--
286- -- 'DontResolve ' collapses @..@ and @.@ textually and performs no
287+ -- 'DontResolveSymlinks ' collapses @..@ and @.@ textually and performs no
287288-- symlink resolution (so the base is not canonicalized - the result
288289-- may differ from 'UseTargetParents' when the base contains symlinks):
289290--
290- -- >>> r <- realPath (opts DontResolve ) (tmp </> "a" </> ".." </> "b")
291+ -- >>> r <- resolvePath (opts DontResolveSymlinks ) (tmp </> "a" </> ".." </> "b")
291292-- >>> r == tmp </> "b"
292293-- True
293- -- >>> r <- realPath (opts DontResolve ) (tmp </> "." </> "x")
294+ -- >>> r <- resolvePath (opts DontResolveSymlinks ) (tmp </> "." </> "x")
294295-- >>> r == tmp </> "x"
295296-- True
296- resolveSymlinks :: SymlinkResolution -> RealPathOptions -> RealPathOptions
297- resolveSymlinks mode opts = opts { _symlinkResolution = mode }
297+ resolutionMode :: ResolutionMode -> ResolvePathOptions -> ResolvePathOptions
298+ resolutionMode mode opts = opts { _resolutionMode = mode }
298299
299300-- | Return the canonical path relative to the given base directory.
300301-- Corresponds to GNU @realpath --relative-to=DIR@.
@@ -312,9 +313,9 @@ resolveSymlinks mode opts = opts { _symlinkResolution = mode }
312313-- A path relative to itself is @\".\"@:
313314--
314315-- >>> cwd <- getCurrentDirectory
315- -- >>> realPath (relativeTo cwd) cwd
316+ -- >>> resolvePath (relativeTo cwd) cwd
316317-- "."
317- relativeTo :: FilePath -> RealPathOptions -> RealPathOptions
318+ relativeTo :: FilePath -> ResolvePathOptions -> ResolvePathOptions
318319relativeTo base opts = opts { _relativeTo = Just base }
319320
320321-- | Return a relative path only when the resolved path lies within
@@ -334,16 +335,16 @@ relativeTo base opts = opts { _relativeTo = Just base }
334335--
335336-- >>> tmp <- getTemporaryDirectory
336337-- >>> let child = tmp </> "missing-leaf"
337- -- >>> realPath (relativeIfWithin tmp) child
338+ -- >>> resolvePath (relativeIfWithin tmp) child
338339-- "missing-leaf"
339340--
340341-- Outside the boundary, the absolute path is returned unchanged:
341342--
342- -- >>> r1 <- realPath (relativeIfWithin tmp) "/"
343+ -- >>> r1 <- resolvePath (relativeIfWithin tmp) "/"
343344-- >>> r2 <- canonicalizePath "/"
344345-- >>> r1 == r2
345346-- True
346- relativeIfWithin :: FilePath -> RealPathOptions -> RealPathOptions
347+ relativeIfWithin :: FilePath -> ResolvePathOptions -> ResolvePathOptions
347348relativeIfWithin dir opts = opts { _relativeIfWithin = Just dir }
348349
349350-- Collapse @.@ and @..@ segments lexically. On absolute paths, @..@
@@ -384,14 +385,14 @@ checkExistence check path = case check of
384385 exists <- doesPathExist path
385386 when (not exists) $
386387 ioError
387- (userError (" realPath : path does not exist: " ++ path))
388+ (userError (" resolvePath : path does not exist: " ++ path))
388389 AllParents -> do
389390 let parent = takeDirectory path
390391 parentExists <- doesDirectoryExist parent
391392 when (not parentExists) $
392393 ioError
393394 (userError
394- (" realPath : parent directory does not exist: "
395+ (" resolvePath : parent directory does not exist: "
395396 ++ parent))
396397
397398-- Is the second path a descendant of (or equal to) the first?
@@ -415,21 +416,21 @@ isPathUnder dir p = splitDirectories dir `isPrefixOf` splitDirectories p
415416-- The default-mode result on an existing directory is absolute:
416417--
417418-- >>> cwd <- getCurrentDirectory
418- -- >>> r <- realPath id cwd
419+ -- >>> r <- resolvePath id cwd
419420-- >>> isAbsolute r
420421-- True
421- realPath
422- :: (RealPathOptions -> RealPathOptions )
422+ resolvePath
423+ :: (ResolvePathOptions -> ResolvePathOptions )
423424 -> FilePath
424425 -> IO FilePath
425- realPath modifier path = do
426+ resolvePath modifier path = do
426427 let opts = modifier defaultConfig
427428 checkExistence (_existenceCheck opts) path
428- resolved <- case _symlinkResolution opts of
429+ resolved <- case _resolutionMode opts of
429430 UseTargetParents -> canonicalizePath path
430431 UseOriginalParents ->
431432 fmap lexicalCollapse (makeAbsolute path) >>= canonicalizePath
432- DontResolve -> fmap lexicalCollapse (makeAbsolute path)
433+ DontResolveSymlinks -> fmap lexicalCollapse (makeAbsolute path)
433434 -- Relativization and containment logic:
434435 -- * _relativeTo chooses the target to relativize against.
435436 -- * _relativeIfWithin gates whether relativization fires: if
0 commit comments