hutool-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. hutool/__init__.py +174 -0
  2. hutool/cache/__init__.py +7 -0
  3. hutool/cache/cache_util.py +47 -0
  4. hutool/cache/fifo_cache.py +87 -0
  5. hutool/cache/lfu_cache.py +129 -0
  6. hutool/cache/lru_cache.py +93 -0
  7. hutool/cache/timed_cache.py +115 -0
  8. hutool/captcha/__init__.py +3 -0
  9. hutool/captcha/captcha_util.py +215 -0
  10. hutool/core/__init__.py +23 -0
  11. hutool/core/_base.py +61 -0
  12. hutool/core/bean.py +214 -0
  13. hutool/core/codec.py +111 -0
  14. hutool/core/coll.py +635 -0
  15. hutool/core/date.py +1024 -0
  16. hutool/core/exceptions.py +66 -0
  17. hutool/core/io/__init__.py +0 -0
  18. hutool/core/io/data_size_util.py +79 -0
  19. hutool/core/io/file_name_util.py +111 -0
  20. hutool/core/io/file_util.py +650 -0
  21. hutool/core/io/io_util.py +133 -0
  22. hutool/core/io/path_util.py +247 -0
  23. hutool/core/io/resource_util.py +137 -0
  24. hutool/core/map.py +933 -0
  25. hutool/core/math_util.py +105 -0
  26. hutool/core/net.py +288 -0
  27. hutool/core/text/__init__.py +0 -0
  28. hutool/core/text/csv_util.py +54 -0
  29. hutool/core/text/str_builder.py +224 -0
  30. hutool/core/text/unicode_util.py +58 -0
  31. hutool/core/tree.py +242 -0
  32. hutool/core/util/__init__.py +63 -0
  33. hutool/core/util/array_util.py +503 -0
  34. hutool/core/util/boolean_util.py +124 -0
  35. hutool/core/util/charset_util.py +60 -0
  36. hutool/core/util/class_util.py +136 -0
  37. hutool/core/util/coordinate_util.py +186 -0
  38. hutool/core/util/credit_code_util.py +110 -0
  39. hutool/core/util/desensitized_util.py +194 -0
  40. hutool/core/util/enum_util.py +94 -0
  41. hutool/core/util/escape_util.py +97 -0
  42. hutool/core/util/hash_util.py +243 -0
  43. hutool/core/util/hex_util.py +140 -0
  44. hutool/core/util/id_util.py +147 -0
  45. hutool/core/util/idcard_util.py +300 -0
  46. hutool/core/util/number_util.py +720 -0
  47. hutool/core/util/object_util.py +294 -0
  48. hutool/core/util/page_util.py +61 -0
  49. hutool/core/util/phone_util.py +140 -0
  50. hutool/core/util/random_util.py +112 -0
  51. hutool/core/util/re_util.py +231 -0
  52. hutool/core/util/reflect_util.py +135 -0
  53. hutool/core/util/runtime_util.py +89 -0
  54. hutool/core/util/str_util.py +2320 -0
  55. hutool/core/util/system_util.py +62 -0
  56. hutool/core/util/url_util.py +232 -0
  57. hutool/core/util/version_util.py +41 -0
  58. hutool/core/util/xml_util.py +158 -0
  59. hutool/core/util/zip_util.py +126 -0
  60. hutool/cron/__init__.py +4 -0
  61. hutool/cron/cron_pattern.py +123 -0
  62. hutool/cron/cron_util.py +115 -0
  63. hutool/crypto/__init__.py +5 -0
  64. hutool/crypto/digest_util.py +167 -0
  65. hutool/crypto/secure_util.py +311 -0
  66. hutool/crypto/sign_util.py +74 -0
  67. hutool/dfa/__init__.py +3 -0
  68. hutool/dfa/sensitive_util.py +114 -0
  69. hutool/extra/__init__.py +6 -0
  70. hutool/extra/emoji_util.py +90 -0
  71. hutool/extra/pinyin_util.py +44 -0
  72. hutool/extra/qr_code_util.py +58 -0
  73. hutool/extra/template_util.py +41 -0
  74. hutool/http/__init__.py +6 -0
  75. hutool/http/html_util.py +88 -0
  76. hutool/http/http_request.py +188 -0
  77. hutool/http/http_response.py +139 -0
  78. hutool/http/http_util.py +237 -0
  79. hutool/json_util.py +251 -0
  80. hutool/jwt_util.py +57 -0
  81. hutool/setting/__init__.py +5 -0
  82. hutool/setting/props_util.py +79 -0
  83. hutool/setting/setting_util.py +80 -0
  84. hutool/setting/yaml_util.py +45 -0
  85. hutool_python-1.0.0.dist-info/LICENSE +127 -0
  86. hutool_python-1.0.0.dist-info/METADATA +438 -0
  87. hutool_python-1.0.0.dist-info/RECORD +89 -0
  88. hutool_python-1.0.0.dist-info/WHEEL +5 -0
  89. hutool_python-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,650 @@
