From 7cfff1821e92b34f63a5b890c626a5ca03060fef Mon Sep 17 00:00:00 2001 From: Beman Date: Mon, 5 May 2014 11:54:27 -0400 Subject: [PATCH 1/3] initial implementation of relative(). --- include/boost/filesystem/path.hpp | 3 +++ src/path.cpp | 44 +++++++++++++++++++++++++++++++ test/path_unit_test.cpp | 4 +++ 3 files changed, 51 insertions(+) diff --git a/include/boost/filesystem/path.hpp b/include/boost/filesystem/path.hpp index 2dd1b00..84f91c8 100644 --- a/include/boost/filesystem/path.hpp +++ b/include/boost/filesystem/path.hpp @@ -647,6 +647,9 @@ namespace filesystem inline path operator/(const path& lhs, const path& rhs) { return path(lhs) /= rhs; } + BOOST_FILESYSTEM_DECL + path relative(const path& p, const path& base); + // inserters and extractors // use boost::io::quoted() to handle spaces in paths // use '&' as escape character to ease use for Windows paths diff --git a/src/path.cpp b/src/path.cpp index a3b3710..df244c1 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -24,6 +24,7 @@ #include #include +#include // for filesystem_error #include #include #include @@ -783,6 +784,49 @@ namespace filesystem it.m_element.m_pathname = separator_string; // generic format; see docs } + //--------------------------------------------------------------------------------------// + // // + // class path non-member functions implementation // + // // + //--------------------------------------------------------------------------------------// + + namespace detail + { + // C++14 provide a mismatch algorithm with four iterator arguments(), but earlier + // standard libraries didn't, so provide the needed functionality here in detail. + inline + std::pair mismatch(path::iterator it1, + path::iterator it1end, path::iterator it2, path::iterator it2end) + { + for (; it1 != it1end && it2 != it2end && *it1 == *it2;) + { + ++it1; + ++it2; + } + return std::make_pair(it1, it2); + } + } + + BOOST_FILESYSTEM_DECL + path relative(const path& p, const path& base) + { + std::pair mm + = detail::mismatch(p.begin(), p.end(), base.begin(), base.end()); + if (mm.first == p.end() + || mm.second != base.end()) + { + throw filesystem_error("p does not being with base, so can not be made relative to base", p, base, + boost::system::error_code(boost::system::errc::invalid_argument, + boost::system::generic_category())); + } + path tmp(*mm.first++); + for (; mm.first != p.end(); ++mm.first) + { + tmp /= *mm.first; + } + return tmp; + } + } // namespace filesystem } // namespace boost diff --git a/test/path_unit_test.cpp b/test/path_unit_test.cpp index 0ae4519..f5d1d89 100644 --- a/test/path_unit_test.cpp +++ b/test/path_unit_test.cpp @@ -576,6 +576,10 @@ namespace CHECK(path("").remove_filename() == ""); CHECK(path("foo").remove_filename() == ""); CHECK(path("foo/bar").remove_filename() == "foo"); + + // relative + + BOOST_TEST(fs::relative("/abc/def", "/abc") == path("def")); } // // test_modifiers ------------------------------------------------------------------// From aa89af3387fc849cf26f4143ed75bad70679429b Mon Sep 17 00:00:00 2001 From: Beman Date: Mon, 5 May 2014 14:23:59 -0400 Subject: [PATCH 2/3] Add test cases, correct typo --- src/path.cpp | 2 +- test/path_test.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/path.cpp b/src/path.cpp index df244c1..13d2746 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -815,7 +815,7 @@ namespace filesystem if (mm.first == p.end() || mm.second != base.end()) { - throw filesystem_error("p does not being with base, so can not be made relative to base", p, base, + throw filesystem_error("p does not begin with base, so can not be made relative to base", p, base, boost::system::error_code(boost::system::errc::invalid_argument, boost::system::generic_category())); } diff --git a/test/path_test.cpp b/test/path_test.cpp index 620aa0a..9c63ad1 100644 --- a/test/path_test.cpp +++ b/test/path_test.cpp @@ -669,7 +669,7 @@ namespace if (platform == "Windows") { - std::cout << "Windows relational tests..." << std::endl; + std::cout << " Windows relational tests..." << std::endl; path p10 ("c:\\file"); path p11 ("c:/file"); // check each overload @@ -733,6 +733,22 @@ namespace BOOST_TEST(!(L"c:\\file" > p11)); BOOST_TEST(!(L"c:/file" > p11)); } + + // relative + + BOOST_TEST(fs::relative("/abc/def", "/abc") == path("def")); + BOOST_TEST(fs::relative("abc/def", "abc") == path("def")); + BOOST_TEST(fs::relative("/abc/xyz/def", "/abc") == path("xyz/def")); + BOOST_TEST(fs::relative("abc/xyz/def", "abc") == path("xyz/def")); + + if (platform == "Windows") + { + std::cout << " Windows relatie tests..." << std::endl; + BOOST_TEST(fs::relative("\\abc\\xyz\\def", "/abc") == path("xyz/def")); + std::cout << " fs::relative(\"/abc/xyz/def\", \"/abc\") is " + << fs::relative("/abc/xyz/def", "/abc") << std::endl; + BOOST_TEST(fs::relative("abc\\xyz\\def", "abc") == path("xyz/def")); + } } // query_and_decomposition_tests ---------------------------------------------------// From b1b1cea3c41dd303f0c7a8e672ea48b2b70cc71e Mon Sep 17 00:00:00 2001 From: Beman Date: Thu, 29 May 2014 15:42:11 -0400 Subject: [PATCH 3/3] Rename relative to lexically_relative. Add semi_canonical() and relative(). --- include/boost/filesystem/operations.hpp | 20 ++++++++++++++++++++ include/boost/filesystem/path.hpp | 2 +- src/path.cpp | 7 ++++--- test/operations_test.cpp | 20 +++++++++++++++++++- test/path_unit_test.cpp | 6 ++++-- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/include/boost/filesystem/operations.hpp b/include/boost/filesystem/operations.hpp index 043481b..10338a3 100644 --- a/include/boost/filesystem/operations.hpp +++ b/include/boost/filesystem/operations.hpp @@ -517,6 +517,26 @@ namespace boost inline void resize_file(const path& p, uintmax_t size, system::error_code& ec) {detail::resize_file(p, size, &ec);} + // TODO: Add error handling + // TODO: The returned path needs to be normalized. That currently is implied by + // any existing (and thus canonical) portion, but not by the non-existing portion. + inline + path semi_canonical(const path& p) + { + if (exists(p)) + return canonical(p); + return p.parent_path().empty() ? p : semi_canonical(p.parent_path()) / p.filename(); + } + + // TODO: Add error handling + inline + path relative(const path& p, const path& base) + // [Note: If either p or base is_relative(), user may wish to wrap the call to that + // argument in a call to absolute(). -- end note] + { + return lexically_relative(semi_canonical(p), semi_canonical(base)); + } + inline space_info space(const path& p) {return detail::space(p);} diff --git a/include/boost/filesystem/path.hpp b/include/boost/filesystem/path.hpp index 84f91c8..96a88d5 100644 --- a/include/boost/filesystem/path.hpp +++ b/include/boost/filesystem/path.hpp @@ -648,7 +648,7 @@ namespace filesystem inline path operator/(const path& lhs, const path& rhs) { return path(lhs) /= rhs; } BOOST_FILESYSTEM_DECL - path relative(const path& p, const path& base); + path lexically_relative(const path& p, const path& base); // inserters and extractors // use boost::io::quoted() to handle spaces in paths diff --git a/src/path.cpp b/src/path.cpp index 13d2746..fdb4d43 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -808,15 +808,16 @@ namespace filesystem } BOOST_FILESYSTEM_DECL - path relative(const path& p, const path& base) + path lexically_relative(const path& p, const path& base) { std::pair mm = detail::mismatch(p.begin(), p.end(), base.begin(), base.end()); if (mm.first == p.end() || mm.second != base.end()) { - throw filesystem_error("p does not begin with base, so can not be made relative to base", p, base, - boost::system::error_code(boost::system::errc::invalid_argument, + throw filesystem_error( + "lexically_relative: p does not begin with base, so can not be made relative to base", + p, base, boost::system::error_code(boost::system::errc::invalid_argument, boost::system::generic_category())); } path tmp(*mm.first++); diff --git a/test/operations_test.cpp b/test/operations_test.cpp index dccd765..45d1f17 100644 --- a/test/operations_test.cpp +++ b/test/operations_test.cpp @@ -1890,13 +1890,30 @@ namespace #endif } + // semi_canonical_tests ------------------------------------------------------------// + + void semi_canonical_tests() + { + cout << "semi_canonical_tests..." << endl; + cout << " dir is " << dir << endl; + + BOOST_TEST_EQ(fs::semi_canonical("no-such/foo/bar"), "no-such/foo/bar"); + BOOST_TEST_EQ(fs::semi_canonical(dir / "foo/bar"), dir / "foo/bar"); + BOOST_TEST_EQ(fs::semi_canonical("c:/no-such/foo/bar"), "c:/no-such/foo/bar"); + + fs::create_directory_symlink(dir / "d1", dir / "sld1"); + BOOST_TEST_EQ(fs::semi_canonical(dir / "sld1/foo/bar"), dir / "d1/foo/bar"); + + BOOST_TEST_EQ(relative(dir / "sld1/foo/bar/baz", dir / "d1/foo"), "bar/baz"); + } + // _tests --------------------------------------------------------------------------// void _tests() { cout << "_tests..." << endl; } - + } // unnamed namespace //------------------------------------------------------------------------------------// @@ -2000,6 +2017,7 @@ int cpp_main(int argc, char* argv[]) symlink_status_tests(); copy_symlink_tests(f1, d1); canonical_symlink_tests(); + semi_canonical_tests(); } iterator_status_tests(); // lots of cases by now, so a good time to test // dump_tree(dir); diff --git a/test/path_unit_test.cpp b/test/path_unit_test.cpp index f5d1d89..4c111da 100644 --- a/test/path_unit_test.cpp +++ b/test/path_unit_test.cpp @@ -577,9 +577,11 @@ namespace CHECK(path("foo").remove_filename() == ""); CHECK(path("foo/bar").remove_filename() == "foo"); - // relative + // lexically_relative - BOOST_TEST(fs::relative("/abc/def", "/abc") == path("def")); + BOOST_TEST(fs::lexically_relative("/abc/def", "/abc") == path("def")); + BOOST_TEST(fs::lexically_relative("/abc/def/ghi", "/abc/def") == path("ghi")); + BOOST_TEST(fs::lexically_relative("/abc/def/ghi", "/abc") == path("def/ghi")); } // // test_modifiers ------------------------------------------------------------------//