# 配置 `config/*` 是一个全局、持久化、响应式的键/值存储——相当于 Deft 版的 dconf 或 Windows 注册表。它与 `pub/*` 位于 同一层(在 Runtime 上,而非 TUI Workspace),因此在独立的 `deft` 二进制和 TUI 宿主中都可用。 键是使用 `/` 作为分隔符的层级字符串,以所属应用 为前缀以实现命名空间隔离:`log/max-lines`、 `editor/font`、`config/auto-save`。没有所属应用的 跨领域键位于顶层:`ui/theme`、`window/width`。 ## `config/*` 标准库函数 | 函数 | 返回值 | 用途 | |---|---|---| | `[config/get "key"]` | 值或 nil | 读取 | | `[config/set "key" value]` | 存储的值 | 写入 + 发布变更 | | `[config/unset "key"]` | bool | 移除;以 `:value nil` 发布 | | `[config/scan "prefix"]` | map | 所有以 prefix 开头的键 | | `[config/keys "prefix"]` | list | 匹配的键名 | | `[config/count]` | number | 键总数 | | `[config/load ?path]` | bool | 加载 `.deft/config.dft` | | `[config/save ?path]` | bool | 写入整个注册表 | | `[config/save-overrides ?path]` | 写入数量 | 与默认值比较;仅写入已更改项 | | `[config/save-map $map ?path]` | bool | 写入给定的 map | ## 前缀——手动指定,而非自动 应用声明一次前缀后重复使用。没有运行时魔法—— “我当前在哪个应用中?”这个问题在 CLI 脚本、托管多个应用的 TUI 面板以及子运行时之间确实存在歧义,因此 注册表从不尝试回答它。
def cfg "log/"

# Explicit prefix at each call site — no hidden resolution
config/set ($cfg + "max-lines") 1000
echo [config/get ($cfg + "max-lines")]

# Or a thin wrapper when call-site noise matters
def set-cfg {|k v| config/set ($cfg + $k) $v]}
def get-cfg {|k| config/get ($cfg + $k)]}

set-cfg "max-lines" 1000
跨应用读取保持显式(从 `log` 内部调用 `[config/get "editor/font"]`),而跨领域键(`ui/theme`)位于 顶层,无需任何扭曲。 ## 响应式 `config/set` 和 `config/unset` 在 `"config"` 发布/订阅主题上 发布变更事件。订阅者在标准事件 map 的 `:data` 下 接收它:
%{ :type :message :topic "config"
   :data %{ :key "log/max-lines" :value 1000 :old 500 } }
在处理函数中按键前缀订阅并过滤:
def on-config {|ev|
    @doc "React to log config changes."
    if [starts-with? $ev~data~key "log/"] {
        echo "log config changed:" $ev~data~key "->" $ev~data~value
        [reload-logger]
    |
    }
}
pub/subscribe "config" "on-config"
参见 Pubsub 了解代理契约。 ## 值——仅限可字符串化的数据 注册表值必须能通过 `deft/stringify` / `deft/parse` 往返转换: - 标量(数字、字符串、布尔值、nil) - 关键字 - 列表、映射、集合 - 带标签的 map 函数值以名称(字符串)形式存储,并在 调用点通过 `[apply $name ...]` 解析。`apply` 接受 字符串/关键字名称(通过命名空间链解析)或 直接的可调用值(闭包、native-func、关键字、map、 带标签的 map、集合、向量)。 ## 持久化 `config/save` 将整个注册表写入一个扁平 map 文件(默认 `.deft/config.dft`),内容为 `%{ "key1" v1 "key2" v2 }`。`config/load` 读取此类文件并替换注册表内容。该文件是纯 Deft 数据——便于 diff、可手工编辑,且对版本控制 友好。
# Per-package defaults ship as small data files (keyword keys are idiomatic):
%{
    :log/max-lines 1000
    :log/wrap true
    :log/severity :all
}
`config/save-overrides` 与已加载的默认值进行 diff,仅写入 已更改的键——这是配置应用的规范保存路径。 加载同时接受字符串键和关键字键;保存输出字符串 键。 ## 启动时加载默认值 典型包的 `on-init` 会在注册表为空时 加载默认值:
def on-init {||
    @doc "Load log defaults on first run."
    if (empty? [config/scan "log/"]) {
        config/load "packages/log/defaults.dft"
    |
    }
    pub/subscribe "config" "on-config"
}
## 配置应用 TUI 自带一个配置应用(见 `packages/config/src/config.dft`), 它会: - 读取 `[config/scan $prefix]` 以列出键。 - 让用户切换布尔值、编辑字符串/数字。 - 通过 `config/set` 写回。 - 通过 `config/save-overrides` 持久化变更——仅保存用户 显式更改的键,而非整个注册表。 ## 各项内容的归属 - `tui/get` / `tui/set`——每个会话短暂的 UI 状态(光标 位置、选择、滚动)。仅限 TUI,从不持久化。不同的 关注点;不要混淆。 - `config/*`——持久化的用户偏好。所有宿主均适用。通过 `config/load` / `config/save` 落盘。 ## 示例 ### 一个简单偏好
config/set "editor/font-size" 14
echo [config/get "editor/font-size"]]           # => 14

config/unset "editor/font-size"
echo [config/get "editor/font-size"]]           # => nil
### 扫描命名空间
def log-config [config/scan "log/"]
# => %{ :log/max-lines 1000 :log/wrap true :log/severity :all }

for k [keys $log-config] {
    echo "$k = $log-config~$k"
}
### 响应式主题切换
def apply-theme {|ev|
    if ($ev~data~key == "ui/theme") {
        echo "switching theme to $ev~data~value"
        [load-theme $ev~data~value]
    |
    }
}
pub/subscribe "config" "apply-theme"

# Anywhere:
config/set "ui/theme" "catppuccin-mocha"
## 快速参考 | 形式 | 用途 | |---|---| | `[config/get "key"]` | 读取 | | `[config/set "key" value]` | 写入 + 发布 | | `[config/unset "key"]` | 移除 + 发布 | | `[config/scan "prefix"]` | 所有匹配的键 | | `[config/keys "prefix"]` | 匹配的键名 | | `[config/count]` | 键总数 | | `[config/load ?path]` | 从磁盘加载 | | `[config/save ?path]` | 全部写入磁盘 | | `[config/save-overrides ?path]` | 仅写入与默认值的差异 | | `[config/save-map $map ?path]` | 写入给定的 map |