Skip to content

Commit 01c95ca

Browse files
authored
Implement an IO function to check path prefixes (#1934)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 34b5768 commit 01c95ca

File tree

4 files changed

+142
-1
lines changed

4 files changed

+142
-1
lines changed

src/lang/io/include/sourcemeta/core/io.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ SOURCEMETA_CORE_IO_EXPORT
5252
auto weakly_canonical(const std::filesystem::path &path)
5353
-> std::filesystem::path;
5454

55+
/// @ingroup io
56+
///
57+
/// Check if a file path starts with another path. This function assumes the
58+
/// paths are canonicalised. For example:
59+
///
60+
/// ```cpp
61+
/// #include <sourcemeta/core/io.h>
62+
/// #include <cassert>
63+
///
64+
/// assert(sourcemeta::core::starts_with("/foo/bar", "/foo"));
65+
/// ```
66+
SOURCEMETA_CORE_IO_EXPORT
67+
auto starts_with(const std::filesystem::path &path,
68+
const std::filesystem::path &prefix) -> bool;
69+
5570
/// @ingroup io
5671
///
5772
/// A convenience function to open a stream from a file. For example:

src/lang/io/io.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,21 @@ auto weakly_canonical(const std::filesystem::path &path)
2020
: std::filesystem::weakly_canonical(path);
2121
}
2222

23+
auto starts_with(const std::filesystem::path &path,
24+
const std::filesystem::path &prefix) -> bool {
25+
auto path_iterator = path.begin();
26+
auto prefix_iterator = prefix.begin();
27+
28+
while (prefix_iterator != prefix.end()) {
29+
if (path_iterator == path.end() || *path_iterator != *prefix_iterator) {
30+
return false;
31+
}
32+
33+
++path_iterator;
34+
++prefix_iterator;
35+
}
36+
37+
return true;
38+
}
39+
2340
} // namespace sourcemeta::core

test/io/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME io
2-
SOURCES io_canonical_test.cc io_weakly_canonical_test.cc io_read_file_test.cc)
2+
SOURCES
3+
io_canonical_test.cc
4+
io_weakly_canonical_test.cc
5+
io_starts_with_test.cc
6+
io_read_file_test.cc)
37

48
target_link_libraries(sourcemeta_core_io_unit
59
PRIVATE sourcemeta::core::io)

test/io/io_starts_with_test.cc

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <sourcemeta/core/io.h>
4+
5+
#ifdef _WIN32
6+
7+
TEST(IO_starts_with, windows_case_insensitive) {
8+
const std::filesystem::path path{"C:\\Foo\\Bar"};
9+
const std::filesystem::path prefix{"C:\\foo"};
10+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
11+
}
12+
13+
TEST(IO_starts_with, windows_different_drive) {
14+
const std::filesystem::path path{"D:\\Foo\\Bar"};
15+
const std::filesystem::path prefix{"C:\\Foo"};
16+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
17+
}
18+
19+
TEST(IO_starts_with, windows_drive_letter_no_slash) {
20+
const std::filesystem::path path{"C:folder\\sub"};
21+
const std::filesystem::path prefix{"C:folder"};
22+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
23+
}
24+
25+
TEST(IO_starts_with, windows_unc_path) {
26+
const std::filesystem::path path{"\\\\server\\share\\folder\\file.txt"};
27+
const std::filesystem::path prefix{"\\\\server\\share\\folder"};
28+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
29+
}
30+
31+
TEST(IO_starts_with, windows_same_chars_not_component) {
32+
const std::filesystem::path path{"C:\\foo\\barbaz"};
33+
const std::filesystem::path prefix{"C:\\foo\\bar"};
34+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
35+
}
36+
37+
#else
38+
39+
TEST(IO_starts_with, posix_exact_match) {
40+
const std::filesystem::path path{"/foo/bar"};
41+
const std::filesystem::path prefix{"/foo/bar"};
42+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
43+
}
44+
45+
TEST(IO_starts_with, posix_nested_dir) {
46+
const std::filesystem::path path{"/foo/bar/baz"};
47+
const std::filesystem::path prefix{"/foo/bar"};
48+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
49+
}
50+
51+
TEST(IO_starts_with, posix_same_chars_not_component) {
52+
const std::filesystem::path path{"/foo/barbaz"};
53+
const std::filesystem::path prefix{"/foo/bar"};
54+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
55+
}
56+
57+
TEST(IO_starts_with, posix_trailing_slash) {
58+
const std::filesystem::path path{"/foo/bar"};
59+
const std::filesystem::path prefix{"/foo/bar/"};
60+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
61+
}
62+
63+
TEST(IO_starts_with, posix_relative_vs_absolute) {
64+
const std::filesystem::path path{"foo/bar"};
65+
const std::filesystem::path prefix{"/foo"};
66+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
67+
}
68+
69+
TEST(IO_starts_with, posix_dot_normalization) {
70+
const std::filesystem::path path{"/foo/./bar"};
71+
const std::filesystem::path prefix{"/foo/bar"};
72+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
73+
}
74+
75+
TEST(IO_starts_with, posix_dotdot_normalization) {
76+
const std::filesystem::path path{"/foo/baz/../bar"};
77+
const std::filesystem::path prefix{"/foo/bar"};
78+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
79+
}
80+
81+
TEST(IO_starts_with, posix_root_path) {
82+
const std::filesystem::path path{"/"};
83+
const std::filesystem::path prefix{"/"};
84+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
85+
}
86+
87+
TEST(IO_starts_with, posix_empty_prefix) {
88+
const std::filesystem::path path{"/foo/bar"};
89+
const std::filesystem::path prefix{};
90+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
91+
}
92+
93+
TEST(IO_starts_with, posix_empty_path) {
94+
const std::filesystem::path path{};
95+
const std::filesystem::path prefix{"/foo"};
96+
EXPECT_FALSE(sourcemeta::core::starts_with(path, prefix));
97+
}
98+
99+
TEST(IO_starts_with, posix_empty_empty) {
100+
const std::filesystem::path path{};
101+
const std::filesystem::path prefix{};
102+
EXPECT_TRUE(sourcemeta::core::starts_with(path, prefix));
103+
}
104+
105+
#endif

0 commit comments

Comments
 (0)