diff --git a/include/boost/filesystem/operations.hpp b/include/boost/filesystem/operations.hpp index f0e09b9..59d67b3 100644 --- a/include/boost/filesystem/operations.hpp +++ b/include/boost/filesystem/operations.hpp @@ -640,6 +640,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 7ae4d2f..ff9e6d7 100644 --- a/include/boost/filesystem/path.hpp +++ b/include/boost/filesystem/path.hpp @@ -695,6 +695,9 @@ namespace filesystem inline path operator/(const path& lhs, const path& rhs) { return path(lhs) /= rhs; } + BOOST_FILESYSTEM_DECL + path lexically_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 22a1285..5fc8606 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -24,6 +24,7 @@ #include #include +#include // for filesystem_error #include #include #include @@ -806,6 +807,50 @@ 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 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( + "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++); + for (; mm.first != p.end(); ++mm.first) + { + tmp /= *mm.first; + } + return tmp; + } + } // namespace filesystem } // namespace boost diff --git a/test/operations_test.cpp b/test/operations_test.cpp index 444c281..02e6e92 100644 --- a/test/operations_test.cpp +++ b/test/operations_test.cpp @@ -2029,6 +2029,23 @@ 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() @@ -2150,6 +2167,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_test.cpp b/test/path_test.cpp index 0714230..9497035 100644 --- a/test/path_test.cpp +++ b/test/path_test.cpp @@ -666,7 +666,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 @@ -730,6 +730,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 ---------------------------------------------------// diff --git a/test/path_unit_test.cpp b/test/path_unit_test.cpp index fe3a427..f4f8c39 100644 --- a/test/path_unit_test.cpp +++ b/test/path_unit_test.cpp @@ -577,6 +577,12 @@ namespace CHECK(path("").remove_filename() == ""); CHECK(path("foo").remove_filename() == ""); CHECK(path("foo/bar").remove_filename() == "foo"); + + // lexically_relative + + 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 ------------------------------------------------------------------//