From 08903414e3546bc2c76bef73b2337ccf79886530 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 18 Jun 2015 14:41:44 +0200 Subject: Move path and filesystem from build2 to libbutl --- butl/buildfile | 2 +- butl/filesystem | 61 ++++++ butl/filesystem.cxx | 101 +++++++++ butl/path | 542 ++++++++++++++++++++++++++++++++++++++++++++++++ butl/path-io | 29 +++ butl/path-map | 81 ++++++++ butl/path.cxx | 109 ++++++++++ butl/path.ixx | 171 +++++++++++++++ butl/path.txx | 256 +++++++++++++++++++++++ butl/timestamp | 54 +++++ butl/timestamp.cxx | 177 ++++++++++++++++ tests/buildfile | 2 +- tests/path/buildfile | 7 + tests/path/driver | Bin 0 -> 188856 bytes tests/path/driver.cxx | 228 ++++++++++++++++++++ tests/prefix-map/driver | Bin 199753 -> 199761 bytes 16 files changed, 1818 insertions(+), 2 deletions(-) create mode 100644 butl/filesystem create mode 100644 butl/filesystem.cxx create mode 100644 butl/path create mode 100644 butl/path-io create mode 100644 butl/path-map create mode 100644 butl/path.cxx create mode 100644 butl/path.ixx create mode 100644 butl/path.txx create mode 100644 butl/timestamp create mode 100644 butl/timestamp.cxx create mode 100644 tests/path/buildfile create mode 100755 tests/path/driver create mode 100644 tests/path/driver.cxx diff --git a/butl/buildfile b/butl/buildfile index c289532..e9fc900 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -2,6 +2,6 @@ # copyright : Copyright (c) 2014-2015 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -lib{butl}: cxx{fdstream process} +lib{butl}: cxx{fdstream filesystem path process timestamp} cxx.poptions += -I$src_root lib{butl}: cxx.export.poptions = -I$src_root diff --git a/butl/filesystem b/butl/filesystem new file mode 100644 index 0000000..10f61f2 --- /dev/null +++ b/butl/filesystem @@ -0,0 +1,61 @@ +// file : butl/filesystem -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_FILESYSTEM +#define BUTL_FILESYSTEM + +#include // mode_t + +#include + +namespace butl +{ + // Return true if the path is to an existing directory. Note that + // this function resolves symlinks. + // + bool + dir_exists (const path&); + + // Return true if the path is to an existing regular file. Note that + // this function resolves symlinks. + // + bool + file_exists (const path&); + + // Try to create a directory unless it already exists. If you expect + // the directory to exist and performance is important, then you + // should first call dir_exists() above since that's what this + // implementation will do to make sure the path is actually a + // directory. + // + // You should also probably use the default mode 0777 and let the + // umask mechanism adjust it to the user's preferences. + // + // Errors are reported by throwing std::system_error. + // + enum class mkdir_status {success, already_exists}; + + mkdir_status + try_mkdir (const path&, mode_t = 0777); + + // Try to remove the directory returning not_exist if it does not + // exist and not_empty if it is not empty. All other errors are + // reported by throwing std::system_error. + // + enum class rmdir_status {success, not_exist, not_empty}; + + rmdir_status + try_rmdir (const path&); + + // Try to remove the file (or symbolic link) returning not_exist if + // it does not exist. All other errors are reported by throwing + // std::system_error. + // + enum class rmfile_status {success, not_exist}; + + rmfile_status + try_rmfile (const path&); +} + +#endif // BUTL_FILESYSTEM diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx new file mode 100644 index 0000000..230bc8e --- /dev/null +++ b/butl/filesystem.cxx @@ -0,0 +1,101 @@ +// file : butl/filesystem.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // rmdir(), unlink() +#include // stat +#include // stat, lstat(), S_IS*, mkdir() + +#include + +using namespace std; + +namespace butl +{ + bool + dir_exists (const path& p) + { + struct stat s; + if (::lstat (p.string ().c_str (), &s) != 0) + { + if (errno == ENOENT || errno == ENOTDIR) + return false; + else + throw system_error (errno, system_category ()); + } + + return S_ISDIR (s.st_mode); + } + + bool + file_exists (const path& p) + { + struct stat s; + if (::lstat (p.string ().c_str (), &s) != 0) + { + if (errno == ENOENT || errno == ENOTDIR) + return false; + else + throw system_error (errno, system_category ()); + } + + return S_ISREG (s.st_mode); + } + + mkdir_status + try_mkdir (const path& p, mode_t m) + { + mkdir_status r (mkdir_status::success); + + if (::mkdir (p.string ().c_str (), m) != 0) + { + int e (errno); + + // EEXIST means the path already exists but not necessarily as + // a directory. + // + if (e == EEXIST && dir_exists (p)) + return mkdir_status::already_exists; + else + throw system_error (e, system_category ()); + } + + return r; + } + + rmdir_status + try_rmdir (const path& p) + { + rmdir_status r (rmdir_status::success); + + if (::rmdir (p.string ().c_str ()) != 0) + { + if (errno == ENOENT) + r = rmdir_status::not_exist; + else if (errno == ENOTEMPTY || errno == EEXIST) + r = rmdir_status::not_empty; + else + throw system_error (errno, system_category ()); + } + + return r; + } + + rmfile_status + try_rmfile (const path& p) + { + rmfile_status r (rmfile_status::success); + + if (::unlink (p.string ().c_str ()) != 0) + { + if (errno == ENOENT || errno == ENOTDIR) + r = rmfile_status::not_exist; + else + throw system_error (errno, system_category ()); + } + + return r; + } +} diff --git a/butl/path b/butl/path new file mode 100644 index 0000000..451600c --- /dev/null +++ b/butl/path @@ -0,0 +1,542 @@ +// file : butl/path -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_PATH +#define BUTL_PATH + +#include +#include // ptrdiff_t +#include // move() +#include +#include +#include // hash + +namespace butl +{ + 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 C const directory_separator = '\\'; + static C const 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 '\'. + // + static bool + is_separator (C c) + { +#ifdef _WIN32 + return c == '\\' || c == '/'; +#else + return c == '/'; +#endif + } + + static size_type + find_separator (string_type const& s, size_type pos = 0) + { + for (size_type n (s.size ()); pos < n; ++pos) + { + if (is_separator (s[pos])) + return pos; + } + + return string_type::npos; + } + + static size_type + rfind_separator (string_type const& s, size_type pos = string_type::npos) + { + if (pos == string_type::npos) + pos = s.size (); + else + pos++; + + for (; pos > 0; --pos) + { + if (is_separator (s[pos - 1])) + return pos - 1; + } + + return string_type::npos; + } + + // Return the position of '.' or npos if there is no extension. + // + static size_type + find_extension (string_type const& s) + { + size_type i (s.size ()); + + for (; i > 0; --i) + { + C c (s[i - 1]); + + if (c == '.') + break; + + if (is_separator (c)) + { + i = 0; + break; + } + } + + // Weed out paths like ".txt" (and "/.txt") and "txt.". + // + if (i > 1 && !is_separator (s[i - 2]) && i != s.size ()) + return i - 1; + else + return string_type::npos; + } + + static int + compare (string_type const& l, string_type const& r) + { + size_type ln (l.size ()), rn (r.size ()), n (ln < rn ? ln : rn); + for (size_type i (0); i != n; ++i) + { +#ifdef _WIN32 + C lc (tolower (l[i])), rc (tolower (r[i])); +#else + C lc (l[i]), rc (r[i]); +#endif + if (is_separator (lc) && is_separator (rc)) + continue; + + if (lc < rc) return -1; + if (lc > rc) return 1; + } + + return ln < rn ? -1 : (ln > rn ? 1 : 0); + } + + // Get/set current working directory. Throw std::system_error + // to report the underlying OS errors. + // + static string_type + current (); + + static void + current (string_type const&); + + private: +#ifdef _WIN32 + static C + tolower (C); +#endif + }; + + template + class invalid_basic_path; + + template + class basic_path; + + template + class path_data; + + template + struct dir_path_kind; + + template + struct any_path_kind + { + typedef path_data base_type; + typedef basic_path> dir_type; + }; + + template + struct dir_path_kind + { + typedef basic_path> base_type; + typedef basic_path> dir_type; + }; + + typedef basic_path> path; + typedef basic_path> dir_path; + typedef invalid_basic_path invalid_path; + + typedef basic_path> wpath; + typedef basic_path> dir_wpath; + typedef invalid_basic_path invalid_wpath; + + // + // + class invalid_path_base: std::exception + { + public: + virtual char const* + what () const throw (); + }; + + template + class invalid_basic_path: public invalid_path_base + { + public: + typedef std::basic_string string_type; + + invalid_basic_path (C const* p): path_ (p) {} + invalid_basic_path (string_type const& p): path_ (p) {} + ~invalid_basic_path () throw () {} + + string_type const& + path () const + { + return path_; + } + + private: + string_type path_; + }; + + template + class path_data + { + public: + typedef std::basic_string string_type; + + path_data () = default; + + explicit + path_data (string_type s): path_ (std::move (s)) {} + + protected: + string_type path_; + }; + + 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; + + // Construct 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 () {}; + + explicit + basic_path (C const* s): base_type (s) {init ();} + + basic_path (C const* s, size_type n) + : base_type (string_type (s, n)) {init ();} + + explicit + basic_path (string_type s): base_type (std::move (s)) {init ();} + + basic_path (const string_type& s, size_type n) + : base_type (string_type (s, 0, n)) {init ();} + + basic_path (const string_type& s, size_type p, size_type n) + : base_type (string_type (s, p, n)) {init ();} + + void + swap (basic_path& p) + { + this->path_.swap (p.path_); + } + + void + clear () + { + this->path_.clear (); + } + + // Get/set current working directory. Throw std::system_error + // to report the underlying OS errors. + // + static dir_type + current () {return dir_type (traits::current ());} + + static void + current (basic_path const&); + + public: + bool + empty () const + { + return this->path_.empty (); + } + + bool + absolute () const; + + bool + relative () const + { + return !absolute (); + } + + bool + root () const; + + // Return true if *this is a sub-path of the specified path (i.e., + // the specified path is a prefix). Expects both paths to be + // normalized. Note that this function returns true if the paths + // are equal. Empty path is considered a prefix of any path. + // + bool + sub (const basic_path&) const; + + // Return true if *this is a super-path of the specified path (i.e., + // the specified path is a suffix). Expects both paths to be + // normalized. Note that this function returns true if the paths + // are equal. Empty path is considered a suffix of any path. + // + bool + sup (const basic_path&) const; + + public: + // Return the path without the directory part. + // + 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. + // + basic_path + leaf (basic_path const&) const; + + // Return the directory part of the path or empty path if + // there is no directory. + // + 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. + // + dir_type + directory (basic_path const&) const; + + // Return the root directory of the path or empty path if + // the directory is not absolute. + // + dir_type + root_directory () const; + + // Return the path without the extension, if any. + // + basic_path + base () const; + + // Return the extension or NULL if not present. + // + const C* + extension () const; + + // Return a path relative to the specified path that is equivalent + // to *this. Throws invalid_path if a relative path cannot be derived + // (e.g., paths are on different drives on Windows). + // + basic_path + relative (basic_path) const; + + // Iteration over path components. + // + public: + struct iterator + { + typedef string_type value_type; + typedef string_type* pointer; + typedef string_type& reference; + typedef std::ptrdiff_t difference_type; + typedef std::forward_iterator_tag iterator_category; + + typedef typename string_type::size_type size_type; + + iterator (): p_ (nullptr) {} + iterator (const string_type& p, size_type b, size_type e) + : p_ (&p), b_ (b), e_ (e) {} + + iterator& + operator++ () + { + b_ = e_; + + if (b_ != string_type::npos) + e_ = traits::find_separator (*p_, ++b_); + + return *this; + } + + iterator + operator++ (int) {iterator r (*this); return ++r;} + + string_type operator* () const + { + return string_type (*p_, b_, (e_ != string_type::npos ? e_ - b_ : e_)); + } + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.p_ == y.p_ && x.b_ == y.b_ && x.e_ == y.e_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + private: + // b != npos && e == npos - last component + // b == npos && e == npos - one past last component (end) + // + const string_type* p_; + size_type b_; + size_type e_; + }; + + iterator begin () const; + iterator end () const; + + 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. Returns *this. + // + basic_path& + normalize (); + + // Make the path absolute using the current directory unless + // it is already absolute. + // + basic_path& + complete (); + + public: + basic_path& + operator/= (basic_path const&); + + basic_path + operator+ (string_type const& s) const + { + return basic_path (this->path_ + s); + } + + basic_path + operator+ (C c) const + { + return basic_path (this->path_ + c); + } + + basic_path& + operator+= (string_type const& s) + { + this->path_ += s; + return *this; + } + + basic_path& + operator+= (C c) + { + this->path_ += c; + return *this; + } + + // Note that comparison is case-insensitive if the filesystem is + // not case-sensitive (e.g., Windows). + // + bool + operator== (basic_path const& x) const + { + return traits::compare (this->path_, x.path_) == 0; + } + + bool + operator!= (basic_path const& x) const + { + return !(*this == x); + } + + bool + operator< (basic_path const& x) const + { + return traits::compare (this->path_, x.path_) < 0; + } + + public: + const string_type& + string () const + { + return this->path_; + } + + // 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 + posix_string () const; + + private: + void + init (); + }; + + template + inline basic_path + operator/ (basic_path const& x, basic_path const& y) + { + basic_path r (x); + r /= y; + return r; + } + + // Additional operators for certain path kind combinations. + // + template + inline basic_path> + operator/ (basic_path> const& x, + basic_path> const& y) + { + basic_path> r (x); + r /= y; + return r; + } + + // For operator<< (ostream) see the path-io header. +} + +namespace std +{ + template + struct hash>: hash> + { + size_t + operator() (const butl::basic_path& p) const noexcept + { + return hash>::operator() (p.string ()); + } + }; +} + +#include +#include + +#endif // BUTL_PATH diff --git a/butl/path-io b/butl/path-io new file mode 100644 index 0000000..2f2072f --- /dev/null +++ b/butl/path-io @@ -0,0 +1,29 @@ +// file : butl/path-io -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_PATH_IO +#define BUTL_PATH_IO + +#include + +#include + +namespace butl +{ + // This is the default path IO implementation. The reason it is + // separate is because one often wants a custom implementation. + // For example, we may want to print paths as relative to the + // working directory. Or we may want to print '~' for the home + // directory prefix. Or we may want to print dir_path with a + // trailing '/'. + // + template + inline std::basic_ostream& + operator<< (std::basic_ostream& os, basic_path const& p) + { + return os << p.string (); + } +} + +#endif // BUTL_PATH_IO diff --git a/butl/path-map b/butl/path-map new file mode 100644 index 0000000..101a2a9 --- /dev/null +++ b/butl/path-map @@ -0,0 +1,81 @@ +// file : butl/path-map -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_PATH_MAP +#define BUTL_PATH_MAP + +#include +#include + +namespace butl +{ + // prefix_map for filesystem paths + // + // Important: the paths should be normalized and canonicalized. + // + // Note that the path's representation of POSIX root ('/') is + // inconsistent in that we have a trailing delimiter at the end of + // the path (its "proper" representation would have been an empty + // string but that would have clashed with empty paths). To work + // around this snag, this implementation, during key comparison, + // detects '/' and treats it as empty. Note that the map will + // still store the key as you have first inserted it. So if you + // want a particular representation (i.e., empty or '/'), pre- + // populate the map with it. + // + template + struct compare_prefix>: compare_prefix> + { + typedef basic_path key_type; + + typedef C delimiter_type; + typedef std::basic_string string_type; + typedef compare_prefix> base; + + explicit + compare_prefix (delimiter_type d): base (d) {} + + bool + operator() (const key_type& x, const key_type& y) const + { + const auto& xs (x.string ()); + const auto& ys (y.string ()); + +#ifdef _WIN32 + return base::compare (xs.c_str (), + xs.size (), + ys.c_str (), + ys.size ()) < 0; +#else + return base::compare (xs.c_str (), + x.root () ? 0 : xs.size (), + ys.c_str (), + y.root () ? 0 : ys.size ()) < 0; +#endif + } + + bool + prefix (const key_type& p, const key_type& k) const + { + const auto& ps (p.string ()); + const auto& ks (k.string ()); + +#ifdef _WIN32 + return base::prefix (ps, ks); +#else + return base::prefix (p.root () ? string_type () : ps, + k.root () ? string_type () : ks); +#endif + } + }; + + template + using path_map = prefix_map; + + template + using dir_path_map = + prefix_map; +} + +#endif // BUTL_PATH_MAP diff --git a/butl/path.cxx b/butl/path.cxx new file mode 100644 index 0000000..5d804a9 --- /dev/null +++ b/butl/path.cxx @@ -0,0 +1,109 @@ +// file : butl/path.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#ifdef _WIN32 +# include // _MAX_PATH +# include // _[w]getcwd, _[w]chdir +#else +# include // EINVAL +# include // mbstowcs, wcstombs +# include // PATH_MAX +# include // getcwd, chdir +#endif + +#include + +using namespace std; + +namespace butl +{ + char const* invalid_path_base:: + what () const throw () + { + return "invalid filesystem path"; + } + + // + // char + // + + template <> + 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) + throw system_error (errno, system_category ()); +#else + char cwd[PATH_MAX]; + if (getcwd (cwd, PATH_MAX) == 0) + throw system_error (errno, system_category ()); +#endif + + return string_type (cwd); + } + + template <> + void path_traits:: + current (string_type const& s) + { +#ifdef _WIN32 + if(_chdir(s.c_str ()) != 0) + throw system_error (errno, system_category ()); +#else + if (chdir (s.c_str ()) != 0) + throw system_error (errno, system_category ()); +#endif + } + + // + // wchar_t + // + + template <> + path_traits::string_type path_traits:: + current () + { +#ifdef _WIN32 + wchar_t wcwd[_MAX_PATH]; + if(_wgetcwd(wcwd, _MAX_PATH) == 0) + throw system_error (errno, system_category ()); +#else + char cwd[PATH_MAX]; + if (getcwd (cwd, PATH_MAX) == 0) + throw system_error (errno, system_category ()); + + wchar_t wcwd[PATH_MAX]; + if (mbstowcs (wcwd, cwd, PATH_MAX) == size_type (-1)) + throw system_error (EINVAL, system_category ()); +#endif + + return string_type (wcwd); + } + + template <> + void path_traits:: + current (string_type const& s) + { +#ifdef _WIN32 + if(_wchdir(s.c_str ()) != 0) + throw system_error (errno, system_category ()); +#else + char ns[PATH_MAX + 1]; + + if (wcstombs (ns, s.c_str (), PATH_MAX) == size_type (-1)) + throw system_error (EINVAL, system_category ()); + + ns[PATH_MAX] = '\0'; + + if (chdir (ns) != 0) + throw system_error (errno, system_category ()); +#endif + } +} diff --git a/butl/path.ixx b/butl/path.ixx new file mode 100644 index 0000000..0cbbeaf --- /dev/null +++ b/butl/path.ixx @@ -0,0 +1,171 @@ +// file : butl/path.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifdef _WIN32 +# include // std::tolower +# include // std::towlower +#endif + +namespace butl +{ +#ifdef _WIN32 + template <> + inline char path_traits:: + tolower (char c) + { + return std::tolower (c); + } + + template <> + inline wchar_t path_traits:: + tolower (wchar_t c) + { + return std::towlower (c); + } +#endif + + template + inline bool basic_path:: + absolute () const + { +#ifdef _WIN32 + return this->path_.size () > 1 && this->path_[1] == ':'; +#else + return !this->path_.empty () && traits::is_separator (this->path_[0]); +#endif + } + + template + inline bool basic_path:: + root () const + { +#ifdef _WIN32 + return this->path_.size () == 2 && this->path_[1] == ':'; +#else + return this->path_.size () == 1 && traits::is_separator (this->path_[0]); +#endif + } + + template + inline bool basic_path:: + sub (const basic_path& p) const + { + size_type n (p.path_.size ()); + + if (n == 0) + return true; + + size_type m (this->path_.size ()); + + // The second condition guards against the /foo-bar vs /foo case. + // + return m >= n && this->path_.compare (0, n, p.path_) == 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 + } + + template + inline bool basic_path:: + sup (const basic_path& p) const + { + size_type n (p.path_.size ()); + + if (n == 0) + return true; + + size_type m (this->path_.size ()); + + // The second condition guards against the /foo-bar vs bar case. + // + return m >= n && this->path_.compare (m - n, n, p.path_) == 0 && + (m == n || // *this == p + traits::is_separator (this->path_[m - n - 1])); // prev char separator + } + + template + inline auto basic_path:: + begin () const -> iterator + { + size_type b, e; + + if (this->path_.empty ()) + b = e = string_type::npos; + +#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); + } + + template + inline auto basic_path:: + end () const -> iterator + { + return iterator (this->path_, string_type::npos, string_type::npos); + } + + template + inline basic_path& basic_path:: + complete () + { + if (relative ()) + *this = current () / *this; + + return *this; + } + + template + inline typename basic_path::dir_type basic_path:: + root_directory () const + { + return absolute () +#ifdef _WIN32 + ? dir_type (this->path_, 2) +#else + ? dir_type ("/") +#endif + : dir_type (); + } + + template + inline basic_path basic_path:: + base () const + { + size_type p (traits::find_extension (this->path_)); + return p != string_type::npos + ? basic_path (this->path_.c_str (), p) + : *this; + } + + template + 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; + } + +#ifndef _WIN32 + template + inline typename basic_path::string_type basic_path:: + posix_string () const + { + return string (); + } +#endif +} diff --git a/butl/path.txx b/butl/path.txx new file mode 100644 index 0000000..6d418ff --- /dev/null +++ b/butl/path.txx @@ -0,0 +1,256 @@ +// file : butl/path.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +namespace butl +{ + template + basic_path basic_path:: + leaf () const + { + size_type p (traits::rfind_separator (this->path_)); + + return p != string_type::npos + ? basic_path (this->path_.c_str () + p + 1, this->path_.size () - p - 1) + : *this; + } + + template + typename basic_path::dir_type basic_path:: + directory () const + { + if (root ()) + return dir_type (); + + size_type p (traits::rfind_separator (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 (); + } + +#ifdef _WIN32 + template + typename basic_path::string_type basic_path:: + posix_string () const + { + if (absolute ()) + throw invalid_basic_path (this->path_); + + string_type r (this->path_); + + // Translate Windows-style separators to the POSIX ones. + // + for (size_type i (0), n (r.size ()); i != n; ++i) + if (r[i] == '\\') + r[i] = '/'; + + return r; + } +#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_); + + if (this->path_.empty () || r.path_.empty ()) + { + this->path_ += r.path_; + return *this; + } + + if (!traits::is_separator (this->path_[this->path_.size () - 1])) + this->path_ += traits::directory_separator; + + this->path_ += r.path_; + 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 + { + basic_path r; + + for (;; d = d.directory ()) + { + if (sub (d)) + break; + + r /= basic_path (".."); + + // Roots of the paths do not match. + // + if (d.root ()) + throw invalid_basic_path (this->path_); + } + + return r / leaf (d); + } + + template + basic_path& basic_path:: + normalize () + { + if (empty ()) + return *this; + + 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)) + { + string_type s (this->path_, b, e == string_type::npos ? e : e - b); + ps.push_back (s); + + if (e == string_type::npos) + break; + + ++e; + + while (e < n && traits::is_separator (this->path_[e])) + ++e; + + if (e == n) + break; + + b = e; + } + + // First collapse '.' and '..'. + // + paths r; + + for (typename paths::const_iterator i (ps.begin ()), e (ps.end ()); + i != e; ++i) + { + string_type const& s (*i); + size_type n (s.size ()); + + if (n == 1 && s[0] == '.') + continue; + + if (n == 2 && s[0] == '.' && s[1] == '.') + { + // Pop the last directory from r unless it is '..'. + // + if (!r.empty ()) + { + string_type const& s1 (r.back ()); + + if (!(s1.size () == 2 && s1[0] == '.' && s1[1] == '.')) + { + // Cannot go past the root directory. + // + if (abs && r.size () == 1) + throw invalid_basic_path (this->path_); + + r.pop_back (); + continue; + } + } + } + + r.push_back (s); + } + + // Reassemble the path. + // + string_type p; + + for (typename paths::const_iterator i (r.begin ()), e (r.end ()); + i != e;) + { + p += *i; + + if (++i != e) + p += traits::directory_separator; + } + + if (p.empty () && !r.empty ()) + p += traits::directory_separator; // Root directory. + + this->path_.swap (p); + return *this; + } + + template + void basic_path:: + current (basic_path const& p) + { + const string_type& s (p.string ()); + + if (s.empty ()) + throw invalid_basic_path (s); + + traits::current (s); + } + + template + void basic_path:: + init () + { + // Strip trailing slashes except for the case where the single + // slash represents the root directory. + // + size_type n (this->path_.size ()); + for (; n > 1 && traits::is_separator (this->path_[n - 1]); --n) ; + this->path_.resize (n); + } +} diff --git a/butl/timestamp b/butl/timestamp new file mode 100644 index 0000000..76df07b --- /dev/null +++ b/butl/timestamp @@ -0,0 +1,54 @@ +// file : butl/timestamp -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_TIMESTAMP +#define BUTL_TIMESTAMP + +#include +#include +#include + +#include + +namespace butl +{ + // On all three main platforms that we target (GNU/Linux, Windows (both + // VC++ and GCC/MinGW64), and MacOS X) with recent C++ runtimes, + // system_clock has nanoseconds resolution and counts from the UNIX + // epoch. The latter is important since struct stat also returns times + // based on UNIX epoch. + // + // The underlying type for nanoseconds duration is signed integer type + // of at least 64 bits (currently int64_t). Because it is signed, we + // will overflow in year 2262 but by then the underlying type will + // most likely have changed to something larger than 64-bit. + // + // So to support other platforms that could possibly use a different + // system_clock resolutions (e.g., microseconds), we actually not going + // to assume anywhere (except perhaps timestamp.cxx) that we are dealing + // with nanoseconds or the 64-bit underlying type. + // + using std::chrono::system_clock; + + using timestamp = system_clock::time_point; + using duration = system_clock::duration; + + const timestamp timestamp_unknown {duration {-1}}; + const timestamp timestamp_nonexistent {duration {0}}; + + std::ostream& + operator<< (std::ostream&, const timestamp&); + + std::ostream& + operator<< (std::ostream&, const duration&); + + // Returns timestamp_nonexistent if the entry at the specified path + // does not exist. All other errors are reported by throwing + // std::system_error. + // + timestamp + path_mtime (const path&); +}; + +#endif // BUTL_TIMESTAMP diff --git a/butl/timestamp.cxx b/butl/timestamp.cxx new file mode 100644 index 0000000..2d02416 --- /dev/null +++ b/butl/timestamp.cxx @@ -0,0 +1,177 @@ +// file : butl/timestamp.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // stat +#include // stat +#include // stat + +#include // localtime, gmtime, strftime + +#include +#include + +using namespace std; + +namespace butl +{ + // Figuring out whether we have the nanoseconds in some form. + // + template + constexpr auto nsec (const S* s) -> decltype(s->st_mtim.tv_nsec) + { + return s->st_mtim.tv_nsec; // POSIX (GNU/Linux, Solaris). + } + + template + constexpr auto nsec (const S* s) -> decltype(s->st_mtimespec.tv_nsec) + { + return s->st_mtimespec.tv_nsec; // MacOS X. + } + + template + constexpr auto nsec (const S* s) -> decltype(s->st_mtime_n) + { + return s->st_mtime_n; // AIX 5.2 and later. + } + + template + constexpr int nsec (...) {return 0;} + + timestamp + path_mtime (const path& p) + { + struct stat s; + if (stat (p.string ().c_str (), &s) != 0) + { + if (errno == ENOENT || errno == ENOTDIR) + return timestamp_nonexistent; + else + throw system_error (errno, system_category ()); + } + + return system_clock::from_time_t (s.st_mtime) + + chrono::duration_cast ( + chrono::nanoseconds (nsec (&s))); + } + + ostream& + operator<< (ostream& os, const timestamp& ts) + { + // @@ replace with put_time() + // + + time_t t (system_clock::to_time_t (ts)); + + if (t == 0) + return os << ""; + + std::tm tm; + if (localtime_r (&t, &tm) == nullptr) + throw system_error (errno, system_category ()); + + // If year is greater than 9999, we will overflow. + // + char buf[20]; // YYYY-MM-DD HH:MM:SS\0 + if (strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S", &tm) == 0) + return os << ""; + + os << buf; + + using namespace chrono; + + timestamp sec (system_clock::from_time_t (t)); + nanoseconds ns (duration_cast (ts - sec)); + + if (ns != nanoseconds::zero ()) + { + os << '.'; + os.width (9); + os.fill ('0'); + os << ns.count (); + } + + return os; + } + + ostream& + operator<< (ostream& os, const duration& d) + { + // @@ replace with put_time() + // + + timestamp ts; // Epoch. + ts += d; + + time_t t (system_clock::to_time_t (ts)); + + const char* fmt (nullptr); + const char* unt ("nanoseconds"); + if (t >= 365 * 12 * 24 * 60 * 60) + { + fmt = "%Y-%m-%d %H:%M:%S"; + unt = "years"; + } + else if (t >= 12 * 24 * 60* 60) + { + fmt = "%m-%d %H:%M:%S"; + unt = "months"; + } + else if (t >= 24 * 60* 60) + { + fmt = "%d %H:%M:%S"; + unt = "days"; + } + else if (t >= 60 * 60) + { + fmt = "%H:%M:%S"; + unt = "hours"; + } + else if (t >= 60) + { + fmt = "%M:%S"; + unt = "minutes"; + } + else if (t >= 1) + { + fmt = "%S"; + unt = "seconds"; + } + + if (fmt != nullptr) + { + std::tm tm; + if (gmtime_r (&t, &tm) == nullptr) + throw system_error (errno, system_category ()); + + char buf[20]; // YYYY-MM-DD HH:MM:SS\0 + if (strftime (buf, sizeof (buf), fmt, &tm) == 0) + return os << ""; + + os << buf; + } + + using namespace chrono; + + timestamp sec (system_clock::from_time_t (t)); + nanoseconds ns (duration_cast (ts - sec)); + + if (ns != nanoseconds::zero ()) + { + if (fmt != nullptr) + { + os << '.'; + os.width (9); + os.fill ('0'); + } + + os << ns.count () << ' ' << unt; + } + else if (fmt == 0) + os << '0'; + + return os; + } +} diff --git a/tests/buildfile b/tests/buildfile index 19d85db..ae4bbb3 100644 --- a/tests/buildfile +++ b/tests/buildfile @@ -2,6 +2,6 @@ # copyright : Copyright (c) 2014-2015 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -d = prefix-map/ +d = path/ prefix-map/ .: $d include $d diff --git a/tests/path/buildfile b/tests/path/buildfile new file mode 100644 index 0000000..9e8f3ae --- /dev/null +++ b/tests/path/buildfile @@ -0,0 +1,7 @@ +# file : tests/path/buildfile +# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +exe{driver}: cxx{driver} ../../butl/lib{butl} + +include ../../butl/ diff --git a/tests/path/driver b/tests/path/driver new file mode 100755 index 0000000..0551c37 Binary files /dev/null and b/tests/path/driver differ diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx new file mode 100644 index 0000000..8fbd134 --- /dev/null +++ b/tests/path/driver.cxx @@ -0,0 +1,228 @@ +// file : tests/path/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +int +main () +{ + 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 ("C:\\tmp\\foo\\").string () == "C:\\tmp\\foo"); +#endif + + // abslote/relative/root + // +#ifndef _WIN32 + assert (path ("/").root ()); + assert (path ("//").root ()); + assert (path ("/").absolute ()); + assert (path ("/foo/bar").absolute ()); + assert (path ("bar/baz").relative ()); +#else + assert (path ("C:").root ()); + assert (path ("C:\\").root ()); + assert (path ("C:\\").absolute ()); + assert (path ("C:\\foo\\bar").absolute ()); + assert (path ("bar\\baz").relative ()); +#endif + + + // leaf + // +#ifndef _WIN32 + assert (path ("/").leaf ().string () == ""); + assert (path ("/tmp").leaf ().string () == "tmp"); + assert (path ("//tmp").leaf ().string () == "tmp"); +#else + assert (path ("C:").leaf ().string () == "C:"); + assert (path ("C:\\tmp").leaf ().string () == "tmp"); + assert (path ("C:\\\\tmp").leaf ().string () == "tmp"); +#endif + + // directory + // +#ifndef _WIN32 + assert (path ("/").directory ().string () == ""); + assert (path ("/tmp").directory ().string () == "/"); + assert (path ("//tmp").directory ().string () == "/"); +#else + 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 ("/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"); +#ifdef _WIN32 + assert (path ("C:").base ().string () == "C:"); + assert (path ("C:\\foo.txt").base ().string () == "C:\\foo"); +#endif + + // iteration + // + { + path p; + assert (p.begin () == p.end ()); + } + { + path p ("foo"); + path::iterator i (p.begin ()); + assert (i != p.end () && *i == "foo"); + assert (++i == p.end ()); + } + { + 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 ()); + } + { + path p ("/foo/bar"); + path::iterator i (p.begin ()); + assert (i != p.end () && *i == ""); + assert (++i != p.end () && *i == "foo"); + assert (++i != p.end () && *i == "bar"); + assert (++i == p.end ()); + } + { + path p ("/"); + path::iterator i (p.begin ()); + assert (i != p.end () && *i == ""); + assert (++i == p.end ()); + } + + // 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 + + // 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"); +#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"); +#endif + + // comparison + // + assert (path ("./foo") == path("./foo")); + assert (path ("./boo") < path("./foo")); +#ifdef _WIN32 + assert (path (".\\foo") == path("./FoO")); + assert (path (".\\boo") < path(".\\Foo")); +#endif + + // posix_string + // + assert (path ("foo/bar/../baz").posix_string () == "foo/bar/../baz"); +#ifdef _WIN32 + assert (path ("foo\\bar\\..\\baz").posix_string () == "foo/bar/../baz"); + try + { + path ("c:\\foo\\bar\\..\\baz").posix_string (); + assert (false); + } + catch (const invalid_path&) {} +#endif + + // 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"))); +#ifdef _WIN32 + assert (path ("c:").sub (path ("c:"))); + assert (!path ("c:").sub (path ("d:"))); + assert (path ("c:\\foo").sub (path ("c:"))); +#else + assert (path ("/foo/bar/baz").sub (path ("/"))); +#endif + + // 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/bar/baz").relative (path ("foo/bar/buz")). + posix_string () == "../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"); +#ifdef _WIN32 + assert (path ("c:\\foo\\bar").relative (path ("c:\\fox\\bar")) == + path ("..\\..\\foo\\bar")); + try + { + path ("c:\\foo\\bar").relative (path ("d:\\fox\\bar")); + assert (false); + } + catch (const invalid_path&) {} +#else + assert (path ("/foo/bar/baz").relative (path ("/")) == + path ("foo/bar/baz")); +#endif + + /* + path p ("../foo"); + p.complete (); + + cerr << path::current () << endl; + cerr << p << endl; + p.normalize (); + cerr << p << endl; + */ +} diff --git a/tests/prefix-map/driver b/tests/prefix-map/driver index 215146d..91fc7f4 100755 Binary files a/tests/prefix-map/driver and b/tests/prefix-map/driver differ -- cgit v1.1