/* This file is part of Raul. Copyright 2007-2014 David Robillard 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 . */ #ifndef RAUL_PATH_HPP #define RAUL_PATH_HPP #include "raul/Exception.hpp" #include "raul/Symbol.hpp" #include #include 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 { 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("/") {} /** 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& path) : std::basic_string(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(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& 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 const 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