2017-04-05 18:53:14 +00:00
|
|
|
/** @addtogroup Utilities
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @file filesystem.hh
|
|
|
|
*
|
|
|
|
* @brief Filesystem abstraction module.
|
|
|
|
*
|
|
|
|
* This module defines the namespace ostd::filesystem, which is either
|
|
|
|
* std::experimental::filesystem or std::filesystem depending on which
|
|
|
|
* is available. For portable applications, only use the subset of the
|
|
|
|
* module covered by both versions.
|
|
|
|
*
|
|
|
|
* It also provides range integration for directory iterators and
|
|
|
|
* ostd::format_traits specialization for std::filesystem::path.
|
2015-09-04 17:25:17 +00:00
|
|
|
*
|
2017-06-10 15:47:26 +00:00
|
|
|
* Additionally, it implements glob matching following POSIX with its
|
|
|
|
* own extensions (mainly recursive glob matching via `**`).
|
|
|
|
*
|
2017-05-03 00:14:27 +00:00
|
|
|
* @include listdir.cc
|
|
|
|
*
|
2017-04-05 18:53:14 +00:00
|
|
|
* @copyright See COPYING.md in the project tree for further information.
|
2015-09-04 17:25:17 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef OSTD_FILESYSTEM_HH
|
|
|
|
#define OSTD_FILESYSTEM_HH
|
|
|
|
|
2017-06-10 15:47:26 +00:00
|
|
|
#include <utility>
|
|
|
|
|
2017-03-10 17:19:59 +00:00
|
|
|
#if __has_include(<filesystem>)
|
|
|
|
# include <filesystem>
|
|
|
|
namespace ostd {
|
|
|
|
namespace filesystem = std::filesystem;
|
|
|
|
}
|
|
|
|
#elif __has_include(<experimental/filesystem>)
|
|
|
|
# include <experimental/filesystem>
|
|
|
|
namespace ostd {
|
|
|
|
namespace filesystem = std::experimental::filesystem;
|
|
|
|
}
|
2016-07-06 17:31:21 +00:00
|
|
|
#else
|
2017-03-10 17:19:59 +00:00
|
|
|
# error "Unsupported platform"
|
2015-09-06 15:07:14 +00:00
|
|
|
#endif
|
2015-09-04 17:25:17 +00:00
|
|
|
|
|
|
|
#include "ostd/range.hh"
|
2017-03-10 17:19:59 +00:00
|
|
|
#include "ostd/format.hh"
|
2015-09-04 17:25:17 +00:00
|
|
|
|
|
|
|
namespace ostd {
|
|
|
|
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @addtogroup Utilities
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @brief Range integration for std::filesystem::directory_iterator.
|
|
|
|
*
|
|
|
|
* Allows directory iterators to be made into ranges via ostd::iter().
|
|
|
|
*
|
|
|
|
* @see ostd::ranged_traits<filesystem::recursive_directory_iterator>
|
|
|
|
*/
|
2017-03-10 17:19:59 +00:00
|
|
|
template<>
|
|
|
|
struct ranged_traits<filesystem::directory_iterator> {
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief The range type for the iterator. */
|
2017-03-10 17:19:59 +00:00
|
|
|
using range = iterator_range<filesystem::directory_iterator>;
|
2015-09-05 02:35:07 +00:00
|
|
|
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief Creates a range for the iterator. */
|
2017-03-10 17:19:59 +00:00
|
|
|
static range iter(filesystem::directory_iterator const &r) {
|
|
|
|
return range{filesystem::begin(r), filesystem::end(r)};
|
2015-09-05 02:35:07 +00:00
|
|
|
}
|
2015-09-04 17:25:17 +00:00
|
|
|
};
|
|
|
|
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief Range integration for std::filesystem::recursive_directory_iterator.
|
|
|
|
*
|
|
|
|
* Allows recursive directory iterators to be made into ranges via ostd::iter().
|
|
|
|
*
|
|
|
|
* @see ostd::ranged_traits<filesystem::directory_iterator>
|
|
|
|
*/
|
2017-03-10 17:19:59 +00:00
|
|
|
template<>
|
|
|
|
struct ranged_traits<filesystem::recursive_directory_iterator> {
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief The range type for the iterator. */
|
2017-03-10 17:19:59 +00:00
|
|
|
using range = iterator_range<filesystem::recursive_directory_iterator>;
|
2015-09-08 00:03:56 +00:00
|
|
|
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief Creates a range for the iterator. */
|
2017-03-10 17:19:59 +00:00
|
|
|
static range iter(filesystem::recursive_directory_iterator const &r) {
|
|
|
|
return range{filesystem::begin(r), filesystem::end(r)};
|
2015-09-08 00:03:56 +00:00
|
|
|
}
|
|
|
|
};
|
2015-09-04 17:25:17 +00:00
|
|
|
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief ostd::format_traits specialization for std::filesystem::path.
|
|
|
|
*
|
|
|
|
* This allows paths to be formatted as strings. The value is formatted
|
|
|
|
* as if `path.string()` was formatted, using the exact ostd::format_spec.
|
|
|
|
*/
|
2017-03-10 17:19:59 +00:00
|
|
|
template<>
|
|
|
|
struct format_traits<filesystem::path> {
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @brief Formats the path's string value.
|
|
|
|
*
|
|
|
|
* This calls exactly
|
|
|
|
*
|
|
|
|
* ~~~{.cc}
|
|
|
|
* fs.format_value(writer, p.string());
|
|
|
|
* ~~~
|
|
|
|
*/
|
2017-03-10 17:19:59 +00:00
|
|
|
template<typename R>
|
|
|
|
static void to_format(
|
|
|
|
filesystem::path const &p, R &writer, format_spec const &fs
|
|
|
|
) {
|
|
|
|
fs.format_value(writer, p.string());
|
2015-09-04 17:25:17 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-06-10 15:47:26 +00:00
|
|
|
namespace detail {
|
2017-06-10 22:02:16 +00:00
|
|
|
inline typename filesystem::path::value_type const *glob_match_brackets(
|
|
|
|
typename filesystem::path::value_type match,
|
|
|
|
typename filesystem::path::value_type const *wp
|
2017-06-11 16:09:57 +00:00
|
|
|
) noexcept {
|
2017-06-10 22:02:16 +00:00
|
|
|
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;
|
|
|
|
bool within = ((match >= c) && (match <= lc));
|
|
|
|
if (neg ? !within : within) {
|
|
|
|
return eb;
|
|
|
|
}
|
|
|
|
c = *wp++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* single-char match */
|
|
|
|
if (neg ? (match != c) : (match == c)) {
|
|
|
|
return eb;
|
|
|
|
}
|
|
|
|
c = *wp++;
|
|
|
|
} while (c != ']');
|
|
|
|
|
|
|
|
/* loop ended, so no match */
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-06-10 18:30:48 +00:00
|
|
|
inline bool glob_match_filename_impl(
|
2017-06-10 15:47:26 +00:00
|
|
|
typename filesystem::path::value_type const *fname,
|
|
|
|
typename filesystem::path::value_type const *wname
|
2017-06-11 16:09:57 +00:00
|
|
|
) noexcept {
|
2017-06-10 15:47:26 +00:00
|
|
|
/* skip any matching prefix if present */
|
2017-06-10 17:18:07 +00:00
|
|
|
while (*wname && (*wname != '*')) {
|
|
|
|
if (!*wname || (*wname == '*')) {
|
|
|
|
break;
|
|
|
|
}
|
2017-06-10 22:02:16 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2017-06-10 17:18:07 +00:00
|
|
|
if ((*wname == '?') && *fname) {
|
|
|
|
++wname;
|
|
|
|
++fname;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (*fname++ != *wname++) {
|
|
|
|
return false;
|
2017-06-10 15:47:26 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-10 17:03:58 +00:00
|
|
|
/* skip * wildcards; a wildcard matches 0 or more */
|
2017-06-10 15:47:26 +00:00
|
|
|
if (*wname == '*') {
|
|
|
|
while (*wname == '*') {
|
|
|
|
++wname;
|
|
|
|
}
|
|
|
|
/* was trailing so everything matches */
|
|
|
|
if (!*wname) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* prefix skipping matched entire filename */
|
|
|
|
if (!*fname) {
|
|
|
|
return true;
|
|
|
|
}
|
2017-06-10 16:21:11 +00:00
|
|
|
/* empty pattern and non-empty filename */
|
|
|
|
if (!*wname) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-10 15:47:26 +00:00
|
|
|
/* keep incrementing filename until it matches */
|
|
|
|
while (*fname) {
|
2017-06-10 18:30:48 +00:00
|
|
|
if (glob_match_filename_impl(fname, wname)) {
|
2017-06-10 15:47:26 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
++fname;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-10 18:30:48 +00:00
|
|
|
} /* namespace detail */
|
2017-06-10 15:47:26 +00:00
|
|
|
|
2017-06-11 16:09:57 +00:00
|
|
|
/** @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().
|
|
|
|
*/
|
2017-06-10 18:30:48 +00:00
|
|
|
inline bool glob_match_filename(
|
|
|
|
filesystem::path const &filename, filesystem::path const &pattern
|
2017-06-11 16:09:57 +00:00
|
|
|
) noexcept {
|
2017-06-10 18:30:48 +00:00
|
|
|
return detail::glob_match_filename_impl(filename.c_str(), pattern.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace detail {
|
2017-06-10 15:47:26 +00:00
|
|
|
template<typename OR>
|
|
|
|
inline void glob_match_impl(
|
|
|
|
OR &out,
|
|
|
|
typename filesystem::path::iterator beg,
|
|
|
|
typename filesystem::path::iterator &end,
|
|
|
|
filesystem::path pre
|
|
|
|
) {
|
|
|
|
while (beg != end) {
|
|
|
|
auto cur = *beg;
|
|
|
|
auto *cs = cur.c_str();
|
|
|
|
/* this part of the path might contain wildcards */
|
|
|
|
for (auto c = *cs; c; c = *++cs) {
|
2017-06-10 16:57:58 +00:00
|
|
|
/* ** as a name does recursive expansion */
|
|
|
|
if ((c == '*') && (*(cs + 1) == '*') && !*(cs + 2)) {
|
2017-06-10 15:47:26 +00:00
|
|
|
++beg;
|
2017-06-10 18:26:34 +00:00
|
|
|
auto ip = pre.empty() ? "." : pre;
|
|
|
|
if (!filesystem::is_directory(ip)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-06-10 16:57:58 +00:00
|
|
|
filesystem::recursive_directory_iterator it{ip};
|
|
|
|
for (auto &de: it) {
|
|
|
|
/* followed by more path, only consider dirs */
|
|
|
|
auto dp = de.path();
|
|
|
|
if ((beg != end) && !filesystem::is_directory(dp)) {
|
|
|
|
continue;
|
2017-06-10 15:47:26 +00:00
|
|
|
}
|
2017-06-10 16:57:58 +00:00
|
|
|
/* otherwise also match files */
|
2017-06-10 19:17:58 +00:00
|
|
|
if (pre.empty()) {
|
|
|
|
/* avoid ugly formatting, sadly we have to do
|
|
|
|
* with experimental fs api so no better way...
|
|
|
|
*/
|
|
|
|
auto dpb = dp.begin(), dpe = dp.end();
|
|
|
|
if (*dpb == ".") {
|
|
|
|
++dpb;
|
|
|
|
}
|
|
|
|
filesystem::path ap;
|
|
|
|
while (dpb != dpe) {
|
|
|
|
ap /= *dpb;
|
|
|
|
++dpb;
|
|
|
|
}
|
|
|
|
glob_match_impl(out, beg, end, ap);
|
|
|
|
} else {
|
|
|
|
glob_match_impl(out, beg, end, dp);
|
|
|
|
}
|
2017-06-10 16:57:58 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2017-06-10 22:02:16 +00:00
|
|
|
/* wildcards *, ?, [...] */
|
|
|
|
if ((c == '*') || (c == '?') || (c == '[')) {
|
2017-06-10 16:57:58 +00:00
|
|
|
++beg;
|
2017-06-10 18:26:34 +00:00
|
|
|
auto ip = pre.empty() ? "." : pre;
|
|
|
|
if (!filesystem::is_directory(ip)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-06-10 16:57:58 +00:00
|
|
|
filesystem::directory_iterator it{ip};
|
|
|
|
for (auto &de: it) {
|
2017-06-10 19:17:58 +00:00
|
|
|
auto p = de.path().filename();
|
2017-06-10 18:30:48 +00:00
|
|
|
if (!glob_match_filename(p, cur)) {
|
2017-06-10 16:57:58 +00:00
|
|
|
continue;
|
2017-06-10 15:47:26 +00:00
|
|
|
}
|
2017-06-10 19:17:58 +00:00
|
|
|
glob_match_impl(out, beg, end, pre / p);
|
2017-06-10 15:47:26 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pre /= cur;
|
|
|
|
++beg;
|
|
|
|
}
|
|
|
|
out.put(pre);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-11 16:09:57 +00:00
|
|
|
/** @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, filesystem::path const &path
|
|
|
|
) {
|
2017-06-10 15:47:26 +00:00
|
|
|
auto pend = path.end();
|
|
|
|
detail::glob_match_impl(out, path.begin(), pend, filesystem::path{});
|
2017-06-11 16:09:57 +00:00
|
|
|
return std::forward<OutputRange>(out);
|
2017-06-10 15:47:26 +00:00
|
|
|
}
|
|
|
|
|
2017-04-05 18:53:14 +00:00
|
|
|
/** @} */
|
|
|
|
|
2015-09-04 17:25:17 +00:00
|
|
|
} /* namespace ostd */
|
|
|
|
|
2016-02-07 21:17:15 +00:00
|
|
|
#endif
|
2017-04-05 18:53:14 +00:00
|
|
|
|
|
|
|
/** @} */
|