mirror of
https://github.com/ksherlock/mpw-shell.git
synced 2025-01-06 21:32:07 +00:00
286 lines
5.2 KiB
C++
286 lines
5.2 KiB
C++
|
|
#include "filesystem.h"
|
|
|
|
|
|
namespace filesystem {
|
|
|
|
namespace {
|
|
const path::value_type separator = '/';
|
|
|
|
// hmmm... these could be pre-studied since they're constant?
|
|
const path path_dot = ".";
|
|
const path path_dotdot = "..";
|
|
const path path_sep = "/";
|
|
}
|
|
|
|
void path::study() const {
|
|
|
|
if (_info.valid) return;
|
|
int length = _path.length();
|
|
if (length == 0)
|
|
{
|
|
_info.valid = true;
|
|
_info.special = 0;
|
|
_info.stem = _info.extension = 0;
|
|
return;
|
|
}
|
|
|
|
auto back = _path[length - 1];
|
|
|
|
// check for special cases (part 1)
|
|
// if the path is all /s, filename is /
|
|
// if the path ends with a / (but contains a non-/ char),
|
|
// the filename is .
|
|
|
|
if (back == separator) {
|
|
value_type special = '.';
|
|
if (_path.find_first_not_of(separator) == _path.npos)
|
|
special = separator;
|
|
|
|
_info.valid = true;
|
|
_info.extension = length;
|
|
_info.stem = length;
|
|
_info.special = special;
|
|
return;
|
|
}
|
|
|
|
int stem = 0;
|
|
int extension = length;
|
|
for (int i = length; i; ) {
|
|
auto c = _path[--i];
|
|
if (c == '.' && extension == length)
|
|
extension = i;
|
|
if (c == '/') {
|
|
stem = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// check for special cases (part 2)
|
|
// ".." and "." are not extensions.
|
|
if (back == '.') {
|
|
int xlength = length - stem;
|
|
if (xlength == 1)
|
|
extension = length;
|
|
if (xlength == 2 && _path[stem] == '.')
|
|
extension = length;
|
|
}
|
|
|
|
_info.valid = true;
|
|
_info.stem = stem;
|
|
_info.extension = extension;
|
|
_info.special = 0;
|
|
}
|
|
|
|
|
|
#if 0
|
|
path::path(const path &rhs) :
|
|
_path(rhs._path), _info(rhs._info)
|
|
{}
|
|
|
|
path::path(path &&rhs) noexcept :
|
|
_path(std::move(rhs._path))
|
|
{
|
|
rhs.invalidate();
|
|
}
|
|
#endif
|
|
|
|
|
|
// private. neither this->_path nor s are empty.
|
|
path &path::append_common(const std::string &s)
|
|
{
|
|
invalidate();
|
|
if (_path.back() != separator && s.front() != separator)
|
|
_path.push_back(separator);
|
|
|
|
_path.append(s);
|
|
|
|
return *this;
|
|
}
|
|
|
|
path& path::append(const path& p)
|
|
{
|
|
if (p.empty()) return *this;
|
|
|
|
if (empty()) {
|
|
return (*this = p);
|
|
}
|
|
|
|
invalidate();
|
|
|
|
// check for something stupid like xx.append(xx);
|
|
if (&p == this) {
|
|
return append_common(string_type(p._path));
|
|
}
|
|
|
|
return append_common(p._path);
|
|
}
|
|
|
|
path& path::append(const string_type &s)
|
|
{
|
|
if (s.empty()) return *this;
|
|
invalidate();
|
|
|
|
if (empty()) {
|
|
_path = s;
|
|
return *this;
|
|
}
|
|
|
|
|
|
if (&s == &_path) {
|
|
string_type tmp(s);
|
|
if (_path.back() != separator && tmp[0] != separator)
|
|
_path.push_back(separator);
|
|
_path.append(tmp);
|
|
return *this;
|
|
}
|
|
|
|
if (_path.back() != separator && s[0] != separator)
|
|
_path.push_back(separator);
|
|
|
|
_path.append(s);
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
|
|
path path::filename() const {
|
|
|
|
if (empty()) return *this;
|
|
if (!_info.valid) study();
|
|
|
|
if (_info.special == separator) return path_sep;
|
|
if (_info.special == '.') return path_dot;
|
|
|
|
if (_info.stem == 0) return *this;
|
|
return _path.substr(_info.stem);
|
|
}
|
|
|
|
path path::stem() const {
|
|
|
|
// filename without the extension.
|
|
|
|
if (empty()) return *this;
|
|
if (!_info.valid) study();
|
|
|
|
if (_info.special == separator) return path_sep;
|
|
if (_info.special == '.') return path_dot;
|
|
|
|
return _path.substr(_info.stem, _info.extension - _info.stem);
|
|
}
|
|
|
|
path path::extension() const {
|
|
|
|
if (empty()) return *this;
|
|
if (!_info.valid) study();
|
|
|
|
return _path.substr(_info.extension);
|
|
}
|
|
|
|
|
|
bool path::has_parent_path() const
|
|
{
|
|
// if there is a /, it has a parent path.
|
|
// ... unless it's /.
|
|
|
|
if (empty()) return false;
|
|
|
|
if (!_info.valid) study();
|
|
|
|
if (_info.special == '/') return false;
|
|
|
|
return _path.find(separator) != _path.npos;
|
|
}
|
|
|
|
path path::parent_path() const {
|
|
|
|
/*
|
|
* special cases:
|
|
* /abc -> /
|
|
* /abc/ -> /abc
|
|
* all trailing /s are removed.
|
|
*
|
|
*/
|
|
|
|
|
|
if (empty()) return *this;
|
|
|
|
if (!_info.valid) study();
|
|
|
|
// "/" is a file of "/" with a parent of ""
|
|
if (_info.special == separator) return path();
|
|
|
|
// stem starts at 0, eg "abc"
|
|
if (!_info.stem) return path();
|
|
|
|
|
|
auto tmp = _path.substr(0, _info.stem - 1);
|
|
|
|
// remove trailing slashes, but return "/" if nothing BUT /s.
|
|
while (!tmp.empty() && tmp.back() == separator) tmp.pop_back();
|
|
if (tmp.empty()) return path_sep;
|
|
|
|
return path(tmp);
|
|
}
|
|
|
|
path path::root_directory() const {
|
|
// for unix, root directory is / or "".
|
|
if (empty()) return *this;
|
|
|
|
return _path.front() == '/' ? path_sep : path();
|
|
}
|
|
|
|
path path::root_name() const {
|
|
/*
|
|
* boost (unix) considers // or //component
|
|
* to be a root name (and only those cases).
|
|
*
|
|
* I do not.
|
|
*/
|
|
|
|
return path();
|
|
}
|
|
|
|
path path::root_path() const {
|
|
// root_name + root_directory.
|
|
// since root_name is always empty...
|
|
|
|
return root_directory();
|
|
}
|
|
|
|
|
|
path path::relative_path() const {
|
|
// first pathname *after* the root path
|
|
// root_path is first / in this implementation.
|
|
|
|
if (is_relative()) return *this;
|
|
|
|
auto pos = _path.find_first_not_of(separator);
|
|
if (pos == _path.npos) return path();
|
|
|
|
return path(_path.substr(pos));
|
|
}
|
|
|
|
|
|
|
|
// compare
|
|
int path::compare(const path& p) const noexcept {
|
|
if (&p == this) return 0;
|
|
return _path.compare(p._path);
|
|
}
|
|
|
|
int path::compare(const std::string& s) const {
|
|
if (&s == &_path) return 0;
|
|
return _path.compare(s);
|
|
}
|
|
|
|
int path::compare(const value_type* s) const {
|
|
return _path.compare(s);
|
|
}
|
|
|
|
|
|
}
|