pathutils.nim 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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
  12. type
  13. AbsoluteFile* = distinct string
  14. AbsoluteDir* = distinct string
  15. RelativeFile* = distinct string
  16. RelativeDir* = distinct string
  17. AnyPath* = AbsoluteFile|AbsoluteDir|RelativeFile|RelativeDir
  18. proc isEmpty*(x: AnyPath): bool {.inline.} = x.string.len == 0
  19. proc copyFile*(source, dest: AbsoluteFile) =
  20. os.copyFile(source.string, dest.string)
  21. proc removeFile*(x: AbsoluteFile) {.borrow.}
  22. proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] =
  23. let (a, b, c) = splitFile(x.string)
  24. result = (dir: AbsoluteDir(a), name: b, ext: c)
  25. proc extractFilename*(x: AbsoluteFile): string {.borrow.}
  26. proc fileExists*(x: AbsoluteFile): bool {.borrow.}
  27. proc dirExists*(x: AbsoluteDir): bool {.borrow.}
  28. proc quoteShell*(x: AbsoluteFile): string {.borrow.}
  29. proc quoteShell*(x: AbsoluteDir): string {.borrow.}
  30. proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.}
  31. proc createDir*(x: AbsoluteDir) {.borrow.}
  32. proc toAbsoluteDir*(path: string): AbsoluteDir =
  33. result = if path.isAbsolute: AbsoluteDir(path)
  34. else: AbsoluteDir(getCurrentDir() / path)
  35. proc `$`*(x: AnyPath): string = x.string
  36. when true:
  37. proc eqImpl(x, y: string): bool {.inline.} =
  38. result = cmpPaths(x, y) == 0
  39. proc `==`*[T: AnyPath](x, y: T): bool = eqImpl(x.string, y.string)
  40. template postProcessBase(base: AbsoluteDir): untyped =
  41. # xxx: as argued here https://github.com/nim-lang/Nim/pull/10018#issuecomment-448192956
  42. # empty paths should not mean `cwd` so the correct behavior would be to throw
  43. # here and make sure `outDir` is always correctly initialized; for now
  44. # we simply preserve pre-existing external semantics and treat it as `cwd`
  45. when false:
  46. doAssert isAbsolute(base.string), base.string
  47. base
  48. else:
  49. if base.isEmpty: getCurrentDir().AbsoluteDir else: base
  50. proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile =
  51. let base = postProcessBase(base)
  52. assert(not isAbsolute(f.string), f.string)
  53. result = AbsoluteFile newStringOfCap(base.string.len + f.string.len)
  54. var state = 0
  55. addNormalizePath(base.string, result.string, state)
  56. addNormalizePath(f.string, result.string, state)
  57. proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir =
  58. let base = postProcessBase(base)
  59. assert(not isAbsolute(f.string))
  60. result = AbsoluteDir newStringOfCap(base.string.len + f.string.len)
  61. var state = 0
  62. addNormalizePath(base.string, result.string, state)
  63. addNormalizePath(f.string, result.string, state)
  64. proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir;
  65. sep = DirSep): RelativeFile =
  66. # this currently fails for `tests/compilerapi/tcompilerapi.nim`
  67. # it's needed otherwise would returns an absolute path
  68. # assert not baseFilename.isEmpty, $fullPath
  69. result = RelativeFile(relativePath(fullPath.string, baseFilename.string, sep))
  70. proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile =
  71. if isAbsolute(file): result = AbsoluteFile(file)
  72. else: result = base / RelativeFile file
  73. proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
  74. proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
  75. proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
  76. proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
  77. proc writeFile*(x: AbsoluteFile; content: string) {.borrow.}
  78. when isMainModule:
  79. doAssert AbsoluteDir"/Users/me///" / RelativeFile"z.nim" == AbsoluteFile"/Users/me/z.nim"
  80. doAssert AbsoluteDir"/Users/me" / RelativeFile"../z.nim" == AbsoluteFile"/Users/z.nim"
  81. doAssert AbsoluteDir"/Users/me/" / RelativeFile"../z.nim" == AbsoluteFile"/Users/z.nim"
  82. doAssert relativePath("/foo/bar.nim", "/foo/", '/') == "bar.nim"
  83. doAssert $RelativeDir"foo/bar" == "foo/bar"
  84. doAssert RelativeDir"foo/bar" == RelativeDir"foo/bar"
  85. doAssert RelativeFile"foo/bar".changeFileExt(".txt") == RelativeFile"foo/bar.txt"
  86. doAssert RelativeFile"foo/bar".addFileExt(".txt") == RelativeFile"foo/bar.txt"
  87. doAssert not RelativeDir"foo/bar".isEmpty
  88. doAssert RelativeDir"".isEmpty
  89. when isMainModule and defined(windows):
  90. let nasty = string(AbsoluteDir(r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\linkedPkgs\pkgB-#head\../../simplePkgs/pkgB-#head/") / RelativeFile"pkgA/module.nim")
  91. doAssert nasty.replace('/', '\\') == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim"