diff options
Diffstat (limited to 'butl/path')
-rw-r--r-- | butl/path | 542 |
1 files changed, 542 insertions, 0 deletions
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 |