aboutsummaryrefslogtreecommitdiff
path: root/butl
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-06-18 14:41:44 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-06-18 14:41:44 +0200
commit08903414e3546bc2c76bef73b2337ccf79886530 (patch)
tree17d76af2771ad209b781b13156da5160ba0f07ae /butl
parent5a79b446475c9643346024a83bb14c2ba9c55dbd (diff)
Move path and filesystem from build2 to libbutl
Diffstat (limited to 'butl')
-rw-r--r--butl/buildfile2
-rw-r--r--butl/filesystem61
-rw-r--r--butl/filesystem.cxx101
-rw-r--r--butl/path542
-rw-r--r--butl/path-io29
-rw-r--r--butl/path-map81
-rw-r--r--butl/path.cxx109
-rw-r--r--butl/path.ixx171
-rw-r--r--butl/path.txx256
-rw-r--r--butl/timestamp54
-rw-r--r--butl/timestamp.cxx177
11 files changed, 1582 insertions, 1 deletions
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 <sys/types.h> // mode_t
+
+#include <butl/path>
+
+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 <butl/filesystem>
+
+#include <unistd.h> // rmdir(), unlink()
+#include <sys/types.h> // stat
+#include <sys/stat.h> // stat, lstat(), S_IS*, mkdir()
+
+#include <system_error>
+
+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 <string>
+#include <cstddef> // ptrdiff_t
+#include <utility> // move()
+#include <iterator>
+#include <exception>
+#include <functional> // hash
+
+namespace butl
+{
+ template <typename C>
+ struct path_traits
+ {
+ typedef std::basic_string<C> 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 <typename C>
+ class invalid_basic_path;
+
+ template <typename C, typename K>
+ class basic_path;
+
+ template <typename C>
+ class path_data;
+
+ template <typename C>
+ struct dir_path_kind;
+
+ template <typename C>
+ struct any_path_kind
+ {
+ typedef path_data<C> base_type;
+ typedef basic_path<C, dir_path_kind<C>> dir_type;
+ };
+
+ template <typename C>
+ struct dir_path_kind
+ {
+ typedef basic_path<C, any_path_kind<C>> base_type;
+ typedef basic_path<C, dir_path_kind<C>> dir_type;
+ };
+
+ typedef basic_path<char, any_path_kind<char>> path;
+ typedef basic_path<char, dir_path_kind<char>> dir_path;
+ typedef invalid_basic_path<char> invalid_path;
+
+ typedef basic_path<wchar_t, any_path_kind<wchar_t>> wpath;
+ typedef basic_path<wchar_t, dir_path_kind<wchar_t>> dir_wpath;
+ typedef invalid_basic_path<wchar_t> invalid_wpath;
+
+ //
+ //
+ class invalid_path_base: std::exception
+ {
+ public:
+ virtual char const*
+ what () const throw ();
+ };
+
+ template <typename C>
+ class invalid_basic_path: public invalid_path_base
+ {
+ public:
+ typedef std::basic_string<C> 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 <typename C>
+ class path_data
+ {
+ public:
+ typedef std::basic_string<C> string_type;
+
+ path_data () = default;
+
+ explicit
+ path_data (string_type s): path_ (std::move (s)) {}
+
+ protected:
+ string_type path_;
+ };
+
+ template <typename C, typename K>
+ class basic_path: public K::base_type
+ {
+ public:
+ typedef std::basic_string<C> 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<C> 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 <typename C, typename K>
+ inline basic_path<C, K>
+ operator/ (basic_path<C, K> const& x, basic_path<C, K> const& y)
+ {
+ basic_path<C, K> r (x);
+ r /= y;
+ return r;
+ }
+
+ // Additional operators for certain path kind combinations.
+ //
+ template <typename C>
+ inline basic_path<C, any_path_kind<C>>
+ operator/ (basic_path<C, dir_path_kind<C>> const& x,
+ basic_path<C, any_path_kind<C>> const& y)
+ {
+ basic_path<C, any_path_kind<C>> r (x);
+ r /= y;
+ return r;
+ }
+
+ // For operator<< (ostream) see the path-io header.
+}
+
+namespace std
+{
+ template <typename C, typename K>
+ struct hash<butl::basic_path<C, K>>: hash<basic_string<C>>
+ {
+ size_t
+ operator() (const butl::basic_path<C, K>& p) const noexcept
+ {
+ return hash<basic_string<C>>::operator() (p.string ());
+ }
+ };
+}
+
+#include <butl/path.ixx>
+#include <butl/path.txx>
+
+#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 <ostream>
+
+#include <butl/path>
+
+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 <typename C, typename K>
+ inline std::basic_ostream<C>&
+ operator<< (std::basic_ostream<C>& os, basic_path<C, K> 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 <butl/path>
+#include <butl/prefix-map>
+
+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 <typename C, typename K>
+ struct compare_prefix<basic_path<C, K>>: compare_prefix<std::basic_string<C>>
+ {
+ typedef basic_path<C, K> key_type;
+
+ typedef C delimiter_type;
+ typedef std::basic_string<C> string_type;
+ typedef compare_prefix<std::basic_string<C>> 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 <typename T>
+ using path_map = prefix_map<path, T, path::traits::directory_separator>;
+
+ template <typename T>
+ using dir_path_map =
+ prefix_map<dir_path, T, dir_path::traits::directory_separator>;
+}
+
+#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 <butl/path>
+
+#ifdef _WIN32
+# include <stdlib.h> // _MAX_PATH
+# include <direct.h> // _[w]getcwd, _[w]chdir
+#else
+# include <errno.h> // EINVAL
+# include <stdlib.h> // mbstowcs, wcstombs
+# include <limits.h> // PATH_MAX
+# include <unistd.h> // getcwd, chdir
+#endif
+
+#include <system_error>
+
+using namespace std;
+
+namespace butl
+{
+ char const* invalid_path_base::
+ what () const throw ()
+ {
+ return "invalid filesystem path";
+ }
+
+ //
+ // char
+ //
+
+ template <>
+ path_traits<char>::string_type path_traits<char>::
+ 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<char>::
+ 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<wchar_t>::string_type path_traits<wchar_t>::
+ 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<wchar_t>::
+ 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 <cctype> // std::tolower
+# include <cwctype> // std::towlower
+#endif
+
+namespace butl
+{
+#ifdef _WIN32
+ template <>
+ inline char path_traits<char>::
+ tolower (char c)
+ {
+ return std::tolower (c);
+ }
+
+ template <>
+ inline wchar_t path_traits<wchar_t>::
+ tolower (wchar_t c)
+ {
+ return std::towlower (c);
+ }
+#endif
+
+ template <typename C, typename K>
+ inline bool basic_path<C, K>::
+ 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 <typename C, typename K>
+ inline bool basic_path<C, K>::
+ 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 <typename C, typename K>
+ inline bool basic_path<C, K>::
+ 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 <typename C, typename K>
+ inline bool basic_path<C, K>::
+ 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 <typename C, typename K>
+ inline auto basic_path<C, K>::
+ 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 <typename C, typename K>
+ inline auto basic_path<C, K>::
+ end () const -> iterator
+ {
+ return iterator (this->path_, string_type::npos, string_type::npos);
+ }
+
+ template <typename C, typename K>
+ inline basic_path<C, K>& basic_path<C, K>::
+ complete ()
+ {
+ if (relative ())
+ *this = current () / *this;
+
+ return *this;
+ }
+
+ template <typename C, typename K>
+ inline typename basic_path<C, K>::dir_type basic_path<C, K>::
+ root_directory () const
+ {
+ return absolute ()
+#ifdef _WIN32
+ ? dir_type (this->path_, 2)
+#else
+ ? dir_type ("/")
+#endif
+ : dir_type ();
+ }
+
+ template <typename C, typename K>
+ inline basic_path<C, K> basic_path<C, K>::
+ base () const
+ {
+ size_type p (traits::find_extension (this->path_));
+ return p != string_type::npos
+ ? basic_path (this->path_.c_str (), p)
+ : *this;
+ }
+
+ template <typename C, typename K>
+ inline const C* basic_path<C, K>::
+ 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 <typename C, typename K>
+ inline typename basic_path<C, K>::string_type basic_path<C, K>::
+ 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 <vector>
+
+namespace butl
+{
+ template <typename C, typename K>
+ basic_path<C, K> basic_path<C, K>::
+ 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 C, typename K>
+ typename basic_path<C, K>::dir_type basic_path<C, K>::
+ 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 C, typename K>
+ typename basic_path<C, K>::string_type basic_path<C, K>::
+ posix_string () const
+ {
+ if (absolute ())
+ throw invalid_basic_path<C> (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 <typename C, typename K>
+ basic_path<C, K>& basic_path<C, K>::
+ operator/= (basic_path<C, K> const& r)
+ {
+ if (r.absolute () && !this->path_.empty ()) // Allow ('' / '/foo').
+ throw invalid_basic_path<C> (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 <typename C, typename K>
+ basic_path<C, K> basic_path<C, K>::
+ leaf (basic_path<C, K> const& d) const
+ {
+ size_type n (d.path_.size ());
+
+ if (n == 0)
+ return *this;
+
+ if (!sub (d))
+ throw invalid_basic_path<C> (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 C, typename K>
+ typename basic_path<C, K>::dir_type basic_path<C, K>::
+ directory (basic_path<C, K> const& l) const
+ {
+ size_type n (l.path_.size ());
+
+ if (n == 0)
+ return dir_type (this->path_);
+
+ if (!sup (l))
+ throw invalid_basic_path<C> (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 <typename C, typename K>
+ basic_path<C, K> basic_path<C, K>::
+ relative (basic_path<C, K> 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<C> (this->path_);
+ }
+
+ return r / leaf (d);
+ }
+
+ template <typename C, typename K>
+ basic_path<C, K>& basic_path<C, K>::
+ normalize ()
+ {
+ if (empty ())
+ return *this;
+
+ bool abs (absolute ());
+
+ typedef std::vector<string_type> 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<C> (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 <typename C, typename K>
+ void basic_path<C, K>::
+ current (basic_path const& p)
+ {
+ const string_type& s (p.string ());
+
+ if (s.empty ())
+ throw invalid_basic_path<char> (s);
+
+ traits::current (s);
+ }
+
+ template <typename C, typename K>
+ void basic_path<C, K>::
+ 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 <chrono>
+#include <string>
+#include <iosfwd>
+
+#include <butl/path>
+
+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 <butl/timestamp>
+
+#include <unistd.h> // stat
+#include <sys/types.h> // stat
+#include <sys/stat.h> // stat
+
+#include <time.h> // localtime, gmtime, strftime
+
+#include <ostream>
+#include <system_error>
+
+using namespace std;
+
+namespace butl
+{
+ // Figuring out whether we have the nanoseconds in some form.
+ //
+ template <typename S>
+ constexpr auto nsec (const S* s) -> decltype(s->st_mtim.tv_nsec)
+ {
+ return s->st_mtim.tv_nsec; // POSIX (GNU/Linux, Solaris).
+ }
+
+ template <typename S>
+ constexpr auto nsec (const S* s) -> decltype(s->st_mtimespec.tv_nsec)
+ {
+ return s->st_mtimespec.tv_nsec; // MacOS X.
+ }
+
+ template <typename S>
+ constexpr auto nsec (const S* s) -> decltype(s->st_mtime_n)
+ {
+ return s->st_mtime_n; // AIX 5.2 and later.
+ }
+
+ template <typename S>
+ 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<duration> (
+ chrono::nanoseconds (nsec<struct stat> (&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 << "<nonexistent>";
+
+ 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 << "<beyond year 9999>";
+
+ os << buf;
+
+ using namespace chrono;
+
+ timestamp sec (system_clock::from_time_t (t));
+ nanoseconds ns (duration_cast<nanoseconds> (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 << "<beyond 9999>";
+
+ os << buf;
+ }
+
+ using namespace chrono;
+
+ timestamp sec (system_clock::from_time_t (t));
+ nanoseconds ns (duration_cast<nanoseconds> (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;
+ }
+}