MainShortcuts2 2.2.4__tar.gz → 2.3.1__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 (28) hide show
  1. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/PKG-INFO +2 -2
  2. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/pyproject.toml +5 -2
  3. mainshortcuts2-2.3.1/src/MainShortcuts2/__main__.py +74 -0
  4. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/_module_info.py +1 -1
  5. mainshortcuts2-2.3.1/src/MainShortcuts2/api/base.py +35 -0
  6. mainshortcuts2-2.3.1/src/MainShortcuts2/api/gigachat.py +65 -0
  7. mainshortcuts2-2.3.1/src/MainShortcuts2/api/russian_trusted_root_ca_pem.crt +33 -0
  8. mainshortcuts2-2.3.1/src/MainShortcuts2/api/russian_trusted_sub_ca_pem.crt +41 -0
  9. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/core.py +1 -0
  10. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/file.py +25 -6
  11. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/json.py +79 -14
  12. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/path.py +3 -0
  13. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/term.py +6 -0
  14. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/utils.py +40 -3
  15. mainshortcuts2-2.2.4/src/MainShortcuts2/__main__.py +0 -3
  16. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/README.md +0 -0
  17. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/__init__.py +0 -0
  18. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/advanced.py +0 -0
  19. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/cfg.py +0 -0
  20. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/dict.py +0 -0
  21. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/dir.py +0 -0
  22. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/list.py +0 -0
  23. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/proc.py +0 -0
  24. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/regex.py +0 -0
  25. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/special_chars.py +0 -0
  26. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/str.py +0 -0
  27. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/types.py +0 -0
  28. {mainshortcuts2-2.2.4 → mainshortcuts2-2.3.1}/src/MainShortcuts2/win.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MainShortcuts2
3
- Version: 2.2.4
4
- Summary: Сокращение и улучшение функций
3
+ Version: 2.3.1
4
+ Summary: Сокращение и улучшение функций + консольные утилиты
5
5
  Home-page: https://github.com/MainPlay-TG/MainShortcuts2.py
6
6
  Author: MainPlay TG
7
7
  Author-email: xbox.roman6666666666@gmail.com
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
- version = "2.2.4"
2
+ version = "2.3.1"
3
3
  name = "MainShortcuts2"
4
- description = "Сокращение и улучшение функций"
4
+ description = "Сокращение и улучшение функций + консольные утилиты"
5
5
  authors = [ "MainPlay TG <xbox.roman6666666666@gmail.com>",]
6
6
  readme = "README.md"
7
7
  repository = "https://github.com/MainPlay-TG/MainShortcuts2.py"
@@ -11,6 +11,9 @@ packages = [
11
11
 
12
12
  [tool.poetry.scripts]
13
13
  ms2-import_example = "MainShortcuts2.__main__:import_example"
14
+ nano-json = "MainShortcuts2.__main__:nano_json"
15
+ nginx-reload = "MainShortcuts2.__main__:nginx_reload"
16
+ nginx-restart = "MainShortcuts2.__main__:nginx_restart"
14
17
 
15
18
  [tool.poetry.dependencies]
16
19
  python = "^3.6"
@@ -0,0 +1,74 @@
1
+ import argparse
2
+ import sys
3
+ from MainShortcuts2 import ms
4
+
5
+
6
+ def import_example():
7
+ argp = argparse.ArgumentParser("ms2-import_example", description="код для импорта MainShortcuts2")
8
+ argp.parse_args()
9
+ print("from MainShortcuts2 import ms")
10
+
11
+
12
+ def nano_json():
13
+ ms.utils.check_programs("nano")
14
+ import subprocess
15
+ argp = argparse.ArgumentParser("nano-json", description="форматирование JSON файлов и редактирование в GNU NANO")
16
+ argp.add_argument("files", nargs="+", help="пути к файлам JSON")
17
+ argp.add_argument("--nano-help", action="store_true", help="показать помощь nano")
18
+ argp.add_argument("-f", "--rcfile", help="использовать только этот файл для настройки nano")
19
+ argp.add_argument("-m", "--mode", choices=ms.json.MODES, default="p", help="режим сохранения редактирования")
20
+ args = argp.parse_args()
21
+ if args.nano_help:
22
+ return subprocess.call(["nano", "--help"])
23
+ nano_args = ["nano"]
24
+ if nano_args.rcfile:
25
+ nano_args += ["--rcfile", args.rcfile]
26
+ nano_args += args.files
27
+ for i in args.files:
28
+ try:
29
+ data = ms.json.read(i)
30
+ ms.json.write(i + ms.file.TMP_SUFFIX, data, ensure_ascii=False, mode="p")
31
+ ms.file.delete(i)
32
+ ms.file.move(i + ms.file.TMP_SUFFIX, i)
33
+ except Exception as err:
34
+ print(err, file=sys.stderr)
35
+ subprocess.call(nano_args)
36
+ for i in args.files:
37
+ try:
38
+ data = ms.json.read(i)
39
+ ms.json.write(i + ms.file.TMP_SUFFIX, data, mode=args.mode)
40
+ ms.file.delete(i)
41
+ ms.file.move(i + ms.file.TMP_SUFFIX, i)
42
+ except Exception as err:
43
+ print(err, file=sys.stderr)
44
+
45
+
46
+ def _check_nginx() -> int:
47
+ import subprocess
48
+ with subprocess.Popen(["nginx", "-t"], stderr=subprocess.PIPE) as p:
49
+ code = p.wait()
50
+ if code != 0:
51
+ sys.stderr.buffer.write(p.stderr.read())
52
+ return code
53
+
54
+
55
+ def nginx_reload():
56
+ ms.utils.check_programs("nginx")
57
+ import subprocess
58
+ argp = argparse.ArgumentParser("nginx-reload", description="проверка конфига и перезагрузка Nginx")
59
+ argp.parse_args()
60
+ code = _check_nginx()
61
+ if code != 0:
62
+ sys.exit(code)
63
+ sys.exit(subprocess.call(["nginx", "-s", "reload"]))
64
+
65
+
66
+ def nginx_restart():
67
+ ms.utils.check_programs("nginx", "systemctl")
68
+ import subprocess
69
+ argp = argparse.ArgumentParser("nginx-reload", description="проверка конфига и перезапуск Nginx через systemctl")
70
+ argp.parse_args()
71
+ code = _check_nginx()
72
+ if code != 0:
73
+ sys.exit(code)
74
+ sys.exit(subprocess.call(["systemctl", "restart", "nginx"]))
@@ -1,2 +1,2 @@
1
1
  name = "MainShortcuts2"
2
- version = "2.2.4"
2
+ version = "2.3.1"
@@ -0,0 +1,35 @@
1
+ import requests
2
+
3
+
4
+ class Base:
5
+ def __init__(self, session: requests.Session = None):
6
+ self._headers = {}
7
+ self._http = requests.Session() if session is None else session
8
+ self._params = {}
9
+ self._url_data = {}
10
+ self._url = "https://example.com/api/{method}"
11
+
12
+ @property
13
+ def http(self) -> requests.Session:
14
+ return self._http
15
+
16
+ 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):
17
+ _headers = self._headers.copy()
18
+ _params = self._params.copy()
19
+ _url_data = self._url_data.copy()
20
+ _url_data["method"] = api_method
21
+ _headers.update({} if headers is None else headers)
22
+ _params.update({} if params is None else params)
23
+ _url_data.update({} if url_data is None else url_data)
24
+ kw["headers"] = _headers
25
+ kw["method"] = http_method
26
+ kw["params"] = _params
27
+ kw["url"] = self._url.format(**_url_data)
28
+ result = self.http.request(**kw)
29
+ if raise_for_status:
30
+ result.raise_for_status()
31
+ return result
32
+
33
+ def request(self, *args, **kwargs) -> requests.Response:
34
+ """Отправить запрос к API"""
35
+ return self._request(*args, **kwargs)
@@ -0,0 +1,65 @@
1
+ import os
2
+ from .base import Base, requests
3
+ from datetime import timedelta
4
+ from time import time
5
+ CERT_PATH = os.path.dirname(__file__) + "/russian_trusted_root_ca_pem.crt"
6
+
7
+
8
+ class GigaChat(Base):
9
+ def __init__(self, auth_data: str, client_id: str, *,
10
+ cert_path: str = None,
11
+ **kw):
12
+ Base.__init__(self, **kw)
13
+ self._access_token = {"expire_at": 0, "kw": None, "token": None}
14
+ self._auth_data: str = auth_data
15
+ self._client_id: str = client_id
16
+ self._url = "https://gigachat.devices.sberbank.ru/api/v1/{method}"
17
+ self.http.verify = CERT_PATH if cert_path is None else cert_path
18
+
19
+ @property
20
+ def access_token(self) -> str:
21
+ if True: # TODO Сделать проверку истёк ли токен доступа. Пока что каждый раз новый токен
22
+ if self._access_token["kw"] is None: # Если изменились данные для авторизации
23
+ kw = {}
24
+ kw["data"] = {}
25
+ kw["headers"] = {}
26
+ kw["stream"] = False
27
+ kw["url"] = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth"
28
+ kw["data"]["scope"] = "GIGACHAT_API_PERS"
29
+ kw["headers"]["Authorization"] = "Basic: " + self.auth_data
30
+ kw["headers"]["Content-Type"] = "application/x-www-form-urlencoded"
31
+ kw["headers"]["RqUID"] = self.client_id
32
+ self._access_token["kw"] = kw
33
+ else:
34
+ kw = self._access_token["kw"]
35
+ with self.http.post(**kw) as resp: # Получить новый токен
36
+ resp.raise_for_status()
37
+ json = resp.json()
38
+ self._access_token["expire_at"] = json["expires_at"] / 1000
39
+ self._access_token["token"] = json["access_token"]
40
+ return self._access_token["token"]
41
+
42
+ @property
43
+ def auth_data(self) -> str:
44
+ return self._auth_data
45
+
46
+ @auth_data.setter
47
+ def auth_data(self, v: str):
48
+ self._access_token["kw"] = None
49
+ self._auth_data = v
50
+
51
+ @property
52
+ def client_id(self) -> str:
53
+ return self._client_id
54
+
55
+ @client_id.setter
56
+ def client_id(self, v: str):
57
+ self._access_token["kw"] = None
58
+ self._client_id = v
59
+
60
+ def request(self, http_method: str, api_method: str, **kw):
61
+ self._headers["Authorization"] = "Bearer " + self.access_token
62
+ kw["api_method"] = api_method
63
+ kw["http_method"] = http_method
64
+ return self._request(**kw).json()
65
+ # TODO Добавить методы
@@ -0,0 +1,33 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
3
+ PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
4
+ ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
5
+ Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
6
+ VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
7
+ YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
8
+ dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
9
+ qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
10
+ XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
11
+ zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
12
+ YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
13
+ Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
14
+ U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
15
+ 4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
16
+ G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
17
+ BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
18
+ ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
19
+ OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
20
+ BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
21
+ BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
22
+ AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
23
+ tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
24
+ W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
25
+ /3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
26
+ AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
27
+ C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
28
+ 4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
29
+ WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
30
+ D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
31
+ EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
32
+ 391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
33
+ -----END CERTIFICATE-----
@@ -0,0 +1,41 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIHQjCCBSqgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
3
+ PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
4
+ ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
5
+ Q0EwHhcNMjIwMzAyMTEyNTE5WhcNMjcwMzA2MTEyNTE5WjBvMQswCQYDVQQGEwJS
6
+ VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
7
+ YW5kIENvbW11bmljYXRpb25zMR8wHQYDVQQDDBZSdXNzaWFuIFRydXN0ZWQgU3Vi
8
+ IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9YPqBKOk19NFymrE
9
+ wehzrhBEgT2atLezpduB24mQ7CiOa/HVpFCDRZzdxqlh8drku408/tTmWzlNH/br
10
+ HuQhZ/miWKOf35lpKzjyBd6TPM23uAfJvEOQ2/dnKGGJbsUo1/udKSvxQwVHpVv3
11
+ S80OlluKfhWPDEXQpgyFqIzPoxIQTLZ0deirZwMVHarZ5u8HqHetRuAtmO2ZDGQn
12
+ vVOJYAjls+Hiueq7Lj7Oce7CQsTwVZeP+XQx28PAaEZ3y6sQEt6rL06ddpSdoTMp
13
+ BnCqTbxW+eWMyjkIn6t9GBtUV45yB1EkHNnj2Ex4GwCiN9T84QQjKSr+8f0psGrZ
14
+ vPbCbQAwNFJjisLixnjlGPLKa5vOmNwIh/LAyUW5DjpkCx004LPDuqPpFsKXNKpa
15
+ L2Dm6uc0x4Jo5m+gUTVORB6hOSzWnWDj2GWfomLzzyjG81DRGFBpco/O93zecsIN
16
+ 3SL2Ysjpq1zdoS01CMYxie//9zWvYwzI25/OZigtnpCIrcd2j1Y6dMUFQAzAtHE+
17
+ qsXflSL8HIS+IJEFIQobLlYhHkoE3avgNx5jlu+OLYe0dF0Ykx1PGNjbwqvTX37R
18
+ Cn32NMjlotW2QcGEZhDKj+3urZizp5xdTPZitA+aEjZM/Ni71VOdiOP0igbw6asZ
19
+ 2fxdozZ1TnSSYNYvNATwthNmZysCAwEAAaOCAeUwggHhMBIGA1UdEwEB/wQIMAYB
20
+ Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTR4XENCy2BTm6KSo9MI7NM
21
+ XqtpCzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzCBxwYIKwYBBQUH
22
+ AQEEgbowgbcwOwYIKwYBBQUHMAKGL2h0dHA6Ly9yb3N0ZWxlY29tLnJ1L2NkcC9y
23
+ b290Y2Ffc3NsX3JzYTIwMjIuY3J0MDsGCCsGAQUFBzAChi9odHRwOi8vY29tcGFu
24
+ eS5ydC5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNydDA7BggrBgEFBQcwAoYv
25
+ aHR0cDovL3JlZXN0ci1wa2kucnUvY2RwL3Jvb3RjYV9zc2xfcnNhMjAyMi5jcnQw
26
+ gbAGA1UdHwSBqDCBpTA1oDOgMYYvaHR0cDovL3Jvc3RlbGVjb20ucnUvY2RwL3Jv
27
+ b3RjYV9zc2xfcnNhMjAyMi5jcmwwNaAzoDGGL2h0dHA6Ly9jb21wYW55LnJ0LnJ1
28
+ L2NkcC9yb290Y2Ffc3NsX3JzYTIwMjIuY3JsMDWgM6Axhi9odHRwOi8vcmVlc3Ry
29
+ LXBraS5ydS9jZHAvcm9vdGNhX3NzbF9yc2EyMDIyLmNybDANBgkqhkiG9w0BAQsF
30
+ AAOCAgEARBVzZls79AdiSCpar15dA5Hr/rrT4WbrOfzlpI+xrLeRPrUG6eUWIW4v
31
+ Sui1yx3iqGLCjPcKb+HOTwoRMbI6ytP/ndp3TlYua2advYBEhSvjs+4vDZNwXr/D
32
+ anbwIWdurZmViQRBDFebpkvnIvru/RpWud/5r624Wp8voZMRtj/cm6aI9LtvBfT9
33
+ cfzhOaexI/99c14dyiuk1+6QhdwKaCRTc1mdfNQmnfWNRbfWhWBlK3h4GGE9JK33
34
+ Gk8ZS8DMrkdAh0xby4xAQ/mSWAfWrBmfzlOqGyoB1U47WTOeqNbWkkoAP2ys94+s
35
+ Jg4NTkiDVtXRF6nr6fYi0bSOvOFg0IQrMXO2Y8gyg9ARdPJwKtvWX8VPADCYMiWH
36
+ h4n8bZokIrImVKLDQKHY4jCsND2HHdJfnrdL2YJw1qFskNO4cSNmZydw0Wkgjv9k
37
+ F+KxqrDKlB8MZu2Hclph6v/CZ0fQ9YuE8/lsHZ0Qc2HyiSMnvjgK5fDc3TD4fa8F
38
+ E8gMNurM+kV8PT8LNIM+4Zs+LKEV8nqRWBaxkIVJGekkVKO8xDBOG/aN62AZKHOe
39
+ GcyIdu7yNMMRihGVZCYr8rYiJoKiOzDqOkPkLOPdhtVlgnhowzHDxMHND/E2WA5p
40
+ ZHuNM/m0TXt2wTTPL7JH2YC0gPz/BvvSzjksgzU5rLbRyUKQkgU=
41
+ -----END CERTIFICATE-----
@@ -58,6 +58,7 @@ class MS2:
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__
61
+ self.use_tmp_file: bool = False
61
62
  self.reload()
