pathnorm.nim 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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 "includes/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. it.prev = it.i
  24. if not it.notFirst and x[it.i] in {DirSep, AltSep}:
  25. # absolute path:
  26. inc it.i
  27. when doslikeFileSystem: # UNC paths have leading `\\`
  28. if hasNext(it, x) and x[it.i] == DirSep and
  29. it.i+1 < x.len and x[it.i+1] != DirSep:
  30. inc it.i
  31. else:
  32. while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i
  33. if it.i > it.prev:
  34. result = (it.prev, it.i-1)
  35. elif hasNext(it, x):
  36. result = next(it, x)
  37. # skip all separators:
  38. while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
  39. it.notFirst = true
  40. iterator dirs(x: string): (int, int) =
  41. var it: PathIter
  42. while hasNext(it, x): yield next(it, x)
  43. proc isDot(x: string; bounds: (int, int)): bool =
  44. bounds[1] == bounds[0] and x[bounds[0]] == '.'
  45. proc isDotDot(x: string; bounds: (int, int)): bool =
  46. bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.'
  47. proc isSlash(x: string; bounds: (int, int)): bool =
  48. bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep}
  49. proc addNormalizePath*(x: string; result: var string; state: var int;
  50. dirSep = DirSep) =
  51. ## Low level proc. Undocumented.
  52. # state: 0th bit set if isAbsolute path. Other bits count
  53. # the number of path components.
  54. var it: PathIter
  55. it.notFirst = (state shr 1) > 0
  56. if it.notFirst:
  57. while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
  58. while hasNext(it, x):
  59. let b = next(it, x)
  60. if (state shr 1 == 0) and isSlash(x, b):
  61. if result.len == 0 or result[^1] notin {DirSep, AltSep}:
  62. result.add dirSep
  63. state = state or 1
  64. elif isDotDot(x, b):
  65. if (state shr 1) >= 1:
  66. var d = result.len
  67. # f/..
  68. while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}:
  69. dec d
  70. if d > 0:
  71. setLen(result, d-1)
  72. dec state, 2
  73. else:
  74. if result.len > 0 and result[^1] notin {DirSep, AltSep}:
  75. result.add dirSep
  76. result.add substr(x, b[0], b[1])
  77. elif isDot(x, b):
  78. discard "discard the dot"
  79. elif b[1] >= b[0]:
  80. if result.len > 0 and result[^1] notin {DirSep, AltSep}:
  81. result.add dirSep
  82. result.add substr(x, b[0], b[1])
  83. inc state, 2
  84. if result == "" and x != "": result = "."
  85. proc normalizePath*(path: string; dirSep = DirSep): string =
  86. ## Example:
  87. ##
  88. ## .. code-block:: nim
  89. ## assert normalizePath("./foo//bar/../baz") == "foo/baz"
  90. ##
  91. ##
  92. ## - Turns multiple slashes into single slashes.
  93. ## - Resolves '/foo/../bar' to '/bar'.
  94. ## - Removes './' from the path (but "foo/.." becomes ".")
  95. result = newStringOfCap(path.len)
  96. var state = 0
  97. addNormalizePath(path, result, state, dirSep)