1
0

path_ops.py 7.2 KB

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