mpw-shell/cxx/path.cpp

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);
}
}