2018-04-13 16:57:04 +00:00
|
|
|
/** @addtogroup Utilities
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @file path.hh
|
|
|
|
*
|
|
|
|
* @brief A module for manipulation of filesystem paths.
|
|
|
|
*
|
|
|
|
* The path module implements facilities for manipulation of both pure
|
|
|
|
* paths and actual filesystem paths. POSIX and Windows path encodings
|
|
|
|
* are supported and common filesystem related operations are implemented
|
|
|
|
* in a portable manner.
|
|
|
|
*
|
|
|
|
* This module replaces the C++17 filesystem module, aiming to be simpler
|
|
|
|
* and higher level. For instance, it uses purely 8-bit encoding on all
|
|
|
|
* platforms, taking care of conversions internally.
|
|
|
|
*
|
|
|
|
* @copyright See COPYING.md in the project tree for further information.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef OSTD_PATH_HH
|
|
|
|
#define OSTD_PATH_HH
|
|
|
|
|
2018-04-14 15:46:44 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
2018-04-15 22:19:48 +00:00
|
|
|
#include <stack>
|
|
|
|
#include <list>
|
2018-04-13 16:57:04 +00:00
|
|
|
#include <utility>
|
|
|
|
#include <initializer_list>
|
|
|
|
#include <type_traits>
|
2018-04-13 23:29:01 +00:00
|
|
|
#include <system_error>
|
2018-04-13 16:57:04 +00:00
|
|
|
|
2018-04-13 23:29:01 +00:00
|
|
|
#include <ostd/platform.hh>
|
2018-04-15 16:56:25 +00:00
|
|
|
#include <ostd/range.hh>
|
2018-04-13 16:57:04 +00:00
|
|
|
#include <ostd/string.hh>
|
2018-04-13 23:29:01 +00:00
|
|
|
#include <ostd/format.hh>
|
2018-04-15 16:56:25 +00:00
|
|
|
#include <ostd/algorithm.hh>
|
2018-04-13 23:29:01 +00:00
|
|
|
|
|
|
|
/* path representation is within ostd namespace, there aren't any APIs to
|
|
|
|
* do actual filesystem manipulation, that's all in the fs namespace below
|
|
|
|
*/
|
2018-04-13 16:57:04 +00:00
|
|
|
|
|
|
|
namespace ostd {
|
|
|
|
|
|
|
|
/** @addtogroup Utilities
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
2018-04-15 16:56:25 +00:00
|
|
|
namespace detail {
|
|
|
|
struct path_range;
|
2018-04-15 18:37:37 +00:00
|
|
|
struct path_parent_range;
|
2018-04-15 16:56:25 +00:00
|
|
|
}
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
struct path {
|
2018-04-14 15:46:44 +00:00
|
|
|
#ifdef OSTD_PLATFORM_WIN32
|
|
|
|
static constexpr char native_separator = '\\';
|
|
|
|
#else
|
|
|
|
static constexpr char native_separator = '/';
|
|
|
|
#endif
|
2018-04-13 16:57:04 +00:00
|
|
|
|
|
|
|
enum class format {
|
|
|
|
native = 0,
|
|
|
|
posix,
|
|
|
|
windows
|
|
|
|
};
|
|
|
|
|
2018-04-15 16:56:25 +00:00
|
|
|
using range = detail::path_range;
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
template<typename R>
|
2018-04-15 19:24:14 +00:00
|
|
|
path(R range, format fmt = format::native):
|
|
|
|
p_path("."), p_fmt(path_fmt(fmt))
|
|
|
|
{
|
2018-04-13 16:57:04 +00:00
|
|
|
if constexpr(std::is_constructible_v<std::string, R const &>) {
|
2018-04-14 19:28:01 +00:00
|
|
|
append_str(std::string{range});
|
|
|
|
} else if (!range.empty()) {
|
2018-04-13 16:57:04 +00:00
|
|
|
for (auto const &elem: range) {
|
2018-04-14 19:28:01 +00:00
|
|
|
append_str(std::string{elem});
|
2018-04-13 16:57:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-15 19:24:14 +00:00
|
|
|
path(format fmt = format::native): path(".", path_fmt(fmt)) {}
|
2018-04-13 16:57:04 +00:00
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
path(std::initializer_list<T> init, format fmt = format::native):
|
2018-04-15 19:24:14 +00:00
|
|
|
path(ostd::iter(init), path_fmt(fmt))
|
2018-04-13 16:57:04 +00:00
|
|
|
{}
|
|
|
|
|
2018-04-13 23:29:01 +00:00
|
|
|
path(path const &p):
|
|
|
|
p_path(p.p_path), p_fmt(p.p_fmt)
|
|
|
|
{}
|
|
|
|
|
|
|
|
path(path const &p, format fmt):
|
2018-04-15 19:24:14 +00:00
|
|
|
p_path(p.p_path), p_fmt(path_fmt(fmt))
|
2018-04-14 19:28:01 +00:00
|
|
|
{
|
2018-04-15 19:24:14 +00:00
|
|
|
convert_path(p);
|
2018-04-14 19:28:01 +00:00
|
|
|
}
|
2018-04-13 23:29:01 +00:00
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
path(path &&p) noexcept:
|
2018-04-13 23:42:15 +00:00
|
|
|
p_path(std::move(p.p_path)), p_fmt(p.p_fmt)
|
2018-04-14 19:28:01 +00:00
|
|
|
{
|
|
|
|
p.p_path = ".";
|
|
|
|
}
|
2018-04-13 23:29:01 +00:00
|
|
|
|
2018-04-13 23:42:15 +00:00
|
|
|
path(path &&p, format fmt):
|
2018-04-15 19:24:14 +00:00
|
|
|
p_path(std::move(p.p_path)), p_fmt(path_fmt(fmt))
|
2018-04-14 19:28:01 +00:00
|
|
|
{
|
|
|
|
p.p_path = ".";
|
2018-04-15 19:24:14 +00:00
|
|
|
convert_path(p);
|
2018-04-14 19:28:01 +00:00
|
|
|
}
|
2018-04-13 23:29:01 +00:00
|
|
|
|
|
|
|
path &operator=(path const &p) {
|
|
|
|
p_path = p.p_path;
|
|
|
|
p_fmt = p.p_fmt;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
path &operator=(path &&p) noexcept {
|
2018-04-13 23:29:01 +00:00
|
|
|
swap(p);
|
|
|
|
p.clear();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
char separator() const noexcept {
|
2018-04-14 15:46:44 +00:00
|
|
|
static const char seps[] = { native_separator, '/', '\\' };
|
|
|
|
return seps[std::size_t(p_fmt)];
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range drive() const noexcept {
|
2018-04-14 15:46:44 +00:00
|
|
|
if (is_win()) {
|
2018-04-15 14:34:30 +00:00
|
|
|
string_range path = p_path;
|
|
|
|
if (has_dslash(path)) {
|
2018-04-14 15:46:44 +00:00
|
|
|
char const *endp = strchr(p_path.data() + 2, '\\');
|
|
|
|
if (!endp) {
|
2018-04-15 14:34:30 +00:00
|
|
|
return path;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
2018-04-15 17:10:58 +00:00
|
|
|
char const *pendp = strchr(endp + 1, '\\');
|
2018-04-14 15:46:44 +00:00
|
|
|
if (!pendp) {
|
2018-04-15 14:34:30 +00:00
|
|
|
return path;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
2018-04-15 14:34:30 +00:00
|
|
|
return string_range{path.data(), pendp};
|
|
|
|
} else if (has_letter(path)) {
|
|
|
|
return path.slice(0, 2);
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-15 14:34:30 +00:00
|
|
|
return nullptr;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_drive() const noexcept {
|
2018-04-14 21:21:58 +00:00
|
|
|
if (is_win()) {
|
2018-04-15 14:34:30 +00:00
|
|
|
return (has_letter(p_path) || has_dslash(p_path));
|
2018-04-14 21:21:58 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range root() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
char const *rootp = get_rootp();
|
|
|
|
if (rootp) {
|
|
|
|
return string_range{rootp, rootp + 1};
|
2018-04-14 21:21:58 +00:00
|
|
|
}
|
2018-04-15 14:34:30 +00:00
|
|
|
return nullptr;
|
2018-04-14 21:21:58 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_root() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
return !!get_rootp();
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range anchor() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
string_range dr = drive();
|
|
|
|
if (dr.empty()) {
|
|
|
|
return root();
|
|
|
|
}
|
|
|
|
char const *datap = dr.data();
|
|
|
|
std::size_t datas = dr.size();
|
|
|
|
if (datap[datas] == separator()) {
|
|
|
|
return string_range{datap, datap + datas + 1};
|
|
|
|
}
|
|
|
|
return dr;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_anchor() const noexcept {
|
2018-04-14 21:21:58 +00:00
|
|
|
return has_root() || has_drive();
|
|
|
|
}
|
|
|
|
|
2018-04-14 15:46:44 +00:00
|
|
|
path parent() const {
|
2018-04-15 18:37:37 +00:00
|
|
|
string_range sep;
|
|
|
|
if (is_absolute()) {
|
|
|
|
sep = ostd::find_last(relative_to_str(anchor()), separator());
|
|
|
|
if (sep.empty()) {
|
|
|
|
return path{anchor(), p_fmt};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sep = ostd::find_last(string(), separator());
|
|
|
|
if (sep.empty()) {
|
|
|
|
return *this;
|
|
|
|
}
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
2018-04-15 17:10:58 +00:00
|
|
|
return path{ostd::string_range{p_path.data(), sep.data()}, p_fmt};
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_parent() const noexcept {
|
2018-04-15 18:37:37 +00:00
|
|
|
if (is_absolute()) {
|
|
|
|
return (string() != anchor());
|
|
|
|
}
|
|
|
|
return !ostd::find(string(), separator()).empty();
|
2018-04-14 21:21:58 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 18:37:37 +00:00
|
|
|
detail::path_parent_range parents() const;
|
|
|
|
|
2018-04-14 21:21:58 +00:00
|
|
|
path relative() const {
|
|
|
|
return relative_to(anchor());
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range name() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
string_range rel = relative_to_str(anchor());
|
|
|
|
string_range sep = ostd::find_last(rel, separator());
|
|
|
|
if (sep.empty()) {
|
|
|
|
return rel;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
2018-04-15 14:34:30 +00:00
|
|
|
sep.pop_front();
|
|
|
|
return sep;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_name() const noexcept {
|
2018-04-14 21:21:58 +00:00
|
|
|
return !name().empty();
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range suffix() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
return ostd::find_last(relative_to_str(anchor()), '.');
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range suffixes() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
return ostd::find(name(), '.');
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_suffix() const noexcept {
|
2018-04-14 21:21:58 +00:00
|
|
|
return !suffixes().empty();
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range stem() const noexcept {
|
2018-04-14 15:46:44 +00:00
|
|
|
auto nm = name();
|
2018-04-15 14:34:30 +00:00
|
|
|
return nm.slice(0, nm.size() - ostd::find(nm, '.').size());
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool has_stem() const noexcept {
|
2018-04-14 21:21:58 +00:00
|
|
|
return !stem().empty();
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool is_absolute() const noexcept {
|
2018-04-15 13:30:30 +00:00
|
|
|
if (is_win()) {
|
2018-04-15 14:34:30 +00:00
|
|
|
if (has_dslash(p_path)) {
|
2018-04-15 13:30:30 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return (has_letter(p_path) && (p_path.data()[2] == '\\'));
|
|
|
|
}
|
|
|
|
return (p_path.data()[0] == '/');
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool is_relative() const noexcept {
|
2018-04-15 13:30:30 +00:00
|
|
|
return !is_absolute();
|
|
|
|
}
|
|
|
|
|
2018-04-15 14:34:30 +00:00
|
|
|
path relative_to(path const &other) const {
|
2018-04-15 19:24:14 +00:00
|
|
|
if (other.p_fmt != p_fmt) {
|
2018-04-15 17:10:58 +00:00
|
|
|
return path{relative_to_str(path{other, p_fmt}.p_path), p_fmt};
|
2018-04-15 14:34:30 +00:00
|
|
|
} else {
|
2018-04-15 17:10:58 +00:00
|
|
|
return path{relative_to_str(other.p_path), p_fmt};
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-14 21:21:58 +00:00
|
|
|
path &remove_name() {
|
|
|
|
auto nm = name();
|
|
|
|
if (nm.empty()) {
|
|
|
|
/* TODO: throw */
|
|
|
|
return *this;
|
|
|
|
}
|
2018-04-15 14:34:30 +00:00
|
|
|
p_path.erase(p_path.size() - nm.size() - 1, nm.size() + 1);
|
2018-04-14 21:21:58 +00:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
path without_name() const {
|
|
|
|
path ret{*this};
|
|
|
|
ret.remove_name();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
path &replace_name(string_range name) {
|
|
|
|
remove_name();
|
|
|
|
append_str(std::string{name});
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
path with_name(string_range name) {
|
|
|
|
path ret{*this};
|
|
|
|
ret.replace_name(name);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
path &replace_suffix(string_range sfx) {
|
|
|
|
auto osfx = suffix();
|
|
|
|
if (!osfx.empty()) {
|
2018-04-15 14:34:30 +00:00
|
|
|
p_path.erase(p_path.size() - osfx.size(), osfx.size());
|
2018-04-14 21:21:58 +00:00
|
|
|
}
|
|
|
|
p_path.append(sfx);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
path &replace_suffixes(string_range sfx) {
|
|
|
|
auto sfxs = suffixes();
|
|
|
|
if (!sfxs.empty()) {
|
2018-04-15 14:34:30 +00:00
|
|
|
p_path.erase(p_path.size() - sfxs.size(), sfxs.size());
|
2018-04-14 21:21:58 +00:00
|
|
|
}
|
|
|
|
p_path.append(sfx);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
path with_suffix(string_range sfx) {
|
|
|
|
path ret{*this};
|
|
|
|
ret.replace_suffix(sfx);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
path with_suffixes(string_range sfx) {
|
|
|
|
path ret{*this};
|
|
|
|
ret.replace_suffixes(sfx);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
path join(path const &p) const {
|
|
|
|
path ret{*this};
|
|
|
|
ret.append(p);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
path &append(path const &p) {
|
2018-04-15 19:24:14 +00:00
|
|
|
append_str(p.p_path, p.p_fmt == p_fmt);
|
2018-04-13 16:57:04 +00:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2018-04-14 21:21:58 +00:00
|
|
|
path &append_concat(path const &p) {
|
2018-04-14 23:11:14 +00:00
|
|
|
append_concat_str(p.p_path);
|
2018-04-14 21:21:58 +00:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
path concat(path const &p) const {
|
|
|
|
path ret{*this};
|
|
|
|
ret.append_concat(p);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
path &operator/=(path const &p) {
|
|
|
|
return append(p);
|
|
|
|
}
|
|
|
|
|
2018-04-14 21:21:58 +00:00
|
|
|
path &operator+=(path const &p) {
|
|
|
|
return append_concat(p);
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range string() const noexcept {
|
2018-04-13 16:57:04 +00:00
|
|
|
return p_path;
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
format path_format() const noexcept {
|
2018-04-13 16:57:04 +00:00
|
|
|
return p_fmt;
|
|
|
|
}
|
|
|
|
|
2018-04-13 23:29:01 +00:00
|
|
|
void clear() {
|
2018-04-14 19:28:01 +00:00
|
|
|
p_path = ".";
|
2018-04-13 23:29:01 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 21:23:19 +00:00
|
|
|
bool empty() const {
|
|
|
|
return (p_path == ".");
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
void swap(path &other) noexcept {
|
2018-04-13 23:29:01 +00:00
|
|
|
p_path.swap(other.p_path);
|
|
|
|
std::swap(p_fmt, other.p_fmt);
|
|
|
|
}
|
|
|
|
|
2018-04-15 16:56:25 +00:00
|
|
|
range iter() const noexcept;
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
private:
|
2018-04-15 15:40:20 +00:00
|
|
|
static format path_fmt(format f) noexcept {
|
2018-04-14 15:46:44 +00:00
|
|
|
static const format fmts[] = {
|
|
|
|
#ifdef OSTD_PLATFORM_WIN32
|
|
|
|
format::windows,
|
|
|
|
#else
|
|
|
|
format::posix,
|
|
|
|
#endif
|
|
|
|
format::posix, format::windows
|
|
|
|
};
|
2018-04-14 19:28:01 +00:00
|
|
|
return fmts[std::size_t(f)];
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
static bool is_sep(char c) noexcept {
|
2018-04-14 19:28:01 +00:00
|
|
|
return ((c == '/') || (c == '\\'));
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
bool is_win() const noexcept {
|
2018-04-15 19:24:14 +00:00
|
|
|
return p_fmt == format::windows;
|
2018-04-14 15:46:44 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
static bool has_letter(string_range s) noexcept {
|
2018-04-14 19:28:01 +00:00
|
|
|
if (s.size() < 2) {
|
2018-04-14 16:03:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-04-14 19:28:01 +00:00
|
|
|
char ltr = s[0] | 32;
|
|
|
|
return (s[1] == ':') && (ltr >= 'a') && (ltr <= 'z');
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
static bool has_dslash(string_range s) noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
if (s.size() < 2) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return (s.slice(0, 2) == "\\\\");
|
|
|
|
}
|
|
|
|
|
2018-04-14 23:11:14 +00:00
|
|
|
void cleanup_str(std::string &s, char sep, bool allow_twoslash) {
|
|
|
|
std::size_t start = 0;
|
2018-04-14 19:28:01 +00:00
|
|
|
/* replace multiple separator sequences and . parts */
|
2018-04-14 21:21:58 +00:00
|
|
|
char const *p = &s[start];
|
2018-04-14 23:11:14 +00:00
|
|
|
if (allow_twoslash && is_sep(p[0]) && is_sep(p[1])) {
|
2018-04-14 19:28:01 +00:00
|
|
|
/* it's okay for windows paths to start with double backslash,
|
|
|
|
* but if it's triple or anything like that replace anyway
|
|
|
|
*/
|
2018-04-14 21:21:58 +00:00
|
|
|
start += 1;
|
2018-04-14 19:28:01 +00:00
|
|
|
++p;
|
|
|
|
}
|
|
|
|
/* special case: path starts with ./ or is simply ., erase */
|
|
|
|
if ((*p == '.') && (is_sep(p[1]) || (p[1] == '\0'))) {
|
|
|
|
s.erase(start, 2 - int(p[1] == '\0'));
|
|
|
|
}
|
|
|
|
/* replace // and /./ sequences as well as separators */
|
2018-04-15 14:34:30 +00:00
|
|
|
for (; start < s.size(); ++start) {
|
2018-04-14 19:28:01 +00:00
|
|
|
p = &s[start];
|
|
|
|
if (is_sep(*p)) {
|
|
|
|
std::size_t cnt = 0;
|
|
|
|
for (;;) {
|
2018-04-14 19:38:23 +00:00
|
|
|
if (is_sep(p[cnt + 1])) {
|
2018-04-14 19:28:01 +00:00
|
|
|
++cnt;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
(p[cnt + 1] == '.') &&
|
|
|
|
(is_sep(p[cnt + 2]) || (p[cnt + 2] == '\0'))
|
|
|
|
) {
|
|
|
|
cnt += 2;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s.replace(start, cnt + 1, 1, sep);
|
|
|
|
}
|
|
|
|
}
|
2018-04-14 23:11:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void strip_trailing(char sep) {
|
2018-04-15 14:34:30 +00:00
|
|
|
std::size_t plen = p_path.size();
|
2018-04-14 23:11:14 +00:00
|
|
|
if (sep == '\\') {
|
|
|
|
char const *p = p_path.data();
|
|
|
|
if ((plen <= 2) && (p[0] == '\\') && (p[1] == '\\')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((plen <= 3) && has_letter(p_path)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (plen <= 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p_path.back() == sep) {
|
|
|
|
p_path.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-15 13:17:49 +00:00
|
|
|
void append_str(std::string s, bool norm = false) {
|
2018-04-14 23:11:14 +00:00
|
|
|
char sep = separator();
|
|
|
|
bool win = is_win();
|
|
|
|
/* replace multiple separator sequences and . parts */
|
2018-04-15 13:17:49 +00:00
|
|
|
if (!norm) {
|
|
|
|
cleanup_str(s, sep, win);
|
|
|
|
}
|
2018-04-14 19:28:01 +00:00
|
|
|
/* if the path has a root, replace the previous path, otherwise
|
|
|
|
* append a separator followed by the path and be done with it
|
|
|
|
*
|
|
|
|
* if this is windows and we have a drive, it's like having a root
|
|
|
|
*/
|
|
|
|
if ((s.data()[0] == sep) || (win && has_letter(s))) {
|
|
|
|
p_path = std::move(s);
|
|
|
|
} else if (!s.empty()) {
|
|
|
|
/* empty paths are ., don't forget to clear that */
|
|
|
|
if (p_path == ".") {
|
2018-04-14 23:11:14 +00:00
|
|
|
/* empty path: replace */
|
|
|
|
p_path = std::move(s);
|
|
|
|
} else {
|
|
|
|
if (p_path.back() != sep) {
|
|
|
|
p_path.push_back(sep);
|
|
|
|
}
|
|
|
|
p_path.append(s);
|
2018-04-14 19:28:01 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-14 23:11:14 +00:00
|
|
|
strip_trailing(sep);
|
|
|
|
}
|
|
|
|
|
|
|
|
void append_concat_str(std::string s) {
|
|
|
|
char sep = separator();
|
|
|
|
/* replace multiple separator sequences and . parts */
|
|
|
|
cleanup_str(s, sep, false);
|
|
|
|
if (p_path == ".") {
|
|
|
|
/* empty path: replace */
|
|
|
|
p_path = std::move(s);
|
|
|
|
} else {
|
|
|
|
if ((p_path.back() == sep) && (s.front() == sep)) {
|
|
|
|
p_path.pop_back();
|
|
|
|
}
|
|
|
|
p_path.append(s);
|
2018-04-14 19:34:24 +00:00
|
|
|
}
|
2018-04-14 23:11:14 +00:00
|
|
|
strip_trailing(sep);
|
2018-04-14 19:28:01 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 19:24:14 +00:00
|
|
|
void convert_path(path const &p) {
|
|
|
|
if (p.p_fmt == p_fmt) {
|
|
|
|
return;
|
|
|
|
}
|
2018-04-14 19:28:01 +00:00
|
|
|
char froms = '\\', tos = '/';
|
|
|
|
if (separator() == '\\') {
|
|
|
|
froms = '/';
|
|
|
|
tos = '\\';
|
|
|
|
} else if (p_path.substr(0, 2) == "\\\\") {
|
|
|
|
p_path.replace(0, 2, 1, '/');
|
|
|
|
}
|
|
|
|
for (auto &c: p_path) {
|
|
|
|
if (c == froms) {
|
|
|
|
c = tos;
|
|
|
|
}
|
|
|
|
}
|
2018-04-14 16:03:43 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
string_range relative_to_str(string_range other) const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
if (other == ".") {
|
|
|
|
return p_path;
|
|
|
|
}
|
|
|
|
std::size_t oplen = other.size();
|
|
|
|
if (string_range{p_path}.slice(0, oplen) == other) {
|
|
|
|
if ((p_path.size() > oplen) && (p_path[oplen] == separator())) {
|
|
|
|
++oplen;
|
|
|
|
}
|
|
|
|
auto sl = string_range{p_path};
|
|
|
|
return sl.slice(oplen, sl.size());
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-04-15 15:40:20 +00:00
|
|
|
char const *get_rootp() const noexcept {
|
2018-04-15 14:34:30 +00:00
|
|
|
char const *datap = p_path.data();
|
|
|
|
if (is_win()) {
|
|
|
|
if (*datap == '\\') {
|
|
|
|
return datap;
|
|
|
|
}
|
|
|
|
if (has_letter(p_path) && (datap[2] == '\\')) {
|
|
|
|
return datap + 2;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if (*p_path.data() == '/') {
|
|
|
|
return datap;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
std::string p_path;
|
|
|
|
format p_fmt;
|
|
|
|
};
|
|
|
|
|
|
|
|
inline path operator/(path const &p1, path const &p2) {
|
|
|
|
return p1.join(p2);
|
|
|
|
}
|
|
|
|
|
2018-04-14 21:21:58 +00:00
|
|
|
inline path operator+(path const &p1, path const &p2) {
|
|
|
|
return p1.concat(p2);
|
|
|
|
}
|
|
|
|
|
2018-04-15 16:56:25 +00:00
|
|
|
namespace detail {
|
|
|
|
struct path_range: input_range<path_range> {
|
|
|
|
using range_category = forward_range_tag;
|
|
|
|
using value_type = string_range;
|
|
|
|
using reference = string_range;
|
|
|
|
using size_type = std::size_t;
|
|
|
|
|
|
|
|
path_range() = delete;
|
2018-04-15 16:57:23 +00:00
|
|
|
path_range(path const &p) noexcept: p_rest(p.string()) {
|
2018-04-15 16:56:25 +00:00
|
|
|
string_range drive = p.drive();
|
|
|
|
if (!drive.empty()) {
|
|
|
|
p_current = p.anchor();
|
|
|
|
/* windows drive without root, cut rest a character earlier so
|
|
|
|
* that the next segment can be retrieved consistently
|
|
|
|
*/
|
|
|
|
if (p_current.size() == drive.size()) {
|
|
|
|
p_rest = p_rest.slice(drive.size() - 1, p_rest.size());
|
|
|
|
} else {
|
|
|
|
p_rest = p_rest.slice(drive.size(), p_rest.size());
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
string_range root = p.root();
|
|
|
|
if (!root.empty()) {
|
|
|
|
p_current = root;
|
|
|
|
/* leave p_rest alone so that it begins with a separator */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto sep = ostd::find(p_rest, p.separator());
|
|
|
|
if (!sep.empty()) {
|
|
|
|
p_current = string_range{p_rest.data(), sep.data()};
|
|
|
|
} else {
|
|
|
|
p_current = p_rest;
|
|
|
|
}
|
|
|
|
p_rest = p_rest.slice(p_current.size(), p_rest.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool empty() const noexcept { return p_current.empty(); }
|
|
|
|
|
|
|
|
void pop_front() noexcept {
|
|
|
|
string_range ncur = p_rest;
|
|
|
|
if (!ncur.empty()) {
|
|
|
|
char sep = ncur.front();
|
|
|
|
if (sep != '/') {
|
|
|
|
sep = '\\';
|
|
|
|
}
|
|
|
|
ncur.pop_front();
|
|
|
|
string_range nsep = ostd::find(ncur, sep);
|
|
|
|
p_current = ncur.slice(0, ncur.size() - nsep.size());
|
|
|
|
p_rest = nsep;
|
|
|
|
} else {
|
|
|
|
p_current = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string_range front() const noexcept {
|
|
|
|
return p_current;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
string_range p_current, p_rest;
|
|
|
|
};
|
2018-04-15 18:37:37 +00:00
|
|
|
|
|
|
|
struct path_parent_range: input_range<path_parent_range> {
|
|
|
|
using range_category = forward_range_tag;
|
|
|
|
using value_type = path;
|
|
|
|
using reference = path;
|
|
|
|
using size_type = std::size_t;
|
|
|
|
|
|
|
|
path_parent_range() = delete;
|
|
|
|
path_parent_range(path const &p): p_path(p) {}
|
|
|
|
|
|
|
|
bool empty() const noexcept { return !p_path.has_parent(); }
|
|
|
|
|
|
|
|
void pop_front() {
|
|
|
|
p_path = p_path.parent();
|
|
|
|
}
|
|
|
|
|
|
|
|
path front() const {
|
|
|
|
return p_path.parent();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
path p_path;
|
|
|
|
};
|
2018-04-15 16:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inline typename path::range path::iter() const noexcept {
|
|
|
|
return typename path::range{*this};
|
|
|
|
}
|
|
|
|
|
2018-04-15 18:37:37 +00:00
|
|
|
inline detail::path_parent_range path::parents() const {
|
|
|
|
return detail::path_parent_range{*this};
|
|
|
|
}
|
|
|
|
|
2018-04-13 23:29:01 +00:00
|
|
|
template<>
|
|
|
|
struct format_traits<path> {
|
|
|
|
template<typename R>
|
|
|
|
static void to_format(path const &p, R &writer, format_spec const &fs) {
|
|
|
|
fs.format_value(writer, p.string());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
/** @} */
|
|
|
|
|
|
|
|
} /* namespace ostd */
|
|
|
|
|
2018-04-13 23:29:01 +00:00
|
|
|
/* filesystem manipulation that relies on path representation above */
|
|
|
|
|
|
|
|
namespace ostd {
|
|
|
|
namespace fs {
|
|
|
|
|
|
|
|
/** @addtogroup Utilities
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
2018-04-15 21:23:19 +00:00
|
|
|
struct directory_entry {
|
|
|
|
directory_entry() {}
|
|
|
|
directory_entry(ostd::path const &p): p_path(p) {}
|
|
|
|
|
|
|
|
ostd::path const &path() const noexcept {
|
|
|
|
return p_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
operator ostd::path const &() const noexcept {
|
|
|
|
return p_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear() {
|
|
|
|
p_path.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void assign(ostd::path const &p) {
|
|
|
|
p_path = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
void assign(ostd::path &&p) {
|
|
|
|
p_path = std::move(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
ostd::path p_path{};
|
|
|
|
};
|
|
|
|
|
2018-04-15 22:19:48 +00:00
|
|
|
namespace detail {
|
|
|
|
}
|
|
|
|
|
2018-04-15 21:23:19 +00:00
|
|
|
struct directory_range: input_range<directory_range> {
|
|
|
|
using range_category = input_range_tag;
|
|
|
|
using value_type = directory_entry;
|
|
|
|
using reference = directory_entry const &;
|
|
|
|
using size_type = std::size_t;
|
|
|
|
|
|
|
|
directory_range() = delete;
|
|
|
|
directory_range(path const &p) {
|
|
|
|
open(p);
|
|
|
|
}
|
|
|
|
|
2018-04-15 22:19:48 +00:00
|
|
|
directory_range(directory_range const &r):
|
2018-04-15 21:23:19 +00:00
|
|
|
p_current(r.p_current), p_dir(r.p_dir),
|
|
|
|
p_handle(r.p_handle), p_owned(false)
|
|
|
|
{}
|
|
|
|
|
|
|
|
~directory_range() {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
|
|
|
directory_range &operator=(directory_range const &r) noexcept {
|
|
|
|
close();
|
|
|
|
p_handle = r.p_handle;
|
|
|
|
p_owned = false;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool empty() const noexcept {
|
|
|
|
return p_current.path().empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void pop_front() {
|
|
|
|
read_next();
|
|
|
|
}
|
|
|
|
|
|
|
|
reference front() const noexcept {
|
|
|
|
return p_current;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2018-04-15 22:19:48 +00:00
|
|
|
OSTD_EXPORT void open(path const &p);
|
|
|
|
OSTD_EXPORT void close() noexcept;
|
|
|
|
OSTD_EXPORT void read_next();
|
2018-04-15 21:23:19 +00:00
|
|
|
|
|
|
|
directory_entry p_current{};
|
|
|
|
path p_dir{};
|
|
|
|
void *p_handle = nullptr;
|
|
|
|
bool p_owned = false;
|
|
|
|
};
|
|
|
|
|
2018-04-15 22:19:48 +00:00
|
|
|
struct recursive_directory_range: input_range<recursive_directory_range> {
|
|
|
|
using range_category = input_range_tag;
|
|
|
|
using value_type = directory_entry;
|
|
|
|
using reference = directory_entry const &;
|
|
|
|
using size_type = std::size_t;
|
|
|
|
|
|
|
|
recursive_directory_range() = delete;
|
|
|
|
recursive_directory_range(path const &p) {
|
|
|
|
open(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
recursive_directory_range(recursive_directory_range const &r):
|
|
|
|
p_current(r.p_current), p_dir(r.p_dir),
|
|
|
|
p_stack(r.p_stack), p_owned(false)
|
|
|
|
{}
|
|
|
|
|
|
|
|
~recursive_directory_range() {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
|
|
|
recursive_directory_range &operator=(
|
|
|
|
recursive_directory_range const &r
|
|
|
|
) noexcept {
|
|
|
|
close();
|
|
|
|
p_stack = r.p_stack;
|
|
|
|
p_owned = false;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool empty() const noexcept {
|
|
|
|
return p_current.path().empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void pop_front() {
|
|
|
|
read_next();
|
|
|
|
}
|
|
|
|
|
|
|
|
reference front() const noexcept {
|
|
|
|
return p_current;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
using hstack = std::stack<void *, std::list<void *>>;
|
|
|
|
|
|
|
|
OSTD_EXPORT void open(path const &p);
|
|
|
|
OSTD_EXPORT void close() noexcept;
|
|
|
|
OSTD_EXPORT void read_next();
|
|
|
|
|
|
|
|
directory_entry p_current{};
|
|
|
|
path p_dir{};
|
|
|
|
hstack p_handles{};
|
|
|
|
hstack *p_stack = nullptr;
|
|
|
|
bool p_owned = false;
|
|
|
|
};
|
|
|
|
|
2018-04-13 23:29:01 +00:00
|
|
|
OSTD_EXPORT path cwd();
|
|
|
|
OSTD_EXPORT path home();
|
|
|
|
|
|
|
|
/** @} */
|
|
|
|
|
|
|
|
} /* namesapce fs */
|
|
|
|
} /* namesapce ostd */
|
|
|
|
|
2018-04-13 16:57:04 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/** @} */
|