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.
Files changed (24) hide show
  1. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/PKG-INFO +1 -1
  2. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/pyproject.toml +3 -3
  3. mainshortcuts2-2.2.0/src/MainShortcuts2/__main__.py +3 -0
  4. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/_module_info.py +1 -1
  5. mainshortcuts2-2.2.0/src/MainShortcuts2/advanced.py +254 -0
  6. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/cfg.py +2 -2
  7. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/core.py +52 -14
  8. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/file.py +1 -5
  9. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/json.py +5 -2
  10. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/list.py +0 -1
  11. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/path.py +45 -14
  12. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/proc.py +0 -1
  13. mainshortcuts2-2.2.0/src/MainShortcuts2/regex.py +6 -0
  14. mainshortcuts2-2.2.0/src/MainShortcuts2/special_chars.py +10 -0
  15. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/str.py +0 -1
  16. mainshortcuts2-2.2.0/src/MainShortcuts2/types.py +55 -0
  17. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/utils.py +40 -10
  18. mainshortcuts2-2.1.2/src/MainShortcuts2/types.py +0 -30
  19. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/README.md +0 -0
  20. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/__init__.py +0 -0
  21. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/dict.py +0 -0
  22. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/dir.py +0 -0
  23. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/term.py +0 -0
  24. {mainshortcuts2-2.1.2 → mainshortcuts2-2.2.0}/src/MainShortcuts2/win.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MainShortcuts2
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: Сокращение и улучшение функций
5
5
  Home-page: https://github.com/MainPlay-TG/MainShortcuts2.py
6
6
  Author: MainPlay TG
@@ -1,5 +1,5 @@
1
1
  [tool.poetry]
2
- version = "2.1.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
- # [tool.poetry.scripts]
13
- # ms2-import_example = "MainShortcuts2.__main__:import_example"
12
+ [tool.poetry.scripts]
13
+ ms2-import_example = "MainShortcuts2.__main__:import_example"
14
14
 
15
15
  [tool.poetry.dependencies]
16
16
  python = "^3.6"
@@ -0,0 +1,3 @@
1
+ def import_example():
2
+ print("from MainShortcuts2 import ms")
3
+ print("exec(ms.import_code)")
@@ -1,2 +1,2 @@
1
1
  name = "MainShortcuts2"
2
- version = "2.1.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
- # 2.0.0
5
+ from typing import Union
6
6
 
7
7
 
8
- class NoLogger:
9
- def __init__(*a, **b):
10
- pass
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
- def __getattr__(self, k):
13
- return lambda *a, **b: None
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.prog_file = __file__
43
- self.prog_name = __name__
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:
@@ -3,12 +3,8 @@ import builtins
3
3
  import os
4
4
  from .core import ms
5
5
  from .path import PATH_TYPES, path2str
6
+ from .types import NotAFileError
6
7
  from typing import *
7
- # 2.0.0
8
-
9
-
10
- class NotAFileError(Exception):
11
- pass
12
8
 
13
9
 
14
10
  def _check(path, **kw) -> str:
@@ -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, mode: str = "p", **kw):
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
 
@@ -2,7 +2,6 @@
2
2
  import re
3
3
  from .core import ms
4
4
  from typing import *
5
- # 2.0.0
6
5
 
7
6
 
8
7
  def filter(a: list, whitelist: list = None, blacklist: list = [], regex: str = False, begin: str = None, end: str = None):
@@ -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).replace("\\", PATHSEP)
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
- @property
40
- def cwd():
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
@@ -3,7 +3,6 @@ import subprocess
3
3
  from .core import ms
4
4
  from functools import wraps
5
5
  from typing import *
6
- # 2.0.0
7
6
  Popen_kw = {
8
7
  "stderr": subprocess.PIPE,
9
8
  "stdin": subprocess.PIPE,
@@ -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"}
@@ -1,7 +1,6 @@
1
1
  """Работа со строками"""
2
2
  from .core import ms
3
3
  from typing import *
4
- # 2.0.0
5
4
 
6
5
 
7
6
  def list2str(a: Union[Iterable]) -> list:
@@ -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.completed:
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