Add a minimal C++17-like filesystem library

pull/3/head
Hleb Valoshka 2019-08-15 22:14:34 +03:00
parent 6d81fc780a
commit ceaed57aa5
5 changed files with 877 additions and 0 deletions

View File

@ -0,0 +1,6 @@
set(CELCOMPAT_SOURCES
fs.cpp
fs.h
)
add_library(celcompat OBJECT ${CELCOMPAT_SOURCES})

View File

@ -0,0 +1,14 @@
#pragma once
#include <config.h>
#if __cplusplus >= 201703L
#include <filesystem>
namespace fs = std::filesystem;
#elif defined(HAVE_EXPERIMENTAL_FILESYSTEM)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#include "fs.h"
namespace fs = celestia::filesystem;
#endif

View File

@ -0,0 +1,415 @@
#include "fs.h"
#include <vector>
#include <memory>
#ifdef _WIN32
#include <celutil/winutil.h>
#else
#include <sys/stat.h>
#endif
namespace celestia
{
namespace filesystem
{
// we should skip "." and ".."
template<typename CharT> static bool is_special_dir(const CharT s[])
{
return s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'));
}
path operator/(const path& lhs, const path& rhs)
{
return path(lhs) /= rhs;
}
std::ostream& operator<<(std::ostream& os, const path& p)
{
os << '"' << p.string() << '"';
return os;
}
path u8path(const std::string& source)
{
#ifdef _WIN32
return UTF8ToWide(source);
#else
return source; // FIXME
#endif
}
#ifdef _WIN32
void path::fixup_separators()
{
if (m_fmt == native_format)
return;
string_type::size_type pos = 0;
for(;;)
{
pos = m_path.find(L'/', pos);
if (pos == path::string_type::npos)
break;
m_path[pos] = preferred_separator;
pos++;
}
}
#endif
path path::filename() const
{
auto pos = m_path.rfind(preferred_separator);
if (pos == string_type::npos)
pos = 0;
else
pos++;
auto fn = m_path.substr(pos);
if (is_special_dir(fn.c_str()))
return path();
path p(fn);
return p;
}
path path::stem() const
{
auto fn = filename().native();
auto pos = fn.rfind('.');
if (pos == 0 || pos == string_type::npos)
return fn;
return fn.substr(0, pos);
}
path path::extension() const
{
auto fn = filename().native();
auto pos = fn.rfind('.');
if (pos == 0 || pos == string_type::npos)
return path();
return fn.substr(pos);
}
path path::parent_path() const
{
auto pos = m_path.rfind(preferred_separator);
if (pos == 0)
return path(m_path.substr(0, 1));
if (pos == string_type::npos)
return path();
return path(m_path.substr(0, pos));
}
bool path::is_absolute() const
{
#ifdef _WIN32
return m_path[0] == preferred_separator || m_path[1] == L':';
#else
return m_path[0] == preferred_separator;
#endif
}
struct directory_iterator::SearchImpl
{
path m_path {};
#ifdef _WIN32
HANDLE m_handle { INVALID_HANDLE_VALUE };
#else
DIR *m_dir { nullptr };
#endif
explicit SearchImpl(const path& p) :
m_path(p)
{
};
~SearchImpl();
bool advance(directory_entry&);
};
directory_iterator::SearchImpl::~SearchImpl()
{
#ifdef _WIN32
if (m_handle != INVALID_HANDLE_VALUE)
{
FindClose(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
#else
if (m_dir != nullptr)
{
closedir(m_dir);
m_dir = nullptr;
}
#endif
}
bool directory_iterator::SearchImpl::advance(directory_entry& entry)
{
#ifdef _WIN32
WIN32_FIND_DATAW findData;
if (m_handle == INVALID_HANDLE_VALUE)
{
m_handle = FindFirstFileW((m_path / L"*").c_str(), &findData);
if (m_handle == INVALID_HANDLE_VALUE)
return false;
}
else
{
if (!FindNextFileW(m_handle, &findData))
return false;
}
while (is_special_dir(findData.cFileName))
{
if (!FindNextFileW(m_handle, &findData))
return false;
}
entry = directory_entry(std::move(m_path / findData.cFileName));
return true;
#else
if (m_dir == nullptr)
{
m_dir = opendir(m_path.c_str());
if (m_dir == nullptr)
return false;
}
struct dirent* ent = nullptr;
do
{
ent = readdir(m_dir);
if (ent == nullptr)
return false;
}
while (is_special_dir(ent->d_name));
entry = directory_entry(std::move(m_path / ent->d_name));
return true;
#endif
}
directory_iterator::directory_iterator(const path& p) :
directory_iterator::directory_iterator(p, m_ec)
{
}
directory_iterator::directory_iterator(const path& p, std::error_code& ec) :
m_path(p),
m_ec(ec),
m_search(std::make_shared<SearchImpl>(p))
{
if (!m_search->advance(m_entry))
reset();
}
void directory_iterator::reset()
{
*this = directory_iterator();
}
bool directory_iterator::operator==(const directory_iterator& other) const noexcept
{
return m_search == other.m_search && m_entry == other.m_entry;
}
directory_iterator& directory_iterator::operator++()
{
// *this != end(*this) ...
if (m_search != nullptr && !m_search->advance(m_entry))
reset();
return *this;
}
struct recursive_directory_iterator::DirStack
{
std::vector<directory_iterator> m_dirIters;
};
recursive_directory_iterator::recursive_directory_iterator(const path& p)
{
if (m_dirs == nullptr)
m_dirs = std::make_shared<DirStack>();
m_iter = directory_iterator(p);
}
bool recursive_directory_iterator::operator==(const recursive_directory_iterator& other) const noexcept
{
return m_depth == other.m_depth &&
m_pending == other.m_pending &&
m_dirs == other.m_dirs &&
m_iter == other.m_iter;
}
recursive_directory_iterator& recursive_directory_iterator::operator++()
{
// *this == end(*this)
if (m_dirs == nullptr)
return *this;
auto& p = m_iter->path();
if (m_pending && is_directory(p))
{
m_dirs->m_dirIters.emplace_back(m_iter);
m_iter = directory_iterator(p);
}
else
{
++m_iter;
}
while (m_iter == end(m_iter))
{
if (m_dirs->m_dirIters.empty())
{
reset();
return *this;
}
m_iter = std::move(m_dirs->m_dirIters.back());
++m_iter;
m_dirs->m_dirIters.pop_back();
}
return *this;
}
void recursive_directory_iterator::pop()
{
std::error_code ec;
pop(ec);
}
void recursive_directory_iterator::pop(std::error_code& ec)
{
m_dirs->m_dirIters.pop_back();
}
void recursive_directory_iterator::reset()
{
*this = recursive_directory_iterator();
}
uintmax_t file_size(const path& p, std::error_code& ec) noexcept
{
#ifdef _WIN32
WIN32_FILE_ATTRIBUTE_DATA attr;
LARGE_INTEGER fileSize = { 0 };
if (GetFileAttributesExW(p.c_str(), GetFileExInfoStandard, &attr))
{
fileSize.LowPart = attr.nFileSizeLow;
fileSize.HighPart = attr.nFileSizeHigh;
return static_cast<uintmax_t>(fileSize.QuadPart);
}
else
{
ec = std::error_code(errno, std::system_category());
return static_cast<uintmax_t>(-1);
}
#else
struct stat stat_buf;
int rc = stat(p.c_str(), &stat_buf);
if (rc == -1)
{
ec = std::error_code(errno, std::system_category());
return static_cast<uintmax_t>(-1);
}
return static_cast<uintmax_t>(stat_buf.st_size);
#endif
}
uintmax_t file_size(const path& p)
{
std::error_code ec;
uintmax_t s = file_size(p, ec);
if (ec)
throw filesystem_error(ec, "celfs::file_size error");
return s;
}
bool exists(const path& p, std::error_code& ec) noexcept
{
#ifdef _WIN32
DWORD attr = GetFileAttributesW(&p.native()[0]);
if (attr != INVALID_FILE_ATTRIBUTES)
return true;
switch (GetLastError())
{
// behave like boost::filesystem
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_NAME:
case ERROR_INVALID_DRIVE:
case ERROR_NOT_READY:
case ERROR_INVALID_PARAMETER:
case ERROR_BAD_PATHNAME:
case ERROR_BAD_NETPATH:
return false;
default:
break;
}
#else
struct stat buf;
if (stat(p.c_str(), &buf) == 0)
return true;
if (errno == ENOENT)
return false;
#endif
ec = std::error_code(errno, std::system_category());
return false;
}
bool exists(const path& p)
{
std::error_code ec;
bool r = exists(p, ec);
if (ec)
throw filesystem_error(ec, "celfs::exists error");
return r;
}
bool is_directory(const path& p, std::error_code& ec) noexcept
{
#ifdef _WIN32
DWORD attr = GetFileAttributesW(&p.native()[0]);
if (attr == INVALID_FILE_ATTRIBUTES)
{
ec = std::error_code(errno, std::system_category());
return false;
}
return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
struct stat buf;
return (stat(p.c_str(), &buf) == 0) && S_ISDIR(buf.st_mode);
#endif
}
bool is_directory(const path& p)
{
std::error_code ec;
bool r = is_directory(p, ec);
if (ec)
throw filesystem_error(ec, "celfs::is_directory error");
return r;
}
}
}

412
src/celcompat/fs.h 100644
View File

@ -0,0 +1,412 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#endif
#include <system_error>
#include <string>
#include <iostream>
#include <memory>
#ifdef _WIN32
#include <celutil/winutil.h>
#endif
namespace celestia
{
namespace filesystem
{
class filesystem_error : std::system_error
{
public:
filesystem_error(std::error_code ec, const char* msg) :
std::system_error(ec, msg)
{
}
}; // filesystem_error
class path
{
public:
#ifdef _WIN32
using string_type = std::wstring;
#else
using string_type = std::string;
#endif
using value_type = string_type::value_type;
#ifdef _WIN32
static constexpr value_type preferred_separator = L'\\';
#else
static constexpr value_type preferred_separator = '/';
#endif
enum format
{
native_format,
generic_format,
auto_format
};
path() noexcept
{
};
path(const path&) = default;
path(path&& p) noexcept :
m_path(std::move(p.m_path)),
m_fmt(p.m_fmt)
{
};
path(string_type&& p, format fmt = auto_format) :
m_path(std::move(p)),
m_fmt(fmt)
{
#ifdef _WIN32
fixup_separators();
#endif
};
template<typename T> path(const T& p, format fmt = auto_format ) :
m_path(encconv(p)),
m_fmt(fmt)
{
#ifdef _WIN32
fixup_separators();
#endif
};
~path() = default;
path& operator=(const path& p) = default;
path& operator=(path&&) = default;
template<typename T> path& append(const T& p)
{
if (empty())
m_path = p;
else
m_path.append(1, preferred_separator).append(p);
return *this;
}
template<typename T> path& operator/=(const T& p)
{
return append(p);
}
template<typename T> path& concat(const T& p)
{
m_path += p;
return *this;
}
template<typename T> path& operator+=(const T& p)
{
return concat(p);
}
bool operator==(const path& other) const noexcept
{
return m_path == other.m_path && m_fmt == other.m_fmt;
}
bool operator!=(const path& other) const noexcept
{
return !(*this == other);
}
bool operator<(const path& other) const noexcept
{
return m_path < other.m_path;
}
bool operator>(const path& other) const noexcept
{
return m_path > other.m_path;
}
bool operator<=(const path& other) const noexcept
{
return m_path <= other.m_path;
}
bool operator>=(const path& other) const noexcept
{
return m_path >= other.m_path;
}
std::string string() const noexcept
{
#ifdef _WIN32
return WideToCurrentCP(m_path);
#else
return m_path;
#endif
}
std::wstring wstring() const noexcept
{
#ifdef _WIN32
return m_path;
#else
return L""; // FIXME
#endif
}
std::string u8string() const noexcept
{
#ifdef _WIN32
return WideToUTF8(m_path);
#else
return m_path; // FIXME
#endif
}
const value_type* c_str() const noexcept
{
return m_path.c_str();
}
const string_type& native() const noexcept
{
return m_path;
}
operator string_type() const noexcept
{
return m_path;
}
bool empty() const noexcept
{
return m_path.empty();
}
path filename() const;
path stem() const;
path extension() const;
path parent_path() const;
bool is_relative() const
{
return !is_absolute();
}
bool is_absolute() const;
private:
inline string_type encconv(const std::string& p) const
{
#ifdef _WIN32
return CurrentCPToWide(p);
#else
return p;
#endif
}
inline string_type encconv(const std::wstring& p) const
{
#ifdef _WIN32
return p;
#else
return ""; // FIXME
#endif
}
#ifdef _WIN32
void fixup_separators();
#endif
string_type m_path;
format m_fmt { auto_format };
}; // path
path operator/(const path& lhs, const path& rhs);
std::ostream& operator<<(std::ostream& os, const path& p);
path u8path(const std::string& source);
class directory_iterator;
class recursive_directory_iterator;
class directory_entry
{
public:
directory_entry() = default;
directory_entry(const directory_entry&) = default;
directory_entry(directory_entry&&) = default;
explicit directory_entry(const path& p) :
m_path(p)
{
};
directory_entry& operator=(const directory_entry&) = default;
directory_entry& operator=(directory_entry&&) = default;
const class path& path() const noexcept
{
return m_path;
}
operator const class path& () const noexcept
{
return m_path;
};
bool operator==(const directory_entry& other) const noexcept
{
return m_path == other.m_path;
}
bool operator!=(const directory_entry& other) const noexcept
{
return !(*this == other);
}
private:
friend class directory_iterator;
friend class recursive_directory_iterator;
class path m_path;
}; // directory_entry
class directory_iterator
{
public:
using value_type = directory_entry;
using difference_type = std::ptrdiff_t;
using pointer = const directory_entry*;
using reference = const directory_entry&;
using iterator_category = std::input_iterator_tag;
directory_iterator() = default;
explicit directory_iterator(const path& p);
directory_iterator(const path& p, std::error_code& ec);
directory_iterator(const directory_iterator&) = default;
directory_iterator(directory_iterator&&) = default;
~directory_iterator() = default;
directory_iterator& operator=(const directory_iterator&) = default;
directory_iterator& operator=(directory_iterator&&) = default;
directory_iterator& operator++();
const directory_entry& operator*() const noexcept
{
return m_entry;
}
const directory_entry* operator->() const noexcept
{
return &m_entry;
}
bool operator==(const directory_iterator& other) const noexcept;
bool operator!=(const directory_iterator& other) const noexcept
{
return !(*this == other);
}
private:
struct SearchImpl;
void reset();
path m_path {};
directory_entry m_entry {};
std::error_code m_ec {};
std::shared_ptr<SearchImpl> m_search { nullptr };
}; // directory_iterator
inline directory_iterator begin(directory_iterator iter) noexcept
{
return iter;
}
inline directory_iterator end(directory_iterator) noexcept
{
return directory_iterator();
}
class recursive_directory_iterator
{
public:
using value_type = directory_entry;
using difference_type = std::ptrdiff_t;
using pointer = const directory_entry*;
using reference = const directory_entry&;
using iterator_category = std::input_iterator_tag;
recursive_directory_iterator() = default;
recursive_directory_iterator(const recursive_directory_iterator&) = default;
recursive_directory_iterator(recursive_directory_iterator&&) noexcept = default;
explicit recursive_directory_iterator(const path& p);
// recursive_directory_iterator(const path&, directory_options);
// recursive_directory_iterator(const path&, directory_options, std::error_code&);
recursive_directory_iterator(const path& p, std::error_code& ec);
~recursive_directory_iterator() = default;
recursive_directory_iterator& operator=(const recursive_directory_iterator&) = default;
recursive_directory_iterator& operator=(recursive_directory_iterator&&) = default;
bool recursion_pending() const
{
return m_pending;
};
void disable_recursion_pending()
{
m_pending = false;
};
void pop();
void pop(std::error_code& ec);
recursive_directory_iterator& operator++();
const directory_entry& operator*() const noexcept
{
return *m_iter;
}
const directory_entry* operator->() const noexcept
{
return &*m_iter;
}
bool operator==(const recursive_directory_iterator& other) const noexcept;
bool operator!=(const recursive_directory_iterator& other) const noexcept
{
return !(*this == other);
}
private:
void reset();
std::error_code m_ec {};
int m_depth { 0 };
bool m_pending { true };
struct DirStack;
std::shared_ptr<DirStack> m_dirs { nullptr };
directory_iterator m_iter {};
}; // recursive_directory_iterator
inline recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept
{
return iter;
}
inline recursive_directory_iterator end(recursive_directory_iterator) noexcept
{
return recursive_directory_iterator();
}
uintmax_t file_size(const path& p, std::error_code& ec) noexcept;
uintmax_t file_size(const path& p);
bool exists(const path& p);
bool exists(const path& p, std::error_code& ec) noexcept;
bool is_directory(const path& p);
bool is_directory(const path& p, std::error_code& ec) noexcept;
};
};

View File

@ -0,0 +1,30 @@
#include <iostream>
#include "fs.h"
namespace fs = celestia::filesystem;
int main()
{
std::cout << fs::path("/foo/bar.txt").extension() << '\n'
<< fs::path("/foo/bar.").extension() << '\n'
<< fs::path("/foo/bar").extension() << '\n'
<< fs::path("/foo/bar.txt/bar.cc").extension() << '\n'
<< fs::path("/foo/bar.txt/bar.").extension() << '\n'
<< fs::path("/foo/bar.txt/bar").extension() << '\n'
<< fs::path("/foo/.").extension() << '\n'
<< fs::path("/foo/..").extension() << '\n'
<< fs::path("/foo/.hidden").extension() << '\n'
<< fs::path("/foo/..bar").extension() << '\n';
std::cout << "----------\n";
std::cout << fs::path("/foo/bar.txt").stem() << '\n'
<< fs::path("/foo/.bar").stem() << '\n';
for (fs::path p = "foo.bar.baz.tar"; !p.extension().empty(); p = p.stem())
std::cout << p.extension() << '\n';
std::cout << "----------\n";
std::cout << fs::path("/foo/bar.txt") << '\n';
std::cout << fs::path("baz/foo/bar.txt") << '\n';
std::cout << fs::path("c:\\foo\\bar.txt") << '\n';
std::cout << fs::path(L"c:\\foo\\bar.txt") << '\n';
}