easyrip 3.13.2__py3-none-any.whl → 4.9.1__py3-none-any.whl

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 (36) hide show
  1. easyrip/__init__.py +5 -1
  2. easyrip/__main__.py +124 -15
  3. easyrip/easyrip_command.py +457 -148
  4. easyrip/easyrip_config/config.py +269 -0
  5. easyrip/easyrip_config/config_key.py +28 -0
  6. easyrip/easyrip_log.py +120 -42
  7. easyrip/easyrip_main.py +509 -259
  8. easyrip/easyrip_mlang/__init__.py +20 -45
  9. easyrip/easyrip_mlang/global_lang_val.py +18 -16
  10. easyrip/easyrip_mlang/lang_en.py +1 -1
  11. easyrip/easyrip_mlang/lang_zh_Hans_CN.py +101 -77
  12. easyrip/easyrip_mlang/translator.py +12 -10
  13. easyrip/easyrip_prompt.py +73 -0
  14. easyrip/easyrip_web/__init__.py +2 -1
  15. easyrip/easyrip_web/http_server.py +56 -42
  16. easyrip/easyrip_web/third_party_api.py +60 -8
  17. easyrip/global_val.py +21 -1
  18. easyrip/ripper/media_info.py +10 -3
  19. easyrip/ripper/param.py +482 -0
  20. easyrip/ripper/ripper.py +260 -574
  21. easyrip/ripper/sub_and_font/__init__.py +10 -0
  22. easyrip/ripper/{font_subset → sub_and_font}/ass.py +95 -84
  23. easyrip/ripper/{font_subset → sub_and_font}/font.py +72 -79
  24. easyrip/ripper/{font_subset → sub_and_font}/subset.py +122 -81
  25. easyrip/utils.py +129 -27
  26. easyrip-4.9.1.dist-info/METADATA +92 -0
  27. easyrip-4.9.1.dist-info/RECORD +31 -0
  28. easyrip/easyrip_config.py +0 -198
  29. easyrip/ripper/__init__.py +0 -10
  30. easyrip/ripper/font_subset/__init__.py +0 -7
  31. easyrip-3.13.2.dist-info/METADATA +0 -89
  32. easyrip-3.13.2.dist-info/RECORD +0 -29
  33. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/WHEEL +0 -0
  34. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/entry_points.txt +0 -0
  35. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/licenses/LICENSE +0 -0
  36. {easyrip-3.13.2.dist-info → easyrip-4.9.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,269 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Literal, get_origin, overload
5
+
6
+ from ..easyrip_log import log
7
+ from ..easyrip_mlang import all_supported_lang_map, gettext
8
+ from ..global_val import CONFIG_DIR
9
+ from ..utils import type_match
10
+ from .config_key import CONFIG_TYPE_DICT, CONFIG_VERSION, Config_key
11
+
12
+ CONFIG_DEFAULT_DICT: dict[Config_key, str | bool | list[str]] = {
13
+ Config_key.language: "auto",
14
+ Config_key.check_update: True,
15
+ Config_key.check_dependent: True,
16
+ Config_key.startup_dir: "",
17
+ Config_key.startup_dir_blacklist: [],
18
+ Config_key.force_log_file_path: "",
19
+ Config_key.log_print_level: log.LogLevel.send.name,
20
+ Config_key.log_write_level: log.LogLevel.send.name,
21
+ Config_key.save_prompt_history: True,
22
+ }
23
+
24
+ assert all(k in CONFIG_DEFAULT_DICT for k in Config_key), [
25
+ k.name for k in Config_key if k not in CONFIG_DEFAULT_DICT
26
+ ]
27
+
28
+
29
+ class config:
30
+ _config_dir: Path
31
+ _config_file: Path
32
+ _config: dict | None = None
33
+
34
+ @classmethod
35
+ def init(cls) -> None:
36
+ cls._config_dir = CONFIG_DIR
37
+ cls._config_file = cls._config_dir / "config.json"
38
+
39
+ if not cls._config_file.is_file():
40
+ cls._config_dir.mkdir(exist_ok=True)
41
+ with cls._config_file.open("wt", encoding="utf-8", newline="\n") as f:
42
+ config_default_dict: dict[str, str | bool | list[str]] = {
43
+ k.name: v for k, v in CONFIG_DEFAULT_DICT.items()
44
+ }
45
+ json.dump(
46
+ {
47
+ "version": CONFIG_VERSION,
48
+ "user_profile": config_default_dict,
49
+ },
50
+ f,
51
+ ensure_ascii=False,
52
+ indent=3,
53
+ )
54
+ else:
55
+ with cls._config_file.open("rt", encoding="utf-8") as f:
56
+ try:
57
+ data = json.load(f)
58
+ if data.get("version") != CONFIG_VERSION:
59
+ log.warning(
60
+ "The config version is not match, use '{}' to regenerate config file",
61
+ "config clear",
62
+ )
63
+ except json.JSONDecodeError as e:
64
+ log.error(f"{e!r} {e}", deep=True)
65
+
66
+ cls._read_config()
67
+
68
+ @classmethod
69
+ def open_config_dir(cls) -> None:
70
+ if not cls._config_dir.is_dir():
71
+ cls.init()
72
+ os.startfile(cls._config_dir)
73
+
74
+ @classmethod
75
+ def regenerate_config(cls) -> None:
76
+ cls._config_file.unlink(missing_ok=True)
77
+
78
+ cls.init()
79
+ log.info("Regenerate config file")
80
+
81
+ @classmethod
82
+ def _read_config(cls) -> bool:
83
+ try:
84
+ if not cls._config_dir.is_dir():
85
+ raise AttributeError
86
+ except AttributeError:
87
+ cls.init()
88
+
89
+ with cls._config_file.open("rt", encoding="utf-8") as f:
90
+ try:
91
+ cls._config = json.load(f)
92
+ except json.JSONDecodeError as e:
93
+ log.error(f"{e!r} {e}", deep=True)
94
+ return False
95
+ return True
96
+
97
+ @classmethod
98
+ def _write_config(cls, new_config: dict | None = None) -> bool:
99
+ if not cls._config_dir.is_dir():
100
+ cls.init()
101
+ if new_config is not None:
102
+ cls._config = new_config
103
+ del new_config
104
+
105
+ with cls._config_file.open("wt", encoding="utf-8", newline="\n") as f:
106
+ try:
107
+ json.dump(cls._config, f, ensure_ascii=False, indent=3)
108
+ except json.JSONDecodeError as e:
109
+ log.error(f"{e!r} {e}", deep=True)
110
+ return False
111
+ return True
112
+
113
+ @classmethod
114
+ def set_user_profile(
115
+ cls,
116
+ key: str,
117
+ val: str | bool | list[str],
118
+ ) -> bool:
119
+ if cls._config is None and not cls._read_config():
120
+ return False
121
+
122
+ if cls._config is None:
123
+ log.error("Config is None")
124
+ return False
125
+
126
+ if "user_profile" not in cls._config:
127
+ log.error("User profile is not found in config file")
128
+ return False
129
+
130
+ if key in Config_key._member_map_:
131
+ need_type = CONFIG_TYPE_DICT[Config_key[key]]
132
+ if not type_match(val, need_type):
133
+ log.error(
134
+ "Type mismatch: need '{}'",
135
+ need_type if get_origin(need_type) else need_type.__name__,
136
+ )
137
+ return False
138
+ cls._config["user_profile"][key] = val
139
+ else:
140
+ log.error("Key '{}' is not found in user profile", key)
141
+ return False
142
+ return cls._write_config()
143
+
144
+ @overload
145
+ @classmethod
146
+ def get_user_profile(
147
+ cls,
148
+ config_key: Literal[
149
+ Config_key.language,
150
+ Config_key.startup_dir,
151
+ Config_key.force_log_file_path,
152
+ Config_key.log_print_level,
153
+ Config_key.log_write_level,
154
+ ],
155
+ ) -> str | None: ...
156
+
157
+ @overload
158
+ @classmethod
159
+ def get_user_profile(
160
+ cls,
161
+ config_key: Literal[
162
+ Config_key.check_update,
163
+ Config_key.check_dependent,
164
+ Config_key.save_prompt_history,
165
+ ],
166
+ ) -> bool | None: ...
167
+
168
+ @overload
169
+ @classmethod
170
+ def get_user_profile(
171
+ cls,
172
+ config_key: Literal[Config_key.startup_dir_blacklist],
173
+ ) -> list[str] | None: ...
174
+
175
+ @overload
176
+ @classmethod
177
+ def get_user_profile(
178
+ cls,
179
+ config_key: str,
180
+ ) -> str | bool | list[str] | None: ...
181
+
182
+ @classmethod
183
+ def get_user_profile(
184
+ cls,
185
+ config_key: Config_key | str,
186
+ ) -> str | bool | list[str] | None:
187
+ key = config_key.name if isinstance(config_key, Config_key) else config_key
188
+
189
+ if key not in Config_key._member_map_:
190
+ log.error("The key '{}' is not a config", key)
191
+ return None
192
+
193
+ if cls._config is None:
194
+ cls._read_config()
195
+ if cls._config is None:
196
+ return None
197
+ if not isinstance(cls._config["user_profile"], dict):
198
+ log.error("User profile is not a valid dictionary")
199
+ return None
200
+ if key not in cls._config["user_profile"]:
201
+ log.error("Key '{}' is not found in user profile", key)
202
+ return None
203
+ return cls._config["user_profile"][key]
204
+
205
+ @classmethod
206
+ def show_config_list(cls) -> None:
207
+ if cls._config is None:
208
+ cls.init()
209
+ if cls._config is None:
210
+ log.error("Config is None")
211
+ return
212
+
213
+ user_profile: dict = cls._config["user_profile"]
214
+ length_key = max(len(k) for k in user_profile)
215
+ length_val = max(len(str(v)) for v in user_profile.values())
216
+ for k, v in user_profile.items():
217
+ log.send(
218
+ f"{k:>{length_key}} = {v!s:<{length_val}} - {cls._get_config_about(k)}",
219
+ )
220
+
221
+ @classmethod
222
+ def _get_config_about(cls, key: str) -> str:
223
+ return (
224
+ {
225
+ Config_key.language.name: gettext(
226
+ "Easy Rip's language, support incomplete matching. Default: {}. Supported: {}",
227
+ CONFIG_DEFAULT_DICT[Config_key.language],
228
+ ", ".join(("auto", *(str(tag) for tag in all_supported_lang_map))),
229
+ ),
230
+ Config_key.check_update.name: gettext(
231
+ "Auto check the update of Easy Rip. Default: {}",
232
+ CONFIG_DEFAULT_DICT[Config_key.check_update],
233
+ ),
234
+ Config_key.check_dependent.name: gettext(
235
+ "Auto check the versions of all dependent programs. Default: {}",
236
+ CONFIG_DEFAULT_DICT[Config_key.check_dependent],
237
+ ),
238
+ Config_key.startup_dir.name: gettext(
239
+ "Program startup directory, when the value is empty, starts in the working directory. Default: {}",
240
+ CONFIG_DEFAULT_DICT[Config_key.startup_dir] or '""',
241
+ ),
242
+ Config_key.startup_dir_blacklist.name: gettext(
243
+ "Directory list. When the startup directory is a blacklisted directory, rollback to startup directory. Default: {}",
244
+ CONFIG_DEFAULT_DICT[Config_key.startup_dir] or '""',
245
+ ),
246
+ Config_key.force_log_file_path.name: gettext(
247
+ "Force change of log file path, when the value is empty, it is the working directory. Default: {}",
248
+ CONFIG_DEFAULT_DICT[Config_key.force_log_file_path] or '""',
249
+ ),
250
+ Config_key.log_print_level.name: gettext(
251
+ "Logs this level and above will be printed, and if the value is '{}', they will not be printed. Default: {}. Supported: {}",
252
+ log.LogLevel.none.name,
253
+ CONFIG_DEFAULT_DICT[Config_key.log_print_level],
254
+ ", ".join(log.LogLevel._member_names_),
255
+ ),
256
+ Config_key.log_write_level.name: gettext(
257
+ "Logs this level and above will be written, and if the value is '{}', the '{}' only be written when 'server', they will not be written. Default: {}. Supported: {}",
258
+ log.LogLevel.none.name,
259
+ log.LogLevel.send.name,
260
+ CONFIG_DEFAULT_DICT[Config_key.log_write_level],
261
+ ", ".join(log.LogLevel._member_names_),
262
+ ),
263
+ Config_key.save_prompt_history.name: gettext(
264
+ "Save prompt history to config directory, otherwise save to memory. Take effect after reboot. Default: {}",
265
+ CONFIG_DEFAULT_DICT[Config_key.save_prompt_history],
266
+ ),
267
+ }
268
+ | (cls._config or {})
269
+ ).get(key, "None about")
@@ -0,0 +1,28 @@
1
+ import enum
2
+
3
+ CONFIG_VERSION = "4.6.0"
4
+
5
+
6
+ class Config_key(enum.Enum):
7
+ language = enum.auto()
8
+ check_update = enum.auto()
9
+ check_dependent = enum.auto()
10
+ startup_dir = enum.auto()
11
+ startup_dir_blacklist = enum.auto()
12
+ force_log_file_path = enum.auto()
13
+ log_print_level = enum.auto()
14
+ log_write_level = enum.auto()
15
+ save_prompt_history = enum.auto()
16
+
17
+
18
+ CONFIG_TYPE_DICT: dict[Config_key, type] = {
19
+ Config_key.language: str,
20
+ Config_key.check_update: bool,
21
+ Config_key.check_dependent: bool,
22
+ Config_key.startup_dir: str,
23
+ Config_key.startup_dir_blacklist: list[str],
24
+ Config_key.force_log_file_path: str,
25
+ Config_key.log_print_level: str,
26
+ Config_key.log_write_level: str,
27
+ Config_key.save_prompt_history: bool,
28
+ }
easyrip/easyrip_log.py CHANGED
@@ -5,27 +5,30 @@ import os
5
5
  import sys
6
6
  import traceback
7
7
  from ctypes import wintypes
8
+ from typing import TextIO
9
+
10
+ from prompt_toolkit import ANSI, print_formatted_text
8
11
 
9
12
  from . import easyrip_web
10
13
  from .easyrip_mlang import gettext
11
14
 
12
- __all__ = ["Event", "log"]
15
+ __all__ = ["log"]
13
16
 
14
17
 
15
18
  class Event:
16
19
  @staticmethod
17
- def append_http_server_log_queue(message: tuple[str, str, str]):
18
- pass
20
+ def append_http_server_log_queue(message: tuple[str, str, str], /) -> None: ...
19
21
 
20
22
 
21
23
  class log:
22
24
  @classmethod
23
- def init(cls):
25
+ def init(cls) -> None:
24
26
  """
27
+ 初始化日志功能
28
+
25
29
  1. 获取终端颜色
26
- 2. 写入 \\</div>
30
+ 2. 写入 \\</div> 以闭合已有日志
27
31
  """
28
-
29
32
  # 获取终端颜色
30
33
  if os.name == "nt":
31
34
 
@@ -90,6 +93,7 @@ class log:
90
93
  cls.write_html_log("</div></div></div>")
91
94
 
92
95
  class LogLevel(enum.Enum):
96
+ _detail = enum.auto()
93
97
  debug = enum.auto()
94
98
  send = enum.auto()
95
99
  info = enum.auto()
@@ -102,7 +106,7 @@ class log:
102
106
  only_print = enum.auto()
103
107
  only_write = enum.auto()
104
108
 
105
- html_filename: str = "encoding_log.html" # 在调用前覆写
109
+ html_filename: str = "EasyRip_log.html" # 在调用前覆写
106
110
  print_level: LogLevel = LogLevel.send
107
111
  write_level: LogLevel = LogLevel.send
108
112
 
@@ -121,7 +125,24 @@ class log:
121
125
  error_num: int = 0
122
126
  send_num: int = 0
123
127
 
124
- hr = "———————————————————————————————————"
128
+ @staticmethod
129
+ def print(
130
+ value: str,
131
+ end: str = "",
132
+ file: TextIO = sys.stdout,
133
+ ) -> None:
134
+ try:
135
+ print_formatted_text(
136
+ ANSI(value),
137
+ end=end,
138
+ file=file,
139
+ )
140
+ except Exception:
141
+ print(
142
+ value,
143
+ end=end,
144
+ file=file,
145
+ )
125
146
 
126
147
  @classmethod
127
148
  def _do_log(
@@ -130,12 +151,15 @@ class log:
130
151
  mode: LogMode,
131
152
  message: object,
132
153
  *fmt_args: object,
154
+ stream: TextIO,
155
+ print_level: LogLevel,
156
+ write_level: LogLevel,
133
157
  is_format: bool = True,
134
158
  is_deep: bool = False,
135
159
  is_server: bool = False,
136
160
  http_send_header: str = "",
137
161
  **fmt_kwargs: object,
138
- ):
162
+ ) -> None:
139
163
  if log_level == cls.LogLevel.none:
