/base/fs.cpp

https://github.com/aseprite/laf · C++ · 313 lines · 239 code · 58 blank · 16 comment · 90 complexity · f819ae5f7627e3c28f44b5017ca8c638 MD5 · raw file

  1. // LAF Base Library
  2. // Copyright (c) 2021 Igara Studio S.A.
  3. // Copyright (c) 2001-2018 David Capello
  4. //
  5. // This file is released under the terms of the MIT license.
  6. // Read LICENSE.txt for more information.
  7. #ifdef HAVE_CONFIG_H
  8. #include "config.h"
  9. #endif
  10. #include "base/fs.h"
  11. #include "base/split_string.h"
  12. #include "base/string.h"
  13. #if LAF_WINDOWS
  14. #include "base/fs_win32.h"
  15. #else
  16. #include "base/fs_unix.h"
  17. #endif
  18. #include <algorithm>
  19. #include <cctype>
  20. #include <cstdlib>
  21. #include <iterator>
  22. namespace base {
  23. #if LAF_WINDOWS
  24. const std::string::value_type path_separator = '\\';
  25. #else
  26. const std::string::value_type path_separator = '/';
  27. #endif
  28. void make_all_directories(const std::string& path)
  29. {
  30. std::vector<std::string> parts;
  31. split_string(path, parts, "/\\");
  32. std::string intermediate;
  33. for (const std::string& component : parts) {
  34. if (component.empty()) {
  35. if (intermediate.empty())
  36. intermediate += "/";
  37. continue;
  38. }
  39. intermediate = join_path(intermediate, component);
  40. if (is_file(intermediate))
  41. throw std::runtime_error("Error creating directory (a component is a file name)");
  42. else if (!is_directory(intermediate))
  43. make_directory(intermediate);
  44. }
  45. }
  46. std::string get_absolute_path(const std::string& filename)
  47. {
  48. std::string fn = filename;
  49. if (fn.size() > 2 &&
  50. #if LAF_WINDOWS
  51. fn[1] != ':'
  52. #else
  53. fn[0] != '/'
  54. #endif
  55. ) {
  56. fn = base::join_path(base::get_current_path(), fn);
  57. }
  58. fn = base::get_canonical_path(fn);
  59. return fn;
  60. }
  61. bool is_path_separator(std::string::value_type chr)
  62. {
  63. return (chr == '\\' || chr == '/');
  64. }
  65. std::string get_file_path(const std::string& filename)
  66. {
  67. std::string::const_reverse_iterator rit;
  68. std::string res;
  69. for (rit=filename.rbegin(); rit!=filename.rend(); ++rit)
  70. if (is_path_separator(*rit))
  71. break;
  72. if (rit != filename.rend()) {
  73. ++rit;
  74. std::copy(filename.begin(), std::string::const_iterator(rit.base()),
  75. std::back_inserter(res));
  76. }
  77. return res;
  78. }
  79. std::string get_file_name(const std::string& filename)
  80. {
  81. std::string::const_reverse_iterator rit;
  82. std::string result;
  83. for (rit=filename.rbegin(); rit!=filename.rend(); ++rit)
  84. if (is_path_separator(*rit))
  85. break;
  86. std::copy(std::string::const_iterator(rit.base()), filename.end(),
  87. std::back_inserter(result));
  88. return result;
  89. }
  90. std::string get_file_extension(const std::string& filename)
  91. {
  92. std::string::const_reverse_iterator rit;
  93. std::string result;
  94. // search for the first dot from the end of the string
  95. for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
  96. if (is_path_separator(*rit))
  97. return result;
  98. else if (*rit == '.')
  99. break;
  100. }
  101. if (rit != filename.rend()) {
  102. std::copy(std::string::const_iterator(rit.base()), filename.end(),
  103. std::back_inserter(result));
  104. }
  105. return result;
  106. }
  107. std::string replace_extension(const std::string& filename, const std::string& extension)
  108. {
  109. std::string::const_reverse_iterator rit;
  110. std::string result;
  111. // Search for the first dot from the end of the string.
  112. for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
  113. // Here is the dot of the extension.
  114. if (*rit == '.')
  115. break;
  116. // A path separator before a dot, i.e. the filename doesn't have a
  117. // extension.
  118. else if (is_path_separator(*rit)) {
  119. rit = filename.rend();
  120. break;
  121. }
  122. }
  123. if (rit != filename.rend()) {
  124. auto it = std::string::const_iterator(rit.base());
  125. --it;
  126. std::copy(filename.begin(), it,
  127. std::back_inserter(result));
  128. }
  129. else {
  130. result = filename;
  131. }
  132. if (!extension.empty()) {
  133. result.push_back('.');
  134. result += extension;
  135. }
  136. return result;
  137. }
  138. std::string get_file_title(const std::string& filename)
  139. {
  140. std::string::const_reverse_iterator rit;
  141. std::string::const_iterator last_dot = filename.end();
  142. std::string result;
  143. for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
  144. if (is_path_separator(*rit))
  145. break;
  146. else if (*rit == '.' && last_dot == filename.end())
  147. last_dot = rit.base()-1;
  148. }
  149. for (std::string::const_iterator it(rit.base()); it!=filename.end(); ++it) {
  150. if (it == last_dot)
  151. break;
  152. else
  153. result.push_back(*it);
  154. }
  155. return result;
  156. }
  157. std::string get_file_title_with_path(const std::string& filename)
  158. {
  159. std::string::const_reverse_iterator rit;
  160. // search for the first dot from the end of the string
  161. for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
  162. if (is_path_separator(*rit))
  163. return filename;
  164. else if (*rit == '.')
  165. break;
  166. }
  167. if (rit != filename.rend())
  168. return filename.substr(0, rit.base() - filename.begin() - 1);
  169. else
  170. return filename;
  171. }
  172. std::string join_path(const std::string& path, const std::string& file)
  173. {
  174. std::string result(path);
  175. // Add a separator at the end if it is necessay
  176. if (!result.empty() && !is_path_separator(*(result.end()-1)))
  177. result.push_back(path_separator);
  178. // Add the file
  179. result += file;
  180. return result;
  181. }
  182. std::string remove_path_separator(const std::string& path)
  183. {
  184. std::string result(path);
  185. // Erase all trailing separators
  186. while (!result.empty() && is_path_separator(*(result.end()-1)))
  187. result.erase(result.end()-1);
  188. return result;
  189. }
  190. std::string fix_path_separators(const std::string& filename)
  191. {
  192. std::string result(filename);
  193. // Replace any separator with the system path separator.
  194. std::replace_if(result.begin(), result.end(),
  195. is_path_separator, path_separator);
  196. return result;
  197. }
  198. std::string normalize_path(const std::string& filename)
  199. {
  200. std::string fn = base::get_canonical_path(filename);
  201. fn = base::fix_path_separators(fn);
  202. return fn;
  203. }
  204. bool has_file_extension(const std::string& filename, const base::paths& extensions)
  205. {
  206. if (!filename.empty()) {
  207. const std::string ext = get_file_extension(filename);
  208. for (const auto& e : extensions)
  209. if (utf8_icmp(ext, e) == 0)
  210. return true;
  211. }
  212. return false;
  213. }
  214. int compare_filenames(const std::string& a, const std::string& b)
  215. {
  216. utf8_const_iterator a_begin(a.begin()), a_end(a.end());
  217. utf8_const_iterator b_begin(b.begin()), b_end(b.end());
  218. utf8_const_iterator a_it(a_begin);
  219. utf8_const_iterator b_it(b_begin);
  220. for (; a_it != a_end && b_it != b_end; ) {
  221. int a_chr = *a_it;
  222. int b_chr = *b_it;
  223. if ((a_chr >= '0') && (a_chr <= '9') && (b_chr >= '0') && (b_chr <= '9')) {
  224. utf8_const_iterator a_it2 = a_it;
  225. utf8_const_iterator b_it2 = b_it;
  226. while (a_it2 != a_end && (*a_it2 >= '0') && (*a_it2 <= '9')) ++a_it2;
  227. while (b_it2 != b_end && (*b_it2 >= '0') && (*b_it2 <= '9')) ++b_it2;
  228. int a_num = std::strtol(std::string(a_it, a_it2).c_str(), NULL, 10);
  229. int b_num = std::strtol(std::string(b_it, b_it2).c_str(), NULL, 10);
  230. if (a_num != b_num)
  231. return a_num - b_num < 0 ? -1: 1;
  232. a_it = a_it2;
  233. b_it = b_it2;
  234. }
  235. else if (is_path_separator(a_chr) && is_path_separator(b_chr)) {
  236. ++a_it;
  237. ++b_it;
  238. }
  239. else {
  240. a_chr = std::tolower(a_chr);
  241. b_chr = std::tolower(b_chr);
  242. if (a_chr != b_chr)
  243. return a_chr - b_chr < 0 ? -1: 1;
  244. ++a_it;
  245. ++b_it;
  246. }
  247. }
  248. if (a_it == a_end && b_it == b_end)
  249. return 0;
  250. else if (a_it == a_end)
  251. return -1;
  252. else
  253. return 1;
  254. }
  255. } // namespace base