tytcli 0.2.0__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.
tytcli/output.py ADDED
@@ -0,0 +1,82 @@
1
+ """stdout / stderr 的输出契约。
2
+
3
+ 每个命令都通过 :func:`emit_ok` / :func:`emit_error` 汇报结果,所以 stdout 上那一个
4
+ JSON 文档的形状只在**一处**定义。退出码也住在这里:它们属于同一份契约,而
5
+ ``main()`` 返回的就是 ``TytCliError.exit_code`` 携带的值。
6
+
7
+ 从 ``cli.py`` 里拆出来,是为了让命令模块不必 import 入口就能输出结果(那会构成
8
+ 循环依赖)。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from typing import Any
15
+
16
+ import click
17
+
18
+ from .errors import TytCliError
19
+
20
+ EXIT_OK = 0
21
+ EXIT_API = 1
22
+ EXIT_USAGE = 2
23
+ EXIT_AUTH = 3
24
+ EXIT_NETWORK = 4
25
+
26
+
27
+ #: 给 agent 的机器可读提示所用的顶层键。公众号列表命令用它说明"裁掉了哪些字段、
28
+ #: 怎么取回其余的",自更新钩子用它说明"发现了新版但本次没装成"。
29
+ #: 用双下划线包起来,这样它不可能与服务端字段撞名。
30
+ HINT_KEY = "__tytcli_hint__"
31
+
32
+ #: 由 :mod:`tytcli.selfupdate` 在隐式钩子发现"有新版但本进程不会安装"时写入,
33
+ #: 由本进程接下来写出的那一个文档消费。做成模块状态而不是函数参数,是因为钩子
34
+ #: 跑在 ``main()`` 里,那时还没有任何命令知道自己将要输出东西。
35
+ _pending_hint: str | None = None
36
+
37
+
38
+ def set_pending_hint(text: str) -> None:
39
+ """把一条提示排进队列,交给本进程接下来写出的 JSON 文档。"""
40
+ global _pending_hint
41
+ _pending_hint = text
42
+
43
+
44
+ def take_pending_hint() -> str | None:
45
+ """取出并清空排队中的提示。公开是为了让测试能把它排空。"""
46
+ global _pending_hint
47
+ value, _pending_hint = _pending_hint, None
48
+ return value
49
+
50
+
51
+ def emit_ok(data: Any, *, pretty: bool = False, **meta: Any) -> None:
52
+ """向 stdout 写一个成功文档。"""
53
+ document: dict[str, Any] = {"ok": True}
54
+ document.update(meta)
55
+ hint = take_pending_hint()
56
+ if hint is not None:
57
+ # 命令自己可能已经在同一个键下放了提示(公众号列表就会),
58
+ # 所以要合并而不是覆盖。
59
+ existing = document.pop(HINT_KEY, None)
60
+ document[HINT_KEY] = f"{existing}\n{hint}" if existing else hint
61
+ document["data"] = data
62
+ _write(document, pretty)
63
+
64
+
65
+ def emit_error(exc: TytCliError, *, pretty: bool = False) -> None:
66
+ """向 stdout 写一个失败文档。"""
67
+ document: dict[str, Any] = {"ok": False, "error": exc.to_dict()}
68
+ hint = take_pending_hint()
69
+ if hint is not None:
70
+ document[HINT_KEY] = hint
71
+ _write(document, pretty)
72
+
73
+
74
+ def _write(document: dict[str, Any], pretty: bool) -> None:
75
+ text = json.dumps(
76
+ document,
77
+ ensure_ascii=False,
78
+ indent=2 if pretty else None,
79
+ separators=None if pretty else (",", ":"),
80
+ sort_keys=False,
81
+ )
82
+ click.echo(text)