diff --git a/AK/LexicalPath.cpp b/AK/LexicalPath.cpp index 77e09f7abe..4f18a8f8d5 100644 --- a/AK/LexicalPath.cpp +++ b/AK/LexicalPath.cpp @@ -72,6 +72,22 @@ bool LexicalPath::has_extension(StringView extension) const return m_string.ends_with(extension, CaseSensitivity::CaseInsensitive); } +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; + // An absolute path can't meaningfully be a child of a relative path. + if (this->is_absolute() && !possible_parent.is_absolute()) + return false; + + // Two relative paths and two absolute paths can be meaningfully compared. + if (possible_parent.parts_view().size() > this->parts_view().size()) + return false; + auto common_parts_with_parent = this->parts_view().span().trim(possible_parent.parts_view().size()); + return common_parts_with_parent == possible_parent.parts_view().span(); +} + DeprecatedString LexicalPath::canonicalized_path(DeprecatedString path) { if (path.is_null()) diff --git a/AK/LexicalPath.h b/AK/LexicalPath.h index 1001cb2d3f..c4472a616e 100644 --- a/AK/LexicalPath.h +++ b/AK/LexicalPath.h @@ -33,6 +33,7 @@ public: [[nodiscard]] Vector parts() const; bool has_extension(StringView) const; + bool is_child_of(LexicalPath const& possible_parent) const; [[nodiscard]] LexicalPath append(StringView) const; [[nodiscard]] LexicalPath prepend(StringView) const; diff --git a/Tests/AK/TestLexicalPath.cpp b/Tests/AK/TestLexicalPath.cpp index 445689b82c..29574049b2 100644 --- a/Tests/AK/TestLexicalPath.cpp +++ b/Tests/AK/TestLexicalPath.cpp @@ -208,3 +208,44 @@ TEST_CASE(parent) EXPECT_EQ(parent.string(), "/"); } } + +TEST_CASE(is_child_of) +{ + { + LexicalPath parent("/a/parent/directory"); + LexicalPath child("/a/parent/directory/a/child"); + LexicalPath mismatching("/not/a/child/directory"); + EXPECT(child.is_child_of(parent)); + EXPECT(child.is_child_of(child)); + EXPECT(parent.is_child_of(parent)); + EXPECT(!parent.is_child_of(child)); + EXPECT(!mismatching.is_child_of(parent)); + + EXPECT(parent.is_child_of(parent.parent())); + EXPECT(child.parent().parent().is_child_of(parent)); + EXPECT(!child.parent().parent().parent().is_child_of(parent)); + } + { + LexicalPath root("/"); + EXPECT(LexicalPath("/").is_child_of(root)); + EXPECT(LexicalPath("/any").is_child_of(root)); + EXPECT(LexicalPath("/child/directory").is_child_of(root)); + } + { + LexicalPath relative("folder"); + LexicalPath relative_child("folder/sub"); + LexicalPath absolute("/folder"); + LexicalPath absolute_child("/folder/sub"); + EXPECT(relative_child.is_child_of(relative)); + EXPECT(absolute_child.is_child_of(absolute)); + + EXPECT(relative.is_child_of(absolute)); + EXPECT(relative.is_child_of(absolute_child)); + EXPECT(relative_child.is_child_of(absolute)); + EXPECT(relative_child.is_child_of(absolute_child)); + + EXPECT(!absolute.is_child_of(relative)); + EXPECT(!absolute_child.is_child_of(relative)); + EXPECT(!absolute_child.is_child_of(relative_child)); + } +}