add fs APIs for creating/removing/renaming, mtime and globs
parent
64a529254c
commit
307675731c
|
@ -6,15 +6,15 @@
|
|||
#include <vector>
|
||||
|
||||
#include <ostd/io.hh>
|
||||
#include <ostd/filesystem.hh>
|
||||
#include <ostd/path.hh>
|
||||
|
||||
using namespace ostd;
|
||||
|
||||
int main() {
|
||||
writeln("-- all example sources (examples/*.cc) --\n");
|
||||
{
|
||||
auto app = appender<std::vector<filesystem::path>>();
|
||||
glob_match(app, "examples/*.cc");
|
||||
auto app = appender<std::vector<path>>();
|
||||
fs::glob_match(app, "examples/*.cc");
|
||||
|
||||
for (auto &ex: app.get()) {
|
||||
writefln("found: %s", ex);
|
||||
|
@ -22,8 +22,8 @@ int main() {
|
|||
}
|
||||
writeln("\n-- recursive source files (src/**/*.cc) --\n");
|
||||
{
|
||||
auto app = appender<std::vector<filesystem::path>>();
|
||||
glob_match(app, "src/**/*.cc");
|
||||
auto app = appender<std::vector<path>>();
|
||||
fs::glob_match(app, "src/**/*.cc");
|
||||
|
||||
for (auto &ex: app.get()) {
|
||||
writefln("found: %s", ex);
|
||||
|
@ -31,8 +31,8 @@ int main() {
|
|||
}
|
||||
writeln("\n-- 5-character headers (ostd/?????.hh) --\n");
|
||||
{
|
||||
auto app = appender<std::vector<filesystem::path>>();
|
||||
glob_match(app, "ostd/?????.hh");
|
||||
auto app = appender<std::vector<path>>();
|
||||
fs::glob_match(app, "ostd/?????.hh");
|
||||
|
||||
for (auto &ex: app.get()) {
|
||||
writefln("found: %s", ex);
|
||||
|
@ -40,8 +40,8 @@ int main() {
|
|||
}
|
||||
writeln("\n-- examples starting with f-r (examples/[f-r]*.cc) --\n");
|
||||
{
|
||||
auto app = appender<std::vector<filesystem::path>>();
|
||||
glob_match(app, "examples/[f-r]*.cc");
|
||||
auto app = appender<std::vector<path>>();
|
||||
fs::glob_match(app, "examples/[f-r]*.cc");
|
||||
|
||||
for (auto &ex: app.get()) {
|
||||
writefln("found: %s", ex);
|
||||
|
@ -49,8 +49,8 @@ int main() {
|
|||
}
|
||||
writeln("\n-- examples not starting with f-r (examples/[!f-r]*.cc) --\n");
|
||||
{
|
||||
auto app = appender<std::vector<filesystem::path>>();
|
||||
glob_match(app, "examples/[!f-r]*.cc");
|
||||
auto app = appender<std::vector<path>>();
|
||||
fs::glob_match(app, "examples/[!f-r]*.cc");
|
||||
|
||||
for (auto &ex: app.get()) {
|
||||
writefln("found: %s", ex);
|
||||
|
@ -58,8 +58,8 @@ int main() {
|
|||
}
|
||||
writeln("\n-- headers starting with c, f or s (ostd/[cfs]*.hh) --\n");
|
||||
{
|
||||
auto app = appender<std::vector<filesystem::path>>();
|
||||
glob_match(app, "ostd/[cfs]*.hh");
|
||||
auto app = appender<std::vector<path>>();
|
||||
fs::glob_match(app, "ostd/[cfs]*.hh");
|
||||
|
||||
for (auto &ex: app.get()) {
|
||||
writefln("found: %s", ex);
|
||||
|
|
99
ostd/path.hh
99
ostd/path.hh
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include <stack>
|
||||
#include <list>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <initializer_list>
|
||||
|
@ -50,6 +51,10 @@ namespace ostd {
|
|||
namespace detail {
|
||||
struct path_range;
|
||||
struct path_parent_range;
|
||||
|
||||
OSTD_EXPORT bool glob_match_path_impl(
|
||||
char const *fname, char const *wname
|
||||
) noexcept;
|
||||
}
|
||||
|
||||
struct path {
|
||||
|
@ -357,6 +362,43 @@ struct path {
|
|||
return append_concat(p);
|
||||
}
|
||||
|
||||
/** @brief Checks if the given path matches the given glob pattern.
|
||||
*
|
||||
* This matches the given filename against POSIX-style glob patterns.
|
||||
* The following patterns are supported:
|
||||
*
|
||||
* | Pattern | Description |
|
||||
* |---------|----------------------------------------------------|
|
||||
* | * | 0 or more characters |
|
||||
* | ? | any single character |
|
||||
* | [abc] | one character in the brackets |
|
||||
* | [a-z] | one character within the range in the brackets |
|
||||
* | [!abc] | one character not in the brackets |
|
||||
* | [!a-z] | one character not within the range in the brackets |
|
||||
*
|
||||
* The behavior is the same as in POSIX. You can combine ranges and
|
||||
* individual characters in the `[]` pattern together as well as define
|
||||
* multiple ranges in one (e.g. `[a-zA-Z_?]` matching alphabetics,
|
||||
* an underscore and a question mark). The behavior of the range varies
|
||||
* by locale. If the second character in the range is lower in value
|
||||
* than the first one, a match will never happen. To match the `]`
|
||||
* character in the brackets, make it the first one. To match the
|
||||
* dash character, make it the first or the last.
|
||||
*
|
||||
* You can also use the brackets to escape metacharacters. So to
|
||||
* match a literal `*`, use `[*]`.
|
||||
*
|
||||
* Keep in mind that an invalid bracket syntax (unterminated) will
|
||||
* always cause this to return `false`.
|
||||
*
|
||||
* This function is used in ostd::glob_match().
|
||||
*/
|
||||
bool match(path const &pattern) noexcept {
|
||||
return detail::glob_match_path_impl(
|
||||
string().data(), pattern.string().data()
|
||||
);
|
||||
}
|
||||
|
||||
string_range string() const noexcept {
|
||||
return p_path;
|
||||
}
|
||||
|
@ -1076,6 +1118,8 @@ OSTD_EXPORT path current_path();
|
|||
OSTD_EXPORT path home_path();
|
||||
OSTD_EXPORT path temp_path();
|
||||
|
||||
OSTD_EXPORT void current_path(path const &p);
|
||||
|
||||
OSTD_EXPORT path absolute(path const &p);
|
||||
|
||||
OSTD_EXPORT path canonical(path const &p);
|
||||
|
@ -1091,6 +1135,61 @@ OSTD_EXPORT bool exists(path const &p);
|
|||
|
||||
OSTD_EXPORT bool equivalent(path const &p1, path const &p2);
|
||||
|
||||
OSTD_EXPORT bool create_directory(path const &p);
|
||||
OSTD_EXPORT bool create_directory(path const &p, path const &ep);
|
||||
OSTD_EXPORT bool create_directories(path const &p);
|
||||
|
||||
OSTD_EXPORT bool remove(path const &p);
|
||||
OSTD_EXPORT std::uintmax_t remove_all(path const &p);
|
||||
|
||||
OSTD_EXPORT void rename(path const &op, path const &np);
|
||||
|
||||
using file_time_t = std::chrono::time_point<std::chrono::system_clock>;
|
||||
|
||||
OSTD_EXPORT file_time_t last_write_time(path const &p);
|
||||
OSTD_EXPORT void last_write_time(path const &p, file_time_t new_time);
|
||||
|
||||
namespace detail {
|
||||
OSTD_EXPORT void glob_match_impl(
|
||||
void (*out)(path const &, void *),
|
||||
typename path::range r, path pre, void *data
|
||||
);
|
||||
} /* namespace detail */
|
||||
|
||||
/** @brief Expands a path with glob patterns.
|
||||
*
|
||||
* Individual expanded paths are put in `out` and are of the standard
|
||||
* std::filesystem::path type. It supports standard patterns as defined
|
||||
* in ostd::glob_match_filename().
|
||||
*
|
||||
* So for example, `*.cc` will expand to `one.cc`, `two.cc` and so on.
|
||||
* A pattern like `foo/[cb]at.txt` will match `foo/cat.txt` and `foo/bat.txt`
|
||||
* but not `foo/Cat.txt`. The `foo/?at.txt` will match `foo/cat.txt`,
|
||||
* `foo/Cat.txt`, `foo/pat.txt`, `foo/vat.txt` or any other character
|
||||
* in the place.
|
||||
*
|
||||
* Additionally, a special `**` pattern is also supported which is not
|
||||
* matched by ostd::glob_match_filename(). It's only allowed if the entire
|
||||
* filename or directory name is `**`. When used as a directory name, it
|
||||
* will expand to all directories in the location and all subdirectories
|
||||
* of those directories. If used as a filename (at the end of the path),
|
||||
* then it expands to directories and subdirectories aswell as all files
|
||||
* in the location and in the directories or subdirectories. Keep in mind
|
||||
* that it is not a regular pattern and a `**` when found in a regular
|
||||
* context (i.e. not as entire filename/directory name) will be treated
|
||||
* as two regular `*` patterns.
|
||||
*
|
||||
* @throws std::filesystem_error if a filesystem error occurs.
|
||||
* @returns The forwarded `out`.
|
||||
*/
|
||||
template<typename OutputRange>
|
||||
inline OutputRange &&glob_match(OutputRange &&out, path const &pattern) {
|
||||
detail::glob_match_impl([](path const &p, void *outp) {
|
||||
static_cast<std::remove_reference_t<OutputRange> *>(outp)->put(p);
|
||||
}, pattern.iter(), path{}, &out);
|
||||
return std::forward<OutputRange>(out);
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
} /* namesapce fs */
|
||||
|
|
179
src/path.cc
179
src/path.cc
|
@ -3,6 +3,8 @@
|
|||
* This file is part of libostd. See COPYING.md for futher information.
|
||||
*/
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "ostd/platform.hh"
|
||||
|
||||
#if defined(OSTD_PLATFORM_WIN32)
|
||||
|
@ -12,3 +14,180 @@
|
|||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
namespace ostd {
|
||||
namespace detail {
|
||||
|
||||
inline char const *glob_match_brackets(char match, char const *wp) noexcept {
|
||||
bool neg = (*wp == '!');
|
||||
if (neg) {
|
||||
++wp;
|
||||
}
|
||||
|
||||
/* grab the first character as it can be ] */
|
||||
auto c = *wp++;
|
||||
if (!c) {
|
||||
/* unterminated */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* make sure it's terminated somewhere */
|
||||
auto *eb = wp;
|
||||
for (; *eb != ']'; ++eb) {
|
||||
if (!*eb) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
++eb;
|
||||
|
||||
/* no need to worry about \0 from now on */
|
||||
do {
|
||||
/* character range */
|
||||
if ((*wp == '-') && (*(wp + 1) != ']')) {
|
||||
auto lc = *(wp + 1);
|
||||
wp += 2;
|
||||
if ((match >= c) && (match <= lc)) {
|
||||
return neg ? nullptr : eb;
|
||||
}
|
||||
c = *wp++;
|
||||
continue;
|
||||
}
|
||||
/* single-char match */
|
||||
if (match == c) {
|
||||
return neg ? nullptr : eb;
|
||||
}
|
||||
c = *wp++;
|
||||
} while (c != ']');
|
||||
|
||||
/* loop ended, so no match */
|
||||
return neg ? eb : nullptr;
|
||||
}
|
||||
|
||||
OSTD_EXPORT bool glob_match_path_impl(
|
||||
char const *fname, char const *wname
|
||||
) noexcept {
|
||||
/* skip any matching prefix if present */
|
||||
while (*wname && (*wname != '*')) {
|
||||
if (!*wname || (*wname == '*')) {
|
||||
break;
|
||||
}
|
||||
if (*fname) {
|
||||
/* ? wildcard matches any character */
|
||||
if (*wname == '?') {
|
||||
++wname;
|
||||
++fname;
|
||||
continue;
|
||||
}
|
||||
/* [...] wildcard */
|
||||
if (*wname == '[') {
|
||||
wname = glob_match_brackets(*fname, wname + 1);
|
||||
if (!wname) {
|
||||
return false;
|
||||
}
|
||||
++fname;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((*wname == '?') && *fname) {
|
||||
++wname;
|
||||
++fname;
|
||||
continue;
|
||||
}
|
||||
if (*fname++ != *wname++) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* skip * wildcards; a wildcard matches 0 or more */
|
||||
if (*wname == '*') {
|
||||
while (*wname == '*') {
|
||||
++wname;
|
||||
}
|
||||
/* was trailing so everything matches */
|
||||
if (!*wname) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* prefix skipping matched entire filename */
|
||||
if (!*fname) {
|
||||
return true;
|
||||
}
|
||||
/* empty pattern and non-empty filename */
|
||||
if (!*wname) {
|
||||
return false;
|
||||
}
|
||||
/* keep incrementing filename until it matches */
|
||||
while (*fname) {
|
||||
if (glob_match_path_impl(fname, wname)) {
|
||||
return true;
|
||||
}
|
||||
++fname;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} /* namespace detail */
|
||||
} /* namespace ostd */
|
||||
|
||||
namespace ostd {
|
||||
namespace fs {
|
||||
namespace detail {
|
||||
|
||||
OSTD_EXPORT void glob_match_impl(
|
||||
void (*out)(path const &, void *), typename path::range r,
|
||||
path pre, void *data
|
||||
) {
|
||||
while (!r.empty()) {
|
||||
auto cur = std::string{r.front()};
|
||||
auto *cs = cur.c_str();
|
||||
/* this part of the path might contain wildcards */
|
||||
for (auto c = *cs; c; c = *++cs) {
|
||||
/* ** as a name does recursive expansion */
|
||||
if ((c == '*') && (*(cs + 1) == '*') && !*(cs + 2)) {
|
||||
r.pop_front();
|
||||
auto ip = pre.empty() ? "." : pre;
|
||||
if (!is_directory(ip)) {
|
||||
return;
|
||||
}
|
||||
recursive_directory_range dr{ip};
|
||||
/* include "current" dir in the match */
|
||||
if (!r.empty()) {
|
||||
glob_match_impl(out, r, pre, data);
|
||||
}
|
||||
for (auto &de: dr) {
|
||||
/* followed by more path, only consider dirs */
|
||||
auto dp = de.path();
|
||||
if (!r.empty() && !is_directory(dp)) {
|
||||
continue;
|
||||
}
|
||||
/* otherwise also match files */
|
||||
glob_match_impl(out, r, dp, data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* wildcards *, ?, [...] */
|
||||
if ((c == '*') || (c == '?') || (c == '[')) {
|
||||
r.pop_front();
|
||||
auto ip = pre.empty() ? "." : pre;
|
||||
if (!is_directory(ip)) {
|
||||
return;
|
||||
}
|
||||
directory_range dr{ip};
|
||||
for (auto &de: dr) {
|
||||
auto p = path{de.path().name()};
|
||||
if (!p.match(cur)) {
|
||||
continue;
|
||||
}
|
||||
glob_match_impl(out, r, pre / p, data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
pre /= cur;
|
||||
r.pop_front();
|
||||
}
|
||||
out(pre, data);
|
||||
}
|
||||
|
||||
} /* namespace detail */
|
||||
} /* namesapce fs */
|
||||
} /* namespace ostd */
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <pwd.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
|
@ -17,38 +18,6 @@
|
|||
namespace ostd {
|
||||
namespace fs {
|
||||
|
||||
static perms mode_to_perms(mode_t mode) {
|
||||
perms ret = perms::none;
|
||||
switch (mode & S_IRWXU) {
|
||||
case S_IRUSR: ret |= perms::owner_read;
|
||||
case S_IWUSR: ret |= perms::owner_write;
|
||||
case S_IXUSR: ret |= perms::owner_exec;
|
||||
case S_IRWXU: ret |= perms::owner_all;
|
||||
}
|
||||
switch (mode & S_IRWXG) {
|
||||
case S_IRGRP: ret |= perms::group_read;
|
||||
case S_IWGRP: ret |= perms::group_write;
|
||||
case S_IXGRP: ret |= perms::group_exec;
|
||||
case S_IRWXG: ret |= perms::group_all;
|
||||
}
|
||||
switch (mode & S_IRWXO) {
|
||||
case S_IROTH: ret |= perms::others_read;
|
||||
case S_IWOTH: ret |= perms::others_write;
|
||||
case S_IXOTH: ret |= perms::others_exec;
|
||||
case S_IRWXO: ret |= perms::others_all;
|
||||
}
|
||||
if (mode & S_ISUID) {
|
||||
ret |= perms::set_uid;
|
||||
}
|
||||
if (mode & S_ISGID) {
|
||||
ret |= perms::set_gid;
|
||||
}
|
||||
if (mode & S_ISVTX) {
|
||||
ret |= perms::sticky_bit;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static file_type mode_to_type(mode_t mode) {
|
||||
switch (mode & S_IFMT) {
|
||||
case S_IFBLK: return file_type::block;
|
||||
|
@ -71,7 +40,7 @@ OSTD_EXPORT file_mode mode(path const &p) {
|
|||
/* FIXME: throw */
|
||||
abort();
|
||||
}
|
||||
return file_mode{mode_to_type(sb.st_mode), mode_to_perms(sb.st_mode)};
|
||||
return file_mode{mode_to_type(sb.st_mode), perms(sb.st_mode & 07777)};
|
||||
}
|
||||
|
||||
OSTD_EXPORT file_mode symlink_mode(path const &p) {
|
||||
|
@ -83,7 +52,7 @@ OSTD_EXPORT file_mode symlink_mode(path const &p) {
|
|||
/* FIXME: throw */
|
||||
abort();
|
||||
}
|
||||
return file_mode{mode_to_type(sb.st_mode), mode_to_perms(sb.st_mode)};
|
||||
return file_mode{mode_to_type(sb.st_mode), perms(sb.st_mode & 07777)};
|
||||
}
|
||||
|
||||
} /* namespace fs */
|
||||
|
@ -268,6 +237,12 @@ OSTD_EXPORT path temp_path() {
|
|||
return path{"/tmp"};
|
||||
}
|
||||
|
||||
OSTD_EXPORT void current_path(path const &p) {
|
||||
if (chdir(p.string().data())) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
OSTD_EXPORT path absolute(path const &p) {
|
||||
if (p.is_absolute()) {
|
||||
return p;
|
||||
|
@ -338,5 +313,129 @@ OSTD_EXPORT bool equivalent(path const &p1, path const &p2) {
|
|||
return ((sb.st_dev == stdev) && (sb.st_ino == stino));
|
||||
}
|
||||
|
||||
static bool mkdir_p(path const &p, mode_t mode) {
|
||||
if (mkdir(p.string().data(), mode)) {
|
||||
if (errno != EEXIST) {
|
||||
abort();
|
||||
}
|
||||
auto tp = fs::mode(p);
|
||||
if (tp.type() != file_type::directory) {
|
||||
abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
OSTD_EXPORT bool create_directory(path const &p) {
|
||||
return mkdir_p(p, 0777);
|
||||
}
|
||||
|
||||
OSTD_EXPORT bool create_directory(path const &p, path const &ep) {
|
||||
return mkdir_p(p, mode_t(fs::mode(ep).permissions()));
|
||||
}
|
||||
|
||||
OSTD_EXPORT bool create_directories(path const &p) {
|
||||
if (p.has_parent()) {
|
||||
create_directories(p.parent());
|
||||
}
|
||||
return create_directory(p);
|
||||
}
|
||||
|
||||
OSTD_EXPORT bool remove(path const &p) {
|
||||
if (!exists(p)) {
|
||||
return false;
|
||||
}
|
||||
if (::remove(p.string().data())) {
|
||||
abort();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
OSTD_EXPORT std::uintmax_t remove_all(path const &p) {
|
||||
std::uintmax_t ret = 0;
|
||||
if (is_directory(p)) {
|
||||
fs::directory_range ds{p};
|
||||
for (auto &v: ds) {
|
||||
ret += remove_all(v.path());
|
||||
}
|
||||
}
|
||||
ret += remove(p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
OSTD_EXPORT void rename(path const &op, path const &np) {
|
||||
if (::rename(op.string().data(), np.string().data())) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* ugly test for whether nanosecond precision is available in stat
|
||||
* could check for existence of st_mtime macro, but this is more reliable
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
struct test_mtim {
|
||||
template<typename TT, TT> struct test_stat;
|
||||
|
||||
struct fake_stat {
|
||||
struct timespec st_mtim;
|
||||
};
|
||||
|
||||
struct stat_test: fake_stat, T {};
|
||||
|
||||
template<typename TT>
|
||||
static char test(test_stat<struct timespec fake_stat::*, &TT::st_mtim> *);
|
||||
|
||||
template<typename>
|
||||
static int test(...);
|
||||
|
||||
static constexpr bool value = (sizeof(test<stat_test>(0)) == sizeof(int));
|
||||
};
|
||||
|
||||
template<bool B>
|
||||
struct mtime_impl {
|
||||
template<typename S>
|
||||
static file_time_t get(S const &st) {
|
||||
return std::chrono::system_clock::from_time_t(st.st_mtime);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct mtime_impl<true> {
|
||||
template<typename S>
|
||||
static file_time_t get(S const &st) {
|
||||
struct timespec ts = st.st_mtim;
|
||||
auto d = std::chrono::seconds{ts.tv_sec} +
|
||||
std::chrono::nanoseconds{ts.tv_nsec};
|
||||
return file_time_t{
|
||||
std::chrono::duration_cast<std::chrono::system_clock::duration>(d)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using mtime = mtime_impl<test_mtim<struct stat>::value>;
|
||||
|
||||
OSTD_EXPORT file_time_t last_write_time(path const &p) {
|
||||
struct stat sb;
|
||||
if (stat(p.string().data(), &sb) < 0) {
|
||||
abort();
|
||||
}
|
||||
return mtime::get(sb);
|
||||
}
|
||||
|
||||
/* TODO: somehow feature-test for utimensat and fallback to utimes */
|
||||
OSTD_EXPORT void last_write_time(path const &p, file_time_t new_time) {
|
||||
auto d = new_time.time_since_epoch();
|
||||
auto sec = std::chrono::floor<std::chrono::seconds>(d);
|
||||
auto nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(d - sec);
|
||||
struct timespec times[2] = {
|
||||
{0, UTIME_OMIT}, {time_t(sec.count()), long(nsec.count())}
|
||||
};
|
||||
if (utimensat(0, p.string().data(), times, 0)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace fs */
|
||||
} /* namespace ostd */
|
||||
|
|
Loading…
Reference in New Issue