Explorar el Código

AK: Port LexicalPath to Windows

Supported:
* Normal absolute and relative paths: C:\Windows\Fonts, AK\LexicalPath.h
* Forward slashes and multiple separators: C:/Windows///\\\notepad.exe

Not supported:
* Paths starting with two backslashes: \\?\C:\Windows, \\server\share
* Unusual relative paths like C:, C:a\b, \, \a\b

More on Windows path formats: https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
stasoid hace 9 meses
padre
commit
f026d495cd
Se han modificado 4 ficheros con 176 adiciones y 6 borrados
  1. 6 1
      AK/CMakeLists.txt
  2. 6 1
      AK/LexicalPath.cpp
  3. 5 4
      AK/LexicalPath.h
  4. 159 0
      AK/LexicalPathWindows.cpp

+ 6 - 1
AK/CMakeLists.txt

@@ -16,7 +16,6 @@ set(SOURCES
     JsonObject.cpp
     JsonParser.cpp
     JsonValue.cpp
-    LexicalPath.cpp
     MemoryStream.cpp
     NumberFormat.cpp
     OptionParser.cpp
@@ -38,6 +37,12 @@ set(SOURCES
     kmalloc.cpp
 )
 
+if (WIN32)
+    list(APPEND SOURCES LexicalPathWindows.cpp)
+else()
+    list(APPEND SOURCES LexicalPath.cpp)
+endif()
+
 serenity_lib(AK ak)
 
 serenity_install_headers(AK)

+ 6 - 1
AK/LexicalPath.cpp

@@ -58,6 +58,11 @@ LexicalPath::LexicalPath(ByteString path)
     }
 }
 
