MainShortcuts2 2.2.1__tar.gz → 2.2.3__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.2.1 → mainshortcuts2-2.2.3}/PKG-INFO +1 -1
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/pyproject.toml +1 -1
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/_module_info.py +1 -1
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/cfg.py +2 -1
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/core.py +1 -1
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/path.py +15 -13
- mainshortcuts2-2.2.3/src/MainShortcuts2/types.py +147 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/utils.py +84 -0
- mainshortcuts2-2.2.1/src/MainShortcuts2/types.py +0 -55
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/README.md +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/__init__.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/__main__.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/advanced.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/dict.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/dir.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/file.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/json.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/list.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/proc.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/regex.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/special_chars.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/str.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/term.py +0 -0
- {mainshortcuts2-2.2.1 → mainshortcuts2-2.2.3}/src/MainShortcuts2/win.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
name = "MainShortcuts2"
|
|
2
|
-
version = "2.2.
|
|
2
|
+
version = "2.2.3"
|
|
@@ -19,7 +19,8 @@ def _check_type(path: str, type: Union[str, None]):
|
|
|
19
19
|
if not type in types:
|
|
20
20
|
raise Exception("Type %r not supported" % type)
|
|
21
21
|
return type
|
|
22
|
-
_, ext = os.path.splitext(path)
|
|
22
|
+
_, ext = os.path.splitext(path)
|
|
23
|
+
ext = ext[1:].lower()
|
|
23
24
|
if ext in ext2type:
|
|
24
25
|
return ext2type[ext]
|
|
25
26
|
raise Exception("Cannot determine type by extension %r" % ext)
|
|
@@ -54,7 +54,7 @@ class MS2:
|
|
|
54
54
|
self.import_code: str = "from MainShortcuts2 import ms\nms.prog_file,ms.prog_name=__file__,__name__\nms.reload()"
|
|
55
55
|
self.log: Logger = NoLogger("MainShortcuts2") if logger is None else logger
|
|
56
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.
|
|
57
|
+
self.MAIN_DIR: Union[None, str] = None if self.MAIN_FILE is None else os.path.dirname(self.MAIN_FILE)
|
|
58
58
|
self.prog_dir: Union[None, str] = None
|
|
59
59
|
self.prog_file: Union[None, str] = __file__
|
|
60
60
|
self.prog_name: Union[None, str] = __name__
|
|
@@ -45,13 +45,15 @@ def cwd(set_to: PATH_TYPES = None) -> str:
|
|
|
45
45
|
class Path:
|
|
46
46
|
"""Информация и действия с объектом файловой системы"""
|
|
47
47
|
|
|
48
|
-
def __init__(self, path: PATH_TYPES):
|
|
48
|
+
def __init__(self, path: PATH_TYPES, use_cache: bool = True):
|
|
49
|
+
self._path = path2str(path, to_abs=True)
|
|
49
50
|
self.cp = self.copy
|
|
50
51
|
self.ln = self.link
|
|
51
52
|
self.mv = self.move
|
|
52
|
-
self.
|
|
53
|
+
self.reload(full=True)
|
|
53
54
|
self.rm = self.delete
|
|
54
55
|
self.rn = self.rename
|
|
56
|
+
self.use_cache = use_cache
|
|
55
57
|
|
|
56
58
|
def reload(self, full: bool = False):
|
|
57
59
|
"""Удаление кешированной информации"""
|
|
@@ -81,7 +83,7 @@ class Path:
|
|
|
81
83
|
def path(self, v):
|
|
82
84
|
"""Абсолютный путь к объекту"""
|
|
83
85
|
self._path = path2str(v, to_abs=True)
|
|
84
|
-
self.reload(True)
|
|
86
|
+
self.reload(full=True)
|
|
85
87
|
|
|
86
88
|
@property
|
|
87
89
|
def base_name(self) -> str:
|
|
@@ -93,14 +95,14 @@ class Path:
|
|
|
93
95
|
@property
|
|
94
96
|
def created_at(self) -> float:
|
|
95
97
|
"""timestamp создания объекта"""
|
|
96
|
-
if self._created_at is None:
|
|
98
|
+
if self._created_at is None or (not self.use_cache):
|
|
97
99
|
self._created_at = os.path.getctime(self.path)
|
|
98
100
|
return self._created_at
|
|
99
101
|
|
|
100
102
|
@property
|
|
101
103
|
def exists(self) -> bool:
|
|
102
104
|
"""Существует ли объект"""
|
|
103
|
-
if self._exists is None:
|
|
105
|
+
if self._exists is None or (not self.use_cache):
|
|
104
106
|
self._exists = os.path.exists(self.path)
|
|
105
107
|
|
|
106
108
|
@property
|
|
@@ -120,7 +122,7 @@ class Path:
|
|
|
120
122
|
@property
|
|
121
123
|
def is_dir(self) -> bool:
|
|
122
124
|
"""Является ли объект папкой"""
|
|
123
|
-
if self._is_dir is None:
|
|
125
|
+
if self._is_dir is None or (not self.use_cache):
|
|
124
126
|
self._is_dir = os.path.isdir(self.path)
|
|
125
127
|
if self._is_dir:
|
|
126
128
|
self._type = "dir"
|
|
@@ -129,7 +131,7 @@ class Path:
|
|
|
129
131
|
@property
|
|
130
132
|
def is_file(self) -> bool:
|
|
131
133
|
"""Является ли объект файлом"""
|
|
132
|
-
if self._is_file is None:
|
|
134
|
+
if self._is_file is None or (not self.use_cache):
|
|
133
135
|
self._is_file = os.path.isfile(self.path)
|
|
134
136
|
if self._is_file:
|
|
135
137
|
self._type = "file"
|
|
@@ -138,14 +140,14 @@ class Path:
|
|
|
138
140
|
@property
|
|
139
141
|
def is_link(self) -> bool:
|
|
140
142
|
"""Является ли объект ссылкой на другой объект"""
|
|
141
|
-
if self._is_link is None:
|
|
143
|
+
if self._is_link is None or (not self.use_cache):
|
|
142
144
|
self._is_link = os.path.islink(self.path)
|
|
143
145
|
return self._is_link
|
|
144
146
|
|
|
145
147
|
@property
|
|
146
148
|
def modified_at(self) -> float:
|
|
147
149
|
"""timestamp изменения объекта"""
|
|
148
|
-
if self._modified_at is None:
|
|
150
|
+
if self._modified_at is None or (not self.use_cache):
|
|
149
151
|
self._modified_at = os.path.getmtime(self.path)
|
|
150
152
|
return self._modified_at
|
|
151
153
|
|
|
@@ -159,7 +161,7 @@ class Path:
|
|
|
159
161
|
@property
|
|
160
162
|
def realpath(self) -> str:
|
|
161
163
|
"""Настоящий путь к объекту, если это ссылка (может быть неправильным)"""
|
|
162
|
-
if self._realpath is None:
|
|
164
|
+
if self._realpath is None or (not self.use_cache):
|
|
163
165
|
if self.is_link:
|
|
164
166
|
self._realpath = os.readlink(self.path)
|
|
165
167
|
else:
|
|
@@ -169,7 +171,7 @@ class Path:
|
|
|
169
171
|
@property
|
|
170
172
|
def size(self) -> int:
|
|
171
173
|
"""Размер объекта в байтах"""
|
|
172
|
-
if self._size is None:
|
|
174
|
+
if self._size is None or (not self.use_cache):
|
|
173
175
|
self._size = os.path.getsize(self.path)
|
|
174
176
|
return self._size
|
|
175
177
|
|
|
@@ -183,7 +185,7 @@ class Path:
|
|
|
183
185
|
@property
|
|
184
186
|
def type(self) -> str:
|
|
185
187
|
"""Тип объекта (`dir` | `file`)"""
|
|
186
|
-
if self._type is None:
|
|
188
|
+
if self._type is None or (not self.use_cache):
|
|
187
189
|
if self.is_dir:
|
|
188
190
|
self._type = "dir"
|
|
189
191
|
if self.is_file:
|
|
@@ -195,7 +197,7 @@ class Path:
|
|
|
195
197
|
@property
|
|
196
198
|
def used_at(self) -> float:
|
|
197
199
|
"""timestamp последнего использования объекта"""
|
|
198
|
-
if self._used_at is None:
|
|
200
|
+
if self._used_at is None or (not self.use_cache):
|
|
199
201
|
self._used_at = os.path.getatime(self.path)
|
|
200
202
|
return self._used_at
|
|
201
203
|
|
|
@@ -0,0 +1,147 @@
|
|
|
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
|
|
@@ -4,6 +4,7 @@ import sys
|
|
|
4
4
|
from .core import ms
|
|
5
5
|
from functools import wraps
|
|
6
6
|
from typing import *
|
|
7
|
+
cache = {}
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class MiddlewareBase:
|
|
@@ -345,5 +346,88 @@ def shebang_file(path: str, *, exe_name: Union[None, str] = None, exe_path: Unio
|
|
|
345
346
|
return ms.file.write(path, result)
|
|
346
347
|
|
|
347
348
|
|
|
349
|
+
def remove_ANSI(text: str) -> str:
|
|
350
|
+
"""Убрать ANSI коды из текста | `re`"""
|
|
351
|
+
cache_id = "remove_ANSI", 0
|
|
352
|
+
if not cache_id in cache:
|
|
353
|
+
import re
|
|
354
|
+
cache[cache_id] = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
355
|
+
return cache[cache_id].sub("", text)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
OnlyOneInstanceError = ms.types.OnlyOneInstanceError
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class OnlyOneInstance:
|
|
362
|
+
"""Запретить запуск одной программы несколько раз | `tempfile`, `fcntl`"""
|
|
363
|
+
_win = sys.platform == "win32"
|
|
364
|
+
|
|
365
|
+
def __init__(self, name: str = "main", lock_path: str = None):
|
|
366
|
+
import tempfile
|
|
367
|
+
self.name: str = name
|
|
368
|
+
if lock_path is None:
|
|
369
|
+
lock_path = tempfile.gettempdir() + "/" + ms.MAIN_FILE.replace(":", "").replace("/", "_") + "." + name + ".lock"
|
|
370
|
+
self.lock = ms.path.Path(lock_path, use_cache=False)
|
|
371
|
+
if self._win:
|
|
372
|
+
flags = os.O_CREAT | os.O_EXCL | os.O_RDWR
|
|
373
|
+
|
|
374
|
+
def _enter():
|
|
375
|
+
try:
|
|
376
|
+
if self.lock.exists:
|
|
377
|
+
os.unlink(self.lock.path)
|
|
378
|
+
self.fd = os.open(self.lockfile, flags)
|
|
379
|
+
except OSError as err:
|
|
380
|
+
if err.errno == 13:
|
|
381
|
+
raise OnlyOneInstanceError()
|
|
382
|
+
raise
|
|
383
|
+
|
|
384
|
+
def _exit():
|
|
385
|
+
os.close(self.fd)
|
|
386
|
+
os.unlink(self.lock.path)
|
|
387
|
+
else:
|
|
388
|
+
import fcntl
|
|
389
|
+
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
|
|
390
|
+
|
|
391
|
+
def _enter():
|
|
392
|
+
self.fp = open(self.lock.path, "w")
|
|
393
|
+
self.fp.flush()
|
|
394
|
+
try:
|
|
395
|
+
fcntl.lockf(self.fp, flags)
|
|
396
|
+
except IOError:
|
|
397
|
+
raise OnlyOneInstanceError()
|
|
398
|
+
|
|
399
|
+
def _exit():
|
|
400
|
+
fcntl.lockf(self.fp, fcntl.LOCK_UN)
|
|
401
|
+
os.close(self.fp)
|
|
402
|
+
if self.lock.exists:
|
|
403
|
+
os.unlink(self.lock.path)
|
|
404
|
+
self._enter = _enter
|
|
405
|
+
self._exit = _exit
|
|
406
|
+
|
|
407
|
+
def __enter__(self):
|
|
408
|
+
if self.running:
|
|
409
|
+
raise OnlyOneInstanceError()
|
|
410
|
+
self._enter()
|
|
411
|
+
self.running = True
|
|
412
|
+
try:
|
|
413
|
+
self.on_enter()
|
|
414
|
+
except Exception:
|
|
415
|
+
pass
|
|
416
|
+
return self
|
|
417
|
+
|
|
418
|
+
def __exit__(self, a, b, c):
|
|
419
|
+
if not self.running:
|
|
420
|
+
return
|
|
421
|
+
self._exit()
|
|
422
|
+
self.running = False
|
|
423
|
+
self.on_exit()
|
|
424
|
+
|
|
425
|
+
def on_enter(self):
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
def on_exit(self):
|
|
429
|
+
pass
|
|
430
|
+
|
|
431
|
+
|
|
348
432
|
download_file = sync_download_file
|
|
349
433
|
request = sync_request
|
|
@@ -1,55 +0,0 @@
|
|
|
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 UserError(Exception):
|
|
12
|
-
pass
|
|
13
|
-
"""Ошибка, которую допустил пользователь. Например неправильно указал входные данные"""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class AccessDeniedError(UserError):
|
|
17
|
-
pass
|
|
18
|
-
"""Ошибка доступа"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class Empty(Base):
|
|
22
|
-
pass
|
|
23
|
-
"""Пустота (не равно `None`)"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Infinity(Base):
|
|
27
|
-
pass
|
|
28
|
-
"""Бесконечное число"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class NotAFileError(Exception):
|
|
32
|
-
pass
|
|
33
|
-
"""Ошибка 'это не файл'"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class NotANumber(Base):
|
|
37
|
-
pass
|
|
38
|
-
"""Не число"""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class NotFound(Base):
|
|
42
|
-
pass
|
|
43
|
-
"""Не найдено"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class NotFoundError(Exception):
|
|
47
|
-
pass
|
|
48
|
-
"""Ошибка 'не найдено'"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Error401 = AccessDeniedError
|
|
52
|
-
Error403 = AccessDeniedError
|
|
53
|
-
Error404 = NotFoundError
|
|
54
|
-
Inf = Infinity
|
|
55
|
-
NaN = NotANumber
|
|
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
|
|
File without changes
|