path-utils.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. interface PathParts {
  2. readonly headingSlashes: string;
  3. readonly content: string;
  4. readonly trailingSlashes: string;
  5. readonly hasHeadingSlash: boolean;
  6. readonly hasTrailingSlash: boolean;
  7. }
  8. function parsePath(path: string): PathParts | null {
  9. if (typeof path !== 'string' || !path || path === '') return null;
  10. // Special case for root path
  11. if (path === '/') {
  12. return {
  13. headingSlashes: '/',
  14. content: '',
  15. trailingSlashes: '',
  16. hasHeadingSlash: true,
  17. hasTrailingSlash: true,
  18. };
  19. }
  20. let startIndex = 0;
  21. let endIndex = path.length;
  22. // Find leading slashes
  23. while (startIndex < path.length && path[startIndex] === '/') {
  24. startIndex++;
  25. }
  26. // Find trailing slashes
  27. while (endIndex > startIndex && path[endIndex - 1] === '/') {
  28. endIndex--;
  29. }
  30. const headingSlashes = path.substring(0, startIndex);
  31. const content = path.substring(startIndex, endIndex);
  32. const trailingSlashes = path.substring(endIndex);
  33. return {
  34. headingSlashes,
  35. content,
  36. trailingSlashes,
  37. hasHeadingSlash: headingSlashes.length > 0,
  38. hasTrailingSlash: trailingSlashes.length > 0,
  39. };
  40. }
  41. export function hasHeadingSlash(path: string): boolean {
  42. if (path === '/') return true;
  43. const parts = parsePath(path);
  44. return parts?.hasHeadingSlash ?? false;
  45. }
  46. export function hasTrailingSlash(path: string): boolean {
  47. if (path === '/') return true;
  48. const parts = parsePath(path);
  49. return parts?.hasTrailingSlash ?? false;
  50. }
  51. export function addHeadingSlash(path: string): string {
  52. if (path === '/') return path;
  53. if (path === '') return '/';
  54. const parts = parsePath(path);
  55. if (!parts?.hasHeadingSlash) {
  56. return `/${path}`;
  57. }
  58. return path;
  59. }
  60. export function addTrailingSlash(path: string): string {
  61. if (path === '/') return path;
  62. if (path === '') return '/';
  63. const parts = parsePath(path);
  64. if (!parts?.hasTrailingSlash) {
  65. return `${path}/`;
  66. }
  67. return path;
  68. }
  69. export function removeHeadingSlash(path: string): string {
  70. if (path === '/') return path;
  71. if (path === '') return path;
  72. const parts = parsePath(path);
  73. if (!parts?.hasHeadingSlash) return path;
  74. // Special case for '//' -> '/'
  75. if (path === '//') return '/';
  76. // Remove heading slashes and return content + trailing slashes
  77. return parts.content + parts.trailingSlashes;
  78. }
  79. export function removeTrailingSlash(path: string): string {
  80. if (path === '/') return path;
  81. if (path === '') return path;
  82. const parts = parsePath(path);
  83. if (parts == null) return path;
  84. // Return heading slashes + content (without trailing slashes)
  85. return parts.headingSlashes + parts.content;
  86. }
  87. /**
  88. * A short-hand method to add heading slash and remove trailing slash.
  89. */
  90. export function normalizePath(path: string): string {
  91. if (typeof path !== 'string' || path === '' || path === '/') {
  92. return '/';
  93. }
  94. const parts = parsePath(path);
  95. if (parts == null) {
  96. return '/';
  97. }
  98. return `/${parts.content}`;
  99. }
  100. export function attachTitleHeader(path: string): string {
  101. return `# ${path}`;
  102. }
  103. /**
  104. * If the pagePath is top page path, eliminate the pageId from the url path.
  105. */
  106. export function returnPathForURL(path: string, id: string): string {
  107. if (path === '/') {
  108. return path;
  109. }
  110. return addHeadingSlash(id);
  111. }
  112. /**
  113. * Get the parent path of the specified path.
  114. */
  115. export function getParentPath(path: string): string {
  116. return normalizePath(path.split('/').slice(0, -1).join('/'));
  117. }