From 9730f26a59b1c5bf844c056ac413448b2c96d86a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 3 Aug 2015 18:35:03 +0200 Subject: Implement the completion of relative repository location to remote/absolute --- bpkg/manifest | 43 ++++++++++++++- bpkg/manifest.cxx | 66 +++++++++++++++++++---- tests/repository-location/driver.cxx | 101 +++++++++++++++++++++++++++++++++-- 3 files changed, 196 insertions(+), 14 deletions(-) diff --git a/bpkg/manifest b/bpkg/manifest index e8c2714..0ec3a3a 100644 --- a/bpkg/manifest +++ b/bpkg/manifest @@ -302,14 +302,32 @@ namespace bpkg // repository_location () = default; + // Creates remote/absolute repository location. Throws invalid_argument + // if the location is a relative path. + // explicit repository_location (const std::string&); + // Creates a potentially relative repository location. If base is not + // empty, use it to complete the relative location to remote/absolute. + // Throws invalid_argument if base itself is relative or the resulting + // completed location is invalid. + // + repository_location (const std::string&, const repository_location& base); + + // Note that relative locations have no canonical name. + // const std::string& canonical_name () const noexcept {return canonical_name_;} + // There are 3 types of locations: remote, local absolute filesystem + // path and local relative filesystem path. Plus there is the special + // empty location. The following predicates can be used to determine + // what kind of location it is. Note that except for empty(), all the + // other predicates throw std::logic_error for an empty location. + // bool - empty () const noexcept {return canonical_name_.empty ();} + empty () const noexcept {return path_.empty ();} bool local () const @@ -320,6 +338,29 @@ namespace bpkg return host_.empty (); } + bool + remote () const + { + return !local (); + } + + bool + absolute () const + { + return local () && path_.absolute (); + } + + bool + relative () const + { + if (empty ()) + throw std::logic_error ("empty location"); + + // Note that in remote locations path is always absolute. + // + return path_.relative (); + } + const butl::dir_path& path () const { diff --git a/bpkg/manifest.cxx b/bpkg/manifest.cxx index 0393f6f..5e8da1a 100644 --- a/bpkg/manifest.cxx +++ b/bpkg/manifest.cxx @@ -748,14 +748,29 @@ namespace bpkg // repository_location // + // Location parameter type is fully qualified as compiler gets confused with + // string() member. + // + repository_location:: + repository_location (const std::string& l) + : repository_location (l, repository_location ()) // Delegate. + { + if (relative ()) + throw invalid_argument ("relative filesystem path"); + } + repository_location:: - repository_location (const std::string& l): port_ (0) + repository_location (const std::string& l, const repository_location& b) { - // Otherwise compiler gets confused with string() member. Same reason - // constructor parameter type is fully qualified. + // Otherwise compiler gets confused with string() member. // using std::string; + // Base repository location can not be a relative path. + // + if (!b.empty () && b.relative ()) + throw invalid_argument ("base relative filesystem path"); + if (::strncasecmp (l.c_str (), "http://", 7) == 0) { // Split location into host, port and path components. Calculate @@ -842,7 +857,9 @@ namespace bpkg // Chop the port, if present. // - if (pt != he) + if (pt == he) + port_ = 0; + else { unsigned long long n (++pt == he ? 0 : stoull (string (pt, he))); if (n == 0 || n > UINT16_MAX) @@ -874,8 +891,31 @@ namespace bpkg canonical_name_ += ':' + to_string (port_); } else + { path_ = dir_path (l); + if (path_.empty ()) + throw invalid_argument ("empty location"); + + // Complete if we are relative and have base. + // + if (!b.empty () && path_.relative ()) + { + // Convert the relative path location to an absolute or remote one. + // + host_ = b.host_; + port_ = b.port_; + path_ = b.path_ / path_; + + // Set canonical name to the base location canonical name host + // part. The path part of the canonical name is calculated below. + // + if (b.remote ()) + canonical_name_ = + b.canonical_name_.substr (0, b.canonical_name_.find ("/")); + } + } + // Normalize path to avoid different representations of the same location // and canonical name. So a/b/../c/1/x/../y and a/c/1/y to be considered // as same location. @@ -889,14 +929,19 @@ namespace bpkg throw invalid_argument ("invalid path"); } + // Finish calculating the canonical name, unless we are relative. + // + if (relative ()) + return; + // Search for the version path component preceeding canonical name // component. // - auto b (path_.rbegin ()), i (b), e (path_.rend ()); + auto rb (path_.rbegin ()), i (rb), re (path_.rend ()); // Find the version component. // - for (; i != e; ++i) + for (; i != re; ++i) { const string& c (*i); @@ -904,7 +949,7 @@ namespace bpkg break; } - if (i == e) + if (i == re) throw invalid_argument ("missing repository version"); // Validate the version. At the moment the only valid value is 1. @@ -914,7 +959,7 @@ namespace bpkg // Note: allow empty paths (e.g., http://stable.cppget.org/1/). // - string d (dir_path (b, i).posix_string ()); + string d (dir_path (rb, i).posix_string ()); if (!canonical_name_.empty () && !d.empty ()) // If we have host and dir. canonical_name_ += '/'; @@ -986,7 +1031,10 @@ namespace bpkg { try { - location = repository_location (move (v)); + // Call prerequisite repository location constructor, do not + // ammend relative path. + // + location = repository_location (move (v), repository_location ()); } catch (const invalid_argument& e) { diff --git a/tests/repository-location/driver.cxx b/tests/repository-location/driver.cxx index be51eb0..4eab115 100644 --- a/tests/repository-location/driver.cxx +++ b/tests/repository-location/driver.cxx @@ -27,6 +27,20 @@ bad_location (const string& l) } } +static bool +bad_location (const string& l, const repository_location& b) +{ + try + { + repository_location bl (l, b); + return false; + } + catch (const invalid_argument) + { + return true; + } +} + int main (int argc, char* argv[]) { @@ -88,22 +102,40 @@ main (int argc, char* argv[]) // assert (bad_location ("3/aaa/bbb")); + // Invalid prerequisite repository location. + // + assert (bad_location ("a/c/1/bb")); + + assert (bad_location ("a/c/1/bb", + repository_location ("./var/1/stable", + repository_location ()))); + + assert (bad_location ("../../../1/math", + repository_location ( + "http://stable.cppget.org/1/misc"))); + + // Test valid locations. // { - repository_location l ("1/aa/bb"); + repository_location l ("1/aa/bb", repository_location ()); assert (l.string () == "1/aa/bb"); + assert (l.canonical_name ().empty ()); + } + { + repository_location l ("/1/aa/bb", repository_location ()); + assert (l.string () == "/1/aa/bb"); assert (l.canonical_name () == "aa/bb"); } { - repository_location l ("/a/b/../c/1/aa/../bb"); + repository_location l ("/a/b/../c/1/aa/../bb", repository_location ()); assert (l.string () == "/a/c/1/bb"); assert (l.canonical_name () == "bb"); } { - repository_location l ("../c/../c/./1/aa/../bb"); + repository_location l ("../c/../c/./1/aa/../bb", repository_location ()); assert (l.string () == "../c/1/bb"); - assert (l.canonical_name () == "bb"); + assert (l.canonical_name ().empty ()); } { repository_location l ("http://www.a.com:80/1/aa/bb"); @@ -144,6 +176,67 @@ main (int argc, char* argv[]) repository_location l ("http://stable.cppget.org/1/"); assert (l.canonical_name () == "stable.cppget.org"); } + { + repository_location l1 ("http://stable.cppget.org/1/misc"); + repository_location l2 ("../../1/math", l1); + assert (l2.string () == "http://stable.cppget.org/1/math"); + assert (l2.canonical_name () == "stable.cppget.org/math"); + } + { + repository_location l1 ("http://stable.cppget.org/1/misc"); + repository_location l2 ("../math", l1); + assert (l2.string () == "http://stable.cppget.org/1/math"); + assert (l2.canonical_name () == "stable.cppget.org/math"); + } + { + repository_location l1 ("http://www.stable.cppget.org:8080/1"); + repository_location l2 ("../1/math", l1); + assert (l2.string () == "http://www.stable.cppget.org:8080/1/math"); + assert (l2.canonical_name () == "stable.cppget.org:8080/math"); + } + { + repository_location l1 ("/var/r1/1/misc"); + repository_location l2 ("../../../r2/1/math", l1); + assert (l2.string () == "/var/r2/1/math"); + assert (l2.canonical_name () == "math"); + } + { + repository_location l1 ("/var/1/misc"); + repository_location l2 ("../math", l1); + assert (l2.string () == "/var/1/math"); + assert (l2.canonical_name () == "math"); + } + { + repository_location l1 ("/var/1/stable"); + repository_location l2 ("/var/1/test", l1); + assert (l2.string () == "/var/1/test"); + assert (l2.canonical_name () == "test"); + } + { + repository_location l1 ("http://stable.cppget.org/1/misc"); + repository_location l2 ("/var/1/test", l1); + assert (l2.string () == "/var/1/test"); + assert (l2.canonical_name () == "test"); + } + { + repository_location l1 ("http://www.cppget.org/1/stable"); + repository_location l2 ("http://abc.com/1/test", l1); + assert (l2.string () == "http://abc.com/1/test"); + assert (l2.canonical_name () == "abc.com/test"); + } + { + repository_location l1 ("http://stable.cppget.org/1/"); + repository_location l2 ("http://stable.cppget.org/1/", + repository_location ()); + assert (l1.string () == l2.string ()); + assert (l1.canonical_name () == l2.canonical_name ()); + } + { + repository_location l1 ("/var/1/stable"); + repository_location l2 ("/var/1/stable", repository_location ()); + assert (l1.string () == l2.string ()); + assert (l1.canonical_name () == l2.canonical_name ()); + } } catch (const exception& e) { -- cgit v1.1