1
+ """
2
+ 文件工具类,对应 Java cn.hutool.core.io.FileUtil
3
+
4
+ 基于 pathlib.Path 实现,辅以 os 和 shutil。
5
+ """
6
+
7
+ import os
8
+ import shutil
9
+ import tempfile
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Callable, List, Optional, Union
13
+
14
+
15
+ class FileUtil:
16
+ """文件工具类,对应 Java cn.hutool.core.io.FileUtil"""
17
+
18
+ FILE_SEPARATOR: str = os.sep
19
+
20
+ # ------------------------------------------------------------------
21
+ # 判断
22
+ # ------------------------------------------------------------------
23
+
24
+ @staticmethod
25
+ def is_windows() -> bool:
26
+ """判断当前操作系统是否为 Windows"""
27
+ return os.name == "nt"
28
+
29
+ @staticmethod
30
+ def exist(path: Union[str, Path]) -> bool:
31
+ """判断文件或目录是否存在"""
32
+ return Path(path).exists()
33
+
34
+ @staticmethod
35
+ def is_dir(path: Union[str, Path]) -> bool:
36
+ """判断是否为目录"""
37
+ return Path(path).is_dir()
38
+
39
+ @staticmethod
40
+ def is_file(path: Union[str, Path]) -> bool:
41
+ """判断是否为文件"""
42
+ return Path(path).is_file()
43
+
44
+ @staticmethod
45
+ def is_absolute(path: Union[str, Path]) -> bool:
46
+ """判断路径是否为绝对路径"""
47
+ return Path(path).is_absolute()
48
+
49
+ @staticmethod
50
+ def is_empty(path: Union[str, Path]) -> bool:
51
+ """文件或目录是否为空
52
+
53
+ - 文件:大小为 0 视为空。
54
+ - 目录:不含任何子项视为空。
55
+ - 路径不存在视为空。
56
+ """
57
+ p = Path(path)
58
+ if not p.exists():
59
+ return True
60
+ if p.is_file():
61
+ return p.stat().st_size == 0
62
+ if p.is_dir():
63
+ # 如果能列出任何子项则非空
64
+ return not any(p.iterdir())
65
+ return True
66
+
67
+ # ------------------------------------------------------------------
68
+ # 创建
69
+ # ------------------------------------------------------------------
70
+
71
+ @staticmethod
72
+ def file(*names: str) -> Path:
73
+ """根据多个名称段构建文件路径
74
+
75
+ 例如: FileUtil.file("home", "user", "test.txt")
76
+ """
77
+ if not names:
78
+ raise ValueError("至少需要一个路径段")
79
+ result = Path(names[0])
80
+ for name in names[1:]:
81
+ result = result / name
82
+ return result
83
+
84
+ @staticmethod
85
+ def touch(path: Union[str, Path]) -> Path:
86
+ """创建文件(包括父目录)
87
+
88
+ 如果文件已存在则只更新访问/修改时间。
89
+ """
90
+ p = Path(path)
91
+ p.parent.mkdir(parents=True, exist_ok=True)
92
+ p.touch()
93
+ return p
94
+
95
+ @staticmethod
96
+ def mkdir(path: Union[str, Path]) -> Path:
97
+ """创建目录
98
+
99
+ 如果目录已存在则直接返回。
100
+ """
101
+ p = Path(path)
102
+ p.mkdir(parents=True, exist_ok=True)
103
+ return p
104
+
105
+ @staticmethod
106
+ def mkdirs(path: Union[str, Path]) -> Path:
107
+ """创建多级目录(与 mkdir 行为一致,支持多级创建)"""
108
+ p = Path(path)
109
+ p.mkdir(parents=True, exist_ok=True)
110
+ return p
111
+
112
+ @staticmethod
113
+ def create_temp_file(
114
+ prefix: str = "hutool",
115
+ suffix: str = ".tmp",
116
+ parent_dir: Optional[str] = None,
117
+ ) -> Path:
118
+ """创建临时文件
119
+
120
+ :param prefix: 文件名前缀
121
+ :param suffix: 文件名后缀(含点号)
122
+ :param parent_dir: 临时文件所在目录,为 None 时使用系统临时目录
123
+ :return: 创建的临时文件路径
124
+ """
125
+ dir_path: Optional[str] = parent_dir
126
+ fd, tmp_path = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir_path)
127
+ os.close(fd)
128
+ return Path(tmp_path)
129
+
130
+ # ------------------------------------------------------------------
131
+ # 删除
132
+ # ------------------------------------------------------------------
133
+
134
+ @staticmethod
135
+ def del_file(path: Union[str, Path]) -> bool:
136
+ """删除文件或目录
137
+
138
+ - 文件:直接删除。
139
+ - 目录:递归删除整个目录树。
140
+ - 路径不存在时返回 True。
141
+
142
+ :return: 是否删除成功
143
+ """
144
+ p = Path(path)
145
+ if not p.exists():
146
+ return True
147
+ if p.is_file() or p.is_symlink():
148
+ p.unlink()
149
+ return True
150
+ if p.is_dir():
151
+ shutil.rmtree(p)
152
+ return True
153
+ return False
154
+
155
+ @staticmethod
156
+ def clean(path: Union[str, Path]) -> bool:
157
+ """清空目录内容(不删除目录本身)
158
+
159
+ :return: 是否清空成功
160
+ """
161
+ p = Path(path)
162
+ if not p.is_dir():
163
+ return False
164
+ for child in p.iterdir():
165
+ if child.is_dir():
166
+ shutil.rmtree(child)
167
+ else:
168
+ child.unlink()
169
+ return True
170
+
171
+ # ------------------------------------------------------------------
172
+ # 复制 / 移动
173
+ # ------------------------------------------------------------------
174
+
175
+ @staticmethod
176
+ def copy(
177
+ src: Union[str, Path],
178
+ dest: Union[str, Path],
179
+ is_override: bool = True,
180
+ ) -> Path:
181
+ """复制文件或目录
182
+
183
+ :param src: 源路径
184
+ :param dest: 目标路径
185
+ :param is_override: 是否覆盖已存在的目标
186
+ :return: 目标路径
187
+ """
188
+ src_path = Path(src)
189
+ dest_path = Path(dest)
190
+
191
+ if not src_path.exists():
192
+ raise FileNotFoundError(f"源路径不存在: {src_path}")
193
+
194
+ if src_path.is_dir():
195
+ # 复制目录
196
+ if dest_path.exists() and is_override:
197
+ shutil.rmtree(dest_path)
198
+ shutil.copytree(src_path, dest_path)
199
+ else:
200
+ # 复制文件
201
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
202
+ if not is_override and dest_path.exists():
203
+ return dest_path
204
+ shutil.copy2(src_path, dest_path)
205
+ return dest_path
206
+
207
+ @staticmethod
208
+ def copy_file(
209
+ src: Union[str, Path],
210
+ dest: Union[str, Path],
211
+ ) -> Path:
212
+ """复制文件
213
+
214
+ 如果目标目录不存在则自动创建。
215
+ """
216
+ src_path = Path(src)
217
+ dest_path = Path(dest)
218
+
219
+ if not src_path.is_file():
220
+ raise FileNotFoundError(f"源文件不存在或不是文件: {src_path}")
221
+
222
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
223
+ shutil.copy2(src_path, dest_path)
224
+ return dest_path
225
+
226
+ @staticmethod
227
+ def move(
228
+ src: Union[str, Path],
229
+ dest: Union[str, Path],
230
+ is_override: bool = True,
231
+ ) -> Path:
232
+ """移动文件或目录
233
+
234
+ :param src: 源路径
235
+ :param dest: 目标路径
236
+ :param is_override: 是否覆盖已存在的目标
237
+ :return: 目标路径
238
+ """
239
+ src_path = Path(src)
240
+ dest_path = Path(dest)
241
+
242
+ if not src_path.exists():
243
+ raise FileNotFoundError(f"源路径不存在: {src_path}")
244
+
245
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
246
+
247
+ if dest_path.exists():
248
+ if not is_override:
249
+ return dest_path
250
+ if dest_path.is_dir():
251
+ shutil.rmtree(dest_path)
252
+ else:
253
+ dest_path.unlink()
254
+
255
+ shutil.move(str(src_path), str(dest_path))
256
+ return dest_path
257
+
258
+ @staticmethod
259
+ def rename(file: Union[str, Path], new_name: str) -> Path:
260
+ """重命名文件或目录
261
+
262
+ :param file: 源文件或目录路径
263
+ :param new_name: 新名称(仅名称部分,不含路径)
264
+ :return: 重命名后的路径
265
+ """
266
+ p = Path(file)
267
+ if not p.exists():
268
+ raise FileNotFoundError(f"路径不存在: {p}")
269
+ new_path = p.parent / new_name
270
+ p.rename(new_path)
271
+ return new_path
272
+
273
+ # ------------------------------------------------------------------
274
+ # 读写
275
+ # ------------------------------------------------------------------
276
+
277
+ @staticmethod
278
+ def read_string(path: Union[str, Path], charset: str = "utf-8") -> str:
279
+ """读取文件内容为字符串
280
+
281
+ :param path: 文件路径
282
+ :param charset: 字符编码
283
+ :return: 文件内容字符串
284
+ """
285
+ return Path(path).read_text(encoding=charset)
286
+
287
+ @staticmethod
288
+ def read_bytes(path: Union[str, Path]) -> bytes:
289
+ """读取文件内容为字节数组"""
290
+ return Path(path).read_bytes()
291
+
292
+ @staticmethod
293
+ def read_lines(path: Union[str, Path], charset: str = "utf-8") -> List[str]:
294
+ """读取文件内容为字符串列表(每行一个元素,保留换行符)"""
295
+ with open(path, encoding=charset) as f:
296
+ return f.readlines()
297
+
298
+ @staticmethod
299
+ def read_utf8_lines(path: Union[str, Path]) -> List[str]:
300
+ """以 UTF-8 编码读取文件的每一行(去除行尾换行符)"""
301
+ return FileUtil.read_lines_str(path, charset="utf-8")
302
+
303
+ @staticmethod
304
+ def read_lines_str(path: Union[str, Path], charset: str = "utf-8") -> List[str]:
305
+ """读取文件的每一行,去除行尾换行符"""
306
+ with open(path, encoding=charset) as f:
307
+ return f.read().splitlines()
308
+
309
+ @staticmethod
310
+ def write_string(
311
+ path: Union[str, Path],
312
+ content: str,
313
+ charset: str = "utf-8",
314
+ is_append: bool = False,
315
+ ) -> Path:
316
+ """写入字符串到文件
317
+
318
+ :param path: 文件路径
319
+ :param content: 写入内容
320
+ :param charset: 字符编码
321
+ :param is_append: 是否追加模式
322
+ :return: 文件路径
323
+ """
324
+ p = Path(path)
325
+ p.parent.mkdir(parents=True, exist_ok=True)
326
+ mode = "a" if is_append else "w"
327
+ with open(p, mode, encoding=charset) as f:
328
+ f.write(content)
329
+ return p
330
+
331
+ @staticmethod
332
+ def write_bytes(
333
+ path: Union[str, Path],
334
+ data: bytes,
335
+ is_append: bool = False,
336
+ ) -> Path:
337
+ """写字节数组到文件
338
+
339
+ :param path: 文件路径
340
+ :param data: 字节数据
341
+ :param is_append: 是否追加模式
342
+ :return: 文件路径
343
+ """
344
+ p = Path(path)
345
+ p.parent.mkdir(parents=True, exist_ok=True)
346
+ mode = "ab" if is_append else "wb"
347
+ with open(p, mode) as f:
348
+ f.write(data)
349
+ return p
350
+
351
+ @staticmethod
352
+ def write_lines(
353
+ path: Union[str, Path],
354
+ lines: list,
355
+ charset: str = "utf-8",
356
+ is_append: bool = False,
357
+ ) -> Path:
358
+ """写入行列表到文件
359
+
360
+ 每个元素后自动追加换行符。
361
+
362
+ :param path: 文件路径
363
+ :param lines: 行列表
364
+ :param charset: 字符编码
365
+ :param is_append: 是否追加模式
366
+ :return: 文件路径
367
+ """
368
+ p = Path(path)
369
+ p.parent.mkdir(parents=True, exist_ok=True)
370
+ mode = "a" if is_append else "w"
371
+ with open(p, mode, encoding=charset) as f:
372
+ for line in lines:
373
+ f.write(str(line))
374
+ f.write("\n")
375
+ return p
376
+
377
+ @staticmethod
378
+ def append_string(
379
+ path: Union[str, Path],
380
+ content: str,
381
+ charset: str = "utf-8",
382
+ ) -> Path:
383
+ """追加字符串到文件末尾
384
+
385
+ :param path: 文件路径
386
+ :param content: 追加内容
387
+ :param charset: 字符编码
388
+ :return: 文件路径
389
+ """
390
+ return FileUtil.write_string(path, content, charset=charset, is_append=True)
391
+
392
+ @staticmethod
393
+ def append_lines(
394
+ path: Union[str, Path],
395
+ lines: list,
396
+ charset: str = "utf-8",
397
+ ) -> Path:
398
+ """追加行列表到文件末尾
399
+
400
+ :param path: 文件路径
401
+ :param lines: 行列表
402
+ :param charset: 字符编码
403
+ :return: 文件路径
404
+ """
405
+ return FileUtil.write_lines(path, lines, charset=charset, is_append=True)
406
+
407
+ # ------------------------------------------------------------------
408
+ # 遍历
409
+ # ------------------------------------------------------------------
410
+
411
+ @staticmethod
412
+ def loop_files(
413
+ path: Union[str, Path],
414
+ max_depth: Optional[int] = None,
415
+ file_filter: Optional[Callable[[Path], bool]] = None,
416
+ ) -> List[Path]:
417
+ """递归遍历目录下的所有文件
418
+
419
+ :param path: 根目录路径
420
+ :param max_depth: 最大递归深度,None 表示不限制
421
+ :param file_filter: 文件过滤函数,返回 True 保留
422
+ :return: 符合条件的文件路径列表
423
+ """
424
+ root = Path(path)
425
+ if not root.is_dir():
426
+ return []
427
+
428
+ result: List[Path] = []
429
+ FileUtil._walk_files(root, 0, max_depth, file_filter, result)
430
+ return result
431
+
432
+ @staticmethod
433
+ def _walk_files(
434
+ directory: Path,
435
+ current_depth: int,
436
+ max_depth: Optional[int],
437
+ file_filter: Optional[Callable[[Path], bool]],
438
+ result: List[Path],
439
+ ) -> None:
440
+ """递归遍历的内部实现"""
441
+ if max_depth is not None and current_depth > max_depth:
442
+ return
443
+ try:
444
+ for child in sorted(directory.iterdir()):
445
+ if child.is_file():
446
+ if file_filter is None or file_filter(child):
447
+ result.append(child)
448
+ elif child.is_dir():
449
+ FileUtil._walk_files(child, current_depth + 1, max_depth, file_filter, result)
450
+ except PermissionError:
451
+ pass
452
+
453
+ @staticmethod
454
+ def list_file_names(path: Union[str, Path]) -> List[str]:
455
+ """列出目录下的文件名(仅直接子文件,不含子目录内的文件)
456
+
457
+ :param path: 目录路径
458
+ :return: 文件名列表(不含路径前缀)
459
+ """
460
+ p = Path(path)
461
+ if not p.is_dir():
462
+ return []
463
+ return [child.name for child in sorted(p.iterdir()) if child.is_file()]
464
+
465
+ @staticmethod
466
+ def walk_files(
467
+ path: Union[str, Path],
468
+ consumer: Callable[[Path], None],
469
+ ) -> None:
470
+ """递归遍历文件并对每个文件执行操作
471
+
472
+ :param path: 根目录路径
473
+ :param consumer: 对每个文件执行的回调函数
474
+ """
475
+ root = Path(path)
476
+ if not root.exists():
477
+ return
478
+ if root.is_file():
479
+ consumer(root)
480
+ return
481
+ for file_path in FileUtil.loop_files(root):
482
+ consumer(file_path)
483
+
484
+ # ------------------------------------------------------------------
485
+ # 信息
486
+ # ------------------------------------------------------------------
487
+
488
+ @staticmethod
489
+ def size(path: Union[str, Path]) -> int:
490
+ """获取文件大小(字节)
491
+
492
+ 对于目录,返回整个目录树的总大小。
493
+ """
494
+ p = Path(path)
495
+ if p.is_file():
496
+ return p.stat().st_size
497
+ if p.is_dir():
498
+ total = 0
499
+ for child in p.rglob("*"):
500
+ if child.is_file():
501
+ total += child.stat().st_size
502
+ return total
503
+ raise FileNotFoundError(f"路径不存在: {p}")
504
+
505
+ @staticmethod
506
+ def last_modified_time(path: Union[str, Path]) -> datetime:
507
+ """获取文件最后修改时间
508
+
509
+ :return: 最后修改时间的 datetime 对象
510
+ """
511
+ p = Path(path)
512
+ if not p.exists():
513
+ raise FileNotFoundError(f"路径不存在: {p}")
514
+ mtime = p.stat().st_mtime
515
+ return datetime.fromtimestamp(mtime)
516
+
517
+ @staticmethod
518
+ def get_total_lines(path: Union[str, Path]) -> int:
519
+ """获取文件总行数"""
520
+ count = 0
521
+ with open(path, "rb") as f:
522
+ for _ in f:
523
+ count += 1
524
+ return count
525
+
526
+ @staticmethod
527
+ def get_name(path: Union[str, Path]) -> str:
528
+ """获取文件名(含扩展名)
529
+
530
+ 例如: /home/user/test.txt -> test.txt
531
+ """
532
+ return Path(path).name
533
+
534
+ @staticmethod
535
+ def get_suffix(path: Union[str, Path]) -> str:
536
+ """获取文件扩展名(不带点号)
537
+
538
+ 例如: /home/user/test.txt -> txt
539
+
540
+ 如果没有扩展名则返回空字符串。
541
+ """
542
+ name = Path(path).name
543
+ dot_index = name.rfind(".")
544
+ if dot_index <= 0:
545
+ return ""
546
+ return name[dot_index + 1 :]
547
+
548
+ @staticmethod
549
+ def get_prefix(path: Union[str, Path]) -> str:
550
+ """获取文件名前缀(不含扩展名)
551
+
552
+ 例如: /home/user/test.txt -> test
553
+ """
554
+ name = Path(path).name
555
+ dot_index = name.rfind(".")
556
+ if dot_index <= 0:
557
+ return name
558
+ return name[:dot_index]
559
+
560
+ @staticmethod
561
+ def main_name(path: Union[str, Path]) -> str:
562
+ """获取主文件名(同 get_prefix)"""
563
+ return FileUtil.get_prefix(path)
564
+
565
+ @staticmethod
566
+ def normalize(path: Union[str, Path]) -> str:
567
+ """标准化路径,解析 ~ 和相对路径符号
568
+
569
+ :return: 标准化后的路径字符串
570
+ """
571
+ p = Path(path).expanduser()
572
+ try:
573
+ return str(p.resolve())
574
+ except OSError:
575
+ return str(p)
576
+
577
+ # ------------------------------------------------------------------
578
+ # 系统路径
579
+ # ------------------------------------------------------------------
580
+
581
+ @staticmethod
582
+ def get_tmp_dir_path() -> str:
583
+ """获取系统临时目录路径(字符串)"""
584
+ return tempfile.gettempdir()
585
+
586
+ @staticmethod
587
+ def get_tmp_dir() -> Path:
588
+ """获取系统临时目录(Path 对象)"""
589
+ return Path(tempfile.gettempdir())
590
+
591
+ @staticmethod
592
+ def get_user_home_path() -> str:
593
+ """获取用户主目录路径(字符串)"""
594
+ return str(Path.home())
595
+
596
+ @staticmethod
597
+ def get_user_home_dir() -> Path:
598
+ """获取用户主目录(Path 对象)"""
599
+ return Path.home()
600
+
601
+ # ------------------------------------------------------------------
602
+ # 工具
603
+ # ------------------------------------------------------------------
604
+
605
+ @staticmethod
606
+ def newer_than(file: Union[str, Path], reference: Union[str, Path]) -> bool:
607
+ """判断文件是否比参考文件更新
608
+
609
+ :param file: 待比较的文件
610
+ :param reference: 参考文件
611
+ :return: 如果 file 的修改时间晚于 reference 则返回 True
612
+ """
613
+ file_path = Path(file)
614
+ ref_path = Path(reference)
615
+
616
+ if not file_path.exists():
617
+ return False
618
+ if not ref_path.exists():
619
+ return True
620
+
621
+ file_mtime = file_path.stat().st_mtime
622
+ ref_mtime = ref_path.stat().st_mtime
623
+ return file_mtime > ref_mtime
624
+
625
+ @staticmethod
626
+ def is_symlink(path: Union[str, Path]) -> bool:
627
+ """判断是否为符号链接"""
628
+ return Path(path).is_symlink()
629
+
630
+ @staticmethod
631
+ def sub_path(path: Union[str, Path], start: int, end: int) -> str:
632
+ """获取子路径
633
+
634
+ :param path: 源路径
635
+ :param start: 起始索引(含)
636
+ :param end: 结束索引(不含)
637
+ :return: 子路径字符串
638
+
639
+ 例如: sub_path("/home/user/test.txt", 1, 3) -> "user/test.txt"
640
+ """
641
+ p = Path(path)
642
+ parts = p.parts
643
+ if start < 0:
644
+ start = 0
645
+ if end > len(parts):
646
+ end = len(parts)
647
+ if start >= end:
648
+ return ""
649
+ sub_parts = parts[start:end]
650
+ return str(Path(*sub_parts))