kit

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package kit 的进度条行渲染 + diff 输出。

渲染模型(学 indicatif 的 diff 渲染,消除 mpb 的 \e[J 整块清屏闪烁):

  • 每帧把所有 bar 渲染成行字符串数组
  • 与上一帧逐行重写:光标上移 + \r(回车行首) + \e[K(清当前行,非整屏) + 写新行
  • 整个 diff 结果单次 Write,终端原子渲染,无中间空白态

关键:\e[K 只清当前行(不像 mpb 的 \e[J 清整屏),无空白帧 → 无闪烁。

Package kit 是 kite 命令层的共享工具包。

封装 engine 调用、登录会话读写、输出与确认交互,各领域命令包(song/playlist/...) 只依赖本包,不直接碰 engine 细节。

Package kit config.go 实现 config.toml 的加载/校验/写入(PRD-0017)。

设计原则(决策见 CONTEXT.md「不可逆决策」表):

  • config key 只收「已存在 flag 或硬编码默认值」(当前六项,见 configKeys);
  • 文件不存在 = 正常态(全部内置默认);文件存在但坏 = 硬错误,静默回退会掩盖用户设置;
  • set 写盘前完成全部校验,读取侧复用同一套校验(手改文件同样被拦);
  • tmp+rename 原子写,文件 0600、目录 0700(与 session 一致)。

Package kit 的进度条视觉实现 —— 盲文点阵 + true color 渐变。

设计核心:每个盲文字符是 2×4 的 8 点位像素(U+2800..U+28FF),一个终端 cell 承载 8 个亚像素。进度边界用「逐点点亮」平滑过渡,而非整块跳变;配合 24-bit true color 渐变,达到接近 LED 点阵屏的细腻度。

点位编码(Unicode U+2800 基址 + 8 位偏移):

┌──┬──┐
│1 │4 │     1=0x01 2=0x02 3=0x04 4=0x08
│2 │5 │     5=0x10 6=0x20 7=0x40 8=0x80
│3 │6 │     rune(0x2800 | dots)
│7 │8 │     ⣿ = 全 8 点亮(0xFF),⠀ = 全灭(0x00)
└──┴──┘

布局原则(单行):

  • 已完成段:全亮 ⣿,渐变色填充
  • 边界字符:按亚进度点亮 0..8 个点,平滑锯齿
  • 未完成段:稀疏锚点(每隔几列底部一个点),形成「轨道」指引而非空白

Package kit 的进度数值格式化纯函数。

所有函数无副作用、无 I/O,期望值参照 PRD-0013 的进度显示示例:

  • 字节:1024 进制(KiB/MiB/GiB),显示用 KB/MB/GB(PRD 行 80:3.4 MB)
  • 时长:mm:ss,≥1h 用 h:mm:ss(PRD 行 80:0:01)
  • 速度:字节/秒,同字节单位(PRD 行 80:1.8 MB/s)
  • 百分比:整数%,分母零时 0%(PRD 行 80:62%)

位置参数 helper:--id flag 与位置参数(args[0])的统一解析。

动机(issue #24 / PRD 便捷性):`song download 347230` ≡ `song download --id 347230`, 对齐 git/kubectl 习惯。规则:位置参数与 --id 互斥(同时指定 → 用法错误)。

Package kit 的自实现进度条渲染器。

替代 mpb:直接控制每个字节,用 diff 渲染消除闪烁、EWMA 平滑速度、 假时钟注入确定性测试、✓ 完成态(Evil Martians/cli.r-lib 标准)。

核心循环(学 indicatif 的 steady tick):

  • Start() 启动 100ms ticker goroutine,每次 tick 渲染一帧
  • 渲染 = 渲染所有 bar → 与上一帧 diff → 单次 Write(原子,无空白帧)
  • 独立于 Incr 调用频率:即使某 bar 不增长,spinner 照转、ETA 照更新

Package kit proxy.go 实现代理 URL 的解析与校验(PRD-0018)。

flag(--proxy)与 config(proxy key)共用本函数:单一校验真相。 空串 = 未设置(返回 nil,nil,回落环境变量层——那是 Go 默认 transport 的 ProxyFromEnvironment 行为,本包不做任何注入)。

Package kit 的人类可读渲染层。

RenderHuman 是输出层唯一 seam:proto.Message → 文本的纯函数。 含非空 repeated message 字段的响应渲染为分段表格,否则渲染为键值对。

Spinner 独立的不可量化等待指示器(缓冲、初始化)。

用途:进度不可量化时显示转圈(PRD-0013 行 164「缓冲中 ⠼ 4.2s / 5s」)。 与 Progress 的区别:Progress 管 N 个可量化 bar,Spinner 是单行不可量化等待。

三态抑制(遵 PRD-0012 输出层规矩):

  • TTY:渲染 spinner 帧转圈到 err writer(stderr)
  • 非 TTY(管道):完全静默,不刷屏(管道里转圈是垃圾)
  • --json:由调用方不创建 Spinner 实现(此处不查 JSON,保持单一职责)

Start/Stop API:Start 启动 tick 转圈,Stop(msg) 停止并输出终态行(覆盖 spinner)。

Index

Constants

View Source
const (
	ProxySourceFlag   = "flag"   // --proxy 显式覆盖
	ProxySourceConfig = "config" // config.toml 的 proxy key
)

ProxySource 常量:UseProxy 注入时的来源标记,doctor 据此展示解析链。

View Source
const RpcsAnnotationKey = "kite/rpcs"

RpcsAnnotationKey 标记命令消费的 grpc rpc。

值为短形式 "Service/Method"(如 "SongService/GetSongDetail"),多 rpc 用逗号分隔 (如 "SongService/GetSongURL,SongService/GetSongDetail")。值存在(含空串)即表示 命令已被审视;无此 key 的叶子命令会被漏标守护捕获。

Variables

View Source
var ErrCancelled = errors.New("用户已取消")

ErrCancelled 用户取消哨兵错误,Execute 静默吞掉(退出码 0,不打错误信息)。

View Source
var ErrNotLogin = errors.New("未登录")

ErrNotLogin 未登录哨兵错误,Execute 据此映射退出码 3。

View Source
var ErrUsage = errors.New("用法错误")

ErrUsage 用法哨兵错误(flag 解析失败、非 TTY 写操作无 --yes 等),Execute 映射退出码 2。

Functions

func AnnotateRpcs

func AnnotateRpcs(c *cobra.Command, rpcs ...string)

AnnotateRpcs 给命令打 rpc 注解:把 rpcs 列表写入 c.Annotations[RpcsAnnotationKey]。 rpcs 为空时写入空串(标记「已审视,无 rpc」),用于本地命令(recent/doctor/...)。 供各组 NewCommand 在 AddCommand 后集中打标,避免散落。

func ConfigDir

func ConfigDir() (string, error)

ConfigDir 返回 kite 本地状态基目录(所有状态文件派生自此)。 目录在首次写盘时由调用方 MkdirAll(0700);本函数只算路径不创建。

func ConfigKeys added in v0.2.0

func ConfigKeys() []string

ConfigKeys 返回合法 key 列表(顺序即 config get 无参列表顺序,单一真相)。

func ConfigPath added in v0.2.0

func ConfigPath() (string, error)

ConfigPath 返回 config.toml 路径 <ConfigDir>/config.toml(只算路径不创建)。

func Exec

func Exec[Req, Resp any](k *Kit, ctx context.Context, ep *engine.Endpoint[Req, Resp], req Req) (Resp, error)

Exec 执行一个声明式 endpoint(注入当前 cookie)。 是 service 包 executeOverride 的等价物:复制而非 import, 因为 service 包还装配了 gRPC 相关类型,这里只要纯执行逻辑。

func FilenameTemplatePlaceholders added in v0.2.0

func FilenameTemplatePlaceholders() []string

FilenameTemplatePlaceholders 返回 filename_template 的合法占位符(config schema 的 单一真相)。songdl 的执行侧(songdl.FormatFilename)与这里由 songdl 包的守护测试保持同步——公共层只定义 schema,不夹带下载领域逻辑。

func HistoryPath

func HistoryPath() (string, error)

HistoryPath 返回召回池事件流文件路径 <ConfigDir>/history.jsonl。

召回池(#G,PRD-0014)尚未落地;本函数为它预留 seam,与 session 同源派生, 使 #G 落盘路径决策零额外成本。当前无调用者。

func IsKnownConfigKey added in v0.2.0

func IsKnownConfigKey(key string) bool

IsKnownConfigKey 判断 key 是否在 configKeys 枚举内(config get/set 命令层校验用)。

func MaskCookie

func MaskCookie(cookie string) string

MaskCookie 把 cookie 各段的值脱敏:保留首尾各 8 字符,中间省略; 短值(≤20 字符)整体打码。用于 login-status 人类模式防止凭证泄露。

func MountCompletion

func MountCompletion(root *cobra.Command, k *Kit)

MountCompletion 在 root 构造后一次树遍历统一挂载参数补全。

表驱动(flag 名 → 数据源):

  • "id"(单值歌曲 ID)→ 召回池候选(带「歌名 - 艺人」描述列),按 frecency 排序。
  • "level"/"area"/"op" 等 → 固定枚举。

新命令(A 类 rpc 1:1 接入)带同名 flag 时自动获得补全,零登记。个别命令异构需求 可在 MountCompletion 后就地 RegisterFlagCompletionFunc 覆盖——本函数检测已注册 的 flag 会跳过,尊重命令就地覆盖。

**补全绝不触发网络**:--id 候选全部来自召回池(内存优先,磁盘兜底),枚举是静态值 (CONTEXT.md 补全只走缓存段)。

调用点:NewRootCommand 末尾(root 构造后、return 前)。

func ParseProxyURL added in v0.2.0

func ParseProxyURL(raw string) (*url.URL, error)

ParseProxyURL 校验并解析代理地址。

  • 空串 → (nil, nil):未设置,不注入任何代理(环境变量层自然生效)。
  • scheme 必须显式写(http:// / https:// / socks5://):裸 host:port 报错并 引导补前缀——猜测默认值是文档陷阱。
  • host 非空;端口可缺省(用 scheme 默认端口)。

func ParseRpcs

func ParseRpcs(s string) []string

ParseRpcs 把注解值拆成 rpc 列表。空串与缺失 key 都返回 nil(无 rpc,合法)。 逗号分隔,逐项去空白与空串(容错末尾/连续逗号)。

func PrintJSON

func PrintJSON(msg proto.Message) error

PrintJSON 用 protojson 输出 pretty JSON(无条件,raw 路径与过渡期用)。

func PrintRaw

func PrintRaw(raw json.RawMessage)

PrintRaw 直接 pretty 打印原始 JSON(动态 path 接口未经 proto 映射时用)。

func RenderBar

func RenderBar(current, total int64, width int, color bool) string

RenderBar 渲染单条盲文进度条为字符串(纯函数,无副作用,无 I/O 依赖)。

current/total 决定进度;width 是盲文条占的字符数;color 控制是否输出 true color。 total ≤ 0 时按 0% 处理。视觉:已完成段全亮+渐变(青绿→天青→暖橙), 边界水位上涨(8 级亚像素),未完成段轨道锚点。

func RenderExec

func RenderExec[Req any, Resp proto.Message](k *Kit, ep *engine.Endpoint[Req, Resp], req Req) error

RenderExec 执行声明式 endpoint 并按三态规则输出(读命令通用)。

func RenderHuman

func RenderHuman(msg proto.Message) string

RenderHuman 把 proto 响应渲染为人类可读文本(纯函数)。

含非空 repeated message 字段 → 每个字段一段表格(多段带 == 字段名 (数量) == 小标题); 否则按键值对逐行渲染,嵌套子结构退化为紧凑 JSON。

func ResolveID

func ResolveID(flagID int64, args []string) (int64, error)

ResolveID 从 --id flag 或位置参数 args[0] 解析 id(歌曲/歌单通用)。

规则(PRD-0013 便捷性 / CONTEXT.md 位置参数术语):

  • flagID 非 0 且 args 有值 → ErrUsage「不能同时指定 --id 和位置参数」
  • 两者都缺 → ErrUsage「缺少 id」
  • 仅 flagID → 返回 flagID
  • 仅位置参数 → 解析为 int64;非数字 → ErrUsage

位置参数不进 tab 补全(补全只走召回池,见 CONTEXT.md「补全只走缓存」)。 调用方需先把 cobra.Args 设为 cobra.MaximumNArgs(1) 并 drop MarkFlagRequired("id")。

func ResolveKeyword

func ResolveKeyword(flagKeyword string, args []string) (string, error)

ResolveKeyword 从 --keyword flag 或位置参数 args[0] 解析关键词(search 用)。 规则同 ResolveID:flag 与位置参数互斥(同时指定 → ErrUsage);两者都缺 → ErrUsage。

func SessionPath

func SessionPath() (string, error)

SessionPath 返回会话文件路径 <ConfigDir>/session.json。

func SetConfigKey added in v0.2.0

func SetConfigKey(key, value string) error

SetConfigKey 校验并写入单个 key(整文件重写,tmp+rename 原子落盘)。 只落显式设置过的 key——未设置的留空,读取侧回退内置默认, 「来源」列(config get 无参列表)才有区分度。 坏文件在手时同样报错——不允许绕过校验覆盖写。

Types

type Bar

type Bar struct {
	Total   int64    // 总量(字节数)
	Current int64    // 已完成量
	Label   string   // 显示名(如 "Beyond - 海阔天空")
	State   BarState // 当前状态
	IsTotal bool     // 是否总 bar(显示 ETA 而非速度)
	// contains filtered or unexported fields
}

Bar 单个进度条的状态。并发安全:字段由自有 mu 保护, 写走 Incr/Complete/Fail,渲染读走 snapshot()。

func (*Bar) Complete

func (b *Bar) Complete(now time.Time)

Complete 标记完成。

func (*Bar) Fail

func (b *Bar) Fail(msg string, now time.Time)

Fail 标记失败。

func (*Bar) Incr

func (b *Bar) Incr(n int64, now time.Time)

Incr 累加进度并更新 EWMA 速度。首次 Incr 切到 Active。

type BarState

type BarState int

BarState 进度条状态。

const (
	StateWaiting BarState = iota // 等待中(未开始)
	StateActive                  // 进行中
	StateDone                    // 完成(✓)
	StateFailed                  // 失败(✗)
)

type Config added in v0.2.0

type Config struct {
	Level            int    // 默认音质,1=standard 2=exhigh 3=lossless 4=hires
	Output           string // "table" | "json"
	DownloadDir      string // 下载目录(--out)
	FilenameTemplate string // 文件名模板,空 = {artist} - {title}
	Workers          int    // playlist download 并发数(--workers)
	Proxy            string // 代理地址,空 = 未设置(回落环境变量层,PRD-0018)
}

Config 是用户偏好的生效值集合(字段与 configKeys 一一对应)。 零值不可直接使用(Workers=0 非法), 一律经 DefaultConfig() 或 LoadConfig() 获得。

func DefaultConfig added in v0.2.0

func DefaultConfig() Config

DefaultConfig 返回内置默认值(与各 flag 的硬编码默认对齐,独立真相)。

func LoadConfig added in v0.2.0

func LoadConfig() (Config, error)

LoadConfig 读取 config.toml 并校验。文件不存在返回内置默认(正常态,非错误); 文件存在但解析失败/校验不过返回 error(硬错误,调用方应拒绝执行,PRD-0017)。

func LoadConfigWithSet added in v0.2.0

func LoadConfigWithSet() (Config, map[string]bool, error)

LoadConfigWithSet 在 LoadConfig 基础上额外返回「文件里显式设置的 key 集」 (config get 无参列表的「来源」列用)。

type Kit

type Kit struct {

	// JSON 为 true 时强制 protojson 输出(全局 --json);管道时无需设置,自动回退。
	JSON bool
	// Yes 为 true 时写操作跳过交互确认(全局 --yes)。
	Yes bool
	// Config 是生效的用户偏好(PRD-0017)。New() 填内置默认;root 装配时
	// 用 LoadConfig() 的结果覆盖(坏文件在 root 层硬错误)。Output 参与
	// Render 三态:优先级链 --json > 非TTY自动JSON > config > 内置默认。
	Config Config

	// ProxyURL 是当前生效的显式代理(nil = 未注入,环境变量层自然生效);
	// ProxySource 记录来源("flag" / "config" / "")供 doctor 展示解析链(PRD-0018)。
	ProxyURL    *url.URL
	ProxySource string

	// Out 结果输出 writer,默认 os.Stdout(测试可替换)。
	Out io.Writer
	// Err 警告/进度输出 writer,默认 os.Stderr(测试可替换)。
	// 进度类(ProgressBar/Spinner)和一次性警告(Warnf)都走这里,
	// 不污染 stdout(结果数据流),脚本管道友好。
	Err io.Writer
	// contains filtered or unexported fields
}

Kit 持有命令层共享的运行时依赖与全局输出状态。

func New

func New() *Kit

New 创建 Kit。engine 无缓存、无 session 池,纯转发到网易云。 召回池默认指向 kit.HistoryPath()(PRD-0015 #44 可注入路径)。

func (*Kit) ClearSession

func (k *Kit) ClearSession() error

ClearSession 删除本地会话文件,不存在时不算错误。

func (*Kit) ConfirmFatal

func (k *Kit) ConfirmFatal(action string) error

ConfirmFatal 写操作确认的一行化形式:确认返回 nil; 取消返回 ErrCancelled(Execute 退出码 0);非交互未授权返回 ErrUsage(退出码 2)。 13 个写命令调用点统一用这个,不再重复 ok/err 三行判断。

func (*Kit) ConfirmWrite

func (k *Kit) ConfirmWrite(action string) (bool, error)

ConfirmWrite 写操作前确认。

三态:--yes 直通;stdin 非 TTY 且无 --yes 返回用法错误(退出码 2); TTY 提示 y/N,取消返回 false(打印「已取消」,退出码 0)。 提示走 stderr,结果输出不被污染。

func (*Kit) CookieCtx

func (k *Kit) CookieCtx() context.Context

CookieCtx 把当前生效的 cookie 注入 context(无则注入空)。

func (*Kit) CurrentCookie

func (k *Kit) CurrentCookie() string

CurrentCookie 返回当前生效的 cookie: 环境变量 NETEASE_COOKIE 优先,其次本地会话文件。

func (*Kit) HTTPClient added in v0.2.0

func (k *Kit) HTTPClient() *http.Client

HTTPClient 返回直连类路径(音频下载等)应使用的 client:显式代理时 与 engine 同一代理决策派生;未注入时退回 http.DefaultClient(其默认 transport 保留 ProxyFromEnvironment 环境变量行为)。

func (*Kit) HumanOutput

func (k *Kit) HumanOutput() bool

HumanOutput 当前是否走人类可读渲染(WantJSON 的否定)。 命令需要按输出形态调整内容时用(如 login-status 脱敏),与 Render 同源。

func (*Kit) LoadSession

func (k *Kit) LoadSession() (Session, error)

SessionPath/ConfigDir/HistoryPath 见 paths.go(同包,唯一路径 seam)。

LoadSession 读本地会话。文件不存在或损坏时返回 error。

读前惰性触发旧路径迁移:首次发现新路径无文件时,把 ~/.kite/session.json 搬到新路径(若旧路径存在),见 migrateLegacySession。迁移失败不阻塞——按新路径 无文件处理(未登录)。

func (*Kit) NewProgress

func (k *Kit) NewProgress() *Progress

NewProgress 创建进度条渲染器,内部封装三态规矩(PRD-0012 输出层)。

命令层调 k.NewProgress() 即可,不用自己判断 TTY/--json:

  • --json:输出走 io.Discard,完全静默(结果走 protojson,进度文本是污染)。
  • 非 TTY(管道):tty=false,Start 不启动 tick,Wait 输出终态(脚本看最终结果)。
  • TTY:正常渲染,输出走 err(stderr),探测终端宽度 + true color。

进度类走 stderr 不污染 stdout(结果数据流),与 Warnf 一致。

func (*Kit) NewSpinner

func (k *Kit) NewSpinner(label string, opts ...SpinnerOption) *Spinner

NewSpinner 创建转圈指示器,内部封装三态规矩(同 NewProgress)。

  • --json:返回的 Spinner 输出走 io.Discard,完全静默。
  • 非 TTY:tty=false,Start 无操作,Stop 终态仍输出(有用信息)。
  • TTY:正常转圈,输出走 err(stderr)。

opts 透传给底层 NewSpinner(如 WithSpinnerLabelFunc 动态 label)。

func (*Kit) OutWriter

func (k *Kit) OutWriter() io.Writer

OutWriter 返回结果输出 writer(导出版本,供命令层直接写 stdout)。 大多数命令走 Render(自动三态);少数非 proto 结果(如 download 文件信息) 用这个直接写,配合 --json 自行处理双态。

func (*Kit) RawDo

func (k *Kit) RawDo(ctx context.Context, meta engine.Meta, params map[string]any) (json.RawMessage, string, error)

RawDo 执行一个 Meta + 参数 形式的原始调用(动态 path / 写接口用)。

func (*Kit) RecallPool

func (k *Kit) RecallPool() *recall.Pool

RecallPool 返回召回池(供 recent/补全消费)。无池返回 nil。

func (*Kit) Record

func (k *Kit) Record(id int64, name, artist string, src recall.Src)

Record 把一个成功消费的歌曲事件记入召回池(隐式埋点)。

机制选型(PRD-0014 G 片段,方案 c):kit 层提供统一的 Record helper,命令层在 --id 成功消费后显式调用一行。kit.Exec 是 generic,拿不到具体 id/name 字段 (SongId 在各 proto req struct 里),无法在 generic 层自动埋点;故选择显式 helper 而非 (a) interface+wrapper(要为每个 song req 写 wrapper)或 (b) endpoint 层埋点 (改 engine 抽象,被 service 包共用,高风险)。决策记录在 CONTEXT.md。

**只记成功**:调用方负责仅在命令成功(且写操作经 --yes 确认)后调用。 失败/取消不调本函数。Record 自身失败不阻塞主命令——仅 Warnf 告警(召回池是 辅助设施,损坏不应影响播放/下载主流程)。

歌曲类 id 进池(SongId);album/artist/playlist 的 id 不进召回池(召回池语义是 「歌曲」候选,补全 --id 用)。

func (*Kit) Render

func (k *Kit) Render(msg proto.Message) error

Render 按优先级链输出响应:WantJSON → protojson;其余 TTY → 人类可读。 管道 JSON 契约优先于 config(机器本地偏好不覆盖全局脚本契约)。

func (*Kit) RequireLogin

func (k *Kit) RequireLogin() error

RequireLogin 检查当前有可用的登录态(环境变量或本地会话文件)。 未登录返回包装了 ErrNotLogin 的错误,Execute 映射退出码 3。

func (*Kit) SaveSession

func (k *Kit) SaveSession(sess Session) error

SaveSession 把会话写盘(目录 0700 / 文件 0600)。

func (*Kit) SetRecallPool

func (k *Kit) SetRecallPool(p *recall.Pool)

SetRecallPool 注入召回池(测试用:替换为指向临时目录的 Pool)。 生产代码用 New() 默认初始化的池,不调本方法。

func (*Kit) UseProxy added in v0.2.0

func (k *Kit) UseProxy(u *url.URL, source string)

UseProxy 注入显式代理:engine 的 API 路径与下载路径同时生效(PRD-0018 的 双路径不变式)。source 记录来源供 doctor 展示。调用方(root)已保证 flag > config 解析出唯一 URL——决策单一来源。

func (*Kit) WantJSON added in v0.2.0

func (k *Kit) WantJSON() bool

WantJSON 当前输出是否应走 JSON——输出优先级链的单一真相 (PRD-0017/0018):--json > 非TTY自动JSON > config output > 内置默认 table。 Render 与 config 命令组等一切「人类可读 vs 结构化」决策都经此函数,不各自拼条件。

func (*Kit) Warnf

func (k *Kit) Warnf(format string, args ...any)

Warnf 格式化打印一次性警告到 stderr。

用于非阻塞提示(如「⚠ 元数据写入失败,文件已保存」,exit 0)。 所有模式都输出:警告不是结果数据,--json/非 TTY 也不抑制 (脚本作者需要看到警告判断是否可信)。

type Progress

type Progress struct {
	// contains filtered or unexported fields
}

Progress 多 bar 渲染器。

func NewProgress

func NewProgress(out io.Writer, width int, tty bool, opts ...ProgressOption) *Progress

NewProgress 创建渲染器。width 为 0 时按 80 兜底(调用方应传 term.GetSize 结果)。

渲染宽度预留最后一列(width-1):immediate-wrap 终端(Terminal.app 等) 在写满最后一列时立刻折行,行宽顶满会导致帧块每帧多占一行 → 逐帧抖动(闪烁); xterm 系 deferred-wrap 终端虽不受影响,预留一列对两类终端都安全。

func (*Progress) AddBar

func (p *Progress) AddBar(total int64, label string) *Bar

AddBar 添加一个进度条,返回引用供调用方 Incr/Complete。 初始状态 StateWaiting;首次 Incr 切到 StateActive。

func (*Progress) Now

func (p *Progress) Now() time.Time

Now 返回当前时钟(暴露 now 给调用方取时间戳,测试时是假时钟)。

func (*Progress) RenderForTest

func (p *Progress) RenderForTest()

RenderForTest 测试钩子:同步渲染一帧(绕过 ticker,确定性)。 仅供测试用,生产代码用 Start/Wait 驱动异步渲染。 默认按中间帧处理(final=false),测试非 TTY 抑制行为。

func (*Progress) SetWidth

func (p *Progress) SetWidth(width int)

SetWidth 运行期更新渲染宽度(终端拉伸响应),已 Start 时立即重绘一帧。 调用方负责监听 SIGWINCH 并传入新的 term.GetSize 结果; kit 不依赖 signal/fd,保持 io.Writer 层面的纯粹。

缩窄时支持 reflow 的终端(iTerm2/VTE/kitty/WezTerm 等)会把已绘制的 宽行重新折行,旧帧块行数变多,若仍按 len(prev) 上移会留残影(重复)。 因此置 widthDirty:下一帧按新宽度估算旧帧实际占用行数,上移 + \e[J 整块清。 已知取舍:不 reflow 的终端(xterm/Alacritty)缩窄时该估算会多清 块上方几行历史输出——重复残留与误清历史之间,选择适配多数现代终端。

func (*Progress) Start

func (p *Progress) Start()

Start 启动 steady tick。TTY 下隐藏光标 + 启动 100ms ticker; 非 TTY 不启动(只在 Wait 时输出终态)。

func (*Progress) Wait

func (p *Progress) Wait()

Wait 停止 tick,渲染最终帧,恢复光标。

type ProgressOption

type ProgressOption func(*Progress)

ProgressOption 配置 NewProgress。

func WithProgressClock

func WithProgressClock(now func() time.Time) ProgressOption

WithProgressClock 注入假时钟(测试用,保证 ETA 确定性)。

func WithProgressColor

func WithProgressColor(c bool) ProgressOption

WithProgressColor 启用 true color 渐变。

func WithProgressWidthSource

func WithProgressWidthSource(f func() int) ProgressOption

WithProgressWidthSource 轮询式宽度源:每帧渲染前查询终端宽度。

变化时按「去抖」处理:候选宽度连续稳定 3 帧(~300ms)才应用—— 拖动拉伸期间冻结渲染(旧块原地不动),松手后一次性清 reflow 残影 并重绘。为什么不去抖不行:部分终端(如 Wave)的 winsize 更新与 显示层 reflow 不同步,滞后窗口内任何基于行数估算的清除都会估错, 留下残影(重复),且拖动时每帧整块清绘在 Electron 管线下会闪烁。 冻结期间 Incr/EWMA 照常累积,只是画面暂停,拖拽本就短暂。 传 term.GetSize 的薄封装即可,非 TTY 无需设置。

type Session

type Session struct {
	Cookie  string `json:"cookie"`
	UserID  int64  `json:"user_id,omitempty"`
	SavedAt string `json:"saved_at"`
}

Session 是落盘的登录会话。cookie 属于敏感信息: 目录 0700、文件 0600,且不把完整 cookie 打印到终端。

type Spinner

type Spinner struct {
	// contains filtered or unexported fields
}

Spinner 单行转圈指示器。

func NewSpinner

func NewSpinner(out io.Writer, label string, tty bool, opts ...SpinnerOption) *Spinner

NewSpinner 创建转圈指示器。tty=false 时所有渲染静默(非 TTY 抑制)。

func (*Spinner) Start

func (s *Spinner) Start()

Start 启动转圈。TTY 下启动 100ms ticker;非 TTY 无操作。

func (*Spinner) Stop

func (s *Spinner) Stop(msg string)

Stop 停止转圈,输出终态行(覆盖 spinner)。msg 为空则只清行。 非 TTY 也输出终态(完成信息有用,不像转圈是垃圾)。

type SpinnerOption

type SpinnerOption func(*Spinner)

SpinnerOption 配置 NewSpinner。

func WithSpinnerClock

func WithSpinnerClock(now func() time.Time) SpinnerOption

WithSpinnerClock 注入假时钟(测试确定性)。

func WithSpinnerLabelFunc

func WithSpinnerLabelFunc(fn func() string) SpinnerOption

WithSpinnerLabelFunc 动态 label:每帧调用 fn 取最新文本(如缓冲秒数/水位), 替代静态 label 与 elapsed 后缀。用于内容随外部状态变化的等待(PRD-0013 行 164 「缓冲中 ⠼ 4.2s / 5s」中的 4.2s 是 Player.Progress 的已缓冲量,不是耗时)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL