aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2023-03-30 15:48:39 +0200
committerEelco Dolstra <edolstra@gmail.com>2023-03-31 17:15:03 +0200
commit7ebaf0252a9de5f77f4d44491fff5299cd5fc5b0 (patch)
tree5d28d3a498fb5b501ffbe78253f0121e38d5e6cd /src/libutil
parent1829e7ccacc3fa7ee0474b4af3679f96430da3eb (diff)
Add CanonPath::makeRelative()
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/canon-path.cc26
-rw-r--r--src/libutil/canon-path.hh7
-rw-r--r--src/libutil/tests/canon-path.cc11
3 files changed, 44 insertions, 0 deletions
diff --git a/src/libutil/canon-path.cc b/src/libutil/canon-path.cc
index b132b4262..ddf6db6d1 100644
--- a/src/libutil/canon-path.cc
+++ b/src/libutil/canon-path.cc
@@ -100,4 +100,30 @@ std::ostream & operator << (std::ostream & stream, const CanonPath & path)
return stream;
}
+std::string CanonPath::makeRelative(const CanonPath & path) const
+{
+ auto p1 = begin();
+ auto p2 = path.begin();
+
+ for (; p1 != end() && p2 != path.end() && *p1 == *p2; ++p1, ++p2) ;
+
+ if (p1 == end() && p2 == path.end())
+ return ".";
+ else if (p1 == end())
+ return std::string(p2.remaining);
+ else {
+ std::string res;
+ while (p1 != end()) {
+ ++p1;
+ if (!res.empty()) res += '/';
+ res += "..";
+ }
+ if (p2 != path.end()) {
+ if (!res.empty()) res += '/';
+ res += p2.remaining;
+ }
+ return res;
+ }
+}
+
}
diff --git a/src/libutil/canon-path.hh b/src/libutil/canon-path.hh
index 9d5984584..77f98775c 100644
--- a/src/libutil/canon-path.hh
+++ b/src/libutil/canon-path.hh
@@ -85,6 +85,9 @@ public:
bool operator != (const Iterator & x) const
{ return remaining.data() != x.remaining.data(); }
+ bool operator == (const Iterator & x) const
+ { return !(*this != x); }
+
const std::string_view operator * () const
{ return remaining.substr(0, slash); }
@@ -166,6 +169,10 @@ public:
the `allowed` paths are within `this`. (The latter condition
ensures access to the parents of allowed paths.) */
bool isAllowed(const std::set<CanonPath> & allowed) const;
+
+ /* Return a representation `x` of `path` relative to `this`, i.e.
+ `CanonPath(this.makeRelative(x), this) == path`. */
+ std::string makeRelative(const CanonPath & path) const;
};
std::ostream & operator << (std::ostream & stream, const CanonPath & path);
diff --git a/src/libutil/tests/canon-path.cc b/src/libutil/tests/canon-path.cc
index c1c5adadf..8fbcf0ddf 100644
--- a/src/libutil/tests/canon-path.cc
+++ b/src/libutil/tests/canon-path.cc
@@ -152,4 +152,15 @@ namespace nix {
ASSERT_TRUE (CanonPath("/").isAllowed(allowed));
}
}
+
+ TEST(CanonPath, makeRelative) {
+ CanonPath d("/foo/bar");
+ ASSERT_EQ(d.makeRelative(CanonPath("/foo/bar")), ".");
+ ASSERT_EQ(d.makeRelative(CanonPath("/foo")), "..");
+ ASSERT_EQ(d.makeRelative(CanonPath("/")), "../..");
+ ASSERT_EQ(d.makeRelative(CanonPath("/foo/bar/xyzzy")), "xyzzy");
+ ASSERT_EQ(d.makeRelative(CanonPath("/foo/bar/xyzzy/bla")), "xyzzy/bla");
+ ASSERT_EQ(d.makeRelative(CanonPath("/foo/xyzzy/bla")), "../xyzzy/bla");
+ ASSERT_EQ(d.makeRelative(CanonPath("/xyzzy/bla")), "../../xyzzy/bla");
+ }
}