+bool LexicalPath::is_absolute() const
+{
+    return m_string.starts_with('/');
+}
+
 Vector<ByteString> LexicalPath::parts() const
 {
     Vector<ByteString> vector;
@@ -163,7 +168,7 @@ ByteString LexicalPath::relative_path(StringView a_path, StringView a_prefix)
     if (prefix == "/"sv)
         return path.substring_view(1);
 
-    // NOTE: This means the prefix is a direct child of the path.
+    // NOTE: This means the path is a direct child of the prefix.
     if (path.starts_with(prefix) && path[prefix.length()] == '/') {
         return path.substring_view(prefix.length() + 1);
     }

+ 5 - 4
AK/LexicalPath.h

@@ -26,11 +26,11 @@ public:
 
     explicit LexicalPath(ByteString);
 
-    bool is_absolute() const { return !m_string.is_empty() && m_string[0] == '/'; }
+    bool is_absolute() const;
     ByteString const& string() const { return m_string; }
 
     StringView dirname() const { return m_dirname; }
-    StringView basename(StripExtension s = StripExtension::No) const { return s == StripExtension::No ? m_basename : m_basename.substring_view(0, m_basename.length() - m_extension.length() - 1); }
+    StringView basename(StripExtension s = StripExtension::No) const { return s == StripExtension::No ? m_basename : m_title; }
     StringView title() const { return m_title; }
     StringView extension() const { return m_extension; }
 
@@ -46,13 +46,14 @@ public:
 
     [[nodiscard]] static ByteString canonicalized_path(ByteString);
     [[nodiscard]] static ByteString absolute_path(ByteString dir_path, ByteString target);
-    [[nodiscard]] static ByteString relative_path(StringView absolute_path, StringView prefix);
+    [[nodiscard]] static ByteString relative_path(StringView absolute_path, StringView absolute_prefix);
 
     template<typename... S>
     [[nodiscard]] static LexicalPath join(StringView first, S&&... rest)
     {
         StringBuilder builder;
         builder.append(first);
+        // NOTE: On Windows slashes will be converted to backslashes in LexicalPath constructor
         ((builder.append('/'), builder.append(forward<S>(rest))), ...);
 
         return LexicalPath { builder.to_byte_string() };
@@ -88,7 +89,7 @@ private:
     StringView m_dirname;
     StringView m_basename;
     StringView m_title;
-    StringView m_extension;
+    StringView m_extension; // doesn't include the dot
 };
 
 template<>

+ 159 - 0
AK/LexicalPathWindows.cpp

@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
+ * Copyright (c) 2021, Max Wipfli <max.wipfli@serenityos.org>
+ * Copyright (c) 2024, stasoid <stasoid@yahoo.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/LexicalPath.h>
+
+namespace AK {
+
+static bool is_absolute_path(StringView path)
+{
+    return path.length() >= 2 && path[1] == ':';
+}
+
+static bool is_root(auto const& parts)
+{
+    return parts.size() == 1 && is_absolute_path(parts[0]);
+}
+
+LexicalPath::LexicalPath(ByteString path)
+{
+    m_string = canonicalized_path(path);
+    m_parts = m_string.split_view('\\');
+
+    auto last_slash_index = m_string.find_last('\\');
+    if (!last_slash_index.has_value())
+        m_dirname = "."sv;
+    else
+        m_dirname = m_string.substring_view(0, *last_slash_index);
+
+    // NOTE: For "C:\", both m_dirname and m_basename are "C:", which matches the behavior of dirname/basename in Cygwin/MSYS/git (but not MinGW)
+    m_basename = m_parts.last();
+
+    auto last_dot_index = m_basename.find_last('.');
+    // NOTE: If the last dot index is 0, it's not an extension: ".foo".
+    if (last_dot_index.has_value() && *last_dot_index != 0 && m_basename != "..") {
+        m_title = m_basename.substring_view(0, *last_dot_index);
+        m_extension = m_basename.substring_view(*last_dot_index + 1);
+    } else {
+        m_title = m_basename;
+        m_extension = {};
+    }
+}
+
+bool LexicalPath::is_absolute() const
+{
+    return is_absolute_path(m_string);
+}
+
+Vector<ByteString> LexicalPath::parts() const
+{
+    Vector<ByteString> vector;
+    for (auto part : m_parts)
+        vector.append(part);
+    return vector;
+}
+
+bool LexicalPath::has_extension(StringView extension) const
+{
+    if (extension[0] == '.')
+        extension = extension.substring_view(1);
+    return m_extension.equals_ignoring_ascii_case(extension);
+}
+
+bool LexicalPath::is_child_of(LexicalPath const& possible_parent) const
+{
+    // Any relative path is a child of an absolute path.
+    if (!this->is_absolute() && possible_parent.is_absolute())
+        return true;
+
+    return m_string.starts_with(possible_parent.string())
+        && m_string[possible_parent.string().length()] == '\\';
+}
+
+ByteString LexicalPath::canonicalized_path(ByteString path)
+{
+    path = path.replace("/"sv, "\\"sv);
+    auto parts = path.split_view('\\');
+    Vector<ByteString> canonical_parts;
+
+    for (auto part : parts) {
+        if (part == ".")
+            continue;
+        if (part == ".." && !canonical_parts.is_empty()) {
+            // At the root, .. does nothing.
+            if (is_root(canonical_parts))
+                continue;
+            // A .. and a previous non-.. part cancel each other.
+            if (canonical_parts.last() != "..") {
+                canonical_parts.take_last();
+                continue;
+            }
+        }
+        canonical_parts.append(part);
+    }
+
+    StringBuilder builder;
+    builder.join('\\', canonical_parts);
+    // "X:" -> "X:\"
+    if (is_root(canonical_parts))
+        builder.append('\\');
+    path = builder.to_byte_string();
+    return path == "" ? "." : path;
+}
+
+ByteString LexicalPath::absolute_path(ByteString dir_path, ByteString target)
+{
+    if (is_absolute_path(target))
+        return canonicalized_path(target);
+
+    return join(dir_path, target).string();
+}
+
+// Returns relative version of abs_path (relative to abs_prefix), such that join(abs_prefix, rel_path) == abs_path.
+ByteString LexicalPath::relative_path(StringView abs_path, StringView abs_prefix)
+{
+    if (!is_absolute_path(abs_path) || !is_absolute_path(abs_prefix)
+        || abs_path[0] != abs_prefix[0]) // different drives
+        return "";
+
+    auto path = canonicalized_path(abs_path);
+    auto prefix = canonicalized_path(abs_prefix);
+
+    if (path == prefix)
+        return ".";
+
+    auto path_parts = path.split_view('\\');
+    auto prefix_parts = prefix.split_view('\\');
+    size_t first_mismatch = 0;
+    for (; first_mismatch < min(path_parts.size(), prefix_parts.size()); first_mismatch++) {
+        if (path_parts[first_mismatch] != prefix_parts[first_mismatch])
+            break;
+    }
+
+    StringBuilder builder;
+    builder.append_repeated("..\\"sv, prefix_parts.size() - first_mismatch);
+    builder.join('\\', path_parts.span().slice(first_mismatch));
+    return builder.to_byte_string();
+}
+
+LexicalPath LexicalPath::append(StringView value) const
+{
+    return join(m_string, value);
+}
+
+LexicalPath LexicalPath::prepend(StringView value) const
+{
+    return join(value, m_string);
+}
+
+LexicalPath LexicalPath::parent() const
+{
+    return append(".."sv);
+}
+
+}