utils_fs.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import os
  2. import re
  3. import shutil
  4. import warnings
  5. def get_path_stem(path):
  6. """
  7. References:
  8. `std::filesystem::path::stem` since C++17
  9. """
  10. return os.path.splitext(os.path.basename(path))[0]
  11. def replace_path_stem(path, new_stem):
  12. dirname, basename = os.path.split(path)
  13. stem, extension = os.path.splitext(basename)
  14. if isinstance(new_stem, str):
  15. return os.path.join(dirname, new_stem + extension)
  16. elif hasattr(new_stem, '__call__'):
  17. return os.path.join(dirname, new_stem(stem) + extension)
  18. else:
  19. raise ValueError('Unsupported Type!')
  20. def get_path_extension(path):
  21. """
  22. References:
  23. `std::filesystem::path::extension` since C++17
  24. Notes:
  25. Not fully consistent with `std::filesystem::path::extension`
  26. """
  27. return os.path.splitext(os.path.basename(path))[1]
  28. def replace_path_extension(path, new_extension=None):
  29. """Replaces the extension with new_extension or removes it when the default value is used.
  30. Firstly, if this path has an extension, it is removed. Then, a dot character is appended
  31. to the pathname, if new_extension is not empty or does not begin with a dot character.
  32. References:
  33. `std::filesystem::path::replace_extension` since C++17
  34. """
  35. filename_wo_ext = os.path.splitext(path)[0]
  36. if new_extension == '' or new_extension is None:
  37. return filename_wo_ext
  38. elif new_extension.startswith('.'):
  39. return ''.join([filename_wo_ext, new_extension])
  40. else:
  41. return '.'.join([filename_wo_ext, new_extension])
  42. def normalize_extension(extension):
  43. if extension.startswith('.'):
  44. new_extension = extension.lower()
  45. else:
  46. new_extension = '.' + extension.lower()
  47. return new_extension
  48. def is_path_in_extensions(path, extensions):
  49. if isinstance(extensions, str):
  50. extensions = [extensions]
  51. extensions = [normalize_extension(item) for item in extensions]
  52. extension = get_path_extension(path)
  53. return extension.lower() in extensions
  54. def normalize_path(path, norm_case=True):
  55. """
  56. References:
  57. https://en.cppreference.com/w/cpp/filesystem/canonical
  58. """
  59. # On Unix and Windows, return the argument with an initial
  60. # component of ~ or ~user replaced by that user's home directory.
  61. path = os.path.expanduser(path)
  62. # Return a normalized absolutized version of the pathname path.
  63. # On most platforms, this is equivalent to calling the function
  64. # normpath() as follows: normpath(join(os.getcwd(), path)).
  65. path = os.path.abspath(path)
  66. if norm_case:
  67. # Normalize the case of a pathname. On Windows,
  68. # convert all characters in the pathname to lowercase,
  69. # and also convert forward slashes to backward slashes.
  70. # On other operating systems, return the path unchanged.
  71. path = os.path.normcase(path)
  72. return path
  73. def makedirs(name, mode=0o755):
  74. """
  75. References:
  76. mmcv.mkdir_or_exist
  77. """
  78. if name == '':
  79. return
  80. name = os.path.expanduser(name)
  81. os.makedirs(name, mode=mode, exist_ok=True)
  82. def listdirs(paths, path_sep=None, full_path=True):
  83. """Enhancement on `os.listdir`
  84. """
  85. assert isinstance(paths, (str, tuple, list))
  86. if isinstance(paths, str):
  87. path_sep = path_sep or os.path.pathsep
  88. paths = paths.split(path_sep)
  89. all_filenames = []
  90. for path in paths:
  91. path_ex = os.path.expanduser(path)
  92. filenames = os.listdir(path_ex)
  93. if full_path:
  94. filenames = [os.path.join(path_ex, filename) for filename in filenames]
  95. all_filenames.extend(filenames)
  96. return all_filenames
  97. def get_all_filenames(path, extensions=None, is_valid_file=None):
  98. if (extensions is not None) and (is_valid_file is not None):
  99. raise ValueError("Both extensions and is_valid_file cannot "
  100. "be not None at the same time")
  101. if is_valid_file is None:
  102. if extensions is not None:
  103. def is_valid_file(filename):
  104. return is_path_in_extensions(filename, extensions)
  105. else:
  106. def is_valid_file(filename):
  107. return True
  108. all_filenames = []
  109. path_ex = os.path.expanduser(path)
  110. for root, _, filenames in sorted(os.walk(path_ex, followlinks=True)):
  111. for filename in sorted(filenames):
  112. fullname = os.path.join(root, filename)
  113. if is_valid_file(fullname):
  114. all_filenames.append(fullname)
  115. return all_filenames
  116. def get_top_level_dirs(path, full_path=True):
  117. if path is None:
  118. path = os.getcwd()
  119. path_ex = os.path.expanduser(path)
  120. filenames = os.listdir(path_ex)
  121. if full_path:
  122. return [os.path.join(path_ex, item) for item in filenames
  123. if os.path.isdir(os.path.join(path_ex, item))]
  124. else:
  125. return [item for item in filenames
  126. if os.path.isdir(os.path.join(path_ex, item))]
  127. def get_top_level_files(path, full_path=True):
  128. if path is None:
  129. path = os.getcwd()
  130. path_ex = os.path.expanduser(path)
  131. filenames = os.listdir(path_ex)
  132. if full_path:
  133. return [os.path.join(path_ex, item) for item in filenames
  134. if os.path.isfile(os.path.join(path_ex, item))]
  135. else:
  136. return [item for item in filenames
  137. if os.path.isfile(os.path.join(path_ex, item))]
  138. def get_folder_size(dirname):
  139. if not os.path.exists(dirname):
  140. raise ValueError("Incorrect path: {}".format(dirname))
  141. total_size = 0
  142. for root, _, filenames in os.walk(dirname):
  143. for name in filenames:
  144. total_size += os.path.getsize(os.path.join(root, name))
  145. return total_size
  146. def escape_filename(filename, new_char='_'):
  147. assert isinstance(new_char, str)
  148. control_chars = ''.join((map(chr, range(0x00, 0x20))))
  149. pattern = r'[\\/*?:"<>|{}]'.format(control_chars)
  150. return re.sub(pattern, new_char, filename)
  151. def replace_invalid_filename_char(filename, new_char='_'):
  152. warnings.warn('replace_invalid_filename_char will be deprecated, use escape_filename instead!')
  153. return escape_filename(filename, new_char)
  154. def copy_file(src, dst_dir, action_if_exist='rename'):
  155. """
  156. Args:
  157. src: source file path
  158. dst_dir: dest dir
  159. action_if_exist:
  160. None: same as shutil.copy
  161. ignore: when dest file exists, don't copy and return None
  162. rename: when dest file exists, copy after rename
  163. Returns:
  164. dest filename
  165. """
  166. dst = os.path.join(dst_dir, os.path.basename(src))
  167. if action_if_exist is None:
  168. os.makedirs(dst_dir, exist_ok=True)
  169. shutil.copy(src, dst)
  170. elif action_if_exist.lower() == 'ignore':
  171. if os.path.exists(dst):
  172. warnings.warn(f'{dst} already exists, do not copy!')
  173. return dst
  174. os.makedirs(dst_dir, exist_ok=True)
  175. shutil.copy(src, dst)
  176. elif action_if_exist.lower() == 'rename':
  177. suffix = 2
  178. stem, extension = os.path.splitext(os.path.basename(src))
  179. while os.path.exists(dst):
  180. dst = os.path.join(dst_dir, f'{stem} ({suffix}){extension}')
  181. suffix += 1
  182. os.makedirs(dst_dir, exist_ok=True)
  183. shutil.copy(src, dst)
  184. else:
  185. raise ValueError('Invalid action_if_exist, got {}.'.format(action_if_exist))
  186. return dst
  187. def move_file(src, dst_dir, action_if_exist='rename'):
  188. """
  189. Args:
  190. src: source file path
  191. dst_dir: dest dir
  192. action_if_exist:
  193. None: same as shutil.move
  194. ignore: when dest file exists, don't move and return None
  195. rename: when dest file exists, move after rename
  196. Returns:
  197. dest filename
  198. """
  199. dst = os.path.join(dst_dir, os.path.basename(src))
  200. if action_if_exist is None:
  201. os.makedirs(dst_dir, exist_ok=True)
  202. shutil.move(src, dst)
  203. elif action_if_exist.lower() == 'ignore':
  204. if os.path.exists(dst):
  205. warnings.warn(f'{dst} already exists, do not move!')
  206. return dst
  207. os.makedirs(dst_dir, exist_ok=True)
  208. shutil.move(src, dst)
  209. elif action_if_exist.lower() == 'rename':
  210. suffix = 2
  211. stem, extension = os.path.splitext(os.path.basename(src))
  212. while os.path.exists(dst):
  213. dst = os.path.join(dst_dir, f'{stem} ({suffix}){extension}')
  214. suffix += 1
  215. os.makedirs(dst_dir, exist_ok=True)
  216. shutil.move(src, dst)
  217. else:
  218. raise ValueError('Invalid action_if_exist, got {}.'.format(action_if_exist))
  219. return dst
  220. def rename_file(src, dst, action_if_exist='rename'):
  221. """
  222. Args:
  223. src: source file path
  224. dst: dest file path
  225. action_if_exist:
  226. None: same as os.rename
  227. ignore: when dest file exists, don't rename and return None
  228. rename: when dest file exists, rename it
  229. Returns:
  230. dest filename
  231. """
  232. if dst == src:
  233. return dst
  234. dst_dir = os.path.dirname(os.path.abspath(dst))
  235. if action_if_exist is None:
  236. os.makedirs(dst_dir, exist_ok=True)
  237. os.rename(src, dst)
  238. elif action_if_exist.lower() == 'ignore':
  239. if os.path.exists(dst):
  240. warnings.warn(f'{dst} already exists, do not rename!')
  241. return dst
  242. os.makedirs(dst_dir, exist_ok=True)
  243. os.rename(src, dst)
  244. elif action_if_exist.lower() == 'rename':
  245. suffix = 2
  246. stem, extension = os.path.splitext(os.path.basename(dst))
  247. while os.path.exists(dst):
  248. dst = os.path.join(dst_dir, f'{stem} ({suffix}){extension}')
  249. suffix += 1
  250. os.makedirs(dst_dir, exist_ok=True)
  251. os.rename(src, dst)
  252. else:
  253. raise ValueError('Invalid action_if_exist, got {}.'.format(action_if_exist))
  254. return dst