140
164
  return
141
165
 
@@ -159,20 +183,24 @@ class log:
159
183
  if (
160
184
  mode != cls.LogMode.only_write
161
185
  and cls.print_level.value <= cls.LogLevel.debug.value
186
+ and cls.print_level.value <= print_level.value
162
187
  ):
163
- print(
164
- f"{time_str}\033[{cls.debug_color}m [DEBUG] {message}\033[{cls.default_foreground_color}m"
188
+ cls.print(
189
+ f"{time_str}\033[{cls.debug_color}m [DEBUG] {message}\033[{cls.default_foreground_color}m\n",
190
+ end="",
191
+ file=stream,
165
192
  )
166
193
 
167
194
  if (
168
195
  mode != cls.LogMode.only_print
169
196
  and cls.write_level.value <= cls.LogLevel.debug.value
197
+ and cls.write_level.value <= write_level.value
170
198
  ):
171
199
  cls.write_html_log(
172
200
  f'<div style="background-color:#b4b4b4;margin-bottom:2px;white-space:pre-wrap;"><span style="color:green;">{time_now}</span> <span style="color:green;">[DEBUG] {message}</span></div>'
173
201
  )
174
202
 
175
- Event.append_http_server_log_queue((time_now, "INFO", message))
203
+ Event.append_http_server_log_queue((time_now, "DEBUG", message))
176
204
 
177
205
  case cls.LogLevel.info:
178
206
  cls.info_num += 1
@@ -180,14 +208,18 @@ class log:
180
208
  if (
181
209
  mode != cls.LogMode.only_write
182
210
  and cls.print_level.value <= cls.LogLevel.info.value
211
+ and cls.print_level.value <= print_level.value
183
212
  ):
184
- print(
185
- f"{time_str}\033[{cls.info_color}m [INFO] {message}\033[{cls.default_foreground_color}m"
213
+ cls.print(
214
+ f"{time_str}\033[{cls.info_color}m [INFO] {message}\033[{cls.default_foreground_color}m\n",
215
+ end="",
216
+ file=stream,
186
217
  )
187
218
 
188
219
  if (
189
220
  mode != cls.LogMode.only_print
190
221
  and cls.write_level.value <= cls.LogLevel.info.value
222
+ and cls.write_level.value <= write_level.value
191
223
  ):
192
224
  cls.write_html_log(
193
225
  f'<div style="background-color:#b4b4b4;margin-bottom:2px;white-space:pre-wrap;"><span style="color:green;">{time_now}</span> <span style="color:blue;">[INFO] {message}</span></div>'
@@ -201,15 +233,18 @@ class log:
201
233
  if (
202
234
  mode != cls.LogMode.only_write
203
235
  and cls.print_level.value <= cls.LogLevel.warning.value
236
+ and cls.print_level.value <= print_level.value
204
237
  ):
205
- print(
206
- f"{time_str}\033[{cls.warning_color}m [WARNING] {message}\033[{cls.default_foreground_color}m",
207
- file=sys.stderr,
238
+ cls.print(
239
+ f"{time_str}\033[{cls.warning_color}m [WARNING] {message}\033[{cls.default_foreground_color}m\n",
240
+ end="",
241
+ file=stream,
208
242
  )
209
243
 
210
244
  if (
211
245
  mode != cls.LogMode.only_print
212
246
  and cls.write_level.value <= cls.LogLevel.warning.value
247
+ and cls.write_level.value <= write_level.value
213
248
  ):
214
249
  cls.write_html_log(
215
250
  f'<div style="background-color:#b4b4b4;margin-bottom:2px;white-space:pre-wrap;"><span style="color:green;">{time_now}</span> <span style="color:yellow;">[WARNING] {message}</span></div>'
@@ -223,15 +258,18 @@ class log:
223
258
  if (
224
259
  mode != cls.LogMode.only_write
225
260
  and cls.print_level.value <= cls.LogLevel.error.value
261
+ and cls.print_level.value <= print_level.value
226
262
  ):
227
- print(
228
- f"{time_str}\033[{cls.error_color}m [ERROR] {message}\033[{cls.default_foreground_color}m",
229
- file=sys.stderr,
263
+ cls.print(
264
+ f"{time_str}\033[{cls.error_color}m [ERROR] {message}\033[{cls.default_foreground_color}m\n",
265
+ end="",
266
+ file=stream,
230
267
  )
231
268
 
232
269
  if (
233
270
  mode != cls.LogMode.only_print
234
271
  and cls.write_level.value <= cls.LogLevel.error.value
272
+ and cls.write_level.value <= write_level.value
235
273
  ):
236
274
  cls.write_html_log(
237
275
  f'<div style="background-color:#b4b4b4;margin-bottom:2px;white-space:pre-wrap;"><span style="color:green;">{time_now}</span> <span style="color:red;">[ERROR] {message}</span></div>'
@@ -243,12 +281,22 @@ class log:
243
281
  cls.send_num += 1
244
282
 
245
283
  if is_server or easyrip_web.http_server.Event.is_run_command:
246
- if cls.print_level.value <= cls.LogLevel.send.value:
247
- print(
248
- f"{time_str}\033[{cls.send_color}m [Send] {message}\033[{cls.default_foreground_color}m"
284
+ if (
285
+ mode != cls.LogMode.only_write
286
+ and cls.print_level.value <= cls.LogLevel.send.value
287
+ and cls.print_level.value <= print_level.value
288
+ ):
289
+ cls.print(
290
+ f"{time_str}\033[{cls.send_color}m [Send] {message}\033[{cls.default_foreground_color}m\n",
291
+ end="",
292
+ file=stream,
249
293
  )
250
294
 
251
- if cls.write_level.value <= cls.LogLevel.send.value:
295
+ if (
296
+ mode != cls.LogMode.only_print
297
+ and cls.write_level.value <= cls.LogLevel.send.value
298
+ and cls.write_level.value <= write_level.value
299
+ ):
252
300
  cls.write_html_log(
253
301
  f'<div style="background-color:#b4b4b4;margin-bottom:2px;white-space:pre-wrap;"><span style="color:green;white-space:pre-wrap;">{time_now}</span> <span style="color:deeppink;">[Send] <span style="color:green;">{http_send_header}</span>{message}</span></div>'
254
302
  )
@@ -257,8 +305,9 @@ class log:
257
305
  (http_send_header, "Send", message)
258
306
  )
259
307
  elif cls.print_level.value <= cls.LogLevel.send.value:
260
- print(
261
- f"\033[{cls.send_color}m{message}\033[{cls.default_foreground_color}m"
308
+ cls.print(
309
+ f"\033[{cls.send_color}m{message}\033[{cls.default_foreground_color}m\n",
310
+ end="",
262
311
  )
263
312
 
264
313
  @classmethod
@@ -267,17 +316,22 @@ class log:
267
316
  message: object,
268
317
  /,
269
318
  *fmt_args: object,
319
+ stream: TextIO = sys.stderr,
320
+ print_level: LogLevel = LogLevel.debug,
321
+ write_level: LogLevel = LogLevel.debug,
270
322
  is_format: bool = True,
271
323
  deep: bool = False,
272
324
  mode: LogMode = LogMode.normal,
273
- level: LogLevel = LogLevel.debug,
274
325
  **fmt_kwargs: object,
275
- ):
326
+ ) -> None:
276
327
  cls._do_log(
277
- level,
328
+ log.LogLevel.debug,
278
329
  mode,
279
330
  message,
280
331
  *fmt_args,
332
+ stream=stream,
333
+ print_level=print_level,
334
+ write_level=write_level,
281
335
  is_format=is_format,
282
336
  is_deep=deep,
283
337
  is_server=False,
@@ -291,17 +345,22 @@ class log:
291
345
  message: object,
292
346
  /,
293
347
  *fmt_args: object,
348
+ stream: TextIO = sys.stderr,
349
+ print_level: LogLevel = LogLevel.info,
350
+ write_level: LogLevel = LogLevel.info,
294
351
  is_format: bool = True,
295
352
  deep: bool = False,
296
353
  mode: LogMode = LogMode.normal,
297
- level: LogLevel = LogLevel.info,
298
354
  **fmt_kwargs: object,
299
- ):
355
+ ) -> None:
300
356
  cls._do_log(
301
- level,
357
+ log.LogLevel.info,
302
358
  mode,
303
359
  message,
304
360
  *fmt_args,
361
+ stream=stream,
362
+ print_level=print_level,
363
+ write_level=write_level,
305
364
  is_format=is_format,
306
365
  is_deep=deep,
307
366
  is_server=False,
@@ -315,17 +374,22 @@ class log:
315
374
  message: object,
316
375
  /,
317
376
  *fmt_args: object,
377
+ stream: TextIO = sys.stderr,
378
+ print_level: LogLevel = LogLevel.warning,
379
+ write_level: LogLevel = LogLevel.warning,
318
380
  is_format: bool = True,
319
381
  deep: bool = False,
320
382
  mode: LogMode = LogMode.normal,
321
- level: LogLevel = LogLevel.warning,
322
383
  **fmt_kwargs: object,
323
- ):
384
+ ) -> None:
324
385
  cls._do_log(
325
- level,
386
+ log.LogLevel.warning,
326
387
  mode,
327
388
  message,
328
389
  *fmt_args,
390
+ stream=stream,
391
+ print_level=print_level,
392
+ write_level=write_level,
329
393
  is_format=is_format,
330
394
  is_deep=deep,
331
395
  is_server=False,
@@ -333,23 +397,30 @@ class log:
333
397
  **fmt_kwargs,
334
398
  )
335
399
 
400
+ warn = warning
401
+
336
402
  @classmethod
337
403
  def error(
338
404
  cls,
339
405
  message: object,
340
406
  /,
341
407
  *fmt_args: object,
408
+ stream: TextIO = sys.stderr,
409
+ print_level: LogLevel = LogLevel.error,
410
+ write_level: LogLevel = LogLevel.error,
342
411
  is_format: bool = True,
343
412
  deep: bool = False,
344
413
  mode: LogMode = LogMode.normal,
345
- level: LogLevel = LogLevel.error,
346
414
  **fmt_kwargs: object,
347
- ):
415
+ ) -> None:
348
416
  cls._do_log(
349
- level,
417
+ log.LogLevel.error,
350
418
  mode,
351
419
  message,
352
420
  *fmt_args,
421
+ stream=stream,
422
+ print_level=print_level,
423
+ write_level=write_level,
353
424
  is_format=is_format,
354
425
  is_deep=deep,
355
426
  is_server=False,
@@ -357,24 +428,31 @@ class log:
357
428
  **fmt_kwargs,
358
429
  )
359
430
 
431
+ err = error
432
+
360
433
  @classmethod
361
434
  def send(
362
435
  cls,
363
436
  message: object,
364
437
  /,
365
438
  *fmt_args: object,
439
+ stream: TextIO = sys.stdout,
440
+ print_level: LogLevel = LogLevel.send,
441
+ write_level: LogLevel = LogLevel.send,
366
442
  is_format: bool = True,
367
443
  mode: LogMode = LogMode.normal,
368
444
  is_server: bool = False,
369
445
  http_send_header: str = "",
370
- level: LogLevel = LogLevel.send,
371
446
  **fmt_kwargs: object,
372
- ):
447
+ ) -> None:
373
448
  cls._do_log(
374
- level,
449
+ log.LogLevel.send,
375
450
  mode,
376
451
  message,
377
452
  *fmt_args,
453
+ stream=stream,
454
+ print_level=print_level,
455
+ write_level=write_level,
378
456
  is_format=is_format,
379
457
  is_deep=False,
380
458
  is_server=is_server,
@@ -386,7 +464,7 @@ class log:
386
464
  def write_html_log(
387
465
  cls,
388
466
  message: str,
389
- ):
467
+ ) -> None:
390
468
  try:
391
469
  with open(cls.html_filename, "at", encoding="utf-8") as f:
392
470
  f.write(message)