mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
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
This commit is contained in:
parent
9ebed7d8d5
commit
f026d495cd
Notes:
github-actions[bot]
2024-11-09 19:43:37 +00:00
Author: https://github.com/stasoid Commit: https://github.com/LadybirdBrowser/ladybird/commit/f026d495cd4 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1918 Reviewed-by: https://github.com/ADKaster ✅
4 changed files with 176 additions and 6 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
AK/LexicalPathWindows.cpp
Normal file
159
AK/LexicalPathWindows.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue