Skip to content

Commit 28497cb

Browse files
authored
Merge pull request #21 from chrisis58/dev-version
feat: 优化非交互式的输出
2 parents 1810e68 + 0e0d266 commit 28497cb

28 files changed

+271
-79
lines changed

.github/workflows/release-package.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ jobs:
1515
steps:
1616
- name: Checkout code
1717
uses: actions/checkout@v4
18+
with:
19+
# Ensure full history for versioning tools `setuptools-scm`
20+
fetch-depth: 0
1821

1922
- name: Set up Python
2023
uses: actions/setup-python@v5

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ __pycache__/
1717
*$py.class
1818

1919
*.egg-info/
20-
dist/
20+
dist/
21+
22+
# version file generated by setuptools-scm
23+
src/kmdr/_version.py

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "kmoe-manga-downloader"
3-
version = "1.2.2"
3+
dynamic = ["version"]
44
authors = [
55
{ name="Chris Zheng", email="chrisis58@outlook.com" },
66
]
@@ -36,5 +36,12 @@ kmdr = "kmdr.main:entry_point"
3636
[tool.setuptools]
3737
package-dir = {"" = "src"}
3838

39+
[build-system]
40+
requires = ["setuptools>=61.0", "setuptools-scm[toml]>=8.0"]
41+
build-backend = "setuptools.build_meta"
42+
43+
[tool.setuptools_scm]
44+
write_to = "src/kmdr/_version.py"
45+
3946
[tool.pytest.ini_options]
4047
pythonpath = "src"

src/kmdr/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
try:
2+
from ._version import __version__
3+
except ImportError:
4+
__version__ = "unknown"

src/kmdr/core/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
from .structure import VolInfo, BookInfo, VolumeType
33
from .bases import AUTHENTICATOR, LISTERS, PICKERS, DOWNLOADER, CONFIGURER, SESSION_MANAGER
44

5-
from .defaults import argument_parser, console
5+
from .defaults import argument_parser, post_init
66

77
from .error import KmdrError, LoginError
88

9-
from .session import KmdrSessionManager
9+
from .session import KmdrSessionManager
10+
11+
from .console import info, debug, exception, log

src/kmdr/core/bases.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import asyncio
55
from aiohttp import ClientSession
66

7+
from .console import *
78
from .error import LoginError
89
from .registry import Registry
910
from .structure import VolInfo, BookInfo
@@ -41,8 +42,8 @@ async def authenticate(self) -> None:
4142
try:
4243
assert await async_retry()(self._authenticate)()
4344
except LoginError as e:
44-
self._console.print(f"[yellow]详细信息:{e}[/yellow]")
45-
self._console.print("[red]认证失败。请检查您的登录凭据或会话 cookie。[/red]")
45+
info(f"[yellow]详细信息:{e}[/yellow]")
46+
info("[red]认证失败。请检查您的登录凭据或会话 cookie。[/red]")
4647
exit(1)
4748

