From b6f166c4ed98f94bdd2cc82885d61173a101abfd Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 26 Jul 2016 15:12:54 +0200 Subject: Redesign path to store trailing slash for directories --- butl/path.ixx | 352 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 273 insertions(+), 79 deletions(-) (limited to 'butl/path.ixx') diff --git a/butl/path.ixx b/butl/path.ixx index 48d6576..3d1f20c 100644 --- a/butl/path.ixx +++ b/butl/path.ixx @@ -25,41 +25,57 @@ namespace butl } #endif - // @@ Should only enable_if P is basic_path. - // + template + inline basic_path + path_cast_impl (const basic_path& p, basic_path*) + { + typename basic_path::data_type d ( + typename basic_path::string_type (p.path_), p.diff_); + K1::cast (d); + return basic_path (std::move (d)); + } + + template + inline basic_path + path_cast_impl (basic_path&& p, basic_path*) + { + typename basic_path::data_type d (std::move (p.path_), p.diff_); + K1::cast (d); + return basic_path (std::move (d)); + } + template inline P path_cast (const basic_path& p) { - return P (p.path_, false); + return path_cast_impl (p, static_cast (nullptr)); } template inline P path_cast (basic_path&& p) { - return P (std::move (p.path_), false); + return path_cast_impl (std::move (p), static_cast (nullptr)); } template inline bool basic_path:: simple () const { - return -#ifndef _WIN32 - root () || -#endif - traits::find_separator (this->path_) == string_type::npos; + return empty () || + traits::rfind_separator (this->path_, _size () - 1) == string_type::npos; } template inline bool basic_path:: absolute () const { + const string_type& s (this->path_); + #ifdef _WIN32 - return this->path_.size () > 1 && this->path_[1] == ':'; + return s.size () > 1 && s[1] == ':'; #else - return !this->path_.empty () && traits::is_separator (this->path_[0]); + return s.size () != 0 && traits::is_separator (s[0]); #endif } @@ -67,10 +83,12 @@ namespace butl inline bool basic_path:: root () const { + const string_type& s (this->path_); + #ifdef _WIN32 - return this->path_.size () == 2 && this->path_[1] == ':'; + return s.size () == 2 && s[1] == ':'; #else - return this->path_.size () == 1 && traits::is_separator (this->path_[0]); + return s.size () == 1 && traits::is_separator (s[0]); #endif } @@ -78,95 +96,116 @@ namespace butl inline bool basic_path:: sub (const basic_path& p) const { - size_type n (p.path_.size ()); + // The thinking here is that we can use the full string representations + // (including the trailing slash in "/"). + // + const string_type& ps (p.path_); + size_type pn (ps.size ()); - if (n == 0) + if (pn == 0) return true; - size_type m (this->path_.size ()); + const string_type& s (this->path_); + size_type n (s.size ()); // The second condition guards against the /foo-bar vs /foo case. // - return m >= n && - traits::compare (this->path_.c_str (), n, p.path_.c_str (), n) == 0 && - (traits::is_separator (p.path_.back ()) || // p ends with a separator - m == n || // *this == p - traits::is_separator (this->path_[n])); // next char is a separator + return n >= pn && + traits::compare (s.c_str (), pn, ps.c_str (), pn) == 0 && + (traits::is_separator (ps.back ()) || // p ends with a separator + n == pn || // *this == p + traits::is_separator (s[pn])); // next char is a separator } template inline bool basic_path:: sup (const basic_path& p) const { - size_type n (p.path_.size ()); + // The thinking here is that we can use the full string representations + // (including the trailing slash in "/"). + // + const string_type& ps (p.path_); + size_type pn (ps.size ()); - if (n == 0) + if (pn == 0) return true; - size_type m (this->path_.size ()); + const string_type& s (this->path_); + size_type n (s.size ()); // The second condition guards against the /foo-bar vs bar case. // - return m >= n && - traits::compare ( - this->path_.c_str () + m - n, n, p.path_.c_str (), n) == 0 && - (m == n || // *this == p - traits::is_separator (this->path_[m - n - 1])); // prev char separator + return n >= pn && + traits::compare (s.c_str () + n - pn, pn, ps.c_str (), pn) == 0 && + (n == pn || // *this == p + traits::is_separator (s[n - pn - 1])); // previous char is a separator + } + + template + inline basic_path basic_path:: + leaf () const + { + const string_type& s (this->path_); + size_type n (_size ()); + + size_type p (n != 0 + ? traits::rfind_separator (s, n - 1) + : string_type::npos); + + return p != string_type::npos + ? basic_path (data_type (string_type (s, p + 1), this->diff_)) + : *this; + } + + template + inline typename basic_path::dir_type basic_path:: + directory () const + { + const string_type& s (this->path_); + size_type n (_size ()); + + size_type p (n != 0 + ? traits::rfind_separator (s, n - 1) + : string_type::npos); + + return p != string_type::npos + ? dir_type (data_type (string_type (s, 0, p + 1))) // Include slash. + : dir_type (); } template inline auto basic_path:: begin () const -> iterator { - size_type b, e; + const string_type& s (this->path_); - if (this->path_.empty ()) - b = e = string_type::npos; + size_type b (s.empty () ? string_type::npos : 0); + size_type e (b == 0 ? traits::find_separator (s) : b); -#ifndef _WIN32 - else if (root ()) - { - // We want to return a single empty component. Here we return - // the begin position one past the end. Not sure if this legal. - // - b = 1; - e = string_type::npos; - } -#endif - else - { - b = 0; - e = traits::find_separator (this->path_); - } - - return iterator (this->path_, b, e); + return iterator (this, b, e); } template inline auto basic_path:: end () const -> iterator { - return iterator (this->path_, string_type::npos, string_type::npos); + return iterator (this, string_type::npos, string_type::npos); } template inline basic_path:: basic_path (const iterator& b, const iterator& e) + : base_type ( + b == e + ? data_type () + // We need to include the trailing separator but it is implied if + // e == end(). + // + : (e.b_ != string_type::npos + ? data_type (string_type (b.p_->path_, b.b_, e.b_ - b.b_)) + : data_type (string_type (b.p_->path_, b.b_), b.p_->diff_))) { //assert (b.p_ == e.p_); - - if (b != e) - { - this->path_.assign ( - *b.p_, b.b_, (e.b_ != string_type::npos ? e.b_ - b.b_ - 1 : e.b_)); - -#ifndef _WIN32 - if (this->path_.empty ()) - this->path_ = '/'; -#endif - - // No init() should be necessary. - } } template @@ -187,7 +226,7 @@ namespace butl complete (); normalize (); #else - traits::realize (this->path_); + traits::realize (this->path_); // Note: we retail trailing slash. #endif return *this; } @@ -196,24 +235,34 @@ namespace butl inline typename basic_path::dir_type basic_path:: root_directory () const { - return absolute () #ifdef _WIN32 - // Disambiguate with dir_type(string_type, bool). - // - ? dir_type (this->path_, static_cast (2)) + // Note: on Windows we may have "c:" but still need to return "c:\". + // + const string_type& s (this->path_); + + return absolute () + ? dir_type ( + s.size () > 2 + ? data_type (string_type (s, 0, 3)) + : data_type (string_type (s), this->diff_ != 0 ? this->diff_ : 1)) + : dir_type (); #else - ? dir_type ("/") -#endif + return absolute () + ? dir_type (data_type ("/", -1)) : dir_type (); +#endif + } template inline basic_path basic_path:: base () const { - size_type p (traits::find_extension (this->path_)); + const string_type& s (this->path_); + size_type p (traits::find_extension (s)); + return p != string_type::npos - ? basic_path (this->path_.c_str (), p) + ? basic_path (data_type (string_type (s, 0, p), this->diff_)) : *this; } @@ -221,8 +270,9 @@ namespace butl inline const C* basic_path:: extension () const { - size_type p (traits::find_extension (this->path_)); - return p != string_type::npos ? this->path_.c_str () + p + 1 : nullptr; + const string_type& s (this->path_); + size_type p (traits::find_extension (s)); + return p != string_type::npos ? s.c_str () + p + 1 : nullptr; } #ifndef _WIN32 @@ -236,16 +286,160 @@ namespace butl template inline void basic_path:: - combine (const C* r, size_type rn) + combine (const C* r, size_type rn, difference_type rd) { - size_type ln (this->path_.size ()); + //assert (rn != 0); - if (ln != 0 && rn != 0) + string_type& l (this->path_); + difference_type& d (this->diff_); + + // Handle the separator. LHS should be empty or already have one. + // + switch (d) { - if (!traits::is_separator (this->path_[ln - 1])) - this->path_ += traits::directory_separator; + case 0: if (!l.empty ()) throw invalid_basic_path (l); break; + case -1: break; // Already in the string. + default: l += path_traits::directory_separators[d - 1]; } + l.append (r, rn); + d = rd; // New trailing separator from RHS. + } + + template + inline void basic_path:: + combine (const C* r, size_type rn) + { + // If we do (dir_path / path) then we will end up with path. What should + // we end up if we do (dir_path / "foo") vs (dir_path / "foo/")? We cannot + // choose at runtime what kind of path to return. One (elaborate) option + // would be to handle the trailing slash but also call K::cast() so that + // dir_path gets the canonical trailing slash if one wasn't there. + // + // For now we won't allow the slash and will always add the canonical one + // for dir_path (via cast()). + // + if (traits::find_separator (r, rn) != nullptr) + throw invalid_basic_path (r); + + combine (r, rn, 0); + K::cast (*this); + } + + template + inline basic_path& basic_path:: + operator/= (basic_path const& r) + { + if (r.absolute () && !empty ()) // Allow ('' / '/foo'). + throw invalid_basic_path (r.path_); + + if (!r.empty ()) + combine (r.path_.c_str (), r.path_.size (), r.diff_); + + return *this; + } + + template + inline basic_path& basic_path:: + operator/= (string_type const& r) + { + if (size_type rn = r.size ()) + combine (r.c_str (), rn); + + return *this; + } + + template + inline basic_path& basic_path:: + operator/= (const C* r) + { + if (size_type rn = string_type::traits_type::length (r)) + combine (r, rn); + + return *this; + } + + template + inline void basic_path:: + append (const C* r, size_type rn) + { + //assert (this->diff_ != -1); // Append to root? this->path_.append (r, rn); } + + template + inline basic_path& basic_path:: + operator+= (string_type const& s) + { + append (s.c_str (), s.size ()); + return *this; + } + + template + inline basic_path& basic_path:: + operator+= (const C* s) + { + append (s, string_type::traits_type::length (s)); + return *this; + } + + template + inline basic_path& basic_path:: + operator+= (C c) + { + append (&c, 1); + return *this; + } + + template + inline auto basic_path:: + representation () const& -> string_type + { + string_type r (this->path_); + + if (this->diff_ > 0) + r += path_traits::directory_separators[this->diff_ - 1]; + + return r; + } + + template + inline auto basic_path:: + representation () && -> string_type + { + string_type r; + r.swap (this->path_); + + if (this->diff_ > 0) + r += path_traits::directory_separators[this->diff_ - 1]; + + return r; + } + + template + inline C basic_path:: + separator () const + { + return (this->diff_ == 0 ? 0 : + this->diff_ == -1 ? this->path_[0] : + path_traits::directory_separators[this->diff_ - 1]); + } + + template + inline auto basic_path:: + separator_string () const -> string_type + { + C c (separator ()); + return c == 0 ? string_type () : string_type (1, c); + } + + template + inline void dir_path_kind:: + cast (data_type& d) + { + // Add trailing slash if one isn't already there. + // + if (!d.path_.empty () && d.diff_ == 0) + d.diff_ = 1; // Canonical separator is always first. + } } -- cgit v1.1