path.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. export function stripFileProtocol(input: string) {
  2. if (!input.startsWith("file://")) return input
  3. return input.slice("file://".length)
  4. }
  5. export function stripQueryAndHash(input: string) {
  6. const hashIndex = input.indexOf("#")
  7. const queryIndex = input.indexOf("?")
  8. if (hashIndex !== -1 && queryIndex !== -1) {
  9. return input.slice(0, Math.min(hashIndex, queryIndex))
  10. }
  11. if (hashIndex !== -1) return input.slice(0, hashIndex)
  12. if (queryIndex !== -1) return input.slice(0, queryIndex)
  13. return input
  14. }
  15. export function unquoteGitPath(input: string) {
  16. if (!input.startsWith('"')) return input
  17. if (!input.endsWith('"')) return input
  18. const body = input.slice(1, -1)
  19. const bytes: number[] = []
  20. for (let i = 0; i < body.length; i++) {
  21. const char = body[i]!
  22. if (char !== "\\") {
  23. bytes.push(char.charCodeAt(0))
  24. continue
  25. }
  26. const next = body[i + 1]
  27. if (!next) {
  28. bytes.push("\\".charCodeAt(0))
  29. continue
  30. }
  31. if (next >= "0" && next <= "7") {
  32. const chunk = body.slice(i + 1, i + 4)
  33. const match = chunk.match(/^[0-7]{1,3}/)
  34. if (!match) {
  35. bytes.push(next.charCodeAt(0))
  36. i++
  37. continue
  38. }
  39. bytes.push(parseInt(match[0], 8))
  40. i += match[0].length
  41. continue
  42. }
  43. const escaped =
  44. next === "n"
  45. ? "\n"
  46. : next === "r"
  47. ? "\r"
  48. : next === "t"
  49. ? "\t"
  50. : next === "b"
  51. ? "\b"
  52. : next === "f"
  53. ? "\f"
  54. : next === "v"
  55. ? "\v"
  56. : next === "\\" || next === '"'
  57. ? next
  58. : undefined
  59. bytes.push((escaped ?? next).charCodeAt(0))
  60. i++
  61. }
  62. return new TextDecoder().decode(new Uint8Array(bytes))
  63. }
  64. export function createPathHelpers(scope: () => string) {
  65. const normalize = (input: string) => {
  66. const root = scope()
  67. const prefix = root.endsWith("/") ? root : root + "/"
  68. let path = unquoteGitPath(stripQueryAndHash(stripFileProtocol(input)))
  69. if (path.startsWith(prefix)) {
  70. path = path.slice(prefix.length)
  71. }
  72. if (path.startsWith(root)) {
  73. path = path.slice(root.length)
  74. }
  75. if (path.startsWith("./")) {
  76. path = path.slice(2)
  77. }
  78. if (path.startsWith("/")) {
  79. path = path.slice(1)
  80. }
  81. return path
  82. }
  83. const tab = (input: string) => {
  84. const path = normalize(input)
  85. return `file://${path}`
  86. }
  87. const pathFromTab = (tabValue: string) => {
  88. if (!tabValue.startsWith("file://")) return
  89. return normalize(tabValue)
  90. }
  91. const normalizeDir = (input: string) => normalize(input).replace(/\/+$/, "")
  92. return {
  93. normalize,
  94. tab,
  95. pathFromTab,
  96. normalizeDir,
  97. }
  98. }