4849
@abstractmethod
@@ -82,16 +83,24 @@ def __init__(self,
8283

8384
async def download(self, book: BookInfo, volumes: list[VolInfo]):
8485
if not volumes:
85-
self._console.print("没有可下载的卷。", style="blue")
86+
info("没有可下载的卷。", style="blue")
8687
exit(0)
8788

8889
try:
8990
with self._progress:
9091
tasks = [self._download(book, volume) for volume in volumes]
91-
await asyncio.gather(*tasks, return_exceptions=True)
92+
results = await asyncio.gather(*tasks, return_exceptions=True)
93+
94+
exceptions = [res for res in results if isinstance(res, Exception)]
95+
if exceptions:
96+
info(f"[red]下载过程中出现 {len(exceptions)} 个错误:[/red]")
97+
for exc in exceptions:
98+
info(f"[red]- {exc}[/red]")
99+
exception(exc)
100+
exit(1)
92101

93102
except KeyboardInterrupt:
94-
self._console.print("\n操作已取消(KeyboardInterrupt)")
103+
info("\n操作已取消(KeyboardInterrupt)")
95104
exit(130)
96105

97106
@abstractmethod

src/kmdr/core/console.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
KMDR 用于管理控制台输出的模块。
3+
4+
提供信息、调试和日志记录功能,确保在交互式和非交互式环境中均能正确输出。
5+
"""
6+
from typing import Any
7+
import sys
8+
import io
9+
10+
from rich.console import Console
11+
from rich.traceback import Traceback
12+
13+
from kmdr.core.defaults import is_verbose
14+
15+
_console_config = dict[str, Any](
16+
log_time_format="[%Y-%m-%d %H:%M:%S]",
17+
)
18+
19+
try:
20+
utf8_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='backslashreplace')
21+
_console_config['file'] = utf8_stdout
22+
except io.UnsupportedOperation:
23+
pass
24+
25+
_console = Console(**_console_config)
26+
27+
def info(*args, **kwargs):
28+
"""
29+
在终端中输出信息
30+
31+
会根据终端是否为交互式选择合适的输出方式。
32+
"""
33+
if _console.is_interactive:
34+
_console.print(*args, **kwargs)
35+
else:
36+
_console.log(*args, **kwargs, _stack_offset=2)
37+
38+
def debug(*args, **kwargs):
39+
"""
40+
在终端中输出调试信息
41+
42+
`info` 的条件版本,仅当启用详细模式时才会输出。
43+
"""
44+
if is_verbose():
45+
if _console.is_interactive:
46+
_console.print("[dim]DEBUG:[/]", *args, **kwargs)
47+
else:
48+
_console.log("DEBUG:", *args, **kwargs, _stack_offset=2)
49+
50+
def log(*args, debug=False, **kwargs):
51+
"""
52+
仅在非交互式终端中记录日志信息
53+
54+
:warning: 仅在非交互式终端中输出日志信息,避免干扰交互式用户界面。
55+
"""
56+
if _console.is_interactive:
57+
# 如果是交互式终端,则不记录日志
58+
return
59+
60+
if debug and is_verbose():
61+
# 仅在调试模式和启用详细模式时记录调试日志
62+
_console.log("DEBUG:", *args, **kwargs, _stack_offset=2)
63+
else:
64+
_console.log(*args, **kwargs, _stack_offset=2)
65+
66+
def exception(exception: Exception):
67+
_console.print((Traceback.from_exception(type(exception), exception, exception.__traceback__)))

src/kmdr/core/context.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1+
from typing import Optional
2+
13
from aiohttp import ClientSession
4+
from rich.progress import Progress
25

6+
from .defaults import Configurer as InnerConfigurer, UserProfile, session_var, base_url_var, progress_definition
7+
from .console import _console
38

4-
from .defaults import Configurer as InnerConfigurer, UserProfile, session_var, progress, console, base_url_var
9+
_lazy_progress: Optional[Progress] = None
510

611
class TerminalContext:
712

813
def __init__(self, *args, **kwargs):
914
super().__init__()
10-
self._progress = progress
11-
self._console = console
15+
self._console = _console
16+
17+
@property
18+
def _progress(self) -> Progress:
19+
global _lazy_progress
20+
if _lazy_progress is None:
21+
_lazy_progress = Progress(*progress_definition, console=self._console)
22+
return _lazy_progress
1223

1324
class UserProfileContext:
1425

src/kmdr/core/defaults.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import io
2+
import sys
13
import os
24
import json
35
from typing import Optional, Any
@@ -21,12 +23,8 @@
2123
'User-Agent': 'kmdr/1.0 (https://github.com/chrisis58/kmoe-manga-downloader)'
2224
}
2325

24-
console = Console()
2526

26-
def console_print(*args, **kwargs):
27-
console.print(*args, **kwargs)
28-
29-
progress = Progress(
27+
progress_definition = (
3028
TextColumn("[blue]{task.fields[filename]}", justify="left"),
3129
TextColumn("{task.fields[status]}", justify="right"),
3230
TextColumn("{task.percentage:>3.1f}%"),
@@ -38,7 +36,6 @@ def console_print(*args, **kwargs):
3836
",",
3937
TimeRemainingColumn(),
4038
"]",
41-
console=console,
4239
)
4340

4441
session_var = ContextVar('session')
@@ -52,8 +49,13 @@ def argument_parser():
5249
return parser
5350

5451
parser = argparse.ArgumentParser(description='Kmoe 漫画下载器')
52+
53+
parser.add_argument('-v', '--verbose', action='store_true', help='启用详细输出')
54+
5555
subparsers = parser.add_subparsers(title='可用的子命令', dest='command')
5656

57+
version_parser = subparsers.add_parser('version', help='显示当前版本信息')
58+
5759
download_parser = subparsers.add_parser('download', help='下载指定的漫画')
5860
download_parser.add_argument('-d', '--dest', type=str, help='指定下载文件的保存路径,默认为当前目录', required=False)
5961
download_parser.add_argument('-l', '--book-url', type=str, help='漫画详情页面的 URL', required=False)
@@ -65,6 +67,7 @@ def argument_parser():
6567
download_parser.add_argument('-p', '--proxy', type=str, help='设置下载使用的代理服务器', required=False)
6668
download_parser.add_argument('-r', '--retry', type=int, help='网络请求失败时的重试次数', required=False)
6769
download_parser.add_argument('-c', '--callback', type=str, help='每个卷下载完成后执行的回调脚本,例如: `echo {v.name} downloaded!`', required=False)
70+
download_parser.add_argument('-m', '--method', type=int, help='下载方法,对应网站上的不同下载方式', required=False, choices=[1, 2], default=1)
6871

6972
login_parser = subparsers.add_parser('login', help='登录到 Kmoe')
7073
login_parser.add_argument('-u', '--username', type=str, help='用户名', required=True)
@@ -185,7 +188,7 @@ def set_base_url(self, value: str):
185188
self._config.base_url = value
186189
self.update()
187190

188-
def get_base_url(self) -> str:
191+
def get_base_url(self) -> Optional[str]:
189192
return self._config.base_url
190193

191194
def update(self):
@@ -237,3 +240,12 @@ def combine_args(dest: argparse.Namespace) -> argparse.Namespace:
237240
return __combine_args(dest, option)
238241

239242
base_url_var = ContextVar('base_url', default=Configurer().base_url)
243+
244+
_verbose = False
245+
246+
def is_verbose() -> bool:
247+
return _verbose
248+
249+
def post_init(args) -> None:
250+
global _verbose
251+
_verbose = getattr(args, 'verbose', False)

src/kmdr/core/error.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ def __init__(self, message, solution: Optional[list[str]] = None):
1414
def __str__(self):
1515
return f"{self.message}\n{self._solution}"
1616

17+
class ArgsResolveError(KmdrError):
18+
def __init__(self, message, solution: Optional[list[str]] = None):
19+
super().__init__(message, solution)
20+
21+
def __str__(self):
22+
return f"{self.message}\n{self._solution}"
23+
1724
class LoginError(KmdrError):
1825
def __init__(self, message, solution: Optional[list[str]] = None):
1926
super().__init__(message, solution)

0 commit comments

Comments
 (0)