62
63
 
63
64
  def reload(self):
@@ -5,6 +5,7 @@ from .core import ms
5
5
  from .path import PATH_TYPES, path2str
6
6
  from .types import NotAFileError
7
7
  from typing import *
8
+ TMP_SUFFIX = ".MS2_TMP"
8
9
 
9
10
 
10
11
  def _check(path, **kw) -> str:
@@ -25,15 +26,24 @@ def read(path: PATH_TYPES, encoding: str = None, **kw) -> str:
25
26
  return f.read()
26
27
 
27
28
 
28
- def write(path: PATH_TYPES, data: str, encoding: str = None, mkdir: bool = False, **kw) -> int:
29
+ def write(path: PATH_TYPES, data: str, encoding: str = None, mkdir: bool = False, use_tmp_file: bool = None, **kw) -> int:
29
30
  """Записать текст в файл"""
31
+ file = _check(path)
32
+ if use_tmp_file is None:
33
+ use_tmp_file = ms.use_tmp_file
30
34
  kw["encoding"] = ms.encoding if encoding is None else encoding
31
- kw["file"] = _check(path)
35
+ kw["file"] = file
32
36
  kw["mode"] = "w"
33
37
  if mkdir:
34
38
  ms.dir.create(os.path.dirname(kw["file"]))
