vebplib 1.0.0__tar.gz
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.
- vebplib-1.0.0/LICENSE.txt +21 -0
- vebplib-1.0.0/PKG-INFO +14 -0
- vebplib-1.0.0/README.md +1 -0
- vebplib-1.0.0/pyproject.toml +23 -0
- vebplib-1.0.0/scripts/build.py +4 -0
- vebplib-1.0.0/scripts/install.py +5 -0
- vebplib-1.0.0/scripts/upload.py +4 -0
- vebplib-1.0.0/setup.cfg +4 -0
- vebplib-1.0.0/test/message.py +5 -0
- vebplib-1.0.0/vebplib/__init__.py +11 -0
- vebplib-1.0.0/vebplib/args.py +12 -0
- vebplib-1.0.0/vebplib/color/__init__.py +203 -0
- vebplib-1.0.0/vebplib/color/_console/__init__.py +53 -0
- vebplib-1.0.0/vebplib/color/util.py +40 -0
- vebplib-1.0.0/vebplib/file/__init__.py +151 -0
- vebplib-1.0.0/vebplib/file/json.py +24 -0
- vebplib-1.0.0/vebplib/file/plist.py +24 -0
- vebplib-1.0.0/vebplib/func/__init__.py +80 -0
- vebplib-1.0.0/vebplib/func/injection.py +54 -0
- vebplib-1.0.0/vebplib/func/wrapper.py +16 -0
- vebplib-1.0.0/vebplib/launch.py +19 -0
- vebplib-1.0.0/vebplib/math.py +102 -0
- vebplib-1.0.0/vebplib/message.py +21 -0
- vebplib-1.0.0/vebplib/modulelib.py +92 -0
- vebplib-1.0.0/vebplib/output.py +22 -0
- vebplib-1.0.0/vebplib/path.py +30 -0
- vebplib-1.0.0/vebplib/system/__init__.py +164 -0
- vebplib-1.0.0/vebplib/text.py +64 -0
- vebplib-1.0.0/vebplib/types.py +4 -0
- vebplib-1.0.0/vebplib/venvs/__init__.py +2 -0
- vebplib-1.0.0/vebplib/venvs/path.py +12 -0
- vebplib-1.0.0/vebplib/venvs/version.py +12 -0
- vebplib-1.0.0/vebplib/zip.py +37 -0
- vebplib-1.0.0/vebplib.egg-info/PKG-INFO +14 -0
- vebplib-1.0.0/vebplib.egg-info/SOURCES.txt +36 -0
- vebplib-1.0.0/vebplib.egg-info/dependency_links.txt +1 -0
- vebplib-1.0.0/vebplib.egg-info/requires.txt +1 -0
- vebplib-1.0.0/vebplib.egg-info/top_level.txt +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
GPL-3.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 [Ndrzy]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
vebplib-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vebplib
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: python lib
|
|
5
|
+
Author-email: ndrzy <dandan0019@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/meatdumplings0019/vebplib
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Requires-Dist: colorama
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# VebpLib
|
vebplib-1.0.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# VebpLib
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 48.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vebplib"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "python lib"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "ndrzy", email = "dandan0019@outlook.com" }
|
|
11
|
+
]
|
|
12
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
dependencies = [
|
|
16
|
+
'colorama'
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/meatdumplings0019/vebplib"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = [""]
|
vebplib-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
def parse_args(tokens: list[str], parser: argparse.ArgumentParser) -> Optional[argparse.Namespace]:
|
|
6
|
+
try:
|
|
7
|
+
return parser.parse_args(tokens)
|
|
8
|
+
except SystemExit:
|
|
9
|
+
return None
|
|
10
|
+
except Exception as e:
|
|
11
|
+
print(f"参数解析错误: {str(e)}")
|
|
12
|
+
return None
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from vebplib.color._console import *
|
|
2
|
+
from vebplib.color.util import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MColor:
|
|
6
|
+
CONSOLE_COLOR = ForeWhite()
|
|
7
|
+
|
|
8
|
+
def __init__(self, r: int = 255, g: int = 255, b: int = 255, a: int = 255):
|
|
9
|
+
"""
|
|
10
|
+
增强版的Color, 支持rgb转hex, 可以直接作为pygame的Color
|
|
11
|
+
:param r: R
|
|
12
|
+
:param g: G
|
|
13
|
+
:param b: B
|
|
14
|
+
:param a: A
|
|
15
|
+
"""
|
|
16
|
+
self._r, self._g, self._b, self._a = self.vali_rgba(r, g, b, a)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def from_hex(hex_str: str = "#FFFFFF"):
|
|
20
|
+
"""
|
|
21
|
+
将16进制颜色代码转为MColor
|
|
22
|
+
:param hex_str: 16进制string
|
|
23
|
+
:return: new MColor
|
|
24
|
+
"""
|
|
25
|
+
r, g, b, a = hex_to_rgb(hex_str)
|
|
26
|
+
return MColor(r, g, b, a)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def vali_rgba(r, g, b, a=255):
|
|
30
|
+
return vali_rgba(r, g, b, a)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def r(self) -> int:
|
|
34
|
+
return self._r
|
|
35
|
+
|
|
36
|
+
@r.setter
|
|
37
|
+
def r(self, value: int) -> None:
|
|
38
|
+
if 0 < value <= 255:
|
|
39
|
+
self._r = value
|
|
40
|
+
|
|
41
|
+
self.r = 255
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def float_r(self) -> float:
|
|
45
|
+
return self.r / 255.0
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def g(self) -> int:
|
|
49
|
+
return self._g
|
|
50
|
+
|
|
51
|
+
@g.setter
|
|
52
|
+
def g(self, value: int) -> None:
|
|
53
|
+
if 0 < value <= 255:
|
|
54
|
+
self._g = value
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
self.g = 255
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def float_g(self) -> float:
|
|
61
|
+
return self.g / 255.0
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def b(self) -> int:
|
|
65
|
+
return self._b
|
|
66
|
+
|
|
67
|
+
@b.setter
|
|
68
|
+
def b(self, value: int) -> None:
|
|
69
|
+
if 0 < value <= 255:
|
|
70
|
+
self._b = value
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
self.b = 255
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def float_b(self) -> float:
|
|
77
|
+
return self.b / 255.0
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def a(self) -> int:
|
|
81
|
+
return self._a
|
|
82
|
+
|
|
83
|
+
@a.setter
|
|
84
|
+
def a(self, value: int) -> None:
|
|
85
|
+
if 0 < value <= 255:
|
|
86
|
+
self._a = value
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
self.a = 255
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def float_a(self) -> float:
|
|
93
|
+
return self.a / 255.0
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def rgb(self) -> tuple[int, int, int]:
|
|
97
|
+
return self.r, self.g, self.b
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def float_rgb(self) -> tuple[float, float, float]:
|
|
101
|
+
return self.float_r, self.float_g, self.float_b
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def rgba(self) -> tuple[int, int, int, int]:
|
|
105
|
+
return self.rgb[0], self.rgb[1], self.rgb[2], self.a
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def float_rgba(self) -> tuple[float, float, float, float]:
|
|
109
|
+
return self.float_rgb[0], self.float_rgb[1], self.float_rgb[2], self.float_a
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def hex(self) -> str:
|
|
113
|
+
return rgb_to_hex(self.r, self.g, self.b)
|
|
114
|
+
|
|
115
|
+
def __iter__(self):
|
|
116
|
+
yield self.r
|
|
117
|
+
yield self.g
|
|
118
|
+
yield self.b
|
|
119
|
+
yield self.a
|
|
120
|
+
|
|
121
|
+
def __len__(self):
|
|
122
|
+
return 4
|
|
123
|
+
|
|
124
|
+
def __getitem__(self, item):
|
|
125
|
+
prop = self.r, self.g, self.b, self.a
|
|
126
|
+
|
|
127
|
+
return prop[item]
|
|
128
|
+
|
|
129
|
+
def __add__(self, other):
|
|
130
|
+
if isinstance(other, MColor):
|
|
131
|
+
r, g, b, a = other
|
|
132
|
+
return MColor(self.r + r, self.g + g, self.b + b, self.a + a)
|
|
133
|
+
|
|
134
|
+
raise TypeError(other)
|
|
135
|
+
|
|
136
|
+
def __sub__(self, other):
|
|
137
|
+
if isinstance(other, MColor):
|
|
138
|
+
r, g, b, a = other
|
|
139
|
+
return MColor(self.r - r, self.g - g, self.b - b, self.a - a)
|
|
140
|
+
|
|
141
|
+
raise TypeError(other)
|
|
142
|
+
|
|
143
|
+
def __repr__(self):
|
|
144
|
+
return f"[MColor r={self.r} g={self.g} b={self.b} a={self.a}]"
|
|
145
|
+
|
|
146
|
+
def __str__(self):
|
|
147
|
+
return f'{self.CONSOLE_COLOR}'
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Black(MColor):
|
|
151
|
+
CONSOLE_COLOR = ForeBlack()
|
|
152
|
+
|
|
153
|
+
def __init__(self, a: int = 255):
|
|
154
|
+
super().__init__(0, 0, 0, a)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class Red(MColor):
|
|
158
|
+
CONSOLE_COLOR = ForeRed()
|
|
159
|
+
|
|
160
|
+
def __init__(self, a: int = 255):
|
|
161
|
+
super().__init__(255, 0, 0, a)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Green(MColor):
|
|
165
|
+
CONSOLE_COLOR = ForeGreen()
|
|
166
|
+
|
|
167
|
+
def __init__(self, a: int = 255):
|
|
168
|
+
super().__init__(0, 255, 0, a)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Yellow(MColor):
|
|
172
|
+
CONSOLE_COLOR = ForeYellow()
|
|
173
|
+
|
|
174
|
+
def __init__(self, a: int = 255):
|
|
175
|
+
super().__init__(255, 255, 0, a)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Blue(MColor):
|
|
179
|
+
CONSOLE_COLOR = ForeBlue()
|
|
180
|
+
|
|
181
|
+
def __init__(self, a: int = 255):
|
|
182
|
+
super().__init__(0, 0, 255, a)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Magenta(MColor):
|
|
186
|
+
CONSOLE_COLOR = ForeMagenta()
|
|
187
|
+
|
|
188
|
+
def __init__(self, a: int = 255):
|
|
189
|
+
super().__init__(255, 0, 255, a)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Cyan(MColor):
|
|
193
|
+
CONSOLE_COLOR = ForeCyan()
|
|
194
|
+
|
|
195
|
+
def __init__(self, a: int = 255):
|
|
196
|
+
super().__init__(0, 255, 255, a)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class White(MColor):
|
|
200
|
+
CONSOLE_COLOR = ForeWhite()
|
|
201
|
+
|
|
202
|
+
def __init__(self, a: int = 255):
|
|
203
|
+
super().__init__(255, 255, 255, a)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from colorama import Fore
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConsoleColor:
|
|
5
|
+
def __init__(self, fore):
|
|
6
|
+
self._fore = fore
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def fore(self):
|
|
10
|
+
return self._fore
|
|
11
|
+
|
|
12
|
+
def __str__(self):
|
|
13
|
+
return self.fore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ForeBlack(ConsoleColor):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(Fore.BLACK)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ForeRed(ConsoleColor):
|
|
22
|
+
def __init__(self):
|
|
23
|
+
super().__init__(Fore.RED)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ForeGreen(ConsoleColor):
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__(Fore.GREEN)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ForeYellow(ConsoleColor):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__(Fore.YELLOW)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ForeBlue(ConsoleColor):
|
|
37
|
+
def __init__(self):
|
|
38
|
+
super().__init__(Fore.BLUE)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ForeMagenta(ConsoleColor):
|
|
42
|
+
def __init__(self):
|
|
43
|
+
super().__init__(Fore.MAGENTA)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ForeCyan(ConsoleColor):
|
|
47
|
+
def __init__(self):
|
|
48
|
+
super().__init__(Fore.CYAN)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ForeWhite(ConsoleColor):
|
|
52
|
+
def __init__(self):
|
|
53
|
+
super().__init__(Fore.WHITE)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
def vali_rgba(r, g, b, a=255):
|
|
2
|
+
return (
|
|
3
|
+
min(255, max(0, r)),
|
|
4
|
+
min(255, max(0, g)),
|
|
5
|
+
min(255, max(0, b)),
|
|
6
|
+
min(255, max(0, a))
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
def hex_to_rgb(hex_str: str) -> tuple[int, int, int, int]:
|
|
10
|
+
r, g, b, a = 0, 0, 0, 255
|
|
11
|
+
|
|
12
|
+
hex_str = hex_str.lstrip('#')
|
|
13
|
+
|
|
14
|
+
if len(hex_str) not in (3, 4, 6, 8):
|
|
15
|
+
raise ValueError("十六进制颜色代码必须是3, 4, 6或8个字符")
|
|
16
|
+
|
|
17
|
+
if len(hex_str) == 3:
|
|
18
|
+
hex_str = ''.join([c * 2 for c in hex_str])
|
|
19
|
+
elif len(hex_str) == 4:
|
|
20
|
+
hex_str = ''.join([c * 2 for c in hex_str])
|
|
21
|
+
alpha_hex = hex_str[6:8]
|
|
22
|
+
a = int(alpha_hex, 16)
|
|
23
|
+
hex_str = hex_str[0:6]
|
|
24
|
+
|
|
25
|
+
if len(hex_str) == 8:
|
|
26
|
+
alpha_hex = hex_str[6:8]
|
|
27
|
+
a = int(alpha_hex, 16)
|
|
28
|
+
hex_str = hex_str[0:6]
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
r = int(hex_str[0:2], 16)
|
|
32
|
+
g = int(hex_str[2:4], 16)
|
|
33
|
+
b = int(hex_str[4:6], 16)
|
|
34
|
+
except ValueError:
|
|
35
|
+
raise ValueError("无效的十六进制颜色代码")
|
|
36
|
+
|
|
37
|
+
return r, g, b, a
|
|
38
|
+
|
|
39
|
+
def rgb_to_hex(r, g, b) -> str:
|
|
40
|
+
return '#%02x%02x%02x' % vali_rgba(r, g, b)[0:3]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Iterator
|
|
5
|
+
|
|
6
|
+
from vebplib.path import MPath
|
|
7
|
+
from vebplib.types import path_type
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Stream:
|
|
11
|
+
def __init__(self, path: path_type):
|
|
12
|
+
self._path = MPath.to_path(path)
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def name(self) -> str:
|
|
16
|
+
return self.path.name
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def path(self) -> Path:
|
|
20
|
+
return self._path
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def exists(self) -> bool:
|
|
24
|
+
return self.path.exists()
|
|
25
|
+
|
|
26
|
+
def copy(self, destination, ignore=None):
|
|
27
|
+
if ignore is None:
|
|
28
|
+
ignore = []
|
|
29
|
+
|
|
30
|
+
source = MPath.to_path(self.path)
|
|
31
|
+
destination = MPath.to_path(destination)
|
|
32
|
+
|
|
33
|
+
FolderStream(destination).create()
|
|
34
|
+
|
|
35
|
+
if not source.exists():
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
if source.is_dir():
|
|
39
|
+
shutil.copytree(
|
|
40
|
+
source,
|
|
41
|
+
destination,
|
|
42
|
+
dirs_exist_ok=True,
|
|
43
|
+
ignore=shutil.ignore_patterns(*ignore),
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
shutil.copy(
|
|
47
|
+
source,
|
|
48
|
+
destination,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
def delete(self):
|
|
54
|
+
source = MPath.to_path(self.path)
|
|
55
|
+
|
|
56
|
+
if source.is_dir():
|
|
57
|
+
shutil.rmtree(source, ignore_errors=True)
|
|
58
|
+
else:
|
|
59
|
+
source.unlink(missing_ok=True)
|
|
60
|
+
|
|
61
|
+
def __eq__(self, other):
|
|
62
|
+
return self.path == other.root
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
return str(self.path)
|
|
66
|
+
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
return f"[{self.__class__.__name__} {self}]"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class FileStream(Stream):
|
|
72
|
+
@property
|
|
73
|
+
def suffix(self) -> str:
|
|
74
|
+
return self.path.suffix
|
|
75
|
+
|
|
76
|
+
def read(self) -> Optional[str]:
|
|
77
|
+
if not self.exists:
|
|
78
|
+
raise FileNotFoundError("File not found")
|
|
79
|
+
|
|
80
|
+
with open(self.path, "r", encoding="utf-8") as f:
|
|
81
|
+
return f.read()
|
|
82
|
+
|
|
83
|
+
def write(self, text: str):
|
|
84
|
+
if not self.exists:
|
|
85
|
+
raise FileNotFoundError("File not found")
|
|
86
|
+
|
|
87
|
+
with open(self.path, "w", encoding="utf-8") as f:
|
|
88
|
+
f.write(text)
|
|
89
|
+
|
|
90
|
+
def create(self, value: str = ""):
|
|
91
|
+
if self.exists:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
with open(self.path, "w", encoding="utf-8") as f:
|
|
95
|
+
f.write(value)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DirectoryInfo:
|
|
99
|
+
def __init__(self, path, folders, files) -> None:
|
|
100
|
+
self._path = os.path.normpath(path)
|
|
101
|
+
self._folders = folders
|
|
102
|
+
self._files = files
|
|
103
|
+
|
|
104
|
+
def __iter__(self) -> Iterator:
|
|
105
|
+
yield self._path
|
|
106
|
+
yield self._folders
|
|
107
|
+
yield self._files
|
|
108
|
+
|
|
109
|
+
def __repr__(self) -> str:
|
|
110
|
+
return f"<DirectoryInfo: {self._path}>"
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def path(self) -> str:
|
|
114
|
+
return self._path
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def folders(self) -> list["FolderStream"]:
|
|
118
|
+
return self._folders
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def files(self) -> list[FileStream]:
|
|
122
|
+
return self._files
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class FolderStream(Stream):
|
|
126
|
+
def create(self):
|
|
127
|
+
if self.exists:
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
self.path.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def walk(self) -> Optional[DirectoryInfo]:
|
|
135
|
+
if not self.exists: return None
|
|
136
|
+
|
|
137
|
+
folders = []
|
|
138
|
+
files = []
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
with os.scandir(self._path) as entries:
|
|
142
|
+
for entry in entries:
|
|
143
|
+
if entry.is_dir():
|
|
144
|
+
sub_folder = FolderStream(entry.path)
|
|
145
|
+
folders.append(sub_folder)
|
|
146
|
+
elif entry.is_file():
|
|
147
|
+
files.append(FileStream(entry.path))
|
|
148
|
+
except PermissionError:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
return DirectoryInfo(self._path, folders, files)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
|
|
4
|
+
from rushlib.file import FileStream
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JsonStream(FileStream):
|
|
8
|
+
@property
|
|
9
|
+
def is_json(self) -> bool:
|
|
10
|
+
return self.suffix == ".json"
|
|
11
|
+
|
|
12
|
+
def read(self) -> Optional[dict[str, Any]]:
|
|
13
|
+
if not self.exists:
|
|
14
|
+
raise FileNotFoundError("File not found")
|
|
15
|
+
|
|
16
|
+
with open(self.path, mode='r', encoding='utf-8') as f:
|
|
17
|
+
return json.load(f)
|
|
18
|
+
|
|
19
|
+
def write(self, data: dict, indent=4) -> None:
|
|
20
|
+
if not self.exists:
|
|
21
|
+
raise FileNotFoundError("File not found")
|
|
22
|
+
|
|
23
|
+
with open(self._path, 'w', encoding="utf-8") as file:
|
|
24
|
+
json.dump(data, file, indent=indent, ensure_ascii=False)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import plistlib
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
|
|
4
|
+
from vebplib.file import FileStream
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PlistStream(FileStream):
|
|
8
|
+
@property
|
|
9
|
+
def is_plist(self) -> bool:
|
|
10
|
+
return self.suffix == ".json"
|
|
11
|
+
|
|
12
|
+
def read(self) -> Optional[dict[str, Any]]:
|
|
13
|
+
if not self.exists:
|
|
14
|
+
raise FileNotFoundError("File not found")
|
|
15
|
+
|
|
16
|
+
with open(self.path, mode='rb', encoding='utf-8') as f:
|
|
17
|
+
return plistlib.load(f)
|
|
18
|
+
|
|
19
|
+
def write(self, data: dict, indent=4) -> None:
|
|
20
|
+
if not self.exists:
|
|
21
|
+
raise FileNotFoundError("File not found")
|
|
22
|
+
|
|
23
|
+
with open(self._path, 'wb', encoding="utf-8") as file:
|
|
24
|
+
plistlib.dump(data, file)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from inspect import Parameter
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def smart_call(func: Callable, *args: Any, **kwargs: Any) -> Any:
|
|
8
|
+
"""
|
|
9
|
+
智能调用函数,根据目标函数的参数签名自动匹配传入的参数
|
|
10
|
+
|
|
11
|
+
:param func: 要调用的目标函数
|
|
12
|
+
:return: 目标函数的执行结果
|
|
13
|
+
"""
|
|
14
|
+
try:
|
|
15
|
+
# 获取函数签名
|
|
16
|
+
sig = inspect.signature(func)
|
|
17
|
+
except (ValueError, TypeError):
|
|
18
|
+
# 无法获取签名时直接尝试调用
|
|
19
|
+
return func(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
# 准备参数绑定
|
|
22
|
+
bound_args = {}
|
|
23
|
+
params = sig.parameters
|
|
24
|
+
|
|
25
|
+
# 处理位置参数
|
|
26
|
+
args_iter = iter(args)
|
|
27
|
+
for name, param in params.items():
|
|
28
|
+
if param.kind in (Parameter.POSITIONAL_ONLY,
|
|
29
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
|
30
|
+
Parameter.KEYWORD_ONLY):
|
|
31
|
+
# 尝试从位置参数获取值
|
|
32
|
+
if args_iter:
|
|
33
|
+
try:
|
|
34
|
+
bound_args[name] = next(args_iter)
|
|
35
|
+
continue
|
|
36
|
+
except StopIteration:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
# 如果位置参数用完,尝试从关键字参数获取
|
|
40
|
+
if name in kwargs:
|
|
41
|
+
bound_args[name] = kwargs[name]
|
|
42
|
+
elif param.default is not Parameter.empty:
|
|
43
|
+
# 使用默认值
|
|
44
|
+
bound_args[name] = param.default
|
|
45
|
+
else:
|
|
46
|
+
# 必需参数缺失
|
|
47
|
+
raise TypeError(f"Missing required argument: {name}")
|
|
48
|
+
|
|
49
|
+
elif param.kind == Parameter.VAR_POSITIONAL:
|
|
50
|
+
# 处理 *args 参数
|
|
51
|
+
bound_args[name] = tuple(args_iter)
|
|
52
|
+
args_iter = None # 标记位置参数已耗尽
|
|
53
|
+
|
|
54
|
+
# 处理剩余的关键字参数
|
|
55
|
+
for name, param in params.items():
|
|
56
|
+
if param.kind == Parameter.VAR_KEYWORD:
|
|
57
|
+
# 处理 **kwargs 参数
|
|
58
|
+
bound_args[name] = {
|
|
59
|
+
k: v for k, v in kwargs.items()
|
|
60
|
+
if k not in bound_args
|
|
61
|
+
}
|
|
62
|
+
break
|
|
63
|
+
elif name not in bound_args and param.kind == Parameter.KEYWORD_ONLY:
|
|
64
|
+
# 处理仅关键字参数
|
|
65
|
+
if name in kwargs:
|
|
66
|
+
bound_args[name] = kwargs[name]
|
|
67
|
+
elif param.default is not Parameter.empty:
|
|
68
|
+
bound_args[name] = param.default
|
|
69
|
+
else:
|
|
70
|
+
raise TypeError(f"Missing required keyword argument: {name}")
|
|
71
|
+
|
|
72
|
+
return func(**bound_args)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def adapt_args(func):
|
|
76
|
+
@wraps(func)
|
|
77
|
+
def wrapper(*args, **kwargs):
|
|
78
|
+
return smart_call(func, *args, **kwargs)
|
|
79
|
+
|
|
80
|
+
return wrapper
|