path_ops.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. """Path operations."""
  2. from __future__ import annotations
  3. import json
  4. import os
  5. import re
  6. import shutil
  7. import stat
  8. from pathlib import Path
  9. from reflex.config import environment, get_config
  10. # Shorthand for join.
  11. join = os.linesep.join
  12. def chmod_rm(path: Path):
  13. """Remove a file or directory with chmod.
  14. Args:
  15. path: The path to the file or directory.
  16. """
  17. path.chmod(stat.S_IWRITE)
  18. if path.is_dir():
  19. shutil.rmtree(path)
  20. elif path.is_file():
  21. path.unlink()
  22. def rm(path: str | Path):
  23. """Remove a file or directory.
  24. Args:
  25. path: The path to the file or directory.
  26. """
  27. path = Path(path)
  28. if path.is_dir():
  29. # In Python 3.12, onerror is deprecated in favor of onexc
  30. shutil.rmtree(path, onerror=lambda _func, _path, _info: chmod_rm(path))
  31. elif path.is_file():
  32. path.unlink()
  33. def cp(
  34. src: str | Path,
  35. dest: str | Path,
  36. overwrite: bool = True,
  37. ignore: tuple[str, ...] | None = None,
  38. ) -> bool:
  39. """Copy a file or directory.
  40. Args:
  41. src: The path to the file or directory.
  42. dest: The path to the destination.
  43. overwrite: Whether to overwrite the destination.
  44. ignore: Ignoring files and directories that match one of the glob-style patterns provided
  45. Returns:
  46. Whether the copy was successful.
  47. """
  48. src, dest = Path(src), Path(dest)
  49. if src == dest:
  50. return False
  51. if not overwrite and dest.exists():
  52. return False
  53. if src.is_dir():
  54. rm(dest)
  55. shutil.copytree(
  56. src,
  57. dest,
  58. ignore=shutil.ignore_patterns(*ignore) if ignore is not None else ignore,
  59. )
  60. else:
  61. shutil.copyfile(src, dest)
  62. return True
  63. def mv(src: str | Path, dest: str | Path, overwrite: bool = True) -> bool:
  64. """Move a file or directory.
  65. Args:
  66. src: The path to the file or directory.
  67. dest: The path to the destination.
  68. overwrite: Whether to overwrite the destination.
  69. Returns:
  70. Whether the move was successful.
  71. """
  72. src, dest = Path(src), Path(dest)
  73. if src == dest:
  74. return False
  75. if not overwrite and dest.exists():
  76. return False
  77. rm(dest)
  78. shutil.move(src, dest)
  79. return True
  80. def mkdir(path: str | Path):
  81. """Create a directory.
  82. Args:
  83. path: The path to the directory.
  84. """
  85. Path(path).mkdir(parents=True, exist_ok=True)
  86. def ls(path: str | Path) -> list[Path]:
  87. """List the contents of a directory.
  88. Args:
  89. path: The path to the directory.
  90. Returns:
  91. A list of paths to the contents of the directory.
  92. """
  93. return list(Path(path).iterdir())
  94. def ln(src: str | Path, dest: str | Path, overwrite: bool = False) -> bool:
  95. """Create a symbolic link.
  96. Args:
  97. src: The path to the file or directory.
  98. dest: The path to the destination.
  99. overwrite: Whether to overwrite the destination.
  100. Returns:
  101. Whether the link was successful.
  102. """
  103. src, dest = Path(src), Path(dest)
  104. if src == dest:
  105. return False
  106. if not overwrite and (dest.exists() or dest.is_symlink()):
  107. return False
  108. if src.is_dir():
  109. rm(dest)
  110. src.symlink_to(dest, target_is_directory=True)
  111. else:
  112. src.symlink_to(dest)
  113. return True
  114. def which(program: str | Path) -> Path | None:
  115. """Find the path to an executable.
  116. Args:
  117. program: The name of the executable.
  118. Returns:
  119. The path to the executable.
  120. """
  121. which_result = shutil.which(program)
  122. return Path(which_result) if which_result else None
  123. def use_system_bun() -> bool:
  124. """Check if the system bun should be used.
  125. Returns:
  126. Whether the system bun should be used.
  127. """
  128. return environment.REFLEX_USE_SYSTEM_BUN.get()
  129. def get_node_bin_path() -> Path | None:
  130. """Get the node binary dir path.
  131. Returns:
  132. The path to the node bin folder.
  133. """
  134. return bin_path.parent.absolute() if (bin_path := get_node_path()) else None
  135. def get_node_path() -> Path | None:
  136. """Get the node binary path.
  137. Returns:
  138. The path to the node binary file.
  139. """
  140. return which("node")
  141. def get_npm_path() -> Path | None:
  142. """Get npm binary path.
  143. Returns:
  144. The path to the npm binary file.
  145. """
  146. return npm_path.absolute() if (npm_path := which("npm")) else None
  147. def get_bun_path() -> Path | None:
  148. """Get bun binary path.
  149. Returns:
  150. The path to the bun binary file.
  151. """
  152. bun_path = get_config().bun_path
  153. if use_system_bun() or not bun_path.exists():
  154. bun_path = which("bun")
  155. return bun_path.absolute() if bun_path else None
  156. def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
  157. """Update the contents of a json file.
  158. Args:
  159. file_path: the path to the JSON file.
  160. update_dict: object to update json.
  161. """
  162. fp = Path(file_path)
  163. # Create the parent directory if it doesn't exist.
  164. fp.parent.mkdir(parents=True, exist_ok=True)
  165. # Create the file if it doesn't exist.
  166. fp.touch(exist_ok=True)
  167. # Create an empty json object if file is empty
  168. fp.write_text("{}") if fp.stat().st_size == 0 else None
  169. # Read the existing json object from the file.
  170. json_object = {}
  171. if fp.stat().st_size:
  172. with fp.open() as f:
  173. json_object = json.load(f)
  174. # Update the json object with the new data.
  175. json_object.update(update_dict)
  176. # Write the updated json object to the file
  177. with fp.open("w") as f:
  178. json.dump(json_object, f, ensure_ascii=False)
  179. def find_replace(directory: str | Path, find: str, replace: str):
  180. """Recursively find and replace text in files in a directory.
  181. Args:
  182. directory: The directory to search.
  183. find: The text to find.
  184. replace: The text to replace.
  185. """
  186. directory = Path(directory)
  187. for root, _dirs, files in os.walk(directory):
  188. for file in files:
  189. filepath = Path(root, file)
  190. text = filepath.read_text(encoding="utf-8")
  191. text = re.sub(find, replace, text)
  192. filepath.write_text(text, encoding="utf-8")
  193. def samefile(file1: Path, file2: Path) -> bool:
  194. """Check if two files are the same.
  195. Args:
  196. file1: The first file.
  197. file2: The second file.
  198. Returns:
  199. Whether the files are the same. If either file does not exist, returns False.
  200. """
  201. if file1.exists() and file2.exists():
  202. return file1.samefile(file2)
  203. return False
  204. def update_directory_tree(src: Path, dest: Path):
  205. """Recursively copies a directory tree from src to dest.
  206. Only copies files if the destination file is missing or modified earlier than the source file.
  207. Args:
  208. src: Source directory
  209. dest: Destination directory
  210. Raises:
  211. ValueError: If the source is not a directory
  212. """
  213. if not src.is_dir():
  214. raise ValueError(f"Source {src} is not a directory")
  215. # Ensure the destination directory exists
  216. dest.mkdir(parents=True, exist_ok=True)
  217. for item in src.iterdir():
  218. dest_item = dest / item.name
  219. if item.is_dir():
  220. # Recursively copy subdirectories
  221. update_directory_tree(item, dest_item)
  222. elif item.is_file() and (
  223. not dest_item.exists() or item.stat().st_mtime > dest_item.stat().st_mtime
  224. ):
  225. # Copy file if it doesn't exist in the destination or is older than the source
  226. shutil.copy2(item, dest_item)