diff options
Diffstat (limited to 'include/raul/Path.hpp')
-rw-r--r-- | include/raul/Path.hpp | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/include/raul/Path.hpp b/include/raul/Path.hpp new file mode 100644 index 0000000..6237ddd --- /dev/null +++ b/include/raul/Path.hpp @@ -0,0 +1,207 @@ +/* + This file is part of Raul. + Copyright 2007-2014 David Robillard <http://drobilla.net> + + Raul is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or any later version. + + Raul is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RAUL_PATH_HPP +#define RAUL_PATH_HPP + +#include "raul/Exception.hpp" +#include "raul/Symbol.hpp" + +#include <algorithm> +#include <string> + +namespace Raul { + +/** A restricted path of Symbols separated by, and beginning with, "/". + * + * A Path never ends with a "/", except for the root Path "/", which is the + * only valid single-character Path. + * + * @ingroup raul + */ +class Path : public std::basic_string<char> { +public: + /** Attempt to construct an invalid Path. */ + class BadPath : public Exception { + public: + explicit BadPath(const std::string& path) : Exception(path) {} + }; + + /** Construct an uninitialzed path, because the STL is annoying. */ + Path() : std::basic_string<char>("/") {} + + /** Construct a Path from a C++ string. + * + * This will throw an exception if `path` is invalid. To avoid this, use + * is_valid() first to check. + */ + explicit Path(const std::basic_string<char>& path) + : std::basic_string<char>(path) + { + if (!is_valid(path)) { + throw BadPath(path); + } + } + + /** Construct a Path from a C string. + * + * This will throw an exception if `path` is invalid. To avoid this, use + * is_valid() first to check. + */ + explicit Path(const char* path) + : std::basic_string<char>(path) + { + if (!is_valid(path)) { + throw BadPath(path); + } + } + + /** Copy a Path. + * + * Note this is faster than constructing a Path from another Path's string + * since validation is unnecessary. + */ + Path(const Path& path) = default; + + /** Return true iff `c` is a valid Path character. */ + static inline bool is_valid_char(char c) { + return c == '/' || Symbol::is_valid_char(c); + } + + /** Return true iff `str` is a valid Path. */ + static inline bool is_valid(const std::basic_string<char>& str) { + if (str.empty() || (str[0] != '/')) { + return false; // Must start with '/' + } + + if (str != "/" && *str.rbegin() == '/') { + return false; // Must not end with '/' except for the root + } + + for (size_t i = 1; i < str.length(); ++i) { + if (!is_valid_char(str[i])) { + return false; // All characters must be /, _, a-z, A-Z, 0-9 + } else if (str[i - 1] == '/') { + if (str[i] == '/') { + return false; // Must not contain "//" + } else if (!Symbol::is_valid_start_char(str[i])) { + return false; // Invalid symbol start character (digit) + } + } + } + + return true; + } + + /** Return true iff this path is the root path ("/"). */ + inline bool is_root() const { return *this == "/"; } + + /** Return true iff this path is a child of `parent` at any depth. */ + inline bool is_child_of(const Path& parent) const { + const std::string parent_base = parent.base(); + return substr(0, parent_base.length()) == parent_base; + } + + /** Return true iff this path is a parent of `child` at any depth. */ + inline bool is_parent_of(const Path& child) const { + return child.is_child_of(*this); + } + + /** Return the symbol of this path (everything after the last '/'). + * + * This is what is called the "basename" for file paths, the "method name" + * for OSC paths, and so on. Since the root path does not have a symbol, + * this does not return a Raul::Symbol but may return the empty string. + */ + inline const char* symbol() const { + if (!is_root()) { + const size_t last_sep = rfind('/'); + if (last_sep != std::string::npos) { + return c_str() + last_sep + 1; + } + } + return ""; + } + + /** Return the parent's path. + * + * Calling this on the path "/" will return "/". + * This is the (deepest) "container path" for OSC paths. + */ + inline Path parent() const { + if (is_root()) { + return *this; + } else { + const size_t first_sep = find('/'); + const size_t last_sep = find_last_of('/'); + return (first_sep == last_sep) + ? Path("/") : Path(substr(0, last_sep)); + } + } + + /** Return a child Path of this path. */ + inline Path child(const Path& p) const { + return p.is_root() ? *this : Path(base() + p.substr(1)); + } + + /** Return a direct child Path of this Path with the given Symbol. */ + inline Path child(const Raul::Symbol& symbol) const { + return Path(base() + symbol.c_str()); + } + + /** Return path with a trailing "/". + * + * The returned string is such that appending a valid Symbol to it is + * guaranteed to form a valid path. + */ + inline std::string base() const { + if (is_root()) { + return *this; + } else { + return *this + '/'; + } + } + + /** Return the lowest common ancestor of a and b. */ + static inline Path lca(const Path& a, const Path& b) { + const size_t len = std::min(a.length(), b.length()); + size_t last_sep = 0; + for (size_t i = 0; i < len; ++i) { + if (a[i] == '/' && b[i] == '/') { + last_sep = i; + } + if (a[i] != b[i]) { + break; + } + } + + if (last_sep <= 1) { + return Path("/"); + } + + return Path(a.substr(0, last_sep)); + } + + /** Return true iff `child` is equal to, or a descendant of `parent`. */ + static inline bool descendant_comparator(const Path& parent, + const Path& child) { + return (child == parent || child.is_child_of(parent)); + } +}; + +} // namespace Raul + +#endif // RAUL_PATH_HPP |