pathnorm.nim 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #
  2. #
  3. # Nim's Runtime Library
  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. ## OS-Path normalization. Used by `os.nim` but also
  10. ## generally useful for dealing with paths.
  11. ##
  12. ## Unstable API.
  13. # Yes, this uses import here, not include so that
  14. # we don't end up exporting these symbols from pathnorm and os:
  15. import std/private/osseps
  16. type
  17. PathIter* = object
  18. i, prev: int
  19. notFirst: bool
  20. proc hasNext*(it: PathIter; x: string): bool =
  21. it.i < x.len
  22. proc next*(it: var PathIter; x: string): (int, int) =
  23. result = (0, 0)
  24. it.prev = it.i
  25. if not it.notFirst and x[it.i] in {DirSep, AltSep}:
  26. # absolute path:
  27. inc it.i
  28. else:
  29. while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i
  30. if it.i > it.prev:
  31. result = (it.prev, it.i-1)
  32. elif hasNext(it, x):
  33. result = next(it, x)
  34. # skip all separators:
  35. while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
  36. it.notFirst = true
  37. iterator dirs(x: string): (int, int) =
  38. var it = default PathIter
  39. while hasNext(it, x): yield next(it, x)
  40. proc isDot(x: string; bounds: (int, int)): bool =
  41. bounds[1] == bounds[0] and x[bounds[0]] == '.'
  42. proc isDotDot(x: string; bounds: (int, int)): bool =
  43. bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.'
  44. proc isSlash(x: string; bounds: (int, int)): bool =
  45. bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep}
  46. when doslikeFileSystem:
  47. import std/private/ntpath
  48. proc addNormalizePath*(x: string; result: var string; state: var int;
  49. dirSep = DirSep) =
  50. ## Low level proc. Undocumented.
  51. when doslikeFileSystem: # Add Windows drive at start without normalization
  52. var x = x
  53. if result == "":
  54. let (drive, file) = splitDrive(x)
  55. x = file
  56. result.add drive
  57. for c in result.mitems:
  58. if c in {DirSep, AltSep}:
  59. c = dirSep
  60. # state: 0th bit set if isAbsolute path. Other bits count
  61. # the number of path components.
  62. var it: PathIter = default(PathIter)
  63. it.notFirst = (state shr 1) > 0
  64. if it.notFirst:
  65. while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
  66. while hasNext(it, x):
  67. let b = next(it, x)
  68. if (state shr 1 == 0) and isSlash(x, b):
  69. if result.len == 0 or result[result.len - 1] notin {DirSep, AltSep}:
  70. result.add dirSep
  71. state = state or 1
  72. elif isDotDot(x, b):
  73. if (state shr 1) >= 1:
  74. var d = result.len
  75. # f/..
  76. # We could handle stripping trailing sep here: foo// => foo like this:
  77. # while (d-1) > (state and 1) and result[d-1] in {DirSep, AltSep}: dec d
  78. # but right now we instead handle it inside os.joinPath
  79. # strip path component: foo/bar => foo
  80. while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}:
  81. dec d
  82. if d > 0:
  83. setLen(result, d-1)
  84. dec state, 2
  85. else:
  86. if result.len > 0 and result[result.len - 1] notin {DirSep, AltSep}:
  87. result.add dirSep
  88. result.add substr(x, b[0], b[1])
  89. elif isDot(x, b):
  90. discard "discard the dot"
  91. elif b[1] >= b[0]:
  92. if result.len > 0 and result[result.len - 1] notin {DirSep, AltSep}:
  93. result.add dirSep
  94. result.add substr(x, b[0], b[1])
  95. inc state, 2
  96. if result == "" and x != "": result = "."
  97. proc normalizePath*(path: string; dirSep = DirSep): string =
  98. runnableExamples:
  99. when defined(posix):
  100. doAssert normalizePath("./foo//bar/../baz") == "foo/baz"
  101. ## - Turns multiple slashes into single slashes.
  102. ## - Resolves `'/foo/../bar'` to `'/bar'`.
  103. ## - Removes `'./'` from the path, but `"foo/.."` becomes `"."`.
  104. result = newStringOfCap(path.len)
  105. var state = 0
  106. addNormalizePath(path, result, state, dirSep)