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/filesystem.cxx | 2 +- butl/path | 593 +++++++++++++++++++++++++++++++---------------- butl/path.cxx | 32 ++- butl/path.ixx | 352 +++++++++++++++++++++------- butl/path.txx | 263 +++++++++++---------- butl/process.cxx | 4 +- tests/link/driver.cxx | 12 +- tests/path/driver.cxx | 375 ++++++++++++++++++++++-------- tests/process/driver.cxx | 2 +- 9 files changed, 1099 insertions(+), 536 deletions(-) diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx index cabe306..aa9319f 100644 --- a/butl/filesystem.cxx +++ b/butl/filesystem.cxx @@ -146,7 +146,7 @@ namespace butl path ep (p / de.path ()); //@@ Would be good to reuse the buffer. if (de.ltype () == entry_type::directory) - rmdir_r (path_cast (ep), true, ignore_error); + rmdir_r (path_cast (move (ep)), true, ignore_error); else try_rmfile (ep, ignore_error); } diff --git a/butl/path b/butl/path index 9a75b5d..4a80b9b 100644 --- a/butl/path +++ b/butl/path @@ -7,7 +7,7 @@ #include #include // ptrdiff_t -#include // move() +#include // move(), swap() #include #include #include // hash @@ -16,63 +16,68 @@ namespace butl { - // Wish list/ideas for improvements. // - // Ability to convert to directory/leaf/base in-place, without dynamic - // allocation. One idea is something like this: + // - posix_representation() in addition to posix_string() + // + // - Ability to convert to directory/leaf/base in-place, without dynamic + // allocation. One idea is something like this: + // + // p -= "/*"; // directory + // p -= "*/"; // leaf + // p -= ".*"; // base // - // p -= "/*"; // directory - // p -= "*/"; // leaf - // p -= ".*"; // base + // - Faster normalize() implementation. + // + // - We duplicate the interface for path and dir_path while most of it + // is common. Also, we can implicit-cast dir_path& to path& and use + // non-dir-adapted implementation (see where we call K::cast()). // - class LIBBUTL_EXPORT invalid_path_base: std::exception + struct LIBBUTL_EXPORT invalid_path_base: public std::exception { - public: virtual char const* what () const throw (); }; template - class invalid_basic_path: public invalid_path_base + struct invalid_basic_path: invalid_path_base { - public: - typedef std::basic_string string_type; + using string_type = std::basic_string; - invalid_basic_path (C const* p): path_ (p) {} - invalid_basic_path (string_type const& p): path_ (p) {} - ~invalid_basic_path () throw () {} + string_type path; - string_type const& - path () const - { - return path_; - } - - private: - string_type path_; + invalid_basic_path (const C* p): path (p) {} + invalid_basic_path (const string_type& p): path (p) {} }; template struct path_traits { - typedef std::basic_string string_type; - typedef typename string_type::size_type size_type; + using string_type = std::basic_string; + using size_type = typename string_type::size_type; // Canonical directory and path seperators. // #ifdef _WIN32 - static C const directory_separator = '\\'; - static C const path_separator = ';'; + static const C directory_separator = '\\'; + static const C path_separator = ';'; #else static C const directory_separator = '/'; static C const path_separator = ':'; #endif - // Directory separator tests. On some platforms there - // could be multiple seperators. For example, on Windows - // we check for both '/' and '\'. + // Canonical and alternative directory separators. Canonical should be + // first. + // +#ifdef _WIN32 + static constexpr const char* const directory_separators = "\\/"; +#else + static constexpr const char* const directory_separators = "/"; +#endif + + // Directory separator tests. On some platforms there could be multiple + // seperators. For example, on Windows we check for both '/' and '\'. // static bool is_separator (C c) @@ -84,10 +89,28 @@ namespace butl #endif } + // Return 1-based index in directory_separators string or 0 if not a + // separator. + // + static size_type + separator_index (C c) + { +#ifdef _WIN32 + return c == '\\' ? 1 : c == '/' ? 2 : 0; +#else + return c == '/' ? 1 : 0; +#endif + } + static size_type - find_separator (string_type const& s, size_type pos = 0) + find_separator (string_type const& s, + size_type pos = 0, + size_type n = string_type::npos) { - const C* r (find_separator (s.c_str () + pos, s.size () - pos)); + if (n == string_type::npos) + n = s.size (); + + const C* r (find_separator (s.c_str () + pos, n - pos)); return r != nullptr ? r - s.c_str () : string_type::npos; } @@ -223,116 +246,216 @@ namespace butl #endif }; - template - class invalid_basic_path; - + // This implementation of a filesystem path has two types: path, which can + // represent any path (file, directory, etc.) and dir_path, which is derived + // from path. The internal representation of directories maintains a + // trailing slash. However, it is ignored in path comparison, size, and + // string spelling. For example: + // + // path p1 ("foo"); // File path. + // path p2 ("bar/"); // Directory path. + // + // path p3 (p1 / p2); // Throw: p1 is not a directory. + // path p4 (p2 / p1); // Ok, file "bar/foo". + // path p5 (p2 / p2); // Ok, directory "bar/bar/". + // + // dir_path d1 ("foo"); // Directory path "foo/". + // dir_path d2 ("bar\\"); // Directory path "bar\". + // + // dir_path d3 (d2 / d1); // "bar\\foo/" + // + // (p4 == d3); // true + // d3.string (); // "bar\\foo" + // d3.representation (); // "bar\\foo/" + // template class basic_path; - // Cast from one path kind to another without any checking or - // processing. + template struct any_path_kind; + template struct dir_path_kind; + + using path = basic_path>; + using dir_path = basic_path>; + using invalid_path = invalid_basic_path; + + // Cast from one path kind to another. Note that no checking is performed + // (e.g., that there is a trailing slash if casting to dir_path) but the + // representation is adjusted if necessary (e.g., the trailing slash is + // added to dir_path if missing). // template P path_cast (const basic_path&); template P path_cast (basic_path&&); + // Low-level path data storage. It is also by the implementation to pass + // around initialized/valid paths. + // template - class path_data; + struct path_data + { + using string_type = std::basic_string; + using size_type = typename string_type::size_type; + using difference_type = typename string_type::difference_type; - template - struct dir_path_kind; + // The idea is as follows: path_ is always the "traditional" form; that + // is, "/" for the root directory and "/tmp" (no trailing slash) for the + // rest. This means we can return/store references to path_. + // + // Then we have diff_ which is the size difference between path_ and its + // "pure" part, that is, without any trailing slashes, even for "/". So: + // + // diff_ == -1 -- trailing slash in path_ (the "/" case) + // diff_ == 0 -- no trailing slash + // + // Finally, to represent non-root ("/") trailing slashes we use positive + // diff_ values. In this case diff_ is interpreted as a 1-based index in + // the path_traits::directory_separators string. + // + // Notes: + // - If path_ is empty, then diff_ can only be 0. + // - We could have used a much narrower integer for diff_. + // + string_type path_; + difference_type diff_; - template - struct any_path_kind - { - typedef path_data base_type; - typedef basic_path> dir_type; + size_type + _size () const {return path_.size () + (diff_ < 0 ? -1 : 0);} + + void + _swap (path_data& d) {path_.swap (d.path_); std::swap (diff_, d.diff_);} + + void + _clear () {path_.clear (); diff_ = 0;} + + // Constructors. + // + path_data (): diff_ (0) {} + + path_data (string_type&& p, difference_type d) + : path_ (std::move (p)), diff_ (path_.empty () ? 0 : d) {} + + explicit + path_data (string_type&& p) + : path_ (std::move (p)), diff_ (0) + { + size_type n (path_.size ()), i; + + if (n != 0 && (i = path_traits::separator_index (path_[n - 1])) != 0) + { + if (n == 1) // The "/" case. + diff_ = -1; + else + { + diff_ = i; + path_.pop_back (); + } + } + } }; template - struct dir_path_kind + struct any_path_kind { - typedef basic_path> base_type; - typedef basic_path> dir_type; - }; + class base_type: protected path_data // In essence protected path_data. + { + protected: + using path_data::path_data; - typedef basic_path> path; - typedef basic_path> dir_path; - typedef invalid_basic_path invalid_path; + base_type () = default; + base_type (path_data&& d): path_data (std::move (d)) {} + }; - typedef basic_path> wpath; - typedef basic_path> dir_wpath; - typedef invalid_basic_path invalid_wpath; + using dir_type = basic_path>; + + // Init and cast. + // + // If exact is true, return the path if the initialization was successful, + // that is, the passed string is a valid path and no modifications were + // necessary. Otherwise, return the empty object and leave the passed + // string untouched. + // + // If extact is false, throw invalid_path if the string is not a valid + // path (e.g., uses an unsupported path notation on Windows). + // + using data_type = path_data; + using string_type = std::basic_string; + + static data_type + init (string_type&&, bool exact = false); + + static void + cast (data_type&) {} + }; template - class path_data + struct dir_path_kind { - public: - typedef std::basic_string string_type; + using base_type = basic_path>; + using dir_type = basic_path>; - path_data () = default; + // Init and cast. + // + using data_type = path_data; + using string_type = std::basic_string; - explicit - path_data (string_type s): path_ (std::move (s)) {} + static data_type + init (string_type&&, bool exact = false); - protected: - string_type path_; + static void + cast (data_type&); }; template class basic_path: public K::base_type { public: - typedef std::basic_string string_type; - typedef typename string_type::size_type size_type; - - typedef typename K::base_type base_type; - typedef typename K::dir_type dir_type; - - typedef path_traits traits; + using string_type = std::basic_string; + using size_type = typename string_type::size_type; + using difference_type = typename string_type::difference_type; + using traits = path_traits; struct iterator; - typedef std::reverse_iterator reverse_iterator; + using reverse_iterator = std::reverse_iterator; - // Create a special empty path. Note that we have to provide our - // own implementation rather than using '=default' to make clang - // allow default-initialized const instances of this type. + using base_type = typename K::base_type; + using dir_type = typename K::dir_type; + + // Create a special empty path. Note that we have to provide our own + // implementation rather than using '=default' to make clang allow + // default-initialized const instances of this type. // - basic_path () {}; + basic_path () {} // Constructors that initialize a path from a string argument throw the // invalid_path exception if the string is not a valid path (e.g., uses // unsupported path notations on Windows). // explicit - basic_path (C const* s): base_type (s) {init (this->path_);} + basic_path (C const* s): base_type (K::init (s)) {} basic_path (C const* s, size_type n) - : base_type (string_type (s, n)) {init (this->path_);} + : base_type (K::init (string_type (s, n))) {} explicit - basic_path (string_type s): base_type (std::move (s)) {init (this->path_);} + basic_path (string_type s): base_type (K::init (std::move (s))) {} basic_path (const string_type& s, size_type n) - : base_type (string_type (s, 0, n)) {init (this->path_);} + : base_type (K::init (string_type (s, 0, n))) {} basic_path (const string_type& s, size_type p, size_type n) - : base_type (string_type (s, p, n)) {init (this->path_);} + : base_type (K::init (string_type (s, p, n))) {} // Create a path using the exact string representation. If the string is // not a valid path or if it would require a modification, then empty path // is created instead and the passed string rvalue-reference is left // untouched. Note that no exception is thrown if the path is invalid. See - // also string()&& below. + // also representation()&& below. // enum exact_type {exact}; basic_path (string_type&& s, exact_type) - { - if (init (s, true)) - this->path_ = std::move (s); - } + : base_type (K::init (std::move (s), true)) {} - // Create a path as a sub-path identified by the [begin, end) - // range of components. + // Create a path as a sub-path identified by the [begin, end) range of + // components. // basic_path (const iterator& begin, const iterator& end); @@ -340,13 +463,13 @@ namespace butl : basic_path (rend.base (), rbegin.base ()) {} void - swap (basic_path& p) {this->path_.swap (p.path_);} + swap (basic_path& p) {this->_swap (p);} void - clear () {this->path_.clear ();} + clear () {this->_clear ();} - // Get/set current working directory. Throw std::system_error - // to report the underlying OS errors. + // Get/set current working directory. Throw std::system_error to report + // the underlying OS errors. // static dir_type current () {return dir_type (traits::current ());} @@ -375,19 +498,22 @@ namespace butl static basic_path temp_path (const string_type& prefix) { - return temp_directory () / basic_path (traits::temp_name (prefix)); + return temp_directory () / traits::temp_name (prefix); } public: bool empty () const {return this->path_.empty ();} + // Note that size does not include the trailing separator except for + // the root case. + // size_type size () const {return this->path_.size ();} - // Return true if this path doesn't have any directories. Note - // that "/foo" is not a simple path (it is "foo" in root directory) - // while "/" is (it is the root directory). + // Return true if this path doesn't have any directories. Note that "/foo" + // is not a simple path (it is "foo" in root directory) while "/" is (it + // is the root directory). // bool simple () const; @@ -396,10 +522,7 @@ namespace butl absolute () const; bool - relative () const - { - return !absolute (); - } + relative () const {return !absolute ();} bool root () const; @@ -421,33 +544,35 @@ namespace butl sup (const basic_path&) const; public: - // Return the path without the directory part. + // Return the path without the directory part. Leaf of a directory is + // itself a directory (contains trailing slash). Leaf of a root is the + // path itself. // basic_path leaf () const; // Return the path without the specified directory part. Throws - // invalid_path if the directory is not a prefix of *this. Expects - // both paths to be normalized. + // invalid_path if the directory is not a prefix of *this. Expects both + // paths to be normalized. // basic_path leaf (basic_path const&) const; - // Return the directory part of the path or empty path if - // there is no directory. + // Return the directory part of the path or empty path if there is no + // directory. Directory of a root is an empty path. // dir_type directory () const; - // Return the directory part of the path without the specified - // leaf part. Throws invalid_path if the leaf is not a suffix of - // *this. Expects both paths to be normalized. + // Return the directory part of the path without the specified leaf part. + // Throws invalid_path if the leaf is not a suffix of *this. Expects both + // paths to be normalized. // dir_type directory (basic_path const&) const; - // Return the root directory of the path or empty path if - // the directory is not absolute. + // Return the root directory of the path or empty path if the directory is + // not absolute. // dir_type root_directory () const; @@ -476,25 +601,33 @@ namespace butl public: struct iterator { - typedef string_type value_type; - typedef string_type* pointer; - typedef string_type reference; - typedef std::ptrdiff_t difference_type; - typedef std::bidirectional_iterator_tag iterator_category; + using value_type = string_type ; + using pointer = string_type*; + using reference = string_type ; + using size_type = typename string_type::size_type; + using difference_type = std::ptrdiff_t ; + using iterator_category = std::bidirectional_iterator_tag ; - typedef typename string_type::size_type size_type; + using data_type = path_data; iterator (): p_ (nullptr) {} - iterator (const string_type& p, size_type b, size_type e) - : p_ (&p), b_ (b), e_ (e) {} + iterator (const data_type* p, size_type b, size_type e) + : p_ (p), b_ (b), e_ (e) {} iterator& operator++ () { - b_ = e_; + const string_type& s (p_->path_); - if (b_ != string_type::npos) - e_ = traits::find_separator (*p_, ++b_); + // Position past trailing separator, if any. + // + b_ = e_ != string_type::npos && ++e_ != s.size () + ? e_ + : string_type::npos; + + // Find next trailing separator. + // + e_ = b_ != string_type::npos ? traits::find_separator (s, b_) : b_; return *this; } @@ -502,13 +635,21 @@ namespace butl iterator& operator-- () { - e_ = b_; - - b_ = e_ == string_type::npos // Last component? - ? traits::rfind_separator (*p_) - : (--e_ == 0 // First empty component? - ? string_type::npos - : traits::rfind_separator (*p_, e_ - 1)); + const string_type& s (p_->path_); + + // Find the new end. + // + e_ = b_ == string_type::npos // Past end? + ? (traits::is_separator (s.back ()) // Have trailing slash? + ? s.size () - 1 + : string_type::npos) + : b_ - 1; + + // Find the new begin. + // + b_ = e_ == 0 // Empty component? + ? string_type::npos + : traits::rfind_separator (s, e_ != string_type::npos ? e_ - 1 : e_); b_ = b_ == string_type::npos // First component? ? 0 @@ -523,9 +664,26 @@ namespace butl iterator operator-- (int) {iterator r (*this); operator-- (); return r;} - string_type operator* () const + string_type + operator* () const + { + return string_type (p_->path_, + b_, + e_ != string_type::npos ? e_ - b_ : e_); + } + + // Return the directory separator after this component or '\0' if there + // is none. This, for example, can be used to determine if the last + // component is a directory. + // + C + separator () const { - return string_type (*p_, b_, (e_ != string_type::npos ? e_ - b_ : e_)); + return e_ != string_type::npos + ? p_->path_[e_] + : (p_->diff_ > 0 + ? path_traits::directory_separators[p_->diff_ - 1] + : 0); } pointer operator-> () const = delete; @@ -542,10 +700,11 @@ namespace butl private: friend class basic_path; - // b != npos && e == npos - last component + // b - first character of component + // e - separator after component (or npos if none) // b == npos && e == npos - one past last component (end) // - const string_type* p_; + const data_type* p_; size_type b_; size_type e_; }; @@ -558,15 +717,15 @@ namespace butl public: // Normalize the path. This includes collapsing the '.' and '..' - // directories if possible, collapsing multiple directory - // separators, and converting all directory separators to the - // canonical form. Return *this. + // directories if possible, collapsing multiple directory separators, and + // converting all directory separators to the canonical form. Return + // *this. // basic_path& normalize (); - // Make the path absolute using the current directory unless - // it is already absolute. Return *this. + // Make the path absolute using the current directory unless it is already + // absolute. Return *this. // basic_path& complete (); @@ -582,7 +741,7 @@ namespace butl basic_path& operator/= (basic_path const&); - // Append a single path component (must not contain directory separators) + // Combine a single path component (must not contain directory separators) // as a string, without first constructing the path object. // basic_path& @@ -591,47 +750,20 @@ namespace butl basic_path& operator/= (const C*); - basic_path - operator+ (string_type const& s) const - { - return basic_path (this->path_ + s); - } - - basic_path - operator+ (const C* s) const - { - return basic_path (this->path_ + s); - } - - basic_path - operator+ (C c) const - { - return basic_path (this->path_ + c); - } - + // Append to the end of the path (normally an extension, etc). + // basic_path& - operator+= (string_type const& s) - { - this->path_ += s; - return *this; - } + operator+= (string_type const&); basic_path& - operator+= (const C* s) - { - this->path_ += s; - return *this; - } + operator+= (const C*); basic_path& - operator+= (C c) - { - this->path_ += c; - return *this; - } + operator+= (C); - // Note that comparison is case-insensitive if the filesystem is - // not case-sensitive (e.g., Windows). + // Note that comparison is case-insensitive if the filesystem is not + // case-sensitive (e.g., Windows). And it ignored trailing slashes + // except for the root case. // template int @@ -639,56 +771,88 @@ namespace butl return traits::compare (this->path_, x.path_);} public: + // Path string and representation. The string does not contain the + // trailing slash except for the root case. In other words, it is the + // "traditional" spelling of the path that can be passed to system calls, + // etc. Representation, on the other hand is the "precise" spelling that + // includes the trailing slash, if any. One cannot always round-trip a + // path using string() but can using representation(). Note also that + // representation() returns a copy while string() returns a (tracking) + // reference. + // const string_type& string () const& {return this->path_;} - // Moves the underlying path string out of the path object. The - // path object becomes empty. Usage: std::move (p).string (). + string_type + representation () const&; + + // Moves the underlying path string out of the path object. The path + // object becomes empty. Usage: std::move (p).string (). // string_type string () && {string_type r; r.swap (this->path_); return r;} - // If possible, return a POSIX representation of the path. For example, - // for a Windows path in the form foo\bar this function will return - // foo/bar. If it is not possible to create a POSIX representation for - // this path (e.g., c:\foo), this function will throw the invalid_path - // exception. + string_type + representation () &&; + + // Trailing directory separator or '\0' if there is none. + // + C + separator () const; + + // As above but return it as a (potentially empty) string. + // + string_type + separator_string () const; + + // If possible, return a POSIX version of the path. For example, for a + // Windows path in the form foo\bar this function will return foo/bar. If + // it is not possible to create a POSIX version for this path (e.g., + // c:\foo), this function will throw the invalid_path exception. // string_type posix_string () const; + // Implementation details. + // protected: - basic_path (string_type s, bool i): base_type (std::move (s)) - { - if (i) - init (this->path_); - } + using data_type = path_data; + + // Direct initialization without init()/cast(). + // + explicit + basic_path (data_type&& d): base_type (std::move (d)) {} + + using base_type::_size; - // Common implementation for operator=/(). + // Common implementation for operator/= and operator+=. // void - combine (const C*, size_type); + combine (const C*, size_type, difference_type); - private: - template - friend P butl::path_cast (const basic_path&); + void + combine (const C*, size_type); - template - friend P butl::path_cast (basic_path&&); + void + append (const C*, size_type); - // If exact is true, return whether the initialization was successful, - // that is, the passed string is a valid path and no modifications were - // necessary. Otherwise (extact is false), throw invalid_path if the - // string is not a valid path (e.g., uses an unsupported path notation on - // Windows). + // Friends. // - bool - init (string_type& s, bool exact = false); + template + friend class basic_path; + + template + friend basic_path + path_cast_impl (const basic_path&, basic_path*); + + template + friend basic_path + path_cast_impl (basic_path&&, basic_path*); }; template inline basic_path - operator/ (basic_path const& x, basic_path const& y) + operator/ (const basic_path& x, const basic_path& y) { basic_path r (x); r /= y; @@ -697,7 +861,7 @@ namespace butl template inline basic_path - operator/ (basic_path const& x, std::basic_string const& y) + operator/ (const basic_path& x, const std::basic_string& y) { basic_path r (x); r /= y; @@ -706,13 +870,40 @@ namespace butl template inline basic_path - operator/ (basic_path const& x, const C* y) + operator/ (const basic_path& x, const C* y) { basic_path r (x); r /= y; return r; } + template + inline basic_path + operator+ (const basic_path& x, const std::basic_string& y) + { + basic_path r (x); + r += y; + return r; + } + + template + inline basic_path + operator+ (const basic_path& x, const C* y) + { + basic_path r (x); + r += y; + return r; + } + + template + inline basic_path + operator+ (const basic_path& x, C y) + { + basic_path r (x); + r += y; + return r; + } + template inline bool operator== (const basic_path& x, const basic_path& y) diff --git a/butl/path.cxx b/butl/path.cxx index 46e36fd..9325c1e 100644 --- a/butl/path.cxx +++ b/butl/path.cxx @@ -60,8 +60,6 @@ namespace butl LIBBUTL_EXPORT path_traits::string_type path_traits:: current () { - // @@ throw system_error (and in the other current() versions). - #ifdef _WIN32 char cwd[_MAX_PATH]; if (_getcwd (cwd, _MAX_PATH) == 0) @@ -72,7 +70,7 @@ namespace butl throw system_error (errno, system_category ()); #endif - return string_type (cwd); + return cwd; } template <> @@ -147,17 +145,15 @@ namespace butl { #ifdef _WIN32 char d[_MAX_PATH + 1]; - DWORD r (GetTempPathA (_MAX_PATH + 1, d)); - - if (r == 0) + if (GetTempPathA (_MAX_PATH + 1, d) == 0) { string e (last_error_msg ()); throw system_error (ENOTDIR, system_category (), e); } - return string_type (d); + return d; #else - return string_type (butl::temp_directory ()); + return butl::temp_directory (); #endif } @@ -177,12 +173,12 @@ namespace butl home () { #ifndef _WIN32 - return string_type (butl::home ()); + return butl::home (); #else // Could be set by, e.g., MSYS and Cygwin shells. // if (const char* h = getenv ("HOME")) - return string_type (h); + return h; char h[_MAX_PATH]; HRESULT r (SHGetFolderPathA (NULL, CSIDL_PROFILE, NULL, 0, h)); @@ -193,7 +189,7 @@ namespace butl throw system_error (ENOTDIR, system_category (), e); } - return string_type (h); + return h; #endif } @@ -240,7 +236,7 @@ namespace butl throw system_error (EINVAL, system_category ()); #endif - return string_type (wcwd); + return wcwd; } template <> @@ -269,9 +265,7 @@ namespace butl { #ifdef _WIN32 wchar_t d[_MAX_PATH + 1]; - DWORD r (GetTempPathW (_MAX_PATH + 1, d)); - - if (r == 0) + if (GetTempPathW (_MAX_PATH + 1, d) == 0) { string e (last_error_msg ()); throw system_error (ENOTDIR, system_category (), e); @@ -293,7 +287,7 @@ namespace butl throw system_error (ENOTSUP, system_category ()); #endif - return string_type (d); + return d; } template <> @@ -319,12 +313,12 @@ namespace butl if (r == PATH_MAX) throw system_error (ENOTSUP, system_category ()); - return string_type (d); + return d; #else // Could be set by, e.g., MSYS and Cygwin shells. // if (const wchar_t* h = _wgetenv (L"HOME")) - return string_type (h); + return h; wchar_t h[_MAX_PATH]; HRESULT r (SHGetFolderPathW (NULL, CSIDL_PROFILE, NULL, 0, h)); @@ -335,7 +329,7 @@ namespace butl throw system_error (ENOTDIR, system_category (), e); } - return string_type (h); + return h; #endif } 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. + } } diff --git a/butl/path.txx b/butl/path.txx index 94fbd90..1d6995e 100644 --- a/butl/path.txx +++ b/butl/path.txx @@ -8,30 +8,51 @@ namespace butl { template basic_path basic_path:: - leaf () const + leaf (basic_path const& d) const { - size_type p (traits::rfind_separator (this->path_)); + size_type dn (d.path_.size ()); + + if (dn == 0) + return *this; + + const string_type& s (this->path_); - return p != string_type::npos - ? basic_path (this->path_.c_str () + p + 1, this->path_.size () - p - 1) - : *this; + if (!sub (d)) + throw invalid_basic_path (s); + + // If there is implied trailing slash, add it to count. Unless it is + // "matched" by the implied slash on the other side. + // + if (d.diff_ > 0 && dn < s.size ()) + dn++; + + // Preserve trailing slash. + // + return basic_path (data_type (string_type (s, dn, s.size () - dn), + this->diff_)); } template typename basic_path::dir_type basic_path:: - directory () const + directory (basic_path const& l) const { - if (root ()) - return dir_type (); + size_type ln (l.path_.size ()); - size_type p (traits::rfind_separator (this->path_)); + const string_type& s (this->path_); - // Include the trailing slash so that we get correct behavior - // if directory is root. - // - return p != string_type::npos - ? dir_type (this->path_.c_str (), p + 1) - : dir_type (); + if (ln == 0) + { + if (this->diff_ == 0) // Must be a directory. + throw invalid_basic_path (s); + + return dir_type (data_type (string_type (s), this->diff_)); + } + + if (!sup (l)) + throw invalid_basic_path (s); + + return dir_type ( + data_type (string_type (s, 0, s.size () - ln))); // Include slash. } #ifdef _WIN32 @@ -55,85 +76,6 @@ namespace butl #endif template - basic_path& basic_path:: - operator/= (basic_path const& r) - { - if (r.absolute () && !this->path_.empty ()) // Allow ('' / '/foo'). - throw invalid_basic_path (r.path_); - - combine (r.path_.c_str (), r.path_.size ()); - return *this; - } - - template - basic_path& basic_path:: - operator/= (string_type const& r) - { - if (traits::find_separator (r) != string_type::npos) - throw invalid_basic_path (r); - - combine (r.c_str (), r.size ()); - return *this; - } - - template - basic_path& basic_path:: - operator/= (const C* r) - { - size_type rn (string_type::traits_type::length (r)); - - if (traits::find_separator (r, rn) != nullptr) - throw invalid_basic_path (r); - - combine (r, rn); - return *this; - } - - template - basic_path basic_path:: - leaf (basic_path const& d) const - { - size_type n (d.path_.size ()); - - if (n == 0) - return *this; - - if (!sub (d)) - throw invalid_basic_path (this->path_); - - size_type m (this->path_.size ()); - - if (n != m -#ifndef _WIN32 - && !d.root () -#endif - ) - n++; // Skip the directory separator (unless it is POSIX root). - - return basic_path (this->path_.c_str () + n, m - n); - } - - template - typename basic_path::dir_type basic_path:: - directory (basic_path const& l) const - { - size_type n (l.path_.size ()); - - if (n == 0) - return dir_type (this->path_); - - if (!sup (l)) - throw invalid_basic_path (this->path_); - - size_type m (this->path_.size ()); - - if (n != m) - n++; // Skip the directory separator. - - return dir_type (this->path_.c_str (), m - n); - } - - template basic_path basic_path:: relative (basic_path d) const { @@ -144,7 +86,7 @@ namespace butl if (sub (d)) break; - r /= basic_path (".."); + r /= basic_path ("../"); // Roots of the paths do not match. // @@ -162,40 +104,61 @@ namespace butl if (empty ()) return *this; + string_type& s (this->path_); + difference_type& d (this->diff_); + bool abs (absolute ()); typedef std::vector paths; paths ps; - for (size_type b (0), e (traits::find_separator (this->path_)), - n (this->path_.size ());; - e = traits::find_separator (this->path_, b)) + bool tsep (d != 0); // Trailing directory separator. { - string_type s (this->path_, b, e == string_type::npos ? e : e - b); - ps.push_back (s); + size_type n (_size ()); - if (e == string_type::npos) - break; + for (size_type b (0), e (traits::find_separator (s, 0, n)); + ; + e = traits::find_separator (s, b, n)) + { + ps.push_back ( + string_type (s, b, (e == string_type::npos ? n : e) - b)); - ++e; + if (e == string_type::npos) + break; - while (e < n && traits::is_separator (this->path_[e])) ++e; - if (e == n) - break; + // Skip consecutive directory separators. + // + while (e != n && traits::is_separator (s[e])) + ++e; + + if (e == n) + break; - b = e; + b = e; + } + + // If the last component is "." or ".." then this is a directory. + // + if (!tsep) + { + const string_type& l (ps.back ()); + size_type ln (l.size ()); + + if ((ln == 1 && l[0] == '.') || + (ln == 2 && l[0] == '.' && l[1] == '.')) + tsep = true; + } } - // First collapse '.' and '..'. + // Collapse "." and "..". // paths r; - for (typename paths::const_iterator i (ps.begin ()), e (ps.end ()); - i != e; ++i) + for (typename paths::iterator i (ps.begin ()), e (ps.end ()); i != e; ++i) { - string_type const& s (*i); + string_type& s (*i); size_type n (s.size ()); if (n == 1 && s[0] == '.') @@ -222,7 +185,7 @@ namespace butl } } - r.push_back (s); + r.push_back (std::move (s)); } // Reassemble the path. @@ -238,10 +201,20 @@ namespace butl p += traits::directory_separator; } - if (p.empty () && !r.empty ()) - p += traits::directory_separator; // Root directory. + if (tsep && (!p.empty () || abs)) // Distinguish "/"-empty and "."-empty. + { + if (p.empty ()) + { + p += traits::directory_separator; + d = -1; + } + else + d = 1; // Canonical separator is always first. + } + else + d = 0; - this->path_.swap (p); + s.swap (p); return *this; } @@ -257,10 +230,13 @@ namespace butl traits::current (s); } - template - bool basic_path:: - init (string_type& s, bool exact) + template + auto any_path_kind:: + init (string_type&& s, bool exact) -> data_type { + using size_type = typename string_type::size_type; + using difference_type = typename string_type::difference_type; + size_type n (s.size ()); #ifdef _WIN32 @@ -272,25 +248,58 @@ namespace butl (n > 1 && s[0] == '\\' && s[1] == '\\')) { if (exact) - return false; + return data_type (); else throw invalid_basic_path (s); } #endif - // Strip trailing slashes except for the case where the single slash - // represents the root directory. + // Strip trailing slashes. // - for (; n > 1 && traits::is_separator (s[n - 1]); --n) ; + size_type m (n), di (0); + for (size_type i; + m != 0 && (i = path_traits::separator_index (s[m - 1])) != 0; + --m) di = i; - if (n != s.size ()) + difference_type d (0); + if (size_t k = n - m) { - if (!exact) - this->path_.resize (n); + // We can only accomodate one trailing slash in the exact mode. + // + if (exact && k > 1) + return data_type (); - return !exact; + if (m == 0) // The "/" case. + { + ++m; // Keep one slash in the string. + d = -1; + } + else + d = di; + + s.resize (m); } - return true; + return data_type (std::move (s), d); + } + + template + auto dir_path_kind:: + init (string_type&& s, bool exact) -> data_type + { + // If we don't already have the separator then this can't be the exact + // initialization. + // + if (exact && !s.empty () && !path_traits::is_separator (s.back ())) + return data_type (); + + data_type r (any_path_kind::init (std::move (s), exact)); + + // Unless the result is empty, make sure we have the trailing slash. + // + if (!r.path_.empty () && r.diff_ == 0) + r.diff_ = 1; // Canonical separator is always first. + + return r; } } diff --git a/butl/process.cxx b/butl/process.cxx index 9bb0ea2..5c9a0f0 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -281,13 +281,13 @@ namespace butl for (size_t b (0), e (paths.find (traits::path_separator)); b != string::npos;) { - path p (string (paths, b, e != string::npos ? e - b : e)); + dir_path p (string (paths, b, e != string::npos ? e - b : e)); // Empty path (i.e., a double colon or a colon at the beginning or end // of PATH) means search in the current dirrectory. // if (p.empty ()) - p = path ("."); + p = dir_path ("."); path dp (p / f); diff --git a/tests/link/driver.cxx b/tests/link/driver.cxx index a32a8dc..4a4a00f 100644 --- a/tests/link/driver.cxx +++ b/tests/link/driver.cxx @@ -43,8 +43,10 @@ link_file (const path& target, const path& link, bool hard, bool check_content) #ifndef _WIN32 static bool -link_dir ( - const dir_path& target, const dir_path& link, bool hard, bool check_content) +link_dir (const dir_path& target, + const dir_path& link, + bool hard, + bool check_content) { try { @@ -53,8 +55,9 @@ link_dir ( else mksymlink (target, link); } - catch (const system_error&) + catch (const system_error& e) { + //cerr << e.what () << endl; return false; } @@ -62,6 +65,7 @@ link_dir ( return true; dir_path tp (target.absolute () ? target : link.directory () / target); + set> te; for (const dir_entry& de: dir_iterator (tp)) te.emplace (de.ltype (), de.path ()); @@ -112,7 +116,7 @@ main () // Create the file symlink using an unexistent file path. // - assert (link_file (fp / path ("a"), td / path ("sa"), false, false)); + assert (link_file (fp + "-a", td / path ("sa"), false, false)); // Prepare the target directory. // diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx index ad76940..7ca36b7 100644 --- a/tests/path/driver.cxx +++ b/tests/path/driver.cxx @@ -30,14 +30,43 @@ main () static_assert (is_nothrow_move_constructible::value, ""); #endif - assert (path ("/").string () == "/"); - assert (path ("//").string () == "/"); - assert (path ("/tmp/foo/").string () == "/tmp/foo"); + auto test = [] (const char* p, const char* s, const char* r) + { + path x (p); + return x.string () == s && x.representation () == r; + }; + + auto dir_test = [] (const char* p, const char* s, const char* r) + { + dir_path x (p); + return x.string () == s && x.representation () == r; + }; + + assert (test ("/", "/", "/")); + assert (test ("//", "/", "/")); + assert (test ("/tmp/foo", "/tmp/foo", "/tmp/foo")); + assert (test ("/tmp/foo/", "/tmp/foo", "/tmp/foo/")); + assert (test ("/tmp/foo//", "/tmp/foo", "/tmp/foo/")); + #ifdef _WIN32 - assert (path ("/\\").string () == "/"); - assert (path ("C:").string () == "C:"); - assert (path ("C:\\").string () == "C:"); - assert (path ("C:\\tmp\\foo\\").string () == "C:\\tmp\\foo"); + assert (test ("/\\", "/", "/")); + assert (test ("C:", "C:", "C:")); + assert (test ("C:\\", "C:", "C:\\")); + assert (test ("c:/", "c:", "c:/")); + assert (test ("C:\\tmp\\foo\\", "C:\\tmp\\foo", "C:\\tmp\\foo\\")); + assert (test ("C:\\tmp\\foo\\/\\", "C:\\tmp\\foo", "C:\\tmp\\foo\\")); +#endif + + assert (dir_test ("/", "/", "/")); + assert (dir_test ("/tmp/foo/", "/tmp/foo", "/tmp/foo/")); +#ifndef _WIN32 + assert (dir_test ("tmp/foo", "tmp/foo", "tmp/foo/")); +#else + assert (dir_test ("tmp\\foo", "tmp\\foo", "tmp\\foo\\")); + + assert (dir_test ("C:\\", "C:", "C:\\")); + assert (dir_test ("C:\\tmp/foo\\", "C:\\tmp/foo", "C:\\tmp/foo\\")); + assert (dir_test ("c:/tmp\\foo", "c:/tmp\\foo", "c:/tmp\\foo\\")); #endif // absolute/relative/root @@ -45,51 +74,73 @@ main () #ifndef _WIN32 assert (path ("/").root ()); assert (path ("//").root ()); + assert (!path ("/foo").root ()); assert (path ("/").absolute ()); assert (path ("/foo/bar").absolute ()); assert (path ("bar/baz").relative ()); + + assert (path ("/").root_directory ().representation () == "/"); + assert (path ("/bar/baz").root_directory ().representation () == "/"); #else assert (path ("C:").root ()); assert (path ("C:\\").root ()); + assert (!path ("C:\\foo").root ()); + assert (path ("C:").absolute ()); assert (path ("C:\\").absolute ()); assert (path ("C:\\foo\\bar").absolute ()); assert (path ("bar\\baz").relative ()); + + assert (path ("C:").root_directory ().representation () == "C:\\"); + assert (path ("c:/").root_directory ().representation () == "c:/"); + assert (path ("C:\\bar\\baz").root_directory ().representation () == "C:\\"); #endif // leaf // + assert (path ().leaf ().empty ()); #ifndef _WIN32 - assert (path ("/").leaf ().string () == ""); - assert (path ("/tmp").leaf ().string () == "tmp"); - assert (path ("//tmp").leaf ().string () == "tmp"); + assert (path ("/").leaf ().representation () == "/"); + assert (path ("/tmp").leaf ().representation () == "tmp"); + assert (path ("/tmp/").leaf ().representation () == "tmp/"); + assert (path ("//tmp").leaf ().representation () == "tmp"); #else - assert (path ("C:").leaf ().string () == "C:"); - assert (path ("C:\\tmp").leaf ().string () == "tmp"); - assert (path ("C:\\\\tmp").leaf ().string () == "tmp"); + assert (path ("C:\\").leaf ().representation () == "C:\\"); + assert (path ("C:\\tmp").leaf ().representation () == "tmp"); + assert (path ("C:\\tmp\\").leaf ().representation () == "tmp\\"); + assert (path ("C:\\tmp/").leaf ().representation () == "tmp/"); + assert (path ("C:\\\\tmp").leaf ().representation () == "tmp"); #endif // directory // + assert (path ().directory ().empty ()); #ifndef _WIN32 - assert (path ("/").directory ().string () == ""); - assert (path ("/tmp").directory ().string () == "/"); - assert (path ("//tmp").directory ().string () == "/"); + assert (path ("/").directory ().representation () == ""); + assert (path ("/tmp").directory ().representation () == "/"); + assert (path ("/tmp/").directory ().representation () == "/"); + assert (path ("//tmp").directory ().representation () == "//"); + assert (path ("/tmp/foo").directory ().representation () == "/tmp/"); + assert (path ("/tmp/foo/").directory ().representation () == "/tmp/"); #else - assert (path ("C:").directory ().string () == ""); - assert (path ("C:\\tmp").directory ().string () == "C:"); - assert (path ("C:\\\\tmp").directory ().string () == "C:"); + assert (path ("C:").directory ().representation () == ""); + assert (path ("C:\\tmp").directory ().representation () == "C:\\"); + assert (path ("C:\\\\tmp").directory ().representation () == "C:\\\\"); + assert (path ("C:\\tmp\\foo").directory ().representation () == "C:\\tmp\\"); + assert (path ("C:\\tmp/foo\\").directory ().representation () == "C:\\tmp/"); #endif // base // - assert (path ("/").base ().string () == "/"); - assert (path ("/foo.txt").base ().string () == "/foo"); - assert (path (".txt").base ().string () == ".txt"); - assert (path ("/.txt").base ().string () == "/.txt"); - assert (path ("foo.txt.orig").base ().string () == "foo.txt"); + assert (path ("/").base ().representation () == "/"); + assert (path ("/foo.txt").base ().representation () == "/foo"); + assert (path ("/foo.txt/").base ().representation () == "/foo/"); + assert (path (".txt").base ().representation () == ".txt"); + assert (path ("/.txt").base ().representation () == "/.txt"); + assert (path ("foo.txt.orig").base ().representation () == "foo.txt"); #ifdef _WIN32 - assert (path ("C:").base ().string () == "C:"); - assert (path ("C:\\foo.txt").base ().string () == "C:\\foo"); + assert (path ("C:").base ().representation () == "C:"); + assert (path ("C:\\foo.txt").base ().representation () == "C:\\foo"); + assert (path ("C:\\foo.txt\\").base ().representation () == "C:\\foo\\"); #endif // iteration @@ -117,8 +168,15 @@ main () { path p ("foo/bar"); path::iterator i (p.begin ()); - assert (i != p.end () && *i == "foo"); - assert (++i != p.end () && *i == "bar"); + assert (i != p.end () && *i == "foo" && i.separator () == '/'); + assert (++i != p.end () && *i == "bar" && i.separator () == '\0'); + assert (++i == p.end ()); + } + { + path p ("foo/bar/"); + path::iterator i (p.begin ()); + assert (i != p.end () && *i == "foo" && i.separator () == '/'); + assert (++i != p.end () && *i == "bar" && i.separator () == '/'); assert (++i == p.end ()); } { @@ -148,7 +206,7 @@ main () { path p ("/"); path::iterator i (p.begin ()); - assert (i != p.end () && *i == ""); + assert (i != p.end () && *i == "" && i.separator () == '/'); assert (++i == p.end ()); } { @@ -162,87 +220,128 @@ main () // iterator range construction // { - path p; - assert (path (p.begin (), p.end ()) == p); - } - { - path p ("foo"); - assert (path (p.begin (), p.end ()) == p); - assert (path (++p.begin (), p.end ()) == path ()); - } - { - path p ("foo/bar"); - assert (path (p.begin (), p.end ()) == p); - assert (path (++p.begin (), p.end ()) == path ("bar")); - assert (path (p.begin (), ++p.begin ()) == path ("foo")); - } - { - path p ("/foo/bar"); - assert (path (p.begin (), p.end ()) == p); - assert (path (++p.begin (), p.end ()) == path ("foo/bar")); - assert (path (++(++p.begin ()), p.end ()) == path ("bar")); + auto test = [] (const path::iterator& b, const path::iterator& e) + { + return path (b, e).representation (); + }; + + { + path p; + assert (test (p.begin (), p.end ()) == ""); + } + { + path p ("foo"); + assert (test (p.begin (), p.end ()) == "foo"); + assert (test (++p.begin (), p.end ()) == ""); + } + { + path p ("foo/"); + assert (test (p.begin (), p.end ()) == "foo/"); + } + { + path p ("foo/bar"); + assert (test (p.begin (), p.end ()) == "foo/bar"); + assert (test (++p.begin (), p.end ()) == "bar"); + assert (test (p.begin (), ++p.begin ()) == "foo/"); + } + { + path p ("/foo/bar"); + assert (test (p.begin (), p.end ()) == "/foo/bar"); + assert (test (++p.begin (), p.end ()) == "foo/bar"); + assert (test (++(++p.begin ()), p.end ()) == "bar"); #ifndef _WIN32 - assert (path (p.begin (), ++p.begin ()) == path ("/")); + assert (test (p.begin (), ++p.begin ()) == "/"); #endif - assert (path (++p.begin (), ++(++p.begin ())) == path ("foo")); - assert (path (++(++p.begin ()), ++(++(++p.begin ()))) == path ("bar")); - } + assert (test (++p.begin (), ++(++p.begin ())) == "foo/"); + assert (test (++(++p.begin ()), ++(++(++p.begin ()))) == "bar"); + } + { + path p ("/foo/bar/"); + assert (test (p.begin (), p.end ()) == "/foo/bar/"); + assert (test (++p.begin (), p.end ()) == "foo/bar/"); + assert (test (++(++p.begin ()), p.end ()) == "bar/"); + #ifndef _WIN32 - { - path p ("/"); - assert (path (p.begin (), p.end ()) == p); - assert (path (++p.begin (), p.end ()) == path ()); - } + assert (test (p.begin (), ++p.begin ()) == "/"); #endif + assert (test (++p.begin (), ++(++p.begin ())) == "foo/"); + assert (test (++(++p.begin ()), ++(++(++p.begin ()))) == "bar/"); + } +#ifndef _WIN32 + { + path p ("/"); + assert (test (p.begin (), p.end ()) == "/"); + assert (test (++p.begin (), p.end ()) == ""); + } +#endif + } + // operator/ // #ifndef _WIN32 - assert ((path ("/") / path ("tmp")).string () == "/tmp"); - assert ((path ("foo") / path ("bar")).string () == "foo/bar"); + assert ((path ("/") / path ("tmp")).representation () == "/tmp"); + assert ((path ("foo/") / path ("bar")).representation () == "foo/bar"); + assert ((path ("foo/") / path ("bar/")).representation () == "foo/bar/"); + assert ((path ("foo/") / path ()).representation () == "foo/"); #else - assert ((path ("\\") / path ("tmp")).string () == "\\tmp"); - assert ((path ("C:\\") / path ("tmp")).string () == "C:\\tmp"); - assert ((path ("foo") / path ("bar")).string () == "foo\\bar"); + assert ((path ("\\") / path ("tmp")).representation () == "\\tmp"); + assert ((path ("C:\\") / path ("tmp")).representation () == "C:\\tmp"); + assert ((path ("foo\\") / path ("bar")).representation () == "foo\\bar"); + assert ((path ("foo\\") / path ("bar\\")).representation () == "foo\\bar\\"); + assert ((path ("foo\\") / path ("bar/")).representation () == "foo\\bar/"); + assert ((path ("foo/") / path ("bar")).representation () == "foo/bar"); + assert ((path ("foo\\") / path ()).representation () == "foo\\"); #endif // normalize // #ifndef _WIN32 - assert (path ("../foo").normalize ().string () == "../foo"); - assert (path ("..///foo").normalize ().string () == "../foo"); - assert (path ("../../foo").normalize ().string () == "../../foo"); - assert (path (".././foo").normalize ().string () == "../foo"); - assert (path (".").normalize ().string () == ""); - assert (path ("./..").normalize ().string () == ".."); - assert (path ("../.").normalize ().string () == ".."); - assert (path ("foo/./..").normalize ().string () == ""); - assert (path ("/foo/./..").normalize ().string () == "/"); - assert (path ("./foo").normalize ().string () == "foo"); + assert (path ("../foo").normalize ().representation () == "../foo"); + assert (path ("..///foo").normalize ().representation () == "../foo"); + assert (path ("../../foo").normalize ().representation () == "../../foo"); + assert (path (".././foo").normalize ().representation () == "../foo"); + assert (path (".").normalize ().representation () == ""); + assert (path ("././").normalize ().representation () == ""); + assert (path ("./..").normalize ().representation () == "../"); + assert (path ("./../").normalize ().representation () == "../"); + assert (path ("../.").normalize ().representation () == "../"); + assert (path (".././").normalize ().representation () == "../"); + assert (path ("foo/./..").normalize ().representation () == ""); + assert (path ("/foo/./..").normalize ().representation () == "/"); + assert (path ("/foo/./../").normalize ().representation () == "/"); + assert (path ("./foo").normalize ().representation () == "foo"); + assert (path ("./foo/").normalize ().representation () == "foo/"); #else - assert (path ("../foo").normalize ().string () == "..\\foo"); - assert (path ("..///foo").normalize ().string () == "..\\foo"); - assert (path ("..\\../foo").normalize ().string () == "..\\..\\foo"); - assert (path (".././foo").normalize ().string () == "..\\foo"); - assert (path (".").normalize ().string () == ""); - assert (path ("./..").normalize ().string () == ".."); - assert (path ("../.").normalize ().string () == ".."); - assert (path ("foo/./..").normalize ().string () == ""); - assert (path ("C:/foo/./..").normalize ().string () == "C:"); - assert (path ("./foo").normalize ().string () == "foo"); - - assert (path ("C:").normalize ().string () == "C:"); - assert (path ("C:\\Foo12//Bar").normalize ().string () == "C:\\Foo12\\Bar"); + assert (path ("../foo").normalize ().representation () == "..\\foo"); + assert (path ("..///foo").normalize ().representation () == "..\\foo"); + assert (path ("..\\../foo").normalize ().representation () == "..\\..\\foo"); + assert (path (".././foo").normalize ().representation () == "..\\foo"); + assert (path (".").normalize ().representation () == ""); + assert (path (".\\.\\").normalize ().representation () == ""); + assert (path ("./..").normalize ().representation () == "..\\"); + assert (path ("../.").normalize ().representation () == "..\\"); + assert (path ("foo/./..").normalize ().representation () == ""); + assert (path ("C:/foo/./..").normalize ().representation () == "C:\\"); + assert (path ("C:/foo/./../").normalize ().representation () == "C:\\"); + assert (path ("./foo").normalize ().representation () == "foo"); + assert (path ("./foo\\").normalize ().representation () == "foo\\"); + + assert (path ("C:\\").normalize ().representation () == "C:\\"); + assert (path ("C:\\Foo12//Bar").normalize ().representation () == "C:\\Foo12\\Bar"); #endif // comparison // + assert (path ("/") == path ("/")); assert (path ("./foo") == path ("./foo")); + assert (path ("./foo/") == path ("./foo")); assert (path ("./boo") < path ("./foo")); #ifdef _WIN32 assert (path (".\\foo") == path ("./FoO")); + assert (path (".\\foo") == path ("./foo\\")); assert (path (".\\boo") < path (".\\Foo")); #endif @@ -261,29 +360,101 @@ main () // sub // - assert (path ("foo").sub (path ("foo"))); - assert (path ("foo/bar").sub (path ("foo/bar"))); - assert (path ("foo/bar").sub (path ("foo"))); - assert (!path ("foo/bar").sub (path ("bar"))); - assert (path ("/foo/bar").sub (path ("/foo"))); - assert (path ("/foo/bar/baz").sub (path ("/foo/bar"))); - assert (!path ("/foo/bar/baz").sub (path ("/foo/baz"))); + { + auto test = [] (const char* p, const char* pfx) + { + return path (p).sub (path (pfx)); + }; + + assert (test ("foo", "foo")); + assert (test ("foo/bar", "foo/bar")); + assert (test ("foo/bar", "foo")); + assert (test ("foo/bar", "foo/")); + assert (!test ("foo/bar", "bar")); + assert (!test ("/foo-bar", "/foo")); + assert (test ("/foo/bar", "/foo")); + assert (test ("/foo/bar/baz", "/foo/bar")); + assert (!test ("/foo/bar/baz", "/foo/baz")); #ifdef _WIN32 - assert (path ("c:").sub (path ("c:"))); - assert (!path ("c:").sub (path ("d:"))); - assert (path ("c:\\foo").sub (path ("c:"))); + assert (test ("c:", "c:")); + assert (test ("c:", "c:\\")); + assert (!test ("c:", "d:")); + assert (test ("c:\\foo", "c:")); + assert (test ("c:\\foo", "c:\\")); #else - assert (path ("/foo/bar/baz").sub (path ("/"))); + assert (test ("/", "/")); + assert (test ("/foo/bar/baz", "/")); #endif + } + + // sup + // + { + auto test = [] (const char* p, const char* sfx) + { + return path (p).sup (path (sfx)); + }; + + assert (test ("foo", "foo")); + assert (test ("foo/bar", "foo/bar")); + assert (test ("foo/bar", "bar")); + assert (test ("foo/bar/", "bar/")); + assert (!test ("foo/bar", "foo")); + assert (!test ("/foo-bar", "bar")); + assert (test ("/foo/bar", "bar")); + assert (test ("/foo/bar/baz", "bar/baz")); + assert (!test ("/foo/bar/baz", "bar")); + +#ifdef _WIN32 + assert (test ("c:", "c:")); + assert (test ("c:\\", "c:")); + assert (!test ("d:", "c:")); + assert (test ("c:\\foo", "foo")); + assert (test ("c:\\foo\\", "foo\\")); +#else + assert (test ("/", "/")); +#endif + } + + // leaf(path) + // + { + auto test = [] (const char* p, const char* d) + { + return path (p).leaf (path (d)).representation (); + }; + + assert (test ("/foo", "/") == "foo"); + //assert (test ("foo/bar", "foo") == "bar"); + assert (test ("foo/bar", "foo/") == "bar"); + assert (test ("foo/bar/", "foo/") == "bar/"); + assert (test ("/foo/bar", "/foo/") == "bar"); + } + + // directory(path) + // + { + auto test = [] (const char* p, const char* l) + { + return path (p).directory (path (l)).representation (); + }; + + assert (test ("/foo", "foo") == "/"); + assert (test ("foo/bar", "bar") == "foo/"); + assert (test ("foo/bar/", "bar/") == "foo/"); + assert (test ("foo/bar/", "bar") == "foo/"); + assert (test ("foo/bar/baz", "bar/baz") == "foo/"); + assert (test ("/foo/bar/baz", "bar/baz") == "/foo/"); + } // relative // - assert (path ("foo").relative (path ("foo")) == path ()); - assert (path ("foo/bar").relative (path ("foo/bar")) == path ()); - assert (path ("foo/bar/baz").relative (path ("foo/bar")) == path ("baz")); + assert (path ("foo/").relative (path ("foo/")) == path ()); + assert (path ("foo/bar/").relative (path ("foo/bar/")) == path ()); + assert (path ("foo/bar/baz").relative (path ("foo/bar/")) == path ("baz")); assert (path ("foo/bar/baz").relative (path ("foo/bar/buz")). posix_string () == "../baz"); - assert (path ("foo/bar/baz").relative (path ("foo/biz/baz")). + assert (path ("foo/bar/baz").relative (path ("foo/biz/baz/")). posix_string () == "../../bar/baz"); assert (path ("foo/bar/baz").relative (path ("fox/bar/baz")). posix_string () == "../../../foo/bar/baz"); @@ -302,10 +473,10 @@ main () #endif assert (path::temp_directory ().absolute ()); - assert (wpath::temp_directory ().absolute ()); + //assert (wpath::temp_directory ().absolute ()); assert (path::home ().absolute ()); - assert (wpath::home ().absolute ()); + //assert (wpath::home ().absolute ()); /* path p ("../foo"); diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index 5c43fb5..18b4fd8 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -287,7 +287,7 @@ main (int argc, const char* argv[]) // dir_path::current (fp.directory ()); - assert (exec (path (".") / fp.leaf ())); + assert (exec (dir_path (".") / fp.leaf ())); // Fail for unexistent file path. // -- cgit v1.1