Skip to content

Commit 4f206c9

Browse files
committed
Add function to determine the parent directory of a file
1 parent c81e1fb commit 4f206c9

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

elisp/private/tools/system.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ absl::StatusOr<FileName> FileName::FromString(const NativeStringView string) {
128128
return FileName(std::move(name));
129129
}
130130

131+
absl::StatusOr<FileName> FileName::Parent() const {
132+
NativeStringView string = string_;
133+
while (string.back() == kSeparator) string.remove_suffix(1);
134+
NativeStringView::size_type i = string.rfind(kSeparator);
135+
if (i == string.npos || (kWindows && i == 0)) {
136+
return absl::InvalidArgumentError(
137+
absl::StrFormat("File %v has no parent", *this));
138+
}
139+
const NativeStringView element = string.substr(i + 1);
140+
if (element == RULES_ELISP_NATIVE_LITERAL(".") ||
141+
element == RULES_ELISP_NATIVE_LITERAL("..")) {
142+
return absl::InvalidArgumentError(absl::StrFormat(
143+
"Removing trailing component %s would be ambiguous", element));
144+
}
145+
// Root directories need to end in a separator character.
146+
if (i == (kWindows ? 2 : 0)) i++;
147+
return FileName::FromString(string.substr(0, i));
148+
}
149+
131150
absl::StatusOr<FileName> FileName::Child(const FileName& child) const {
132151
const NativeString& string = child.string_;
133152
if (string == RULES_ELISP_NATIVE_LITERAL(".") ||

elisp/private/tools/system.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class FileName final {
8484
return string_.data();
8585
}
8686

87+
absl::StatusOr<FileName> Parent() const;
8788
absl::StatusOr<FileName> Child(const FileName& child) const;
8889
absl::StatusOr<FileName> Child(NativeStringView child) const;
8990
absl::StatusOr<FileName> Join(const FileName& descendant) const;

tests/tools/system_test.cc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,67 @@ TEST(FileNameTest, IsFormattable) {
150150
EXPECT_EQ(absl::StrFormat("foo %v baz", bar), "foo bar äα𝐴🐈' baz");
151151
}
152152

153+
TEST(FileNameTest, ParentRejectsRoot) {
154+
EXPECT_THAT(FileName::FromString(kWindows ? RULES_ELISP_NATIVE_LITERAL("C:\\")
155+
: RULES_ELISP_NATIVE_LITERAL("/"))
156+
.value()
157+
.Parent(),
158+
StatusIs(absl::StatusCode::kInvalidArgument));
159+
}
160+
161+
TEST(FileNameTest, ParentRejectsDot) {
162+
EXPECT_THAT(FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo/."))
163+
.value()
164+
.Parent(),
165+
StatusIs(absl::StatusCode::kInvalidArgument));
166+
}
167+
168+
TEST(FileNameTest, ParentRejectsDotDot) {
169+
EXPECT_THAT(FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo/.."))
170+
.value()
171+
.Parent(),
172+
StatusIs(absl::StatusCode::kInvalidArgument));
173+
}
174+
175+
TEST(FileNameTest, ParentWorksForRelativeName) {
176+
EXPECT_THAT(
177+
FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo/bar"))
178+
.value()
179+
.Parent(),
180+
IsOkAndHolds(
181+
FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo")).value()));
182+
}
183+
184+
TEST(FileNameTest, ParentWorksForAbsoluteName) {
185+
EXPECT_THAT(
186+
FileName::FromString(kWindows ? RULES_ELISP_NATIVE_LITERAL("C:\\Foo")
187+
: RULES_ELISP_NATIVE_LITERAL("/foo"))
188+
.value()
189+
.Parent(),
190+
IsOkAndHolds(FileName::FromString(kWindows
191+
? RULES_ELISP_NATIVE_LITERAL("C:\\")
192+
: RULES_ELISP_NATIVE_LITERAL("/"))
193+
.value()));
194+
EXPECT_THAT(
195+
FileName::FromString(kWindows ? RULES_ELISP_NATIVE_LITERAL("C:\\Foo\\Bar")
196+
: RULES_ELISP_NATIVE_LITERAL("/foo/bar"))
197+
.value()
198+
.Parent(),
199+
IsOkAndHolds(
200+
FileName::FromString(kWindows ? RULES_ELISP_NATIVE_LITERAL("C:\\Foo")
201+
: RULES_ELISP_NATIVE_LITERAL("/foo"))
202+
.value()));
203+
}
204+
205+
TEST(FileNameTest, ParentIgnoresTrailingSlashes) {
206+
EXPECT_THAT(
207+
FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo/bar//"))
208+
.value()
209+
.Parent(),
210+
IsOkAndHolds(
211+
FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo")).value()));
212+
}
213+
153214
TEST(FileNameTest, ChildRejectsDescendant) {
154215
const FileName parent =
155216
FileName::FromString(RULES_ELISP_NATIVE_LITERAL("foo")).value();

0 commit comments

Comments
 (0)