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.
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/PKG-INFO +2 -3
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/README.md +1 -2
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/pyproject.toml +2 -2
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/__main__.py +33 -23
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/_module_info.py +1 -1
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/advanced.py +38 -5
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/base.py +18 -3
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/gigachat.py +1 -1
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/term.py +1 -1
- mainshortcuts2-2.4.4/src/MainShortcuts2/types.py +419 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/utils.py +27 -7
- mainshortcuts2-2.4.2/src/MainShortcuts2/types.py +0 -147
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/__init__.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/_any2json_regs.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/any2json.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/russian_trusted_root_ca_pem.crt +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/russian_trusted_sub_ca_pem.crt +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/cfg.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/core.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/dict.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/dir.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/file.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/json.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/list.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/path.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/proc.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/regex.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/special_chars.py +0 -0
- {mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/str.py +0 -0
- {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.
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
+
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
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
|
+
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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)
|
|
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)
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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,
|
|
344
|
+
def shebang_file(path: str, **kw) -> int:
|
|
342
345
|
"""Вставить/заменить шебанг в файле кода"""
|
|
343
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/russian_trusted_root_ca_pem.crt
RENAMED
|
File without changes
|
{mainshortcuts2-2.4.2 → mainshortcuts2-2.4.4}/src/MainShortcuts2/api/russian_trusted_sub_ca_pem.crt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|