MainShortcuts2 2.1.2__tar.gz → 2.2.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.
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/PKG-INFO +1 -1
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/pyproject.toml +3 -3
- mainshortcuts2-2.2.0/src/MainShortcuts2/__main__.py +3 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/_module_info.py +1 -1
- mainshortcuts2-2.2.0/src/MainShortcuts2/advanced.py +254 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/cfg.py +2 -2
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/core.py +52 -14
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/file.py +1 -5
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/json.py +5 -2
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/list.py +0 -1
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/path.py +45 -14
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/proc.py +0 -1
- mainshortcuts2-2.2.0/src/MainShortcuts2/regex.py +6 -0
- mainshortcuts2-2.2.0/src/MainShortcuts2/special_chars.py +10 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/str.py +0 -1
- mainshortcuts2-2.2.0/src/MainShortcuts2/types.py +55 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/utils.py +40 -10
- mainshortcuts2-2.1.2/src/MainShortcuts2/types.py +0 -30
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/README.md +0 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/__init__.py +0 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/dict.py +0 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/dir.py +0 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/term.py +0 -0
- {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/win.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
|
-
version = "2.
|
|
2
|
+
version = "2.2.0"
|
|
3
3
|
name = "MainShortcuts2"
|
|
4
4
|
description = "Сокращение и улучшение функций"
|
|
5
5
|
authors = [ "MainPlay TG <xbox.roman6666666666@gmail.com>",]
|
|
@@ -9,8 +9,8 @@ packages = [
|
|
|
9
9
|
{ include = "MainShortcuts2", from = "src" },
|
|
10
10
|
]
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
[tool.poetry.scripts]
|
|
13
|
+
ms2-import_example = "MainShortcuts2.__main__:import_example"
|
|
14
14
|
|
|
15
15
|
[tool.poetry.dependencies]
|
|
16
16
|
python = "^3.6"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
name = "MainShortcuts2"
|
|
2
|
-
version = "2.
|
|
2
|
+
version = "2.2.0"
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from .core import ms
|
|
3
|
+
from .path import Path
|
|
4
|
+
from typing import Any, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _check_count(data):
|
|
8
|
+
if len(data) == 0:
|
|
9
|
+
raise ValueError("The list is empty")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MultiLang:
|
|
13
|
+
FORMAT = 1
|
|
14
|
+
|
|
15
|
+
def __init__(self, default_lang: Union[dict, Path, str]):
|
|
16
|
+
self.cache: dict[tuple[str, str, str], str] = {}
|
|
17
|
+
self.cache_builders: dict = {}
|
|
18
|
+
self.files: dict[str, Path] = {}
|
|
19
|
+
self.langs: dict[Union[None, str], dict[str, dict[str, Any]]] = {}
|
|
20
|
+
|
|
21
|
+
def cb_lines(text: list[str]) -> str:
|
|
22
|
+
return "\n".join(text)
|
|
23
|
+
|
|
24
|
+
def cb_normal(text: str) -> str:
|
|
25
|
+
assert type(text) == str
|
|
26
|
+
return text
|
|
27
|
+
self.cache_builders["lines"] = cb_lines
|
|
28
|
+
self.cache_builders["normal"] = cb_normal
|
|
29
|
+
self.add_lang(None, default_lang, load=True)
|
|
30
|
+
|
|
31
|
+
def add_langs(self, langs: dict[str, Union[dict, Path, str]], check_count: bool = True, load: bool = True):
|
|
32
|
+
"""Добавить языки (`dict`) или пути к языковым файлам (`str`, `ms.path.Path`)"""
|
|
33
|
+
if check_count:
|
|
34
|
+
_check_count(langs)
|
|
35
|
+
for k, v in langs.items():
|
|
36
|
+
self.add_lang(k, v, load=load)
|
|
37
|
+
|
|
38
|
+
def add_lang(self, lang_name: str, lang: Union[dict, Path, str], load: bool = True):
|
|
39
|
+
"""Добавить один язык из словаря или пути к файлу"""
|
|
40
|
+
if type(lang) == dict:
|
|
41
|
+
for cat_name, cat in lang.items():
|
|
42
|
+
assert type(cat_name) == str
|
|
43
|
+
assert type(cat) == dict
|
|
44
|
+
for text_name, text in cat.items():
|
|
45
|
+
assert type(text_name) == str
|
|
46
|
+
if type(text) == dict:
|
|
47
|
+
if not "allow_cache" in text:
|
|
48
|
+
text["allow_cache"] = True
|
|
49
|
+
if not "type" in text:
|
|
50
|
+
text["type"] = "normal"
|
|
51
|
+
self.langs[lang_name] = lang
|
|
52
|
+
return
|
|
53
|
+
if type(lang) == str:
|
|
54
|
+
lang = Path(lang)
|
|
55
|
+
if type(lang) == Path:
|
|
56
|
+
self.files[lang_name] = lang
|
|
57
|
+
if load:
|
|
58
|
+
self.load(lang_name)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
def load(self, *names: Union[None, str], check_count: bool = True):
|
|
62
|
+
"""Загрузить языковые файлы"""
|
|
63
|
+
if check_count:
|
|
64
|
+
_check_count(names)
|
|
65
|
+
for i in names:
|
|
66
|
+
path = self.files[i].path
|
|
67
|
+
data = ms.json.read(path)
|
|
68
|
+
self.add_lang(i, data["texts"])
|
|
69
|
+
|
|
70
|
+
def save(self, *names: Union[None, str], check_count: bool = True, **kw) -> int:
|
|
71
|
+
"""Сохранить языковые файлы"""
|
|
72
|
+
if check_count:
|
|
73
|
+
_check_count(names)
|
|
74
|
+
data = {"format": "MainShortcuts2.advanced.MultiLang/%i" % self.FORMAT}
|
|
75
|
+
sum = 0
|
|
76
|
+
for i in names:
|
|
77
|
+
data["texts"] = self.langs[i]
|
|
78
|
+
kw["data"] = data
|
|
79
|
+
kw["path"] = self.files[i].path
|
|
80
|
+
sum += ms.json.write(**kw)
|
|
81
|
+
return sum
|
|
82
|
+
|
|
83
|
+
def build_cache(self, lang_name: Union[None, str], cat_name: str, text_name: str):
|
|
84
|
+
cache_name = (lang_name, cat_name, text_name)
|
|
85
|
+
if cache_name in self.cache:
|
|
86
|
+
return
|
|
87
|
+
text = self.langs[lang_name][cat_name][text_name]
|
|
88
|
+
if type(text) == str:
|
|
89
|
+
self.cache[cache_name] = text
|
|
90
|
+
return
|
|
91
|
+
builder = self.cache_builders[text["type"]]
|
|
92
|
+
result = builder(text)
|
|
93
|
+
assert type(result) == str
|
|
94
|
+
self.cache[cache_name] = result
|
|
95
|
+
|
|
96
|
+
def get_text(self, cat: str, name: str, values=None, *, lang: str = None) -> str:
|
|
97
|
+
if not lang in self.langs:
|
|
98
|
+
self.load(lang)
|
|
99
|
+
raw = self.langs[cat][name]
|
|
100
|
+
if type(raw) == str:
|
|
101
|
+
text = raw
|
|
102
|
+
else:
|
|
103
|
+
if raw.get("allow_cache") == False:
|
|
104
|
+
text = self.cache_builders[raw["type"]](raw)
|
|
105
|
+
else:
|
|
106
|
+
self.build_cache(lang, cat, raw)
|
|
107
|
+
text = self.cache[lang, cat, name]
|
|
108
|
+
if values is None:
|
|
109
|
+
return text
|
|
110
|
+
return text % values
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class PermissionSystem:
|
|
114
|
+
FORMAT = 1
|
|
115
|
+
|
|
116
|
+
def __init__(self, path: str):
|
|
117
|
+
self.path = Path(path)
|
|
118
|
+
self.load()
|
|
119
|
+
|
|
120
|
+
def load(self):
|
|
121
|
+
data = ms.json.read(self.path.path)
|
|
122
|
+
self.default_groups = data["default_groups"] if "default_groups" in data else []
|
|
123
|
+
self.default_perms = data["default_perms"] if "default_perms" in data else {}
|
|
124
|
+
self.groups = {}
|
|
125
|
+
self.users = {}
|
|
126
|
+
for i in ["all", "group", "user"]:
|
|
127
|
+
if not i in self.default_perms:
|
|
128
|
+
self.default_perms[i] = {}
|
|
129
|
+
if "groups" in data:
|
|
130
|
+
for name, item in data["groups"].items():
|
|
131
|
+
if not "admin" in item:
|
|
132
|
+
item["admin"] = False
|
|
133
|
+
if not "perms" in item:
|
|
134
|
+
item["perms"] = self.default_perms["all"].copy()
|
|
135
|
+
item["perms"].update(self.default_perms["groups"])
|
|
136
|
+
if not "priority" in item:
|
|
137
|
+
item["priority"] = 0
|
|
138
|
+
self.groups[name] = item
|
|
139
|
+
if "users" in data:
|
|
140
|
+
for name, item in data["users"].items():
|
|
141
|
+
if not "admin" in item:
|
|
142
|
+
item["admin"] = False
|
|
143
|
+
if not "groups" in item:
|
|
144
|
+
item["groups"] = self.default_groups
|
|
145
|
+
if not "perms" in item:
|
|
146
|
+
item["perms"] = self.default_perms["all"].copy()
|
|
147
|
+
item["perms"].update(self.default_perms["users"])
|
|
148
|
+
self.groups[name] = item
|
|
149
|
+
|
|
150
|
+
def save(self, **kw):
|
|
151
|
+
kw["path"] = self.path.path
|
|
152
|
+
kw["data"] = {"format": "MainShortcuts2.advanced.PermissionSystem/%i" % self.FORMAT}
|
|
153
|
+
kw["data"]["default_groups"] = self.default_groups
|
|
154
|
+
kw["data"]["default_perms"] = self.default_perms
|
|
155
|
+
kw["data"]["groups"] = self.groups
|
|
156
|
+
kw["data"]["users"] = self.users
|
|
157
|
+
return ms.json.write(**kw)
|
|
158
|
+
|
|
159
|
+
def _i_compare(self, perms: dict[str, bool], perm: str) -> bool:
|
|
160
|
+
for k, v in perms:
|
|
161
|
+
if re.match(k, perm):
|
|
162
|
+
return v
|
|
163
|
+
|
|
164
|
+
def _verify(self, username: str, permname: str) -> bool:
|
|
165
|
+
if not username in self.users:
|
|
166
|
+
return False
|
|
167
|
+
user = self.users[username]
|
|
168
|
+
if user["admin"]:
|
|
169
|
+
return True
|
|
170
|
+
if permname in user["perms"]:
|
|
171
|
+
return user["perms"][permname]
|
|
172
|
+
group = None
|
|
173
|
+
for i in user["groups"]:
|
|
174
|
+
if i in self.groups:
|
|
175
|
+
if self.groups[i]["admin"]:
|
|
176
|
+
return True
|
|
177
|
+
if permname in self.groups[i]["perms"]:
|
|
178
|
+
if group is None:
|
|
179
|
+
group = self.groups[i]
|
|
180
|
+
continue
|
|
181
|
+
if self.groups[i]["priority"] > group["priority"]:
|
|
182
|
+
group = self.groups[i]
|
|
183
|
+
continue
|
|
184
|
+
if group is None:
|
|
185
|
+
return False
|
|
186
|
+
return group["perms"][permname]
|
|
187
|
+
|
|
188
|
+
def verify(self, username: str, permname: str, raise_error: bool = True) -> bool:
|
|
189
|
+
"""Проверить есть ли у пользователя данное право. Если пользователь является админом или одна из его групп даёт разрешения админа, права есть. Если права не указаны в пользователе, используются права группы с наивысшим приоритетом, в которой указано это право. Если ни у одной группы нет права, значит права отсутствуют"""
|
|
190
|
+
result = self._verify(username, permname)
|
|
191
|
+
if raise_error:
|
|
192
|
+
if not result:
|
|
193
|
+
raise ms.types.AccessDeniedError(username, permname)
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
def add_group(self, name: str, perms: dict[str, bool] = None, *, add_defaults_perms: bool = True, admin: bool = False, priority: int = 0):
|
|
197
|
+
group = {
|
|
198
|
+
"admin": admin,
|
|
199
|
+
"perms": {} if perms is None else perms.copy(),
|
|
200
|
+
"priority": priority,
|
|
201
|
+
}
|
|
202
|
+
if add_defaults_perms:
|
|
203
|
+
for i in self.default_perms["all"]:
|
|
204
|
+
if not i in group["perms"]:
|
|
205
|
+
group["perms"][i] = self.default_perms["all"][i]
|
|
206
|
+
for i in self.default_perms["group"]:
|
|
207
|
+
if not i in group["perms"]:
|
|
208
|
+
group["perms"][i] = self.default_perms["group"][i]
|
|
209
|
+
self.groups[name] = group
|
|
210
|
+
|
|
211
|
+
def add_user(self, name: str, perms: dict[str, bool] = None, *, add_defaults_perms: bool = True, admin: bool = False, groups: list[str] = None):
|
|
212
|
+
user = {
|
|
213
|
+
"admin": admin,
|
|
214
|
+
"groups": [] if groups is None else groups,
|
|
215
|
+
"perms": {} if perms is None else perms.copy(),
|
|
216
|
+
}
|
|
217
|
+
if add_defaults_perms:
|
|
218
|
+
for i in self.default_perms["all"]:
|
|
219
|
+
if not i in user["perms"]:
|
|
220
|
+
user["perms"][i] = self.default_perms["all"][i]
|
|
221
|
+
for i in self.default_perms["user"]:
|
|
222
|
+
if not i in user["perms"]:
|
|
223
|
+
user["perms"][i] = self.default_perms["user"][i]
|
|
224
|
+
self.users[name] = user
|
|
225
|
+
|
|
226
|
+
def edit_group(self, name: str, *, admin: bool = None, perms: dict[str, Union[None, bool]] = None, priority: int = None):
|
|
227
|
+
group = self.groups[name]
|
|
228
|
+
if not admin is None:
|
|
229
|
+
group["admin"] = bool(admin)
|
|
230
|
+
if not priority is None:
|
|
231
|
+
group["priority"] = int(priority)
|
|
232
|
+
if not perms is None:
|
|
233
|
+
for k, v in perms.items():
|
|
234
|
+
if v is None:
|
|
235
|
+
if k in group["perms"]:
|
|
236
|
+
del group["perms"][k]
|
|
237
|
+
else:
|
|
238
|
+
group["perms"][k] = v
|
|
239
|
+
|
|
240
|
+
def edit_user(self, name: str, *, admin: bool = None, groups: list[str] = None, perms: dict[str, Union[None, bool]] = None):
|
|
241
|
+
user = self.users[name]
|
|
242
|
+
if not admin is None:
|
|
243
|
+
user["admin"] = bool(admin)
|
|
244
|
+
if not groups is None:
|
|
245
|
+
for i in groups:
|
|
246
|
+
assert type(i) == str
|
|
247
|
+
user["groups"] = groups
|
|
248
|
+
if not perms is None:
|
|
249
|
+
for k, v in perms.items():
|
|
250
|
+
if v is None:
|
|
251
|
+
if k in user["perms"]:
|
|
252
|
+
del user["perms"][k]
|
|
253
|
+
else:
|
|
254
|
+
user["perms"][k] = v
|
|
@@ -14,8 +14,8 @@ ext2type = {
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _check_type(path: str, type: Union[str, None]):
|
|
17
|
-
type = type.lower()
|
|
18
17
|
if not type is None:
|
|
18
|
+
type = type.lower()
|
|
19
19
|
if not type in types:
|
|
20
20
|
raise Exception("Type %r not supported" % type)
|
|
21
21
|
return type
|
|
@@ -80,7 +80,7 @@ class cfg:
|
|
|
80
80
|
"""Загрузка, использование и сохранение конфигов"""
|
|
81
81
|
# 2.0.0
|
|
82
82
|
|
|
83
|
-
def __init__(self, path: PATH_TYPES, data: dict = None, default: dict = None, type=None):
|
|
83
|
+
def __init__(self, path: PATH_TYPES, data: dict = None, default: dict = None, type: str = None):
|
|
84
84
|
self.data = {} if data is None else data
|
|
85
85
|
self.default = {} if default is None else default
|
|
86
86
|
self.path = path2str(path, True)
|
|
@@ -2,15 +2,26 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
from . import _module_info
|
|
4
4
|
from logging import Logger
|
|
5
|
-
|
|
5
|
+
from typing import Union
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
def
|
|
10
|
-
|
|
8
|
+
def _get_main_file():
|
|
9
|
+
def func():
|
|
10
|
+
if hasattr(sys, "frozen"):
|
|
11
|
+
if sys.frozen:
|
|
12
|
+
return sys.executable
|
|
13
|
+
if "__main__" in sys.modules:
|
|
14
|
+
if hasattr(sys.modules["__main__"], "__file__"):
|
|
15
|
+
return sys.modules["__main__"].__file__
|
|
16
|
+
result = func()
|
|
17
|
+
if result is None:
|
|
18
|
+
return
|
|
19
|
+
return os.path.abspath(result).replace("\\", "/")
|
|
20
|
+
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
class NoLogger(Logger):
|
|
23
|
+
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False, stacklevel=1):
|
|
24
|
+
pass
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
class MS2:
|
|
@@ -21,6 +32,7 @@ class MS2:
|
|
|
21
32
|
__name__: str = None,
|
|
22
33
|
logger: Logger = None,
|
|
23
34
|
**kw):
|
|
35
|
+
self._advanced = None
|
|
24
36
|
self._cfg = None
|
|
25
37
|
self._dict = None
|
|
26
38
|
self._dir = None
|
|
@@ -29,18 +41,23 @@ class MS2:
|
|
|
29
41
|
self._list = None
|
|
30
42
|
self._path = None
|
|
31
43
|
self._proc = None
|
|
44
|
+
self._regex = None
|
|
45
|
+
self._special_chars = None
|
|
32
46
|
self._str = None
|
|
33
47
|
self._term = None
|
|
34
48
|
self._types = None
|
|
35
49
|
self._utils = None
|
|
36
50
|
self._win = None
|
|
37
|
-
self.args = sys.argv
|
|
38
|
-
self.encoding = "utf-8"
|
|
39
|
-
self.env = os.environ
|
|
40
|
-
self.import_code = "from MainShortcuts2 import ms\nms.prog_file,ms.prog_name=__file__,__name__\nms.reload()"
|
|
51
|
+
self.args: list[str] = sys.argv
|
|
52
|
+
self.encoding: str = "utf-8"
|
|
53
|
+
self.env: dict[str, str] = os.environ
|
|
54
|
+
self.import_code: str = "from MainShortcuts2 import ms\nms.prog_file,ms.prog_name=__file__,__name__\nms.reload()"
|
|
41
55
|
self.log: Logger = NoLogger() if logger is None else logger
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
56
|
+
self.MAIN_FILE: Union[None, str] = _get_main_file()
|
|
57
|
+
self.MAIN_DIR: Union[None, str] = None if self.MAIN_FILE is None else os.path.abspath(self.MAIN_FILE)
|
|
58
|
+
self.prog_dir: Union[None, str] = None
|
|
59
|
+
self.prog_file: Union[None, str] = __file__
|
|
60
|
+
self.prog_name: Union[None, str] = __name__
|
|
44
61
|
self.reload()
|
|
45
62
|
|
|
46
63
|
def reload(self):
|
|
@@ -48,8 +65,8 @@ class MS2:
|
|
|
48
65
|
if not self.prog_file is None:
|
|
49
66
|
self.prog_dir = os.path.dirname(self.prog_file)
|
|
50
67
|
if not self.prog_name is None:
|
|
51
|
-
self.imported = self.prog_name != "__main__"
|
|
52
|
-
self.running = self.prog_name == "__main__"
|
|
68
|
+
self.imported: bool = self.prog_name != "__main__"
|
|
69
|
+
self.running: bool = self.prog_name == "__main__"
|
|
53
70
|
|
|
54
71
|
@property
|
|
55
72
|
def credits(self) -> str:
|
|
@@ -73,6 +90,13 @@ class MS2:
|
|
|
73
90
|
l.append("Спасибо за использование моей библиотеки".center(len(line)))
|
|
74
91
|
return "\n".join(l)
|
|
75
92
|
|
|
93
|
+
@property
|
|
94
|
+
def advanced(self):
|
|
95
|
+
if self._advanced is None:
|
|
96
|
+
from . import advanced
|
|
97
|
+
self._advanced = advanced
|
|
98
|
+
return self._advanced
|
|
99
|
+
|
|
76
100
|
@property
|
|
77
101
|
def cfg(self):
|
|
78
102
|
if self._cfg is None:
|
|
@@ -129,6 +153,20 @@ class MS2:
|
|
|
129
153
|
self._proc = proc
|
|
130
154
|
return self._proc
|
|
131
155
|
|
|
156
|
+
@property
|
|
157
|
+
def regex(self):
|
|
158
|
+
if self._regex is None:
|
|
159
|
+
from . import regex
|
|
160
|
+
self._regex = regex
|
|
161
|
+
return self._regex
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def special_chars(self):
|
|
165
|
+
if self._special_chars is None:
|
|
166
|
+
from . import special_chars
|
|
167
|
+
self._special_chars = special_chars
|
|
168
|
+
return self._special_chars
|
|
169
|
+
|
|
132
170
|
@property
|
|
133
171
|
def str(self):
|
|
134
172
|
if self._str is None:
|
|
@@ -8,7 +8,6 @@ try:
|
|
|
8
8
|
import json5
|
|
9
9
|
except Exception:
|
|
10
10
|
json5 = None
|
|
11
|
-
# 2.0.0
|
|
12
11
|
JSON_TYPES = Union[bool, dict, float, int, list, None, str]
|
|
13
12
|
|
|
14
13
|
|
|
@@ -42,7 +41,7 @@ def encode(data: JSON_TYPES, mode: str = "c", **kw):
|
|
|
42
41
|
return json.dumps(**kw)
|
|
43
42
|
|
|
44
43
|
|
|
45
|
-
def print(data: JSON_TYPES,
|
|
44
|
+
def print(data: JSON_TYPES, **kw):
|
|
46
45
|
"""Напечатать данные в виде текстового JSON"""
|
|
47
46
|
pr_kw = {}
|
|
48
47
|
for i in ["end", "file", "flush", "sep"]:
|
|
@@ -50,6 +49,10 @@ def print(data: JSON_TYPES, mode: str = "p", **kw):
|
|
|
50
49
|
pr_kw[i] = kw.pop(i)
|
|
51
50
|
kw["data"] = data
|
|
52
51
|
kw["mode"] = mode
|
|
52
|
+
if not "ensure_ascii" in kw:
|
|
53
|
+
kw["ensure_ascii"] = False
|
|
54
|
+
if not "mode" in kw:
|
|
55
|
+
kw["mode"] = "p"
|
|
53
56
|
builtins.print(encode(**kw), **pr_kw)
|
|
54
57
|
|
|
55
58
|
|
|
@@ -5,7 +5,6 @@ import pathlib
|
|
|
5
5
|
import shutil
|
|
6
6
|
from .core import ms
|
|
7
7
|
from typing import *
|
|
8
|
-
# 2.0.0
|
|
9
8
|
EXTSEP = "."
|
|
10
9
|
FORBIDDEN_SYMBOLS = [":", "!", "?", "@", "*", "\"", "\n", "%", "+", "<", ">", "|"]
|
|
11
10
|
PATH_TYPES = Union[io.FileIO, pathlib.Path, str]
|
|
@@ -25,7 +24,7 @@ def _path2str(path):
|
|
|
25
24
|
|
|
26
25
|
def path2str(path: PATH_TYPES, to_abs: bool = False, replace_forbidden_to: str = None) -> str:
|
|
27
26
|
"""Преобразовать объект Python в строковый путь"""
|
|
28
|
-
result = _path2str(path)
|
|
27
|
+
result = _path2str(path)
|
|
29
28
|
if not replace_forbidden_to is None:
|
|
30
29
|
for i in FORBIDDEN_SYMBOLS:
|
|
31
30
|
if i in replace_forbidden_to:
|
|
@@ -33,32 +32,26 @@ def path2str(path: PATH_TYPES, to_abs: bool = False, replace_forbidden_to: str =
|
|
|
33
32
|
result = result.replace(i, replace_forbidden_to)
|
|
34
33
|
if to_abs:
|
|
35
34
|
result = os.path.abspath(result)
|
|
36
|
-
return result
|
|
35
|
+
return result.replace("\\", PATHSEP)
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
def cwd(set_to: PATH_TYPES = None) -> str:
|
|
39
|
+
"""Получить путь к рабочей папке"""
|
|
40
|
+
if not set_to is None:
|
|
41
|
+
os.chdir(path2str(set_to))
|
|
42
42
|
return os.getcwd().replace("\\", PATHSEP)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
@cwd.setter
|
|
46
|
-
def cwd(v):
|
|
47
|
-
"""Текущая рабочая папка. Можно изменять (наверное)"""
|
|
48
|
-
os.chdir(path2str(v))
|
|
49
|
-
|
|
50
|
-
|
|
51
45
|
class Path:
|
|
52
46
|
"""Информация и действия с объектом файловой системы"""
|
|
53
|
-
# 2.0.0
|
|
54
47
|
|
|
55
48
|
def __init__(self, path: PATH_TYPES):
|
|
56
49
|
self.cp = self.copy
|
|
57
50
|
self.ln = self.link
|
|
58
51
|
self.mv = self.move
|
|
52
|
+
self.path = path2str(path, to_abs=True)
|
|
59
53
|
self.rm = self.delete
|
|
60
54
|
self.rn = self.rename
|
|
61
|
-
self.path = path
|
|
62
55
|
|
|
63
56
|
def reload(self, full: bool = False):
|
|
64
57
|
"""Удаление кешированной информации"""
|
|
@@ -351,6 +344,44 @@ def rename(path: PATH_TYPES, name: PATH_TYPES, **kw) -> str:
|
|
|
351
344
|
return move(**kw)
|
|
352
345
|
|
|
353
346
|
|
|
347
|
+
class TempFiles:
|
|
348
|
+
"""Удаление временных файлов по окончании операций с `with`"""
|
|
349
|
+
|
|
350
|
+
def __init__(self, *files: PATH_TYPES):
|
|
351
|
+
self.files: list[str] = []
|
|
352
|
+
self.add(*files)
|
|
353
|
+
|
|
354
|
+
def __enter__(self):
|
|
355
|
+
return self
|
|
356
|
+
|
|
357
|
+
def __exit__(self, a, b, c):
|
|
358
|
+
self.remove_files()
|
|
359
|
+
|
|
360
|
+
def __contains__(self, k):
|
|
361
|
+
return k in self.files
|
|
362
|
+
|
|
363
|
+
def __delitem__(self, k):
|
|
364
|
+
if type(k) == str:
|
|
365
|
+
k = self.files.index(k)
|
|
366
|
+
del self.files[k]
|
|
367
|
+
|
|
368
|
+
def add(self, *files: PATH_TYPES):
|
|
369
|
+
"""Добавить файлы в список временных"""
|
|
370
|
+
for i in files:
|
|
371
|
+
path = path2str(i, to_abs=True)
|
|
372
|
+
if not path in self.files:
|
|
373
|
+
self.files.append(path)
|
|
374
|
+
|
|
375
|
+
def remove_files(self, ignore_errors: bool = True):
|
|
376
|
+
"""Удалить все временные файлы"""
|
|
377
|
+
for i in self.files:
|
|
378
|
+
try:
|
|
379
|
+
delete(i)
|
|
380
|
+
except Exception:
|
|
381
|
+
if not ignore_errors:
|
|
382
|
+
raise
|
|
383
|
+
|
|
384
|
+
|
|
354
385
|
cp = copy
|
|
355
386
|
ln = link
|
|
356
387
|
mv = move
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
FIND_EMAIL = r"\S+@\S+\.\S+"
|
|
2
|
+
FIND_IPv4_ADDR = r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
|
|
3
|
+
FIND_MAC_ADDR = r"(?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2})"
|
|
4
|
+
FIND_PHONE_NUMBER = r"\+?[1-9][0-9]{7,14}"
|
|
5
|
+
FIND_URL = r"(?P<domain>\w+\.\w{2,3})"
|
|
6
|
+
FIND_URL2 = r"[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CIRCLE_EMOJI = {"black": "\u26ab", "blue": "\ud83d\udd35", "brown": "\ud83d\udfe4", "green": "\ud83d\udfe2", "orange": "\ud83d\udfe0", "red": "\ud83d\udd34", "violet": "\ud83d\udfe3", "white": "\u26aa", "yellow": "\ud83d\udfe1"}
|
|
2
|
+
CLOCK_EMOJI = {"00:00": "\ud83d\udd5b", "00:30": "\ud83d\udd67", "01:00": "\ud83d\udd50", "01:30": "\ud83d\udd5c", "02:00": "\ud83d\udd51", "02:30": "\ud83d\udd5d", "03:00": "\ud83d\udd52", "03:30": "\ud83d\udd5e", "04:00": "\ud83d\udd53", "04:30": "\ud83d\udd5f", "05:00": "\ud83d\udd54", "05:30": "\ud83d\udd60", "06:00": "\ud83d\udd55", "06:30": "\ud83d\udd61", "07:00": "\ud83d\udd56", "07:30": "\ud83d\udd62", "08:00": "\ud83d\udd57", "08:30": "\ud83d\udd63", "09:00": "\ud83d\udd58", "09:30": "\ud83d\udd64", "10:00": "\ud83d\udd59", "10:30": "\ud83d\udd65", "11:00": "\ud83d\udd5a", "11:30": "\ud83d\udd66"}
|
|
3
|
+
FONT_EMOJI = {"*": "*\ufe0f\u20e3", "#": "#\ufe0f\u20e3", "0": "0\ufe0f\u20e3", "1": "1\ufe0f\u20e3", "10": "\ud83d\udd1f", "2": "2\ufe0f\u20e3", "3": "3\ufe0f\u20e3", "4": "4\ufe0f\u20e3", "5": "5\ufe0f\u20e3", "6": "6\ufe0f\u20e3", "7": "7\ufe0f\u20e3", "8": "8\ufe0f\u20e3", "9": "9\ufe0f\u20e3", "a": "\ud83c\udd70\ufe0f", "ab": "\ud83c\udd8e", "abc": "\ud83d\udd24", "atm": "\ud83c\udfe7", "b": "\ud83c\udd71\ufe0f", "cl": "\ud83c\udd91", "cool": "\ud83c\udd92", "free": "\ud83c\udd93", "i": "\u2139\ufe0f", "m": "\u24c2\ufe0f", "new": "\ud83c\udd95", "ng": "\ud83c\udd96", "o": "\ud83c\udd7e\ufe0f", "ok": "\ud83c\udd97", "p": "\ud83c\udd7f\ufe0f", "sos": "\ud83c\udd98", "up!": "\ud83c\udd99", "vs": "\ud83c\udd9a", "wc": "\ud83d\udebe"}
|
|
4
|
+
FONT_LOWER = {"0": "\u2080", "1": "\u2081", "2": "\u2082", "3": "\u2083", "4": "\u2084", "5": "\u2085", "6": "\u2086", "7": "\u2087", "8": "\u2088", "9": "\u2089"}
|
|
5
|
+
FONT_UPPER = {"-": "\u207b", "(": "\u207d", ")": "\u207e", "+": "\u207a", "=": "\u207c", "0": "\u2070", "1": "\u00b9", "2": "\u00b2", "3": "\u00b3", "4": "\u2074", "5": "\u2075", "6": "\u2076", "7": "\u2077", "8": "\u2078", "9": "\u2079", "n": "\u207f"}
|
|
6
|
+
HEART_EMOJI = {"black": "\ud83d\udda4", "blue": "\ud83d\udc99", "brown": "\ud83e\udd0e", "green": "\ud83d\udc9a", "orange": "\ud83e\udde1", "red": "\u2764\ufe0f", "violet": "\ud83d\udc9c", "white": "\ud83e\udd0d", "yellow": "\ud83d\udc9b"}
|
|
7
|
+
LANG_EN = "abcdefghijklmnopqrstuvwxyz"
|
|
8
|
+
LANG_NUM = "0123456789"
|
|
9
|
+
LANG_RU = "\u0430\u0431\u0432\u0433\u0434\u0435\u0451\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f"
|
|
10
|
+
SQUARE_EMOJI = {"black": "\u2b1b", "blue": "\ud83d\udfe6", "brown": "\ud83d\udfeb", "green": "\ud83d\udfe9", "orange": "\ud83d\udfe7", "red": "\ud83d\udfe5", "violet": "\ud83d\udfea", "white": "\u2b1c", "yellow": "\ud83d\udfe8"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Различные объекты, которые не имеют функционала"""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Base:
|
|
5
|
+
def __init__(self, *args, **kwargs):
|
|
6
|
+
self.args = args
|
|
7
|
+
self.kwargs = kwargs
|
|
8
|
+
self.type = type(self)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AccessDeniedError(UserError):
|
|
12
|
+
pass
|
|
13
|
+
"""Ошибка доступа"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Empty(Base):
|
|
17
|
+
pass
|
|
18
|
+
"""Пустота (не равно `None`)"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Infinity(Base):
|
|
22
|
+
pass
|
|
23
|
+
"""Бесконечное число"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NotAFileError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
"""Ошибка 'это не файл'"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NotANumber(Base):
|
|
32
|
+
pass
|
|
33
|
+
"""Не число"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NotFound(Base):
|
|
37
|
+
pass
|
|
38
|
+
"""Не найдено"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class NotFoundError(Exception):
|
|
42
|
+
pass
|
|
43
|
+
"""Ошибка 'не найдено'"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UserError(Exception):
|
|
47
|
+
pass
|
|
48
|
+
"""Ошибка, которую допустил пользователь. Например неправильно указал входные данные"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Error401 = AccessDeniedError
|
|
52
|
+
Error403 = AccessDeniedError
|
|
53
|
+
Error404 = NotFoundError
|
|
54
|
+
Inf = Infinity
|
|
55
|
+
NaN = NotANumber
|
|
@@ -4,7 +4,6 @@ import sys
|
|
|
4
4
|
from .core import ms
|
|
5
5
|
from functools import wraps
|
|
6
6
|
from typing import *
|
|
7
|
-
# 2.0.0
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class MiddlewareBase:
|
|
@@ -19,22 +18,24 @@ class MiddlewareBase:
|
|
|
19
18
|
self._kwargs = None
|
|
20
19
|
self._result = None
|
|
21
20
|
self._traceback = None
|
|
22
|
-
self.completed_with_exc = None
|
|
23
|
-
self.completed = False
|
|
21
|
+
self.completed_with_exc: Union[None, bool] = None
|
|
22
|
+
self.completed: bool = False
|
|
24
23
|
self.func = func
|
|
25
|
-
self.launched = False
|
|
24
|
+
self.launched: bool = False
|
|
26
25
|
if not hasattr(self, "ignore_exceptions"):
|
|
27
|
-
self.ignore_exceptions = False
|
|
26
|
+
self.ignore_exceptions: bool = False
|
|
28
27
|
|
|
29
28
|
def _check_completed(self):
|
|
30
29
|
if not self.completed:
|
|
31
30
|
raise RuntimeError("The function has not yet been completed")
|
|
32
31
|
|
|
33
32
|
def _check_launched(self):
|
|
34
|
-
if not self.
|
|
33
|
+
if not self.launched:
|
|
35
34
|
raise RuntimeError("The function has not yet been launched")
|
|
36
35
|
|
|
37
36
|
def _run(self, args, kwargs):
|
|
37
|
+
if self.launched:
|
|
38
|
+
raise RuntimeError("You can only start a function once once")
|
|
38
39
|
self._args = args
|
|
39
40
|
self._kwargs = kwargs
|
|
40
41
|
self.launched = True
|
|
@@ -70,10 +71,10 @@ class MiddlewareBase:
|
|
|
70
71
|
return self._kwargs
|
|
71
72
|
|
|
72
73
|
@property
|
|
73
|
-
def result(self) -> Any:
|
|
74
|
+
def result(self) -> Union[Any, NoReturn]:
|
|
74
75
|
self._check_completed()
|
|
75
76
|
if self.completed_with_exc:
|
|
76
|
-
raise self.exception
|
|
77
|
+
raise self.exception # type: ignore
|
|
77
78
|
return self._result
|
|
78
79
|
|
|
79
80
|
@property
|
|
@@ -115,7 +116,7 @@ async def async_download_file(url: str, path: str, *, delete_on_error: bool = Tr
|
|
|
115
116
|
kw["url"] = url
|
|
116
117
|
if not "method" in kw:
|
|
117
118
|
kw["method"] = "GET"
|
|
118
|
-
async with async_request(**kw) as resp:
|
|
119
|
+
async with async_request(**kw) as resp: # type: ignore
|
|
119
120
|
with open(path, "wb") as fd:
|
|
120
121
|
size = 0
|
|
121
122
|
try:
|
|
@@ -286,7 +287,7 @@ def sync_request(method: str, url: str, *, ignore_status: bool = False, **kw):
|
|
|
286
287
|
raise err
|
|
287
288
|
kw["method"] = method
|
|
288
289
|
kw["url"] = url
|
|
289
|
-
resp = requests.request(**kw)
|
|
290
|
+
resp = requests.request(**kw) # type: ignore
|
|
290
291
|
if not ignore_status:
|
|
291
292
|
resp.raise_for_status()
|
|
292
293
|
return resp
|
|
@@ -315,5 +316,34 @@ def timedelta(time: Union[int, float, dict]):
|
|
|
315
316
|
return datetime.timedelta(seconds=time)
|
|
316
317
|
|
|
317
318
|
|
|
319
|
+
def shebang_code(code: str, *, exe_name: Union[None, str] = None, exe_path: Union[None, str] = None, none_if_no_changes: bool = False) -> Union[None, str]:
|
|
320
|
+
"""Вставить/заменить шебанг в коде. Если указать имя интерпретатора, путь будет найден с помощью `shutil.which`"""
|
|
321
|
+
if (exe_name is None) and (exe_path is None):
|
|
322
|
+
raise TypeError("Specify exe_name or exe_path")
|
|
323
|
+
if not exe_name is None:
|
|
324
|
+
if not exe_path is None:
|
|
325
|
+
raise TypeError("exe_name and exe_path cannot be used together")
|
|
326
|
+
if exe_path is None:
|
|
327
|
+
import shutil
|
|
328
|
+
exe_path = shutil.which(exe_name)
|
|
329
|
+
if exe_path is None:
|
|
330
|
+
raise Exception("Command %s not found in PATH" % exe_name)
|
|
331
|
+
lines = code.split("\n")
|
|
332
|
+
if none_if_no_changes:
|
|
333
|
+
if lines[0] == "#!" + exe_path:
|
|
334
|
+
return None
|
|
335
|
+
while len(lines[0].strip()) == 0 or lines[0].startswith("#!"):
|
|
336
|
+
del lines[0]
|
|
337
|
+
return "#!" + exe_path + "\n" + "\n".join(lines)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def shebang_file(path: str, *, exe_name: Union[None, str] = None, exe_path: Union[None, str] = None) -> int:
|
|
341
|
+
"""Вставить/заменить шебанг в файле кода"""
|
|
342
|
+
result = shebang_code(ms.file.read(path), exe_name=exe_name, exe_path=exe_path, none_if_no_changes=True)
|
|
343
|
+
if result is None:
|
|
344
|
+
return 0
|
|
345
|
+
return ms.file.write(path, result)
|
|
346
|
+
|
|
347
|
+
|
|
318
348
|
download_file = sync_download_file
|
|
319
349
|
request = sync_request
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"""Различные объекты, которые не имеют функционала"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Base:
|
|
5
|
-
def __init__(self):
|
|
6
|
-
self.type = type(self)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class UserError(Exception):
|
|
10
|
-
"""Ошибка, которую допустил пользователь. Например неправильно указал входные данные"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Empty(Base):
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class Infinity(Base):
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class NotANumber(Base):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class NotFound(Base):
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Inf = Infinity
|
|
30
|
-
NaN = NotANumber
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|