utils_fs.py 8.9 KB

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