39
+ if use_tmp_file:
40
+ kw["file"] += TMP_SUFFIX
35
41
  with builtins.open(**kw) as f:
36
- return f.write(data)
42
+ result = f.write(data)
43
+ if use_tmp_file:
44
+ delete(file)
45
+ move(kw["file"], file)
46
+ return result
37
47
 
38
48
 
39
49
  def load(path: PATH_TYPES, **kw) -> bytes:
@@ -44,14 +54,23 @@ def load(path: PATH_TYPES, **kw) -> bytes:
44
54
  return f.read()
45
55
 
46
56
 
47
- def save(path: PATH_TYPES, data: bytes, mkdir: bool = False, **kw) -> int:
57
+ def save(path: PATH_TYPES, data: bytes, mkdir: bool = False, use_tmp_file: bool = None, **kw) -> int:
48
58
  """Записать байты в файл"""
49
- kw["file"] = _check(path)
59
+ file = _check(path)
60
+ if use_tmp_file is None:
61
+ use_tmp_file = ms.use_tmp_file
62
+ kw["file"] = file
50
63
  kw["mode"] = "wb"
51
64
  if mkdir:
52
65
  ms.dir.create(os.path.dirname(kw["file"]))
66
+ if use_tmp_file:
67
+ kw["file"] += TMP_SUFFIX
53
68
  with builtins.open(**kw) as f:
