LexicalPath.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Max Wipfli <max.wipfli@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/LexicalPath.h>
  8. #include <AK/StringBuilder.h>
  9. #include <AK/StringView.h>
  10. #include <AK/Vector.h>
  11. namespace AK {
  12. char s_single_dot = '.';
  13. LexicalPath::LexicalPath(String path)
  14. : m_string(canonicalized_path(move(path)))
  15. {
  16. if (m_string.is_empty()) {
  17. m_string = ".";
  18. m_dirname = m_string;
  19. m_basename = {};
  20. m_title = {};
  21. m_extension = {};
  22. m_parts.clear();
  23. return;
  24. }
  25. m_parts = m_string.split_view('/');
  26. auto last_slash_index = m_string.view().find_last('/');
  27. if (!last_slash_index.has_value()) {
  28. // The path contains a single part and is not absolute. m_dirname = "."sv
  29. m_dirname = { &s_single_dot, 1 };
  30. } else if (*last_slash_index == 0) {
  31. // The path contains a single part and is absolute. m_dirname = "/"sv
  32. m_dirname = m_string.substring_view(0, 1);
  33. } else {
  34. m_dirname = m_string.substring_view(0, *last_slash_index);
  35. }
  36. if (m_string == "/")
  37. m_basename = m_string;
  38. else {
  39. VERIFY(m_parts.size() > 0);
  40. m_basename = m_parts.last();
  41. }
  42. auto last_dot_index = m_basename.find_last('.');
  43. // NOTE: if the dot index is 0, this means we have ".foo", it's not an extension, as the title would then be "".
  44. if (last_dot_index.has_value() && *last_dot_index != 0) {
  45. m_title = m_basename.substring_view(0, *last_dot_index);
  46. m_extension = m_basename.substring_view(*last_dot_index + 1);
  47. } else {
  48. m_title = m_basename;
  49. m_extension = {};
  50. }
  51. }
  52. Vector<String> LexicalPath::parts() const
  53. {
  54. Vector<String> vector;
  55. vector.ensure_capacity(m_parts.size());
  56. for (auto& part : m_parts)
  57. vector.unchecked_append(part);
  58. return vector;
  59. }
  60. bool LexicalPath::has_extension(StringView extension) const
  61. {
  62. return m_string.ends_with(extension, CaseSensitivity::CaseInsensitive);
  63. }
  64. String LexicalPath::canonicalized_path(String path)
  65. {
  66. if (path.is_null())
  67. return {};
  68. // NOTE: We never allow an empty m_string, if it's empty, we just set it to '.'.
  69. if (path.is_empty())
  70. return ".";
  71. // NOTE: If there are no dots, no '//' and the path doesn't end with a slash, it is already canonical.
  72. if (!path.contains("."sv) && !path.contains("//"sv) && !path.ends_with('/'))
  73. return path;
  74. auto is_absolute = path[0] == '/';
  75. auto parts = path.split_view('/');
  76. size_t approximate_canonical_length = 0;
  77. Vector<String> canonical_parts;
  78. for (auto& part : parts) {
  79. if (part == ".")
  80. continue;
  81. if (part == "..") {
  82. if (canonical_parts.is_empty()) {
  83. if (is_absolute) {
  84. // At the root, .. does nothing.
  85. continue;
  86. }
  87. } else {
  88. if (canonical_parts.last() != "..") {
  89. // A .. and a previous non-.. part cancel each other.
  90. canonical_parts.take_last();
  91. continue;
  92. }
  93. }
  94. }
  95. approximate_canonical_length += part.length() + 1;
  96. canonical_parts.append(part);
  97. }
  98. if (canonical_parts.is_empty() && !is_absolute)
  99. canonical_parts.append(".");
  100. StringBuilder builder(approximate_canonical_length);
  101. if (is_absolute)
  102. builder.append('/');
  103. builder.join('/', canonical_parts);
  104. return builder.to_string();
  105. }
  106. String LexicalPath::absolute_path(String dir_path, String target)
  107. {
  108. if (LexicalPath(target).is_absolute()) {
  109. return LexicalPath::canonicalized_path(target);
  110. }
  111. return LexicalPath::canonicalized_path(join(dir_path, target).string());
  112. }
  113. String LexicalPath::relative_path(StringView a_path, StringView a_prefix)
  114. {
  115. if (!a_path.starts_with('/') || !a_prefix.starts_with('/')) {
  116. // FIXME: This should probably VERIFY or return an Optional<String>.
  117. return {};
  118. }
  119. if (a_path == a_prefix)
  120. return ".";
  121. // NOTE: Strip optional trailing slashes, except if the full path is only "/".
  122. auto path = canonicalized_path(a_path);
  123. auto prefix = canonicalized_path(a_prefix);
  124. if (path == prefix)
  125. return ".";
  126. // NOTE: Handle this special case first.
  127. if (prefix == "/"sv)
  128. return path.substring_view(1);
  129. // NOTE: This means the prefix is a direct child of the path.
  130. if (path.starts_with(prefix) && path[prefix.length()] == '/') {
  131. return path.substring_view(prefix.length() + 1);
  132. }
  133. // FIXME: It's still possible to generate a relative path in this case, it just needs some "..".
  134. return path;
  135. }
  136. LexicalPath LexicalPath::append(StringView value) const
  137. {
  138. return LexicalPath::join(m_string, value);
  139. }
  140. LexicalPath LexicalPath::prepend(StringView value) const
  141. {
  142. return LexicalPath::join(value, m_string);
  143. }
  144. LexicalPath LexicalPath::parent() const
  145. {
  146. return append("..");
  147. }
  148. }