Skip to content

Commit faac262

Browse files
committed
Speed up splitFileName
1 parent 8a4a7f0 commit faac262

File tree

1 file changed

+38
-4
lines changed

1 file changed

+38
-4
lines changed

System/FilePath/Internal.hs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ import Prelude (Char, Bool(..), Maybe(..), (.), (&&), (<=), not, fst, maybe, (||
115115
import Data.Bifunctor (first)
116116
import Data.Semigroup ((<>))
117117
import qualified Prelude as P
118-
import Data.Maybe(isJust)
118+
import Data.Maybe(fromMaybe, isJust)
119119
import qualified Data.List as L
120120

121121
#ifndef OS_PATH
@@ -604,11 +604,45 @@ splitFileName x = if null path
604604
-- directory to make a valid FILEPATH, and having a "./" appear would
605605
-- look strange and upset simple equality properties. See
606606
-- e.g. replaceFileName.
607+
--
608+
-- A naive implementation is
609+
--
610+
-- splitFileName_ fp = (drv <> dir, file)
611+
-- where
612+
-- (drv, pth) = splitDrive fp
613+
-- (dir, file) = breakEnd isPathSeparator pth
614+
--
615+
-- but it is undesirable for two reasons:
616+
-- * splitDrive is very slow on Windows,
617+
-- * we unconditionally allocate 5 FilePath objects where only 2 would normally suffice.
618+
--
619+
-- In the implementation below we first speculatively split the input by the last path
620+
-- separator. In the vast majority of cases this is already the answer, except
621+
-- two exceptional cases explained below.
622+
--
607623
splitFileName_ :: FILEPATH -> (STRING, STRING)
608-
splitFileName_ fp = (drv <> dir, file)
624+
splitFileName_ fp
625+
-- If dirSlash is empty, @fp@ is either a genuine filename without any dir,
626+
-- or just a Windows drive name without slash like "c:".
627+
-- Run readDriveLetter to figure out.
628+
| isWindows
629+
, null dirSlash
630+
= fromMaybe (mempty, fp) (readDriveLetter fp)
631+
-- Another Windows quirk is that @fp@ could have been a shared drive "\\share"
632+
-- or UNC location "\\?\UNC\foo", where path separator is a part of the drive name.
633+
-- We can test this by trying dropDrive and falling back to splitDrive.
634+
| isWindows
635+
, Just (s1, _s2, bs') <- uncons2 dirSlash
636+
, isPathSeparator s1
637+
-- If bs' is empty, then s2 as the last character of dirSlash must be a path separator,
638+
-- so we are in the middle of shared drive.
639+
-- Otherwise, since s1 is a path separator, we might be in the middle of UNC path.
640+
, null bs' || maybe False (null . snd) (readDriveUNC dirSlash)
641+
= (fp, mempty)
642+
| otherwise
643+
= (dirSlash, file)
609644
where
610-
(drv, pth) = splitDrive fp
611-
(dir, file) = breakEnd isPathSeparator pth
645+
(dirSlash, file) = breakEnd isPathSeparator fp
612646

613647
-- | Set the filename.
614648
--

0 commit comments

Comments
 (0)