54
- return f.write(data)
69
+ result = f.write(data)
70
+ if use_tmp_file:
71
+ delete(file)
72
+ move(kw["file"], file)
73
+ return result
55
74
 
56
75
 
57
76
  def copy(path: PATH_TYPES, dest: PATH_TYPES, **kw):
@@ -1,6 +1,7 @@
1
1
  """Работа с JSON"""
2
- import json
2
+ import atexit
3
3
  import builtins
4
+ import json
4
5
  from .core import ms
5
6
  from .path import PATH_TYPES
6
7
  from typing import *
@@ -9,6 +10,7 @@ try:
9
10
  except Exception:
10
11
  json5 = None
11
12
  JSON_TYPES = Union[bool, dict, float, int, list, None, str]
13
+ MODES = ["c", "compress", "mainplay_tg", "mainplay", "max", "min", "mp_tg", "mp", "p", "pretty", "print", "zip"]
12
14
 
13
15
 
14
16
  def decode(text: str, *, like_json5: bool = False, **kw) -> JSON_TYPES:
@@ -26,18 +28,21 @@ def encode(data: JSON_TYPES, mode: str = "c", **kw):
26
28
  mode = mode.lower()
27
29
  if "force" in kw:
28
30
  del kw["force"]
31
+ import warnings
32
+ warnings.warn("The argument 'force' is no longer used", DeprecationWarning)
29
33
  if "sort" in kw:
