ctrl-utils
filesystem.h
1 
10 #if __cplusplus >= 201703L && (!__GNUG__ || __GNUC__ > 7)
11 #include <filesystem> // std::filesystem
12 #else // __cplusplus
13 
14 #ifndef CTRL_UTILS_FILESYSTEM_H_
15 #define CTRL_UTILS_FILESYSTEM_H_
16 
17 #include <exception> // std::system_error
18 #include <memory> // std::shared_ptr
19 #include <regex> // std::regex, std::regex_replace
20 #include <string> // std::stringstream
21 #include <iostream> // std::basic_ostream
22 
23 #include <dirent.h> // DIR, dirent, opendir, readdir, closedir
24 #include <errno.h> // errno
25 #include <stdlib.h> // realpath
26 #include <string.h> // strcmp
27 #include <unistd.h> // access, getcwd
28 
29 namespace std {
30 namespace filesystem {
31 
32 class filesystem_error : public system_error {
33 };
34 
35 class path {
36 
37  public:
38 
39  path() = default;
40 
41  path(const char* source) : str_(normalize(source)) {}
42 
43  path(const std::string& source) : str_(normalize(source)) {}
44 
45  const char* c_str() const { return str_.c_str(); }
46 
47  const std::string& string() const { return str_; }
48 
49  bool empty() const { return str_.empty(); }
50 
51  path parent_path() const { return str_.substr(0, str_.find_last_of("/\\") + 1); };
52 
53  path stem() const {
54  const size_t idx_start = str_.find_last_of("/\\") + 1;
55  const size_t idx_end = str_.find_last_of(".");
56  if (idx_start == idx_end) {
57  return str_.substr(idx_start);
58  } else {
59  return str_.substr(idx_start, idx_end - idx_start);
60  }
61  }
62 
63  path filename() const {
64  const size_t idx_start = str_.find_last_of("/\\") + 1;
65  return str_.substr(idx_start);
66  }
67 
68  bool operator==(const path& other) const { return str_ == other.str_; }
69 
70  private:
71 
72  static std::string normalize(const std::string& source) {
73  std::string result = std::regex_replace(source, std::regex("/+"), "/");
74  if (!result.empty() && result.back() == '.') {
75  result += "/"; // Append / after . for simpler regex
76  }
77  result = std::regex_replace(result, std::regex("/\\./"), "/");
78  result = std::regex_replace(result, std::regex("^\\./"), "");
79  std::string result_new = std::regex_replace(result, std::regex("[^/\\.]+/\\.\\./"), "");
80  while (result_new != result) {
81  result = result_new;
82  result_new = std::regex_replace(result, std::regex("[^/\\.]+/\\.\\./"), "");
83  }
84  result = std::regex_replace(result, std::regex("^/\\.\\."), "/");
85  result = std::regex_replace(result, std::regex("^\\.\\./$"), "..");
86  if (result.empty()) {
87  result = ".";
88  }
89  return result;
90  }
91 
92  std::string str_;
93 
94 };
95 
96 inline path operator/(const path& a, const path& b) {
97  return a.string() + "/" + b.string();
98 }
99 
100 template<class CharT, class Traits>
101 basic_ostream<CharT, Traits>& operator<<(basic_ostream<CharT, Traits>& os, const path& p) {
102  os << p.string();
103 }
104 
106 
107  public:
108 
109  directory_entry() = default;
110 
111  directory_entry(const path& p) : path_(p) {}
112 
113  const filesystem::path& path() const { return path_; }
114 
115  void assign(const filesystem::path& p) { path_ = p; }
116 
117  bool operator==(const directory_entry& other) const { return path_ == other.path_; }
118 
119  private:
120 
121  filesystem::path path_;
122 
123 };
124 
126 
127  public:
128 
129  using value_type = directory_entry;
130  using difference_type = ptrdiff_t;
131  using pointer = const value_type*;
132  using reference = const value_type&;
133  using iterator_category = std::input_iterator_tag;
134 
135  directory_iterator() = default;
136 
138  : path_(other.path_), entry_(other.entry_), dp_(other.dp_), dirp_(other.dirp_) {}
139 
140  directory_iterator(const path& p)
141  : path_(p) {
142  dp_ = std::shared_ptr<DIR>(opendir(path_.c_str()), closedir);
143  if (!dp_) {
144  throw runtime_error("directory_iterator: invalid path " + path_.string());
145  }
146  dirp_ = readdir(dp_.get());
147  while ((dirp_ != nullptr &&
148  strcmp(dirp_->d_name, ".") == 0) || strcmp(dirp_->d_name, "..") == 0) {
149  dirp_ = readdir(dp_.get());
150  }
151  if (dirp_ != nullptr) {
152  entry_.assign(path(path_.string() + "/" + std::string(dirp_->d_name)));
153  }
154  }
155 
156  directory_iterator& operator++() {
157  dirp_ = readdir(dp_.get());
158  entry_ = dirp_ == nullptr ? path() : path(path_.string() + "/" + std::string(dirp_->d_name));
159  return *this;
160  }
161 
162  bool operator==(const directory_iterator& other) const {
163  if (entry_.path().empty() && other.entry_.path().empty()) return true;
164  return path_ == other.path_ && entry_ == other.entry_;
165  }
166  bool operator!=(const directory_iterator& other) const { return !(*this == other); }
167 
168  reference operator*() const { return entry_; }
169  pointer operator->() const { return &entry_; }
170 
171  private:
172 
173  path path_;
174  directory_entry entry_;
175 
176  std::shared_ptr<DIR> dp_;
177  struct dirent* dirp_ = nullptr;
178 
179 };
180 
181 inline directory_iterator begin(directory_iterator it) {
182  return it;
183 }
184 
185 inline directory_iterator end(const directory_iterator& it) {
186  return directory_iterator();
187 }
188 
189 inline bool exists(const path& p) {
190  return access(p.c_str(), F_OK) == 0;
191 }
192 
193 inline path current_path() {
194  char* c_path = getcwd(nullptr, 0);
195  path p(c_path);
196  free(c_path);
197  return p;
198 }
199 
200 inline path absolute_path(const path& p) {
201  char* c_path = realpath(p.c_str(), nullptr);
202  path p_abs(c_path);
203  free(c_path);
204  return p_abs;
205 }
206 
207 } // namespace filesystem
208 } // namespace std
209 
210 #endif // CTRL_UTILS_FILESYSTEM_H_
211 
212 #endif // __cplusplus
Definition: filesystem.h:105
Definition: filesystem.h:125
Definition: filesystem.h:32
Definition: filesystem.h:35
Definition: chrono.h:15