pathutils.nim 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2018 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Path handling utilities for Nim. Strictly typed code in order
  10. ## to avoid the never ending time sink in getting path handling right.
  11. import os, pathnorm, strutils
  12. when defined(nimPreviewSlimSystem):
  13. import std/[syncio, assertions]
  14. type
  15. AbsoluteFile* = distinct string
  16. AbsoluteDir* = distinct string
  17. RelativeFile* = distinct string
  18. RelativeDir* = distinct string
  19. AnyPath* = AbsoluteFile|AbsoluteDir|RelativeFile|RelativeDir
  20. proc isEmpty*(x: AnyPath): bool {.inline.} = x.string.len == 0
  21. proc copyFile*(source, dest: AbsoluteFile) =
  22. os.copyFile(source.string, dest.string)
  23. proc removeFile*(x: AbsoluteFile) {.borrow.}
  24. proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] =
  25. let (a, b, c) = splitFile(x.string)
  26. result = (dir: AbsoluteDir(a), name: b, ext: c)
  27. proc extractFilename*(x: AbsoluteFile): string {.borrow.}
  28. proc fileExists*(x: AbsoluteFile): bool {.borrow.}
  29. proc dirExists*(x: AbsoluteDir): bool {.borrow.}
  30. proc quoteShell*(x: AbsoluteFile): string {.borrow.}
  31. proc quoteShell*(x: AbsoluteDir): string {.borrow.}
  32. proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.}
  33. proc createDir*(x: AbsoluteDir) {.borrow.}
  34. proc toAbsoluteDir*(path: string): AbsoluteDir =
  35. result = if path.isAbsolute: AbsoluteDir(path)
  36. else: AbsoluteDir(getCurrentDir() / path)
  37. proc `$`*(x: AnyPath): string = x.string
  38. when true:
  39. proc eqImpl(x, y: string): bool {.inline.} =
  40. result = cmpPaths(x, y) == 0
  41. proc `==`*[T: AnyPath](x, y: T): bool = eqImpl(x.string, y.string)
  42. template postProcessBase(base: AbsoluteDir): untyped =
  43. # xxx: as argued here https://github.com/nim-lang/Nim/pull/10018#issuecomment-448192956
  44. # empty paths should not mean `cwd` so the correct behavior would be to throw
  45. # here and make sure `outDir` is always correctly initialized; for now
  46. # we simply preserve pre-existing external semantics and treat it as `cwd`
  47. when false:
  48. doAssert isAbsolute(base.string), base.string
  49. base
  50. else:
  51. if base.isEmpty: getCurrentDir().AbsoluteDir else: base
  52. proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile =
  53. let base = postProcessBase(base)
  54. assert(not isAbsolute(f.string), f.string)
  55. result = AbsoluteFile newStringOfCap(base.string.len + f.string.len)
  56. var state = 0
  57. addNormalizePath(base.string, result.string, state)
  58. addNormalizePath(f.string, result.string, state)
  59. proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir =
  60. let base = postProcessBase(base)
  61. assert(not isAbsolute(f.string))
  62. result = AbsoluteDir newStringOfCap(base.string.len + f.string.len)
  63. var state = 0
  64. addNormalizePath(base.string, result.string, state)
  65. addNormalizePath(f.string, result.string, state)
  66. proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir;
  67. sep = DirSep): RelativeFile =
  68. # this currently fails for `tests/compilerapi/tcompilerapi.nim`
  69. # it's needed otherwise would returns an absolute path
  70. # assert not baseFilename.isEmpty, $fullPath
  71. result = RelativeFile(relativePath(fullPath.string, baseFilename.string, sep))
  72. proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile =
  73. if isAbsolute(file): result = AbsoluteFile(file)
  74. else: result = base / RelativeFile file
  75. proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
  76. proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
  77. proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
  78. proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
  79. proc writeFile*(x: AbsoluteFile; content: string) {.borrow.}
  80. proc skipHomeDir(x: string): int =
  81. when defined(windows):
  82. if x.continuesWith("Users/", len("C:/")):
  83. result = 3
  84. else:
  85. result = 0
  86. else:
  87. if x.startsWith("/home/") or x.startsWith("/Users/"):
  88. result = 3
  89. elif x.startsWith("/mnt/") and x.continuesWith("/Users/", len("/mnt/c")):
  90. result = 5
  91. else:
  92. result = 0
  93. proc relevantPart(s: string; afterSlashX: int): string =
  94. result = newStringOfCap(s.len - 8)
  95. var slashes = afterSlashX
  96. for i in 0..<s.len:
  97. if slashes == 0:
  98. result.add s[i]
  99. elif s[i] == '/':
  100. dec slashes
  101. template canonSlashes(x: string): string =
  102. when defined(windows):
  103. x.replace('\\', '/')
  104. else:
  105. x
  106. proc customPathImpl(x: string): string =
  107. # Idea: Encode a "protocol" via "//protocol/path" which is not ambiguous
  108. # as path canonicalization would have removed the double slashes.
  109. # /mnt/X/Users/Y
  110. # X:\\Users\Y
  111. # /home/Y
  112. # -->
  113. # //user/
  114. if not isAbsolute(x):
  115. result = customPathImpl(canonSlashes(getCurrentDir() / x))
  116. else:
  117. let slashes = skipHomeDir(x)
  118. if slashes > 0:
  119. result = "//user/" & relevantPart(x, slashes)
  120. else:
  121. result = x
  122. proc customPath*(x: string): string =
  123. customPathImpl canonSlashes x