30
34
  kw["sort_keys"] = kw.pop("sort")
31
35
  if mode in ["c", "compress", "min", "zip"]: # Сжатый
32
- kw["indent"] = None
33
- kw["separators"] = [",", ":"]
36
+ kw.setdefault("indent", None)
37
+ kw.setdefault("separators", [",", ":"])
34
38
  if mode in ["p", "pretty", "max", "print"]: # Развёрнутый
35
- if not "indent" in kw:
36
- kw["indent"] = 2
37
- kw["separators"] = None
39
+ kw.setdefault("indent", 2)
40
+ kw.setdefault("separators", [",", ": "])
41
+ if mode == "print":
42
+ kw.setdefault("ensure_ascii", False)
38
43
  if mode in ["mp", "mp_tg", "mainplay", "mainplay_tg"]: # Стиль MainPlay TG
39
- kw["indent"] = 2
40
- kw["separators"] = [",", ":"]
44
+ kw.setdefault("indent", 2)
45
+ kw.setdefault("separators", [",", ":"])
41
46
  return json.dumps(**kw)
42
47
 
43
48
 
@@ -48,11 +53,7 @@ def print(data: JSON_TYPES, **kw):
48
53
  if i in kw:
49
54
  pr_kw[i] = kw.pop(i)
50
55
  kw["data"] = data
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"
56
+ kw.setdefault("mode", "print")
56
57
  builtins.print(encode(**kw), **pr_kw)
57
58
 
58
59
 
@@ -93,9 +94,73 @@ def write(path: PATH_TYPES, data: JSON_TYPES, **kw) -> int:
93
94
  """Прочитать JSON файл"""
94
95
  f_kw = {}
95
96
  kw["data"] = data
96
- for i in ["encoding", "force"]:
97
+ for i in ["encoding", "force", "use_tmp_file"]:
97
98
  if i in kw:
98
99
  f_kw[i] = kw.pop(i)
99
100
  f_kw["path"] = path
100
101
  f_kw["data"] = encode(**kw)
101
102
  return ms.file.write(**f_kw)
