path_ops.py 7.3 KB

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