From 6aeb2e06e9fa137a8e26f8605ec63f7567e65280 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 13 Sep 2010 14:29:04 +0200 Subject: Rework fs::path Use platform-canonical slashes. Add path_traits. Add the notion of an empty path. --- cutl/fs/path.hxx | 123 +++++++++++++++++++++++++++++++++++++++++++---- cutl/fs/path.txx | 65 +++++++++++++------------ tests/fs/path/driver.cxx | 44 +++++++++-------- 3 files changed, 171 insertions(+), 61 deletions(-) diff --git a/cutl/fs/path.hxx b/cutl/fs/path.hxx index aee91b9..fbf179e 100644 --- a/cutl/fs/path.hxx +++ b/cutl/fs/path.hxx @@ -19,6 +19,63 @@ namespace cutl class basic_path; template + struct path_traits + { + typedef std::basic_string string_type; + typedef typename string_type::size_type size_type; + + // Canonical directory and path seperators. + // +#ifdef _WIN32 + static char const directory_separator = '\\'; + static char const path_separator = ';'; +#else + static char const directory_separator = '/'; + static char const path_separator = ':'; +#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) + { +#ifdef _WIN32 + return c == '\\' || c == '/'; +#else + return c == '/'; +#endif + } + + static size_type + find_separator (string_type const& s) + { + for (size_type i (0), n (s.size ()); i < n; ++i) + { + if (is_separator (s[i])) + return i; + } + + return string_type::npos; + } + + static size_type + rfind_separator (string_type const& s) + { + for (size_type i (s.size ()) ; i > 0; --i) + { + if (is_separator (s[i - 1])) + return i - 1; + } + + return string_type::npos; + } + }; + + + template class invalid_basic_path; typedef basic_path path; @@ -63,33 +120,75 @@ namespace cutl typedef std::basic_string string_type; typedef typename string_type::size_type size_type; + typedef path_traits traits; + + // Construct special empty path. + // + basic_path () + { + } + explicit basic_path (C const* s) : path_ (s) { - init (false); + init (); + } + + basic_path (C const* s, size_type n) + : path_ (s, n) + { + init (); } explicit basic_path (string_type const& s) : path_ (s) { - init (false); + init (); } public: + // Return the path without the directory part. + // basic_path leaf () const; + // Return the directory part of the path or empty path if + // there is no directory. + // basic_path directory () const; + // Return the path without the extension, if any. + // basic_path base () const; public: basic_path - operator/ (basic_path const&); + operator/ (basic_path const& x) + { + basic_path r (*this); + r /= x; + return r; + } + + basic_path& + operator/= (basic_path const&); + + basic_path + operator+ (string_type const& s) + { + return basic_path (path_ + s); + } + + basic_path& + operator+= (string_type const& s) + { + path_ += s; + return *this; + } bool operator== (basic_path const& x) const @@ -104,22 +203,26 @@ namespace cutl } public: + bool + empty () const + { + return path_.empty (); + } + string_type string () const { - return path_.empty () ? string_type (1, '/') : path_; + return path_; } private: void - init (bool internal); + init (); - // Assume internal format. - // - basic_path (C const* s, size_type n) - : path_ (s, n) + bool + root () const { - init (true); + return path_.size () == 1 && traits::is_separator (path_[0]); } private: diff --git a/cutl/fs/path.txx b/cutl/fs/path.txx index 44064ca..e955928 100644 --- a/cutl/fs/path.txx +++ b/cutl/fs/path.txx @@ -11,30 +11,28 @@ namespace cutl basic_path basic_path:: leaf () const { - size_type n (path_.size ()), i (n); + size_type p (traits::rfind_separator (path_)); - for (; i > 0; --i) - { - if (path_[i - 1] == '/' || path_[i - 1] == '\\') - break; - } - - return i != 0 ? basic_path (path_.c_str () + i, n - i) : *this; + return p != string_type::npos + ? basic_path (path_.c_str () + p + 1, path_.size () - p - 1) + : *this; } template basic_path basic_path:: directory () const { - size_type i (path_.size ()); + if (root ()) + return basic_path (); - for (; i > 0; --i) - { - if (path_[i - 1] == '/' || path_[i - 1] == '\\') - break; - } + size_type p (traits::rfind_separator (path_)); - return i != 0 ? basic_path (path_.c_str (), i - 1) : *this; + // Include the trailing slash so that we get correct behavior + // if directory is root. + // + return p != string_type::npos + ? basic_path (path_.c_str (), p + 1) + : basic_path (); } template @@ -48,7 +46,7 @@ namespace cutl if (path_[i - 1] == '.') break; - if (path_[i - 1] == '/' || path_[i - 1] == '\\') + if (traits::is_separator (path_[i - 1])) { i = 0; break; @@ -57,7 +55,7 @@ namespace cutl // Weed out paths like ".txt" and "/.txt" // - if (i > 1 && path_[i - 2] != '/' && path_[i - 2] != '\\') + if (i > 1 && !traits::is_separator (path_[i - 2])) { return basic_path (path_.c_str (), i - 1); } @@ -66,30 +64,35 @@ namespace cutl } template - basic_path basic_path:: - operator/ (basic_path const& r) + basic_path& basic_path:: + operator/= (basic_path const& r) { - if (r.path_.empty ()) + if (r.root ()) throw invalid_basic_path (r.path_); - basic_path x (*this); - x.path_ += '/'; - x.path_ += r.path_; - return x; + if (path_.empty () || r.path_.empty ()) + { + path_ += r.path_; + return *this; + } + + if (!root ()) + path_ += traits::directory_separator; + + path_ += r.path_; + + return *this; } template void basic_path:: - init (bool internal) + init () { - if (!internal && path_.empty ()) - throw invalid_basic_path (path_); - - // Strip trailing slashes. This way empty string represents - // root directory. + // Strip trailing slashes except for the case where the single + // slash represents the root directory. // size_type n (path_.size ()); - for (; n > 0 && (path_[n - 1] == '/' || path_[n - 1] == '\\'); --n) ; + for (; n > 1 && traits::is_separator (path_[n - 1]); --n) ; path_.resize (n); } } diff --git a/tests/fs/path/driver.cxx b/tests/fs/path/driver.cxx index 897fd36..1dc74dd 100644 --- a/tests/fs/path/driver.cxx +++ b/tests/fs/path/driver.cxx @@ -12,56 +12,60 @@ using namespace cutl::fs; int main () { - // Construction. - // - try - { - path (""); - assert (false); - } - catch (invalid_path const&) - { - } - assert (path ("/").string () == "/"); assert (path ("//").string () == "/"); - assert (path ("\\\\").string () == "/"); + assert (path ("/tmp/foo/").string () == "/tmp/foo"); +#ifdef _WIN32 + assert (path ("\\\\").string () == "\\"); assert (path ("/\\").string () == "/"); assert (path ("C:").string () == "C:"); assert (path ("C:\\").string () == "C:"); - assert (path ("/tmp/foo/").string () == "/tmp/foo"); assert (path ("C:\\tmp\\foo\\").string () == "C:\\tmp\\foo"); +#endif // leaf // - assert (path ("/").leaf ().string () == "/"); - assert (path ("C:").leaf ().string () == "C:"); + assert (path ("/").leaf ().string () == ""); assert (path ("/tmp").leaf ().string () == "tmp"); - assert (path ("C:\\tmp").leaf ().string () == "tmp"); assert (path ("//tmp").leaf ().string () == "tmp"); +#ifdef _WIN32 + assert (path ("C:").leaf ().string () == "C:"); + assert (path ("C:\\tmp").leaf ().string () == "tmp"); assert (path ("C:\\\\tmp").leaf ().string () == "tmp"); +#endif // directory // - assert (path ("/").directory ().string () == "/"); - assert (path ("C:").directory ().string () == "C:"); + assert (path ("/").directory ().string () == ""); assert (path ("/tmp").directory ().string () == "/"); assert (path ("//tmp").directory ().string () == "/"); +#ifdef _WIN32 + assert (path ("C:").directory ().string () == ""); assert (path ("C:\\tmp").directory ().string () == "C:"); assert (path ("C:\\\\tmp").directory ().string () == "C:"); +#endif // base // assert (path ("/").base ().string () == "/"); - assert (path ("C:").base ().string () == "C:"); assert (path ("/foo.txt").base ().string () == "/foo"); - assert (path ("C:\\foo.txt").base ().string () == "C:\\foo"); assert (path (".txt").base ().string () == ".txt"); assert (path ("/.txt").base ().string () == "/.txt"); assert (path ("foo.txt.orig").base ().string () == "foo.txt"); +#ifdef _WIN32 + assert (path ("C:").base ().string () == "C:"); + assert (path ("C:\\foo.txt").base ().string () == "C:\\foo"); +#endif // operator/ // +#ifndef _WIN32 assert ((path ("/") / path ("tmp")).string () == "/tmp"); assert ((path ("foo") / path ("bar")).string () == "foo/bar"); +#else + assert ((path ("\\") / path ("tmp")).string () == "\\tmp"); + assert ((path ("C:\\") / path ("tmp")).string () == "C:\\tmp"); + assert ((path ("foo") / path ("bar")).string () == "foo\\bar"); +#endif + } -- cgit v1.1