103
+
104
+
105
+ class JsonFile:
106
+ def __init__(self, path: str, autoload: bool = True, save_at_exit: bool = False, **kw):
107
+ atexit.register(self.__exit__)
108
+ self._data = None
109
+ self._loaded = False
110
+ self._path = None
111
+ self.load_kw = {}
112
+ self.save_kw = kw
113
+ self.path = ms.path.Path(path)
114
+ self.save_at_exit = save_at_exit
115
+ if "like_json5" in self.save_kw:
116
+ self.load_kw["like_json5"] = self.save_kw.pop("like_json5")
117
+ if autoload:
118
+ self.load()
119
+
120
+ def __contains__(self, k) -> bool:
121
+ return k in self.data
122
+
123
+ def __delitem__(self, k):
124
+ del self.data[k]
125
+
126
+ def __enter__(self):
127
+ return self
128
+
129
+ def __exit__(self, *a):
130
+ if self.save_at_exit:
131
+ self.save()
132
+
133
+ def __getitem__(self, k):
134
+ return self.data[k]
135
+
136
+ def __setitem__(self, k, v):
137
+ self.data[k] = v
138
+
139
+ @property
140
+ def path(self) -> ms.path.Path:
141
+ return self._path
142
+
143
+ @path.setter
144
+ def path(self, v):
145
+ self._path = ms.path.Path(v)
146
+ self.load_kw["path"] = self._path.path
147
+ self.save_kw["path"] = self._path.path
148
+
149
+ @property
150
+ def data(self):
151
+ if not self._loaded:
152
+ self.load()
153
+ return self._data
154
+
155
+ @data.setter
156
+ def data(self, v):
157
+ self._data = v
158
+ self._loaded = True
159
+
160
+ def load(self):
161
+ self._data = read(**self.load_kw)
162
+ self._loaded = True
163
+
164
+ def save(self):
165
+ self.save_kw["data"] = self.data
166
+ return write(**self.save_kw)
@@ -55,6 +55,9 @@ class Path:
55
55
  self.rn = self.rename
56
56
  self.use_cache = use_cache
57
57
 
58
+ def __fspath__(self) -> str:
59
+ return self.path
60
+
58
61
  def reload(self, full: bool = False):
59
62
  """Удаление кешированной информации"""
60
63
  if full:
@@ -115,4 +115,10 @@ def clear():
115
115
  _clear()
116
116
 
117
117
 
118
+ def set_title(title: str):
119
+ """Установить заголовок окна"""
120
+ # Не работает на bpython: AssertionError
121
+ print("\u001b]2;" + title + "\u0007", end="")
122
+
123
+
118
124
  cls = clear
@@ -374,7 +374,7 @@ class OnlyOneInstance:
374
374
 
375
375
  def _enter():
376
376
  try:
377
- if self.lock.exists:
377
+ if os.path.exists(self.lock.path):
378
378
  os.unlink(self.lock.path)
379
379
  self.fd = os.open(self.lock.path, flags)
380
380
  except OSError as err:
@@ -399,8 +399,8 @@ class OnlyOneInstance:
399
399
 
400
400
  def _exit():
401
401
  fcntl.lockf(self.fp, fcntl.LOCK_UN)
402
- os.close(self.fp)
403
- if self.lock.exists:
402
+ self.fp.close()
403
+ if os.path.exists(self.lock.path):
404
404
  os.unlink(self.lock.path)
405
405
  self._enter = _enter
406
406
  self._exit = _exit
@@ -430,5 +430,42 @@ class OnlyOneInstance:
430
430
  pass
431
431
 
432
432
 
433
+ def multi_and(*values: bool) -> bool:
434
+ for i in values:
435
+ if not i:
436
+ return False
437
+ return True
438
+
439
+
440
+ def multi_or(*values: bool) -> bool:
441
+ for i in values:
442
+ if i:
443
+ return True
444
+ return False
445
+
446
+
447
+ def is_int(value: float) -> bool:
448
+ return value == int(value)
449
+
450
+
451
+ def get_self_module(__name__: str):
452
+ return sys.modules[__name__]
453
+
454
+
455
+ def check_programs(*progs: str, raise_error: bool = True) -> list[str]:
456
+ """Проверить наличие программ в `$PATH` | `shutil`"""
457
+ from shutil import which
458
+ failed = []
459
+ for i in progs:
460
+ if which(i) is None:
461
+ if not i in failed:
462
+ failed.append(i)
463
+ failed.sort()
464
+ if raise_error:
465
+ if len(failed) > 0:
466
+ raise OSError("Failed to find programs " + (", ".join(failed)) + " in $PATH")
467
+ return failed
468
+
469
+
433
470
  download_file = sync_download_file
434
471
  request = sync_request
@@ -1,3 +0,0 @@
1
- def import_example():
2
- print("from MainShortcuts2 import ms")
3
- print("exec(ms.import_code)")
File without changes