MainShortcuts2 2.4.2__tar.gz → 2.4.4__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 (30) hide show
  1. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/PKG-INFO +2 -3
  2. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/README.md +1 -2
  3. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/pyproject.toml +2 -2
  4. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/__main__.py +33 -23
  5. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/_module_info.py +1 -1
  6. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/advanced.py +38 -5
  7. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/base.py +18 -3
  8. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/gigachat.py +1 -1
  9. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/term.py +1 -1
  10. mainshortcuts2-2.4.4/src/MainShortcuts2/types.py +419 -0
  11. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/utils.py +27 -7
  12. mainshortcuts2-2.4.2/src/MainShortcuts2/types.py +0 -147
  13. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/__init__.py +0 -0
  14. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/_any2json_regs.py +0 -0
  15. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/any2json.py +0 -0
  16. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/russian_trusted_root_ca_pem.crt +0 -0
  17. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/russian_trusted_sub_ca_pem.crt +0 -0
  18. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/cfg.py +0 -0
  19. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/core.py +0 -0
  20. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/dict.py +0 -0
  21. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/dir.py +0 -0
  22. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/file.py +0 -0
  23. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/json.py +0 -0
  24. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/list.py +0 -0
  25. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/path.py +0 -0
  26. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/proc.py +0 -0
  27. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/regex.py +0 -0
  28. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/special_chars.py +0 -0
  29. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/str.py +0 -0
  30. {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/win.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MainShortcuts2
3
- Version: 2.4.2
3
+ Version: 2.4.4
4
4
  Summary: Сокращение и улучшение функций + консольные утилиты
5
5
  Home-page: https://github.com/MainPlay-TG/MainShortcuts2.py
6
6
  Author: MainPlay TG
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Project-URL: Repository, https://github.com/MainPlay-TG/MainShortcuts2.py
18
18
  Description-Content-Type: text/markdown
19
19
 
20
- # MainShortcuts
20
+ # MainShortcuts2
21
21
  ## Описание
22
22
  Сокращение и улучшение функций
23
23
  ## Установка
@@ -29,7 +29,6 @@ python3 -m pip install -U MainShortcuts2
29
29
  Импорт модуля
30
30
  ```python
31
31
  from MainShortcuts2 import ms
32
- exec(ms.import_code)
33
32
  ```
34
33
  Встроенная информация о модуле
35
34
  ```python
@@ -1,4 +1,4 @@
1
- # MainShortcuts
1
+ # MainShortcuts2
2
2
  ## Описание
3
3
  Сокращение и улучшение функций
4
4
  ## Установка
@@ -10,7 +10,6 @@ python3 -m pip install -U MainShortcuts2
10
10
  Импорт модуля
11
11
  ```python
12
12
  from MainShortcuts2 import ms
13
- exec(ms.import_code)
14
13
  ```
15
14
  Встроенная информация о модуле
16
15
  ```python
@@ -1,5 +1,5 @@
1
1
  [tool.poetry]
2
- version = "2.4.2"
2
+ version = "2.4.4"
3
3
  name = "MainShortcuts2"
4
4
  description = "Сокращение и улучшение функций + консольные утилиты"
5
5
  authors = [ "MainPlay TG <xbox.roman6666666666@gmail.com>",]
@@ -21,5 +21,5 @@ nginx-restart = "MainShortcuts2.__main__:nginx_restart"
21
21
  python = "^3.6"
22
22
 
23
23
  [build-system]
24
- requires = [ "poetry-core",]
25
24
  build-backend = "poetry.core.masonry.api"
25
+ requires = [ "poetry-core",]
@@ -11,17 +11,19 @@ def import_example():
11
11
  print("from MainShortcuts2 import ms")
12
12
 
13
13
 
14
- def nano_json():
14
+ def nano_json(args: argparse.Namespace = None):
15
15
  ms.utils.check_programs("nano")
16
16
  import subprocess
17
- argp = argparse.ArgumentParser("nano-json", description="форматирование JSON файлов и редактирование в GNU NANO")
18
- argp.add_argument("files", nargs="+", help="пути к файлам JSON")
19
- argp.add_argument("--encoding", default="utf-8", help="кодировка файлов")
20
- argp.add_argument("--nano-help", action="store_true", help="показать помощь nano")
21
- argp.add_argument("--no-escape", action="store_false", help="не использовать Unicode Escape")
22
- argp.add_argument("-f", "--rcfile", help="использовать только этот файл для настройки nano")
23
- argp.add_argument("-m", "--mode", choices=ms.json.MODES, default="p", help="режим сохранения редактирования")
24
- args = argp.parse_args()
17
+ if args is None:
18
+ argp = argparse.ArgumentParser("nano-json", description="форматирование JSON файлов и редактирование в GNU NANO")
19
+ argp.add_argument("files", nargs="+", help="пути к файлам JSON")
20
+ argp.add_argument("--nano-help", action="store_true", help="показать помощь nano")
21
+ argp.add_argument("-e", "--encoding", default="utf-8", help="кодировка файлов")
22
+ argp.add_argument("-f", "--rcfile", help="использовать только этот файл для настройки nano")
23
+ argp.add_argument("-m", "--mode", choices=ms.json.MODES, default="p", help="режим сохранения редактирования")
24
+ argp.add_argument("-s", "--sort", action="store_true", help="сортировать ключи словаря")
25
+ argp.add_argument("-u", "--no-escape", action="store_false", help="не использовать Unicode Escape")
26
+ args = argp.parse_args()
25
27
  if args.nano_help:
26
28
  return subprocess.call(["nano", "--help"])
27
29
  nano_args = ["nano"]
@@ -78,20 +80,22 @@ def nginx_restart():
78
80
  sys.exit(subprocess.call(["systemctl", "restart", "nginx"]))
79
81
 
80
82
 
81
- def hash_gen():
83
+ def hash_gen(args: argparse.Namespace = None):
82
84
  import os
83
85
  import hashlib
84
- argp = argparse.ArgumentParser("ms2-hash-gen", description="создание контрольной суммы для файла")
85
- argp.add_argument("files", nargs="+", help="пути к файлам")
86
- argp.add_argument("-b", "--bar", action="store_true", help="показывать прогрессбар (нужен модуль progressbar2)")
87
- argp.add_argument("-t", "--type", choices=HASH_TYPES, default="sha512", help="тип контрольной суммы")
88
- args = argp.parse_args()
86
+ if args is None:
87
+ argp = argparse.ArgumentParser("ms2-hash-gen", description="создание контрольной суммы для файла")
88
+ argp.add_argument("files", nargs="+", help="пути к файлам")
89
+ argp.add_argument("-b", "--bar", action="store_true", help="показывать прогрессбар (нужен модуль progressbar2)")
90
+ argp.add_argument("-t", "--type", choices=HASH_TYPES, default="sha512", help="тип контрольной суммы")
91
+ args = argp.parse_args()
89
92
  if args.bar:
90
93
  import progressbar
91
94
  pbar_w = [
92
95
  progressbar.Percentage(),
93
96
  progressbar.GranularBar(left="(", right=")"),
94
97
  progressbar.FileTransferSpeed(),
98
+ " ",
95
99
  progressbar.ETA(format="%(eta)8s", format_finished="%(elapsed)8s", format_na=" N/A", format_not_started="--:--:--", format_zero="00:00:00"),
96
100
  ]
97
101
  data = {}
@@ -119,30 +123,35 @@ def hash_gen():
119
123
  c += len(chunk)
120
124
  pbar.update(c)
121
125
  if args.bar:
122
- pbar.finish(dirty=True)
126
+ pbar.finish()
123
127
  data["hash"]["hex"] = hash.hexdigest()
124
128
  ms.json.write(file + HASH_SUFFIX, data)
125
129
 
126
130
 
127
- def hash_check():
131
+ def hash_check(args: argparse.Namespace = None):
128
132
  import hashlib
129
133
  import os
130
134
  import shlex
131
- argp = argparse.ArgumentParser("ms2-hash-check", description="проверка размера и контрольной суммы файла")
132
- argp.add_argument("-b", "--bar", action="store_true", help="показывать прогрессбар (нужен модуль progressbar2)")
133
- argp.add_argument("files", nargs="+", help="пути к файлам")
134
- args = argp.parse_args()
135
+ if args is None:
136
+ argp = argparse.ArgumentParser("ms2-hash-check", description="проверка размера и контрольной суммы файла")
137
+ argp.add_argument("-b", "--bar", action="store_true", help="показывать прогрессбар (нужен модуль progressbar2)")
138
+ argp.add_argument("files", nargs="+", help="пути к файлам")
139
+ args = argp.parse_args()
135
140
  if args.bar:
136
141
  import progressbar
137
142
  pbar_w = [
138
143
  progressbar.Percentage(),
139
144
  progressbar.GranularBar(left="(", right=")"),
140
145
  progressbar.FileTransferSpeed(),
146
+ " ",
141
147
  progressbar.ETA(format="%(eta)8s", format_finished="%(elapsed)8s", format_na=" N/A", format_not_started="--:--:--", format_zero="00:00:00"),
142
148
  ]
149
+ completed = []
143
150
  for file in args.files:
144
151
  if file.lower().endswith(HASH_SUFFIX.lower()):
145
152
  file = file[:0 - len(HASH_SUFFIX)]
153
+ if file in completed:
154
+ continue
146
155
  if not os.path.exists(file + HASH_SUFFIX):
147
156
  print("Ошибка: не найден файл " + shlex.quote(file + HASH_SUFFIX), file=sys.stderr)
148
157
  continue
@@ -165,10 +174,11 @@ def hash_check():
165
174
  hash.update(chunk)
166
175
  if args.bar:
167
176
  c += len(chunk)
168
- pbar.update()
177
+ pbar.update(c)
169
178
  if args.bar:
170
- pbar.finish(dirty=True)
179
+ pbar.finish()
171
180
  if data["hash"]["hex"] == hash.hexdigest():
172
181
  print("Успех: файл " + shlex.quote(file) + " не изменён")
173
182
  else:
174
183
  print("Ошибка: файл " + shlex.quote(file) + " изменён")
184
+ completed.append(file)
@@ -1,2 +1,2 @@
1
1
  name = "MainShortcuts2"
2
- version = "2.4.2"
2
+ version = "2.4.4"
@@ -319,15 +319,18 @@ class DictScriptRunner:
319
319
  self.reg_function(name)(func)
320
320
 
321
321
  def reg_class(self, name: str = None, overwrite: bool = False):
322
- def deco(cls: type):
323
- if name is None: # type: ignore
324
- name = cls.__module__ + "." + cls.__name__
322
+ if not name is None:
323
+ import warnings
324
+ warnings.warn("The argument 'name' temporarily does not work", FutureWarning)
325
+
326
+ def deco(cls: type) -> type:
327
+ name = cls.__module__ + "." + cls.__name__
325
328
  if callable(cls):
326
- self.reg_function(name, overwrite=overwrite)(cls) # type: ignore
329
+ self.reg_function(name, overwrite=overwrite)(cls)
327
330
  for k in dir(cls):
328
331
  v = getattr(cls, k)
329
332
  if callable(v):
330
- self.reg_function(name + "." + k, overwrite=overwrite) # type: ignore
333
+ self.reg_function(name + "." + k, overwrite=overwrite)
331
334
  return cls
332
335
  return deco
333
336
 
@@ -359,3 +362,33 @@ class DictScriptRunner:
359
362
  if "save_to" in act:
360
363
  locals[act["save_to"]] = result
361
364
  return locals
365
+
366
+
367
+ class CodeModule:
368
+ """Импорт модуля из исходного кода. Могут быть баги"""
369
+
370
+ def __init__(self, source: str, globals: dict = None, locals: dict = None):
371
+ if globals is None:
372
+ globals = {}
373
+ if locals is None:
374
+ locals = {}
375
+ args = (source, globals, locals)
376
+ exec(*args)
377
+ self.__dict__["source"] = args[0]
378
+ self.__dict__["globals"] = args[1]
379
+ self.__dict__["globals"].update(args[2])
380
+
381
+ def __delattr__(self, k):
382
+ del self.__dict__["globals"][k]
383
+
384
+ def __dir__(self) -> list[str]:
385
+ return list(self.__dict__["globals"])
386
+
387
+ def __getattr__(self, k):
388
+ return self.__dict__["globals"][k]
389
+
390
+ def __hasattr__(self, k) -> bool:
391
+ return k in self.__dict__["globals"]
392
+
393
+ def __setattr__(self, k, v):
394
+ self.__dict__["globals"][k] = v
@@ -2,15 +2,30 @@ import requests
2
2
 
3
3
 
4
4
  class Base:
5
- def __init__(self, session: requests.Session = None):
5
+ def __init__(self, **kw):
6
+ self._init(**kw)
7
+
8
+ def _init(self, session: requests.Session = None):
6
9
  self._headers = {}
7
- self._http = requests.Session() if session is None else session
10
+ self._http = session
8
11
  self._params = {}
9
- self._url_data = {}
10
12
  self._url = "https://example.com/api/{method}"
13
+ self._url_data = {}
14
+
15
+ def __enter__(self):
16
+ return self
17
+
18
+ def __exit__(self, *a):
19
+ self.http.close()
20
+
21
+ def __repr__(self) -> str:
22
+ cls = type(self)
23
+ return "%s.%s(...)" % (cls.__module__, cls.__name__)
11
24
 
12
25
  @property
13
26
  def http(self) -> requests.Session:
27
+ if self._http is None:
28
+ self._http = requests.Session()
14
29
  return self._http
15
30
 
16
31
  def _request(self, http_method: str, api_method: str, *, headers: dict[str, str] = None, params: dict[str, str] = None, raise_for_status: bool = True, url_data: dict[str, str] = None, **kw):
@@ -9,7 +9,7 @@ class GigaChat(Base):
9
9
  def __init__(self, auth_data: str, client_id: str, *,
10
10
  cert_path: str = None,
11
11
  **kw):
12
- Base.__init__(self, **kw)
12
+ self._init(**kw)
13
13
  self._access_token = {"expire_at": 0, "kw": None, "token": None}
14
14
  self._auth_data: str = auth_data
15
15
  self._client_id: str = client_id
@@ -95,7 +95,7 @@ def color_test(colors: list[str] = None):
95
95
  cprint("$COLOR_RESET%s: $COLOR_%sEXAMPLE \u2591\u2592\u2593 \u2588\u2588\u2588" % (i, i))
96
96
 
97
97
 
98
- def _clear():
98
+ def _clear(): # type: ignore
99
99
  """Если функция недоступна для ОС, дать ошибку"""
100
100
  raise OSError("The function is not available on %s" % sys.platform)
101
101
 
@@ -0,0 +1,419 @@
1
+ """Различные объекты и исключения"""
2
+ from typing import Union
3
+
4
+
5
+ class Base:
6
+ def __init__(self, *args, **kwargs):
7
+ self.args = args
8
+ self.kwargs = kwargs
9
+ self.type = type(self)
10
+
11
+
12
+ class UserError(Exception):
13
+ pass
14
+ """Ошибка, которую допустил пользователь. Например неправильно указал входные данные"""
15
+
16
+
17
+ class AccessDeniedError(UserError):
18
+ pass
19
+ """Ошибка доступа"""
20
+
21
+
22
+ class Empty(Base):
23
+ pass
24
+ """Пустота (не равно `None`)"""
25
+
26
+
27
+ class Infinity(Base):
28
+ pass
29
+ """Бесконечное число"""
30
+
31
+
32
+ class NotAFileError(Exception):
33
+ pass
34
+ """Ошибка 'это не файл'"""
35
+
36
+
37
+ class NotANumber(Base):
38
+ pass
39
+ """Не число"""
40
+
41
+
42
+ class NotFound(Base):
43
+ pass
44
+ """Не найдено"""
45
+
46
+
47
+ class NotFoundError(Exception):
48
+ pass
49
+ """Ошибка 'не найдено'"""
50
+
51
+
52
+ class Action:
53
+ def __init__(self, func, *args, **kwargs):
54
+ self._closed = False
55
+ self._completed = False
56
+ self._error = None
57
+ self._launched = False
58
+ self._result = None
59
+ self.args: tuple = args
60
+ self.func = func
61
+ self.kwargs: dict = kwargs
62
+
63
+ def __enter__(self):
64
+ return self
65
+
66
+ def __exit__(self, a, b, c):
67
+ self.close()
68
+
69
+ def _check(self, launched: bool = None, completed: bool = None, closed: bool = None):
70
+ if not launched is None:
71
+ if launched:
72
+ if not self.launched:
73
+ raise RuntimeError("The action has not yet been launched")
74
+ else:
75
+ if self.launched:
76
+ raise RuntimeError("The action has already been launched")
77
+ if not completed is None:
78
+ if completed:
79
+ if not self.completed:
80
+ raise RuntimeError("The action has not yet been completed")
81
+ else:
82
+ if self.completed:
83
+ raise RuntimeError("The action has already been completed")
84
+ if not closed is None:
85
+ if closed:
86
+ if not self.closed:
87
+ raise RuntimeError("The action has not yet been closed")
88
+ else:
89
+ if self.closed:
90
+ raise RuntimeError("The action has already been closed")
91
+
92
+ def close(self):
93
+ self._closed = True
94
+ self._error = None
95
+ self._result = None
96
+ self.args = None
97
+ self.func = None
98
+ self.kwargs = None
99
+
100
+ @property
101
+ def launched(self) -> bool:
102
+ return self._launched
103
+
104
+ @property
105
+ def completed(self) -> bool:
106
+ return self._completed
107
+
108
+ @property
109
+ def exception(self) -> Union[None, Exception]:
110
+ self._check(launched=True, completed=True, closed=False)
111
+ return self._error
112
+
113
+ @property
114
+ def result(self):
115
+ self._check(launched=True, completed=True, closed=False)
116
+ if not self._error is None:
117
+ raise self._error # type: ignore
118
+ return self._result
119
+
120
+ @property
121
+ def closed(self) -> bool:
122
+ return self._closed
123
+
124
+ def run(self):
125
+ self._check(launched=False, completed=False, closed=False)
126
+ self._launched = True
127
+ try:
128
+ self._result = self.func(*self.args, **self.kwargs) # type: ignore
129
+ except Exception as err:
130
+ self._error = err
131
+ self._completed = True
132
+
133
+
134
+ class OnlyOneInstanceError(BaseException):
135
+ """Ошибка для `.utils.OnlyOneInstance`"""
136
+
137
+ def __init__(self, *args):
138
+ if len(args) == 0:
139
+ args = ("Another instance is already running",)
140
+ BaseException.__init__(self, *args)
141
+
142
+
143
+ class DotDict:
144
+ def __init__(self, data: dict = None):
145
+ self._data = {}
146
+ for k in data:
147
+ self[k] = data[k]
148
+
149
+ def __delattr__(self, k: str):
150
+ if k.startswith("_"):
151
+ return object.__delattr__(self, k)
152
+ del self.data[k]
153
+
154
+ def __getattr__(self, k: str):
155
+ if k.startswith("_"):
156
+ return object.__getattr__(self, k)
157
+ return self[k]
158
+
159
+ def __setattr__(self, k: str, v):
160
+ if k.startswith("_"):
161
+ return object.__setattr__(self, k, v)
162
+ self[k] = v
163
+
164
+ def __contains__(self, k: str):
165
+ return k in self._data
166
+
167
+ def __delitem__(self, k: str):
168
+ del self._data[k]
169
+
170
+ def __getitem__(self, k: str):
171
+ return self._data[k]
172
+
173
+ def __setitem__(self, k: str, v):
174
+ if "." in k:
175
+ k, subk = k.split(".", 1)
176
+ v = {subk: v}
177
+ if isinstance(v, dict):
178
+ v = type(self)(v)
179
+ self._data[k] = v
180
+
181
+
182
+ class Color:
183
+ """Значение цвета"""
184
+
185
+ def __init__(self, *args,
186
+ ahex: str = None,
187
+ hex: str = None,
188
+ hexa: str = None,
189
+ rgb: tuple[int, int, int] = None,
190
+ rgba: tuple[int, int, int, int] = None,
191
+ ):
192
+ self._init_complete = False
193
+ self._readonly = False
194
+ self._red = None
195
+ self._green = None
196
+ self._blue = None
197
+ self._alpha = 255
198
+ largs = len(args)
199
+ if largs == 0:
200
+ pass
201
+ elif largs == 3:
202
+ self._check_init()
203
+ self.rgb = args
204
+ elif largs == 4:
205
+ self._check_init()
206
+ self.rgba = args
207
+ else:
208
+ raise TypeError("Give from 3 to 4 positional arguments or 1 keyword argument")
209
+ if not ahex is None:
210
+ self._check_init()
211
+ self.ahex = ahex
212
+ if not hex is None:
213
+ self._check_init()
214
+ self.hex = hex
215
+ if not hexa is None:
216
+ self._check_init()
217
+ self.hexa = hexa
218
+ if not rgb is None:
219
+ self._check_init()
220
+ self.rgb = rgb
221
+ if not rgba is None:
222
+ self._check_init()
223
+ self.rgba = rgba
224
+ if not self._init_complete:
225
+ raise TypeError("Give from 3 to 4 positional arguments or 1 keyword argument")
226
+
227
+ def __repr__(self):
228
+ cls = type(self)
229
+ return "%s.%s(rgba=%r)" % (cls.__module__, cls.__name__, self.rgba)
230
+
231
+ @property
232
+ def red(self) -> int:
233
+ """Уровень красного от 0 до 255"""
234
+ return self._red
235
+
236
+ @red.setter
237
+ def red(self, v):
238
+ self._check_ro()
239
+ self._red = self._check_num(v)
240
+ self._reset()
241
+
242
+ @property
243
+ def green(self) -> int:
244
+ """Уровень зелёного от 0 до 255"""
245
+ return self._green
246
+
247
+ @green.setter
248
+ def green(self, v):
249
+ self._check_ro()
250
+ self._green = self._check_num(v)
251
+ self._reset()
252
+
253
+ @property
254
+ def blue(self) -> int:
255
+ """Уровень синего от 0 до 255"""
256
+ return self._blue
257
+
258
+ @blue.setter
259
+ def blue(self, v):
260
+ self._check_ro()
261
+ self._blue = self._check_num(v)
262
+ self._reset()
263
+
264
+ @property
265
+ def alpha(self) -> int:
266
+ """Уровень прозрачности от 0 до 255"""
267
+ return self._alpha
268
+
269
+ @alpha.setter
270
+ def alpha(self, v):
271
+ self._check_ro()
272
+ self._alpha = self._check_num(v)
273
+ self._reset()
274
+
275
+ @property
276
+ def ahex(self) -> str:
277
+ """Цвет в формате AHEX (`aarrggbb`)"""
278
+ if self._ahex is None:
279
+ self._ahex = ("%02x" % self.alpha) + self.hex
280
+ return self._ahex
281
+
282
+ @ahex.setter
283
+ def ahex(self, v):
284
+ self.alpha, self.red, self.green, self.blue = self._split_hex(v, 4)
285
+
286
+ @property
287
+ def hex(self) -> str:
288
+ """Цвет в формате HEX (`rrggbb`). **Без прозрачности**"""
289
+ if self._hex is None:
290
+ self._hex = "%02x%02x%02x" % self.rgb
291
+ return self._hex
292
+
293
+ @hex.setter
294
+ def hex(self, v):
295
+ self.rgb = self._split_hex(v, 3)
296
+
297
+ @property
298
+ def hexa(self) -> str:
299
+ """Цвет в формате HEXA (`rrggbbaa`)"""
300
+ if self._hexa is None:
301
+ self._hexa = self.hex + ("%02x" % self.alpha)
302
+ return self._hexa
303
+
304
+ @hexa.setter
305
+ def hexa(self, v):
306
+ self.rgba = self._split_hex(v, 4)
307
+
308
+ @property
309
+ def rgb(self) -> tuple[int, int, int]:
310
+ """Цвет в формате RGB. **Без прозрачности**"""
311
+ if self._rgb is None:
312
+ self._rgb = (self.red, self.green, self.blue)
313
+ return self._rgb
314
+
315
+ @rgb.setter
316
+ def rgb(self, v):
317
+ self.red, self.green, self.blue = v
318
+
319
+ @property
320
+ def rgba(self) -> tuple[int, int, int, int]:
321
+ """Цвет в формате RGBA"""
322
+ if self._rgba is None:
323
+ self._rgba = (self.red, self.green, self.blue, self.alpha)
324
+ return self._rgba
325
+
326
+ @rgba.setter
327
+ def rgba(self, v):
328
+ self.red, self.green, self.blue, self.alpha = v
329
+
330
+ def _check_init(self):
331
+ if self._init_complete:
332
+ raise TypeError("Give from 3 to 4 positional arguments or 1 keyword argument")
333
+ self._init_complete = True
334
+
335
+ def _check_num(self, num: int):
336
+ if not isinstance(num, int):
337
+ raise TypeError(type(num), int)
338
+ if (num < 0) or (num > 255):
339
+ raise ValueError("The number should be in the range from 0 to 255, not %i" % num)
340
+ return num
341
+
342
+ def _check_ro(self):
343
+ if self._readonly:
344
+ raise RuntimeError("Read-only object")
345
+
346
+ def _reset(self):
347
+ self._ahex = None
348
+ self._hex = None
349
+ self._hexa = None
350
+ self._rgb = None
351
+ self._rgba = None
352
+
353
+ def _split_hex(self, hex: str, n: int) -> list[int]:
354
+ if hex[0] == "#":
355
+ hex = hex[1:]
356
+ lhex = len(hex)
357
+ if lhex != n * 2:
358
+ raise ValueError("Invalid HEX string")
359
+ nums = [int(num, 16) for num in [hex[i:i + 2] for i in range(0, lhex, 2)]]
360
+ return nums
361
+
362
+ @classmethod
363
+ def from_random(cls, red: int | range | slice = None, green: int | range | slice = None, blue: int | range | slice = None, alpha: int | range | slice = 255):
364
+ """Случайный цвет"""
365
+ from random import randint, randrange
366
+
367
+ def random(num: int | range | slice):
368
+ if num is None:
369
+ return randint(0, 255)
370
+ if isinstance(num, range) or isinstance(num, slice):
371
+ return randrange(num.start, num.stop, num.step)
372
+ return num
373
+ return cls(random(red), random(green), random(blue), random(alpha))
374
+
375
+
376
+ class _COLORS:
377
+ BLACK = 0, 0, 0
378
+ BLUE = 0, 0, 255
379
+ CYAN = 0, 255, 255
380
+ GREEN = 0, 255, 0
381
+ NULL = 0, 0, 0, 0
382
+ RED = 255, 0, 0
383
+ VIOLET = 255, 0, 255
384
+ WHITE = 255, 255, 255
385
+ YELLOW = 255, 255, 0
386
+
387
+ def __init__(self):
388
+ self._colors = {}
389
+ for name in dir(self):
390
+ if name[0] != "_":
391
+ color = Color(*getattr(self, name))
392
+ color._readonly = True
393
+ self._colors[name] = color
394
+ setattr(self, name, color)
395
+
396
+ def __contains__(self, k) -> bool:
397
+ return k.upper() in self._colors
398
+
399
+ def __getitem__(self, k) -> Color:
400
+ return self._colors[k.upper()]
401
+
402
+
403
+ class AutoaddDict(dict):
404
+ def __init__(self, *args, default_value=None, **kwargs):
405
+ dict.__init__(self, *args, **kwargs)
406
+ self.default_value = default_value
407
+
408
+ def __getitem__(self, k):
409
+ if not k in self:
410
+ self[k] = self.default_value
411
+ return dict.__getitem__(self, k)
412
+
413
+
414
+ COLORS = _COLORS()
415
+ Error401 = AccessDeniedError
416
+ Error403 = AccessDeniedError
417
+ Error404 = NotFoundError
418
+ Inf = Infinity
419
+ NaN = NotANumber
@@ -317,7 +317,7 @@ def timedelta(time: Union[int, float, dict]):
317
317
  return datetime.timedelta(seconds=time)
318
318
 
319
319
 
320
- 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
+ def shebang_code(code: str, *, exe_name: Union[None, str] = None, exe_path: Union[None, str] = None, none_if_no_changes: bool = False, use_env: bool = True) -> Union[None, str]:
321
321
  """Вставить/заменить шебанг в коде. Если указать имя интерпретатора, путь будет найден с помощью `shutil.which`"""
322
322
  if (exe_name is None) and (exe_path is None):
323
323
  raise TypeError("Specify exe_name or exe_path")
@@ -325,10 +325,13 @@ def shebang_code(code: str, *, exe_name: Union[None, str] = None, exe_path: Unio
325
325
  if not exe_path is None:
326
326
  raise TypeError("exe_name and exe_path cannot be used together")
327
327
  if exe_path is None:
328
- import shutil
329
- exe_path = shutil.which(exe_name)
330
- if exe_path is None:
331
- raise Exception("Command %s not found in PATH" % exe_name)
328
+ if use_env:
329
+ exe_path = "/bin/env " + exe_name
330
+ else:
331
+ import shutil
332
+ exe_path = shutil.which(exe_name)
333
+ if exe_path is None:
334
+ raise Exception("Command %s not found in PATH" % exe_name)
332
335
  lines = code.split("\n")
333
336
  if none_if_no_changes:
334
337
  if lines[0] == "#!" + exe_path:
@@ -338,9 +341,11 @@ def shebang_code(code: str, *, exe_name: Union[None, str] = None, exe_path: Unio
338
341
  return "#!" + exe_path + "\n" + "\n".join(lines)
339
342
 
340
343
 
341
- def shebang_file(path: str, *, exe_name: Union[None, str] = None, exe_path: Union[None, str] = None) -> int:
344
+ def shebang_file(path: str, **kw) -> int:
342
345
  """Вставить/заменить шебанг в файле кода"""
343
- result = shebang_code(ms.file.read(path), exe_name=exe_name, exe_path=exe_path, none_if_no_changes=True)
346
+ kw["code"] = ms.file.read(path)
347
+ kw["none_if_no_changes"] = True
348
+ result = shebang_code(**kw)
344
349
  if result is None:
345
350
  return 0
346
351
  return ms.file.write(path, result)
@@ -469,5 +474,20 @@ def check_programs(*progs: str, raise_error: bool = True) -> list[str]:
469
474
  return failed
470
475
 
471
476
 
477
+ def handle_exception(on_exception=return_None, reraise: bool = True, exc_type: type[BaseException] = Exception):
478
+ """Обернуть функцию в `try`/`except`"""
479
+ def deco(func):
480
+ @wraps(func)
481
+ def wrapper(*args, **kwargs):
482
+ try:
483
+ return func(*args, **kwargs)
484
+ except exc_type as exc:
485
+ on_exception(exc)
486
+ if reraise:
487
+ raise
488
+ return wrapper
489
+ return deco
490
+
491
+
472
492
  download_file = sync_download_file
473
493
  request = sync_request
@@ -1,147 +0,0 @@
1
- """Различные объекты и исключения"""
2
- from typing import Union
3
-
4
-
5
- class Base:
6
- def __init__(self, *args, **kwargs):
7
- self.args = args
8
- self.kwargs = kwargs
9
- self.type = type(self)
10
-
11
-
12
- class UserError(Exception):
13
- pass
14
- """Ошибка, которую допустил пользователь. Например неправильно указал входные данные"""
15
-
16
-
17
- class AccessDeniedError(UserError):
18
- pass
19
- """Ошибка доступа"""
20
-
21
-
22
- class Empty(Base):
23
- pass
24
- """Пустота (не равно `None`)"""
25
-
26
-
27
- class Infinity(Base):
28
- pass
29
- """Бесконечное число"""
30
-
31
-
32
- class NotAFileError(Exception):
33
- pass
34
- """Ошибка 'это не файл'"""
35
-
36
-
37
- class NotANumber(Base):
38
- pass
39
- """Не число"""
40
-
41
-
42
- class NotFound(Base):
43
- pass
44
- """Не найдено"""
45
-
46
-
47
- class NotFoundError(Exception):
48
- pass
49
- """Ошибка 'не найдено'"""
50
-
51
-
52
- class Action:
53
- def __init__(self, func, *args, **kwargs):
54
- self._closed = False
55
- self._completed = False
56
- self._error = None
57
- self._launched = False
58
- self._result = None
59
- self.args: tuple = args
60
- self.func = func
61
- self.kwargs: dict = kwargs
62
-
63
- def __enter__(self):
64
- return self
65
-
66
- def __exit__(self, a, b, c):
67
- self.close()
68
-
69
- def _check(self, launched: bool = None, completed: bool = None, closed: bool = None):
70
- if not launched is None:
71
- if launched:
72
- if not self.launched:
73
- raise RuntimeError("The action has not yet been launched")
74
- else:
75
- if self.launched:
76
- raise RuntimeError("The action has already been launched")
77
- if not completed is None:
78
- if completed:
79
- if not self.completed:
80
- raise RuntimeError("The action has not yet been completed")
81
- else:
82
- if self.completed:
83
- raise RuntimeError("The action has already been completed")
84
- if not closed is None:
85
- if closed:
86
- if not self.closed:
87
- raise RuntimeError("The action has not yet been closed")
88
- else:
89
- if self.closed:
90
- raise RuntimeError("The action has already been closed")
91
-
92
- def close(self):
93
- self._closed = True
94
- self._error = None
95
- self._result = None
96
- self.args = None
97
- self.func = None
98
- self.kwargs = None
99
-
100
- @property
101
- def launched(self) -> bool:
102
- return self._launched
103
-
104
- @property
105
- def completed(self) -> bool:
106
- return self._completed
107
-
108
- @property
109
- def exception(self) -> Union[None, Exception]:
110
- self._check(launched=True, completed=True, closed=False)
111
- return self._error
112
-
113
- @property
114
- def result(self):
115
- self._check(launched=True, completed=True, closed=False)
116
- if not self._error is None:
117
- raise self._error # type: ignore
118
- return self._result
119
-
120
- @property
121
- def closed(self) -> bool:
122
- return self._closed
123
-
124
- def run(self):
125
- self._check(launched=False, completed=False, closed=False)
126
- self._launched = True
127
- try:
128
- self._result = self.func(*self.args, **self.kwargs) # type: ignore
129
- except Exception as err:
130
- self._error = err
131
- self._completed = True
132
-
133
-
134
- class OnlyOneInstanceError(BaseException):
135
- """Ошибка для `.utils.OnlyOneInstance`"""
136
-
137
- def __init__(self, *args):
138
- if len(args) == 0:
139
- args = ("Another instance is already running",)
140
- BaseException.__init__(self, *args)
141
-
142
-
143
- Error401 = AccessDeniedError
144
- Error403 = AccessDeniedError
145
- Error404 = NotFoundError
146
- Inf = Infinity
147
- NaN = NotANumber