
spaceship-prompt 通用工具函数完全指南Zsh 提示符 Section 开发者的 API 手册【免费下载链接】spaceship-prompt✨ Minimalistic, powerful and extremely customizable Zsh prompt项目地址: https://gitcode.com/gh_mirrors/sp/spaceship-prompt本文是 spaceship-prompt 通用工具函数General utilities的完整技术指南内容以 docs/api/utils.md乌克兰语镜像见 docs/uk/api/utils.md为骨架并对照 lib/utils.zsh 与 lib/extract.zsh 的源码实现、tests/utils.test.zsh 与 tests/extract.test.zsh 的测试用例进行纵深验证。读完本文你将掌握spaceship::exists、spaceship::is_git、spaceship::upsearch、spaceship::extract等十余个内建工具函数的签名、返回码约定与底层原理并能熟练地在自定义 Section 中调用它们完成命令探测、仓库识别、异步调度、数据文件查询等实战任务。在 spaceship-prompt 中lib/utils.zsh的文件头注释明确写道“This file is used as a API for sections developers”——这份文件就是面向 Section 开发者的公共 API。理解这些工具函数是编写健壮、符合官方规范的自定义 Section 的前提。一、工具函数的设计约定所有通用工具函数都遵循一套统一的调用约定理解它才能正确使用均以spaceship::为命名空间前缀与 Section 的spaceship_前缀命名规范见 docs/api/section.md区分开退出码即返回值多数判断型工具“返回零表示成立、非零表示不成立”可直接用if ...; then、||、等 Zsh 惯用语法串联保持静默除spaceship::upsearch默认输出路径与spaceship::extract输出查询结果外判断类工具不向 stdout 输出任何内容只通过退出码传达结果。二、命令与函数探测spaceship::exists与spaceship::definedspaceship::exists commandspaceship::exists command该工具校验给定程序是否可用于执行它会检查$PATH中的二进制文件、shell 函数以及内建命令builtins。若command存在则返回零退出码否则返回非零退出码。从源码看它的实现极其简洁lib/utils.zshspaceship::exists() { command -v $1 /dev/null 21 }其核心是 Zsh 内建命令command -v同时把标准输出与标准错误全部丢弃仅保留退出码语义。因为command -v能同时命中 PATH 中的可执行文件、已定义的函数和内建命令所以官方文档特别强调“It checks for PATH binaries, functions, and builtins”。典型用途是“探测某程序是否安装再据此决定后续动作”可以返回错误并退出也可以继续执行脚本# 检查多个命令是否存在 if spaceship::exists nvm; then # 提取 nvm 版本 elif spaceship::exists node; then # 提取 node 版本 else return fi # docker 未安装则什么也不做 spaceship::exists docker || returnlib/extract.zsh 内部就大量使用spaceship::exists来探测yq、ruby、python3等解析后端这正体现了“探测-分派”的经典用法。spaceship::defined functionspaceship::defined function该工具与spaceship::exists语义相同但针对的是函数若function此前已被定义则返回零退出码否则返回非零。其实现lib/utils.zsh借助typeset -f 判断spaceship::defined() { typeset -f $1 /dev/null }typeset -f会列出指定函数的定义存在则命令成功配合选项与 /dev/null静默化后仅保留退出码。可用于检查用户此前是否自定义过某个函数# 检查 Section 是否已定义 if spaceship::defined spaceship_section; then spaceship_section else # Section 未找到 fi这个工具在核心渲染流程中承担关键职责lib/core.zsh 遍历 Section 顺序时先用spaceship::defined spaceship_$section判断是否已有同名自定义函数若已定义则直接跳过加载这保证了用户自定义 Section 永远优先于内建 Section。三、版本仓库环境探测spaceship::is_git与spaceship::is_hgspaceship::is_git若当前工作目录位于 Git 仓库内则返回零退出码否则返回非零。官方示例# 当前目录不是 git 仓库则返回 spaceship::is_git || return源码实现lib/utils.zsh委托给 Git 自身完成判断spaceship::is_git() { # See https://git.io/fp8Pa for related discussion [[ $(command git rev-parse --is-inside-work-tree 2/dev/null) true ]] }它执行git rev-parse --is-inside-work-tree并比对输出是否为true。这带来一个重要特性在仓库的任意子目录中调用都会返回真因为--is-inside-work-tree检查的是“是否处于某个工作树内部”而非“当前目录是否是仓库根”。tests/utils.test.zsh 中的test_is_git专门验证了这一点在仓库根目录与子目录foo中均断言为真而在仓库之外断言为假。spaceship::is_hg与spaceship::is_git对应但针对 Mercurial 仓库# 当前目录不是 Mercurial 仓库则返回 spaceship::is_hg || return实现lib/utils.zsh复用了另一个工具函数spaceship::upsearch向上查找.hg目录spaceship::is_hg() { local hg_root$(spaceship::upsearch .hg) [[ -n $hg_root ]] /dev/null }这里体现了工具函数之间的组合复用is_hg通过upsearch向上搜索.hg标记目录若找到路径非空即为 Mercurial 仓库。tests/utils.test.zsh 中的test_is_hg还特别验证了带空格的目录路径foo with space不会被误判为仓库内且当系统未安装hg时测试会跳过。四、异步模式判断spaceship::is_section_async与spaceship::is_prompt_async异步渲染是 spaceship-prompt 的核心性能特性这两个工具是连接通用工具层与异步工作器worker的桥梁。spaceship::is_section_async section通过检查SPACESHIP_SECTION_ASYNC选项判断某 Section 是否为异步执行spaceship::is_section_async section参数说明section必填— 需要检查的 Section 名称。返回约定Section 为异步时返回零退出码否则返回非零。有两个特殊规则若SPACESHIP_PROMPT_ASYNC设为false则所有 Section 一律视为同步部分 Section无论配置如何都强制同步以保证命令行提示符正确工作它们是user、dir、host、exec_time、async、line_sep、jobs、exit_code与char。这两条规则与源码中的判定逻辑完全一致lib/utils.zshspaceship::is_section_async() { local section$1 local sync_sections(user dir host exec_time async line_sep jobs exit_code char) # 某些 Section 必须始终同步 if spaceship::includes sync_sections $section; then return 1 fi # 若用户对整个提示符关闭了异步渲染 if [[ $SPACESHIP_PROMPT_ASYNC ! true ]]; then return 1 fi local async_optionSPACESHIP_${(U)section}_ASYNC [[ ${(P)async_option} true ]] }三个关键细节值得注意强制同步清单以源码中的sync_sections数组为准与文档列出的 9 个 Section 完全一致通过${(U)section}把 Section 名转成大写再拼接出SPACESHIP_SECTION_ASYNC变量名最后用${(P)...}参数间接展开取出该变量的值——这是 Zsh 中“按名字取变量值”的标准写法判定顺序是“先查强制同步清单再查全局开关最后查 Section 专属开关”三者构成 AND 关系。test_is_section_asynctests/utils.test.zsh逐一验证了SPACESHIP_FOO_ASYNCtrue时foo异步成立全局SPACESHIP_PROMPT_ASYNCfalse时即使 Section 开关为真也判定为同步9 个系统 Section 即使显式设置SPACESHIP_NAME_ASYNCtrue也始终返回假。spaceship::is_prompt_async判断整个提示符是否处于异步工作模式spaceship::is_prompt_async返回约定异步模式返回零退出码否则非零。判定条件有两个需同时满足SPACESHIP_PROMPT_ASYNC设为true且zsh-async已成功加载源码中表现为全局变量ASYNC_INIT_DONE为真。实现lib/utils.zshspaceship::is_prompt_async() { [[ $SPACESHIP_PROMPT_ASYNC true ]] (( ASYNC_INIT_DONE )) }ASYNC_INIT_DONE由 zsh-async 库初始化时置位。对应的测试tests/utils.test.zsh覆盖了四种组合开关与初始化都就绪时为真开关关闭为假开关开启但异步库未初始化时同样为假。在核心渲染流程中的调用链这两个工具贯穿了提示符渲染的主流程lib/core.zsh 在加载 Section 时逐个调用spaceship::is_section_async只要存在任一异步 Section 就触发spaceship::worker::load加载异步工作器lib/core.zsh 在spaceship::core::refresh_section中依据spaceship::is_section_async $section决定把 Section 交给后台 worker 异步执行spaceship::worker::run还是同步执行lib/worker.zsh 中worker::init、worker::flush、worker::eval、worker::run四个函数都先用spaceship::is_prompt_async做前置判断确保只在异步模式下才操作 zsh-async 的 worker。可以推断正是这一层“两级异步判断”使得全局开关、Section 级开关与强制同步清单能够以统一、可测试的方式组合生效保证char、dir等关键 Section 永远即时渲染。五、弃用警告spaceship::deprecatedspaceship::deprecated option [message]该工具检查名为option的变量是否已设置若已设置则打印message警告。message支持 Zsh 的 prompt 转义序列escape sequences可设置前景色、背景色及其他视觉效果。参数说明option必填— 被弃用变量的名称。若该变量已设置含任意值将打印%B$deprecated%b is deprecated.其中%B与%b是设置/取消粗体字样的转义序列message可选— 附加的弃用提示文本可包含 prompt 扩展。转义序列的完整语法可查阅 Zsh 文档中 Prompt Expansion 一节。使用示例# 检查 SPACESHIP_BATTERY_ALWAYS_SHOW 是否已设置 spaceship::deprecated SPACESHIP_BATTERY_ALWAYS_SHOW Use %BSPACESHIP_BATTERY_SHOWalways%b instead. # SPACESHIP_BATTERY_ALWAYS_SHOW is deprecated. Use SPACESHIP_BATTERY_SHOWalways instead.实现lib/utils.zsh揭示了三层防御逻辑spaceship::deprecated() { [[ -n $1 ]] || return local deprecated$1 message$2 local deprecated_value${(P)deprecated} # the value of variable name $deprecated [[ -n $deprecated_value ]] || return print -P %B$deprecated%b is deprecated. $message }第一层option参数为空则直接返回避免空指针式的展开错误第二层用${(P)deprecated}间接展开取出变量值第三层变量值也为空即用户未设置该弃用变量则静默返回不打印任何警告。只有当用户确实设置了旧变量时才通过print -P输出其中-P选项启用 prompt 转义解析。测试test_deprecatedtests/utils.test.zsh验证了无附加信息与带附加信息两种输出的精确渲染结果。六、人类可读时间格式化spaceship::displaytimespaceship::displaytime seconds [precision]将秒数转换为易读的时间格式按天d、小时h、分钟m、秒s拆分输出。参数说明seconds必填— 待转换的秒数precision可选— 输出精度秒的小数位数默认值为1。使用示例spaceship::displaytime 123456 # 1d 10h 17m 36.0s spaceship::displaytime 123.45 2 # 2m 3.45s实现lib/utils.zsh先用整数运算拆分天/时/分再用浮点运算保留秒的小数部分spaceship::displaytime() { local duration$1 precision$2 [[ -z $precision ]] precision1 integer D$((duration/60/60/24)) integer H$((duration/60/60%24)) integer M$((duration/60%60)) local S$((duration%60)) [[ $D 0 ]] printf %dd $D [[ $H 0 ]] printf %dh $H [[ $M 0 ]] printf %dm $M printf %.${precision}f%s $S s }细节说明D、H、M声明为integer整数除法自动截断而S保留浮点因此能输出36.0s、3.45s这样带小数的秒天/时/分仅在对应值大于 0 时输出秒则总是输出并带上单位s。例如 123456 秒 1 天 10 小时 17 分 36 秒输出1d 10h 17m 36.0s。tests/utils.test.zsh 用 1234567 秒验证了输出14d 6h 56m 7.0s。该工具最常见的消费方是exec_timeSection用于把命令执行耗时渲染成易读文本。七、数组合并与去重spaceship::unionspaceship::union arr1[ arr2[ ...]]对两个及以上数组执行并集union操作列出所有数组中出现过的内容。参数为待合并的数组列表。官方示例arr1(a b c) arr2(b c d) arr3(c d e) spaceship::union $arr1 $arr2 $arr3 # a b c d e实现lib/utils.zsh借助 Zsh 的typeset -Uunique数组属性一行完成去重spaceship::union() { typeset -U sections($) echo $sections }typeset -U声明数组时自动消除重复元素且保持首次出现的顺序因此结果稳定为a b c d e。对应测试见 tests/utils.test.zsh。spaceship-prompt 内部用它对SPACESHIP_PROMPT_ORDER与SPACESHIP_RPROMPT_ORDER两个 Section 列表做并集以确定“需要加载哪些 Section 文件”lib/core.zsh 的spaceship::core::load_sections用spaceship::union $SPACESHIP_PROMPT_ORDER $SPACESHIP_RPROMPT_ORDER得到去重后的完整 Section 集合lib/core.zsh 的spaceship::core::start同样用它遍历刷新所有 Section。这种设计保证了即使某个 Section 同时出现在主提示符与右侧提示符中也只会被加载、执行一次。八、向上搜索spaceship::upsearchspaceship::upsearch [--silent] paths...从当前目录逐级向上搜索指定的文件或目录返回第一个命中项的完整路径向上最多搜到仓库根或文件系统根目录。该工具对理解当前目录的“项目上下文”非常有用。参数说明paths...必填— 待搜索的路径列表--silent或-s可选— 静默模式只要paths中至少有一个被找到即返回零退出码否则返回非零不打印任何路径。使用示例# 识别项目上下文 spaceship::upsearch -s package.json node_modules echo Node project detected. # 向上查找特定文件 spaceship::upsearch package.json # /path/to/project/package.json实现lib/utils.zsh的算法值得细读spaceship::upsearch() { # 解析 CLI 选项 zparseopts -E -D \ ssilent -silentsilent local files($) local root$(pwd -P) # 逐级向上直到根目录 while [ $root ]; do # 对每个作为参数的文件 for file in ${files[]}; do local find_match$(find $root -maxdepth 1 -name $file -print -quit 2/dev/null) local filename$root/$file if [[ -n $find_match ]]; then [[ -z $silent ]] echo $find_match return 0 elif [[ -e $filename ]]; then [[ -z $silent ]] echo $filename return 0 fi done if [[ -d $root/.git || -d $root/.hg ]]; then # 到达仓库根仍未找到返回非零 return 1 fi # 向上一级 root${root%/*} done # 到达文件系统根仍未找到返回非零 return 1 }要点边界停止条件循环中一旦发现当前目录含有.git或.hg就立即以失败返回非零即搜索不会越过仓库根继续向外扩散——这是is_hg能依赖它的前提搜索顺序在同一级目录内按参数给定的顺序逐一尝试find -maxdepth 1负责处理文件名中的通配符静默语义--silent/-s存在时抑制路径输出仅保留退出码便于/||短路判断。spaceship::upsearch是众多语言/工具 Section 识别项目类型的核心手段仓库内有大量真实用例例如sections/bun.zshspaceship::upsearch -s bun.lockb bun.lock bunfig.toml || returnsections/dart.zsh查找pubspec.yaml pubspec.yml pubspec.lock dart_toolsections/docker.zsh同时探测Dockerfile与一组 compose 文件名sections/ansible.zsh向上查找ansible.cfg/.ansible.cfg。这些 Section 的通用模式是用-s静默探测命中即继续渲染未命中则立即return与文档示例高度一致。九、数据文件查询spaceship::extract别名spaceship::datafile!!! note 本工具的别名是spaceship::datafile向后兼容。spaceship::extract用于从数据文件中查询指定键的值返回该键对应的内容当文件类型未知、数据无法读取或键不存在时以非零退出码结束。spaceship::extract --type file [...keys]参数说明--type必填— 数据文件类型可取json、yaml、toml或xmlfile必填— 数据文件路径key可选— 要在数据文件内查询的键支持点号dot notation路径例如author.name可传多个键作为备选。使用示例spaceship::extract --json package.json author.name # John Doe读取不同格式的数据文件需要相应的命令行工具JSON—jq、yq、python-yq、python、node中的任意一个YAML—yq或python-yq、pythonTOML—tomlq随python-yq附带提供XML—xq随python-yq附带提供。官方文档给出的建议是读取数据文件最通用的解决方案是使用python-yq它同时覆盖 YAML/TOML/XML并附带tomlq与xq。工具缺失时spaceship::extract会返回非零退出码。源码中的后端分派链lib/extract.zsh 中spaceship::extract的实现展示了完整的后端降级策略fallback chain每种格式都按“优先级从高到低”尝试可用工具YAMLyq→ruby→python3JSONjq→yq→ruby→python3→nodeTOMLtomlq→python3Python 3.11 使用标准库tomllib更早版本回退到第三方包tomli见 lib/extract.zshXML仅xq。每一条分派链都以spaceship::exists探测工具是否可用全部缺失则返回 1。这一点再次印证了spaceship::exists作为基础设施工具的价值。别名spaceship::datafile只是简单转发lib/extract.zsh。安全性设计文件路径绝不内插进脚本tests/extract.test.zsh 中一组针对性测试揭示了一个重要的安全设计数据文件路径始终以命令行参数argv方式传给后端解释器而不是拼接进生成的脚本代码中。测试特意构造了包含\touch PWNED 这种恶意片段的文件名并断言Python 后端使用-c执行脚本文件经sys.argv[1]读取脚本源码中不出现文件名字面量test_python_json_passes_file_as_argvRuby 后端在--之后传递文件脚本内通过ARGV[0]引用同样不内插文件名Node 后端通过process.argv[1]配合require(path).resolve解析路径--分隔符保留。这从测试层面确认spaceship::extract能安全处理带特殊字符的路径避免 shell 注入风险属于值得自定义 Section 作者借鉴的实现范式。十、测试与验证工具函数的可观测保证所有工具函数都有对应的 shunit2 测试见 tests/utils.test.zsh 与 tests/extract.test.zsh覆盖了正常路径与边界场景工具函数测试用例验证要点spaceship::existstest_exists内建命令cd为真、随机串为假、函数为真spaceship::definedtest_defined函数为真、内建命令/随机串为假spaceship::is_gittest_is_git仓库根、子目录均为真仓库外为假spaceship::is_hgtest_is_hg仓库根、子目录为真带空格路径不误判spaceship::is_section_asynctest_is_section_asyncSection 开关、全局开关、强制同步清单的组合spaceship::is_prompt_asynctest_is_prompt_async全局开关 × 异步库初始化状态spaceship::deprecatedtest_deprecated警告文本的精确渲染含/不含附加信息spaceship::displaytimetest_displaytime大秒数拆分14d 6h 56m 7.0sspaceship::uniontest_union多数组去重并集a b c d espaceship::extracttest_*_passes_file_as_argv等各后端参数传递与防注入安全十一、小结如何在自己的 Section 中组合使用回顾 docs/api/utils.md 的完整脉络这些工具函数共同构成了一套面向 Section 开发的“标准库”。推荐的实战组合套路如下探测依赖用spaceship::exists tool判断运行时是否就绪未就绪直接return识别项目用spaceship::upsearch -s marker...静默向上查找项目特征文件如package.json、Dockerfile命中才渲染 Section读取元数据用spaceship::extract --json package.json version等从数据文件取出版本号等展示信息仓库判断需要区分 Git/Mercurial 时用spaceship::is_git/spaceship::is_hg尊重异步配置在自定义 Section 中如需感知异步调度通过spaceship::is_section_async与spaceship::is_prompt_async判断注意不要把这些工具与 Section 渲染函数spaceship::section::render混淆后者见 docs/api/section.md处理弃用与时间旧配置项迁移用spaceship::deprecated耗时展示用spaceship::displaytime。若需编写可测试的自定义逻辑可参考 docs/api/testkit.md 了解官方测试工具包并仿照 tests/utils.test.zsh 的结构为自己的函数补充断言。理解了这套“探测—识别—查询—渲染”的 API 体系你就能写出与内建 Section 同样健壮、同样风格统一的自定义提示符组件。【免费下载链接】spaceship-prompt✨ Minimalistic, powerful and extremely customizable Zsh prompt项目地址: https://gitcode.com/gh_mirrors/sp/spaceship-prompt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考