@@ -52,9 +52,20 @@ import "filepath" System.OsString.Internal.Types (WindowsString(..), WindowsChar
5252import  qualified  "filepath" System.OsPath.Data.ByteString.Short.Word16  as  BC 
5353#endif 
5454
55+ #if  defined(LONG_PATHS)
56+ import  Control.Exception  (displayException , Exception )
57+ import  System.IO.Error  (ioeSetFileName , modifyIOError , ioeSetLocation , ioeGetLocation , catchIOError )
58+ import  Data.Char  (isAlpha , isAscii , toUpper )
59+ import  GHC.IO.Encoding.Failure  (CodingFailureMode (TransliterateCodingFailure ))
60+ import  GHC.IO.Encoding.UTF16  (mkUTF16le )
61+ import  qualified  System.Win32.WindowsString.Info  as  WS 
62+ #endif 
63+ 
5564--  |  Open a file and return the 'Handle'. 
5665openFile  ::  WindowsPath  ->  IOMode  ->  IO   Handle 
57- openFile fp iomode =  bracketOnError
66+ openFile fp' iomode =  (`ioeSetWsPath`  fp') `modifyIOError`  $  do 
67+   fp <-  furnishPath fp'
68+   bracketOnError
5869    (WS. createFile
5970      fp
6071      accessMode
@@ -104,7 +115,9 @@ writeShareMode =
104115
105116--  |  Open an existing file and return the 'Handle'. 
106117openExistingFile  ::  WindowsPath  ->  IOMode  ->  IO   Handle 
107- openExistingFile fp iomode =  bracketOnError
118+ openExistingFile fp' iomode =  (`ioeSetWsPath`  fp') `modifyIOError`  $  do 
119+   fp <-  furnishPath fp'
120+   bracketOnError
108121    (WS. createFile
109122      fp
110123      accessMode
@@ -248,3 +261,154 @@ any_ = coerce BC.any
248261
249262#endif 
250263
264+ furnishPath  ::  WindowsPath  ->  IO   WindowsPath 
265+ #if  !defined(LONG_PATHS)
266+ furnishPath path =  pure  path
267+ #else 
268+ furnishPath path = 
269+   (toExtendedLengthPath <$>  rawPrependCurrentDirectory path)
270+     `catchIOError`  \  _ -> 
271+       pure  path
272+ 
273+ toExtendedLengthPath  ::  WindowsPath  ->  WindowsPath 
274+ toExtendedLengthPath path = 
275+   if  WS. isRelative path
276+   then  simplifiedPath
277+   else 
278+     case  WS. toChar <$>  simplifiedPath' of 
279+       ' \\ '   :  ' ?'    :  ' ?'   :  ' \\ '   :  _ ->  simplifiedPath
280+       ' \\ '   :  ' \\ '   :  ' ?'   :  ' \\ '   :  _ ->  simplifiedPath
281+       ' \\ '   :  ' \\ '   :  ' .'   :  ' \\ '   :  _ ->  simplifiedPath
282+       ' \\ '   :  ' \\ '   :  _ -> 
283+         ws " \\\\ ?\\ UNC"   <>  WS. pack (drop  1  simplifiedPath')
284+       _ ->  ws " \\\\ ?\\ "   <>  simplifiedPath
285+   where  simplifiedPath =  simplifyWindows path
286+         simplifiedPath' =  WS. unpack simplifiedPath
287+ 
288+ rawPrependCurrentDirectory  ::  WindowsPath  ->  IO   WindowsPath 
289+ rawPrependCurrentDirectory path
290+   |  WS. isRelative path = 
291+     ((`ioeAddLocation`  " prependCurrentDirectory"  ) . 
292+      (`ioeSetWsPath`  path)) `modifyIOError`  do 
293+       getFullPathName path
294+   |  otherwise  =  pure  path
295+ 
296+ simplifyWindows  ::  WindowsPath  ->  WindowsPath 
297+ simplifyWindows path
298+   |  path ==  mempty          =  mempty 
299+   |  drive' ==  ws " \\\\ ?\\ "   =  drive' <>  subpath
300+   |  otherwise               =  simplifiedPath
301+   where 
302+     simplifiedPath =  WS. joinDrive drive' subpath'
303+     (drive, subpath) =  WS. splitDrive path
304+     drive' =  upperDrive (normaliseTrailingSep (normalisePathSeps drive))
305+     subpath' =  appendSep .  avoidEmpty .  prependSep .  WS. joinPath . 
306+                stripPardirs .  expandDots .  skipSeps . 
307+                WS. splitDirectories $  subpath
308+ 
309+     upperDrive d =  case  WS. unpack d of 
310+       c :  k :  s
311+         |  isAlpha (WS. toChar c), WS. toChar k ==  ' :'  , all  WS. isPathSeparator s -> 
312+           --  unsafeFromChar is safe here since all characters are ASCII.
313+           WS. pack (WS. unsafeFromChar (toUpper (WS. toChar c)) :  WS. unsafeFromChar ' :'   :  s)
314+       _ ->  d
315+     skipSeps = 
316+       (WS. pack <$> ) . 
317+       filter  (not  .  (`elem`  (pure  <$>  WS. pathSeparators))) . 
318+       (WS. unpack <$> )
319+     stripPardirs |  pathIsAbsolute ||  subpathIsAbsolute =  dropWhile  (==  ws " .."  )
320+                  |  otherwise  =  id 
321+     prependSep |  subpathIsAbsolute =  (WS. pack [WS. pathSeparator] <> )
322+                |  otherwise  =  id 
323+     avoidEmpty |  not  pathIsAbsolute
324+                , drive ==  mempty  ||  hasTrailingPathSep --  prefer "C:" over "C:."
325+                  =  emptyToCurDir
326+                |  otherwise  =  id 
327+     appendSep p |  hasTrailingPathSep, not  (pathIsAbsolute &&  p ==  mempty )
328+                   =  WS. addTrailingPathSeparator p
329+                 |  otherwise  =  p
330+     pathIsAbsolute =  not  (WS. isRelative path)
331+     subpathIsAbsolute =  any  WS. isPathSeparator (take  1  (WS. unpack subpath))
332+     hasTrailingPathSep =  WS. hasTrailingPathSeparator subpath
333+ 
334+ expandDots  ::  [WindowsPath ] ->  [WindowsPath ]
335+ expandDots =  reverse  .  go [] 
336+   where 
337+     go ys' xs' = 
338+       case  xs' of 
339+         []  ->  ys'
340+         x :  xs
341+           |  x ==  ws " ."   ->  go ys' xs
342+           |  x ==  ws " .."   -> 
343+               case  ys' of 
344+                 []  ->  go (x :  ys') xs
345+                 y :  ys
346+                   |  y ==  ws " .."   ->  go (x :  ys') xs
347+                   |  otherwise  ->  go ys xs
348+           |  otherwise  ->  go (x :  ys') xs
349+ 
350+ --  |  Remove redundant trailing slashes and pick the right kind of slash. 
351+ normaliseTrailingSep  ::  WindowsPath  ->  WindowsPath 
352+ normaliseTrailingSep path =  do 
353+   let  path' =  reverse  (WS. unpack path)
354+   let  (sep, path'') =  span  WS. isPathSeparator path'
355+   let  addSep =  if  null  sep then  id  else  (WS. pathSeparator : )
356+   WS. pack (reverse  (addSep path''))
357+ 
358+ normalisePathSeps  ::  WindowsPath  ->  WindowsPath 
359+ normalisePathSeps p =  WS. pack (normaliseChar <$>  WS. unpack p)
360+   where  normaliseChar c =  if  WS. isPathSeparator c then  WS. pathSeparator else  c
361+ 
362+ emptyToCurDir  ::  WindowsPath  ->  WindowsPath 
363+ emptyToCurDir path
364+   |  path ==  mempty  =  ws " ." 
365+   |  otherwise       =  path
366+ 
367+ ws  ::  String   ->  WindowsString 
368+ ws =  rightOrError .  WS. encodeUtf
369+ 
370+ rightOrError  ::  Exception  e  =>  Either   e  a  ->  a 
371+ rightOrError (Left   e)  =  error  (displayException e)
372+ rightOrError (Right   a) =  a
373+ 
374+ getFullPathName  ::  WindowsPath  ->  IO   WindowsPath 
375+ getFullPathName path = 
376+   fromExtendedLengthPath <$>  WS. getFullPathName (toExtendedLengthPath path)
377+ 
378+ ioeSetWsPath  ::  IOError   ->  WindowsPath  ->  IOError 
379+ ioeSetWsPath err = 
380+   ioeSetFileName err . 
381+   rightOrError . 
382+   WS. decodeWith (mkUTF16le TransliterateCodingFailure )
383+ 
384+ ioeAddLocation  ::  IOError   ->  String   ->  IOError 
385+ ioeAddLocation e loc =  do 
386+   ioeSetLocation e newLoc
387+   where 
388+     newLoc =  loc <>  if  null  oldLoc then  " "   else  " :"   <>  oldLoc
389+     oldLoc =  ioeGetLocation e
390+ 
391+ fromExtendedLengthPath  ::  WindowsPath  ->  WindowsPath 
392+ fromExtendedLengthPath ePath = 
393+   case  WS. unpack ePath of 
394+     c1 :  c2 :  c3 :  c4 :  path
395+       |  (WS. toChar <$>  [c1, c2, c3, c4]) ==  " \\\\ ?\\ "   -> 
396+       case  path of 
397+         c5 :  c6 :  c7 :  subpath@ (c8 :  _)
398+           |  (WS. toChar <$>  [c5, c6, c7, c8]) ==  " UNC\\ "   -> 
399+             WS. pack (c8 :  subpath)
400+         drive :  col :  subpath
401+           --  if the path is not "regular", then the prefix is necessary
402+           --  to ensure the path is interpreted literally
403+           |  WS. toChar col ==  ' :'  , isDriveChar drive, isPathRegular subpath -> 
404+             WS. pack path
405+         _ ->  ePath
406+     _ ->  ePath
407+   where 
408+     isDriveChar drive =  isAlpha (WS. toChar drive) &&  isAscii (WS. toChar drive)
409+     isPathRegular path = 
410+       not  (' /'   `elem`  (WS. toChar <$>  path) || 
411+            ws " ."   `elem`  WS. splitDirectories (WS. pack path) || 
412+            ws " .."   `elem`  WS. splitDirectories (WS. pack path))
413+ 
414+ #endif 
0 commit comments