Skip to content

Commit 3b75de5

Browse files
author
developerworks
committed
同步 README 双语说明
- 追加中文 README 内容 - 说明依赖方如何 flatten ConfigCommand - 同步中英文 CLI 手册合并步骤
1 parent aa2d78e commit 3b75de5

3 files changed

Lines changed: 323 additions & 0 deletions

File tree

README.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# rust-config-tree
22

3+
[English](#rust-config-tree) | [中文](#中文)
4+
35
`rust-config-tree` provides configuration-tree loading and CLI helpers for Rust
46
applications that use layered config files.
57

@@ -252,6 +254,16 @@ Flatten `ConfigCommand` into your existing clap command enum to add:
252254
- `completions`
253255
- `install-completions`
254256

257+
The consuming application keeps its own `Parser` type and its own command enum.
258+
`rust-config-tree` only contributes reusable subcommands:
259+
260+
1. Add `#[command(subcommand)] command: Command` to the application's parser.
261+
2. Add `#[command(flatten)] Config(ConfigCommand)` to the application's
262+
`Subcommand` enum.
263+
3. Clap expands the flattened variants into the same subcommand level as the
264+
application's own commands.
265+
4. Match that variant and call `handle_config_command::<Cli, AppConfig>`.
266+
255267
```rust
256268
use std::path::PathBuf;
257269
@@ -359,3 +371,300 @@ Licensed under either of:
359371
- MIT license
360372

361373
at your option.
374+
375+
## 中文
376+
377+
`rust-config-tree` 为使用分层配置文件的 Rust 应用提供配置树加载能力和 CLI
378+
辅助能力。
379+
380+
项目手册:<https://developerworks.github.io/rust-config-tree/>。英文手册和
381+
中文手册作为独立的 mdBook 站点发布,并提供语言切换链接。
382+
383+
它提供:
384+
385+
- 通过 Figment runtime provider 将 `confique` schema 加载成可直接使用的
386+
config 对象
387+
- `config-template`、`completions` 和 `install-completions` 命令处理
388+
- YAML、TOML、JSON 和 JSON5 配置模板生成
389+
- 递归 include 遍历
390+
- 合并环境变量前加载 `.env`
391+
- 通过 Figment metadata 追踪配置来源
392+
- 通过 `tracing` 输出 TRACE 级别来源追踪日志
393+
- 相对 include 路径从声明它的文件解析
394+
- 词法路径归一化
395+
- include 循环检测
396+
- 确定性遍历顺序
397+
- 镜像模板目标收集
398+
- 按嵌套 schema section 自动拆分 YAML 模板
399+
400+
应用通过派生 `confique::Config` 并实现 `ConfigSchema` 来提供自己的 schema。
401+
`ConfigSchema` 用于暴露 schema 中的 include 字段。
402+
403+
### 安装
404+
405+
```toml
406+
[dependencies]
407+
rust-config-tree = "0.1"
408+
confique = { version = "0.4", features = ["yaml", "toml", "json5"] }
409+
figment = { version = "0.10", features = ["yaml", "env"] }
410+
serde = { version = "1", features = ["derive"] }
411+
clap = { version = "4", features = ["derive"] }
412+
```
413+
414+
### 配置结构
415+
416+
应用自己的 schema 持有 include 字段。`rust-config-tree` 只需要一个很小的
417+
adapter,用来从中间 `confique` layer 提取 include。
418+
419+
```rust
420+
use std::path::PathBuf;
421+
422+
use confique::Config;
423+
use rust_config_tree::ConfigSchema;
424+
425+
#[derive(Debug, Config)]
426+
struct AppConfig {
427+
#[config(default = [])]
428+
include: Vec<PathBuf>,
429+
430+
#[config(default = "paper")]
431+
mode: String,
432+
433+
#[config(nested)]
434+
server: ServerConfig,
435+
}
436+
437+
#[derive(Debug, Config)]
438+
struct ServerConfig {
439+
#[config(default = 8080)]
440+
port: u16,
441+
}
442+
443+
impl ConfigSchema for AppConfig {
444+
fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
445+
layer.include.clone().unwrap_or_default()
446+
}
447+
}
448+
```
449+
450+
相对 include 路径从声明它的文件解析:
451+
452+
```yaml
453+
# config.yaml
454+
include:
455+
- config/server.yaml
456+
457+
mode: shadow
458+
```
459+
460+
```yaml
461+
# config/server.yaml
462+
server:
463+
port: 7777
464+
```
465+
466+
使用 `load_config` 加载最终 schema:
467+
468+
```rust
469+
use rust_config_tree::load_config;
470+
471+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
472+
let config = load_config::<AppConfig>("config.yaml")?;
473+
println!("{config:#?}");
474+
475+
Ok(())
476+
}
477+
```
478+
479+
`load_config` 会从 root config 所在目录开始向上查找第一个 `.env` 文件并
480+
加载,然后让 Figment 读取 schema 中声明的环境变量。进程里已经存在的环境
481+
变量会保留,并优先于 `.env` 中的值。
482+
483+
运行时配置加载由 Figment 完成。`confique` 仍负责 schema metadata、默认值、
484+
校验和模板生成。环境变量名从 `#[config(env = "...")]` 读取;loader 不使用
485+
`Env::split("_")` 或 `Env::split("__")`,因此 `APP_DATABASE_POOL_SIZE` 可以
486+
映射到 `database.pool_size`,不会把单个 `_` 当成层级分隔符。
487+
488+
`load_config` 不会读取命令行参数,因为 CLI flag 是应用自己的语义。需要 CLI
489+
覆盖配置时,在 `build_config_figment` 之后合并 provider,再通过
490+
`load_config_from_figment` 校验:
491+
492+
```rust
493+
use figment::providers::Serialized;
494+
use serde::Serialize;
495+
use rust_config_tree::{build_config_figment, load_config_from_figment};
496+
497+
#[derive(Debug, Serialize)]
498+
struct CliOverrides {
499+
#[serde(skip_serializing_if = "Option::is_none")]
500+
mode: Option<String>,
501+
}
502+
503+
fn load_with_cli_overrides() -> Result<AppConfig, Box<dyn std::error::Error + Send + Sync>> {
504+
let cli_overrides = CliOverrides {
505+
mode: Some("shadow".to_owned()),
506+
};
507+
508+
let figment = build_config_figment::<AppConfig>("config.yaml")?
509+
.merge(Serialized::defaults(cli_overrides));
510+
511+
let config = load_config_from_figment::<AppConfig>(&figment)?;
512+
Ok(config)
513+
}
514+
```
515+
516+
这样合并 CLI override 后,运行时优先级为:
517+
518+
```text
519+
command-line overrides
520+
> environment variables
521+
> config files
522+
> confique code defaults
523+
```
524+
525+
### 模板生成
526+
527+
模板使用同一份 schema 和 include 遍历规则生成。输出格式由输出路径推断:
528+
529+
- `.yaml` 和 `.yml` 生成 YAML
530+
- `.toml` 生成 TOML
531+
- `.json` 和 `.json5` 生成 JSON5-compatible 模板
532+
- 未知或缺失扩展名生成 YAML
533+
534+
使用 `write_config_templates` 创建 root 模板和 include tree 中的子模板:
535+
536+
```rust
537+
use rust_config_tree::write_config_templates;
538+
539+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
540+
write_config_templates::<AppConfig>("config.yaml", "config.example.yaml")?;
541+
542+
Ok(())
543+
}
544+
```
545+
546+
模板生成按这个顺序选择 source tree:
547+
548+
- 已存在的 config path
549+
- 已存在的 output template path
550+
- 将 output path 作为新的空 template tree
551+
552+
### CLI 集成
553+
554+
依赖 `rust-config-tree` 的项目可以保留自己的 clap parser 和命令枚举。
555+
`rust-config-tree` 只提供可复用的 `ConfigCommand` 子命令:
556+
557+
- `config-template`
558+
- `completions`
559+
- `install-completions`
560+
561+
合并方式如下:
562+
563+
1. 在应用自己的 `Parser` 类型里保留 `#[command(subcommand)] command: Command`。
564+
2. 在应用自己的 `Subcommand` enum 中添加
565+
`#[command(flatten)] Config(ConfigCommand)`
566+
3. Clap 会把 flattened variants 展开到应用自己的同一层子命令里。
567+
4. 在 `match` 中处理这个 variant,并调用
568+
`handle_config_command::<Cli, AppConfig>`
569+
570+
```rust
571+
use std::path::PathBuf;
572+
573+
use clap::{Parser, Subcommand};
574+
use confique::Config;
575+
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};
576+
577+
#[derive(Debug, Config)]
578+
struct AppConfig {
579+
#[config(default = [])]
580+
include: Vec<PathBuf>,
581+
#[config(default = "paper")]
582+
mode: String,
583+
}
584+
585+
impl ConfigSchema for AppConfig {
586+
fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
587+
layer.include.clone().unwrap_or_default()
588+
}
589+
}
590+
591+
#[derive(Debug, Parser)]
592+
#[command(name = "demo")]
593+
struct Cli {
594+
#[arg(long, default_value = "config.yaml")]
595+
config: PathBuf,
596+
#[command(subcommand)]
597+
command: Command,
598+
}
599+
600+
#[derive(Debug, Subcommand)]
601+
enum Command {
602+
Run,
603+
#[command(flatten)]
604+
Config(ConfigCommand),
605+
}
606+
607+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
608+
let cli = Cli::parse();
609+
match cli.command {
610+
Command::Run => {
611+
let config = load_config::<AppConfig>(&cli.config)?;
612+
println!("{config:#?}");
613+
}
614+
Command::Config(command) => {
615+
handle_config_command::<Cli, AppConfig>(command, &cli.config)?;
616+
}
617+
}
618+
619+
Ok(())
620+
}
621+
```
622+
623+
`config-template --output <path>` 将模板写入指定路径。未提供 output path 时,
624+
写入当前目录下的 `config.example.yaml`。
625+
626+
`completions <shell>` 将 completions 输出到 stdout。
627+
628+
`install-completions <shell>` 将 completions 写入用户 home 目录,并在 shell
629+
需要时更新启动文件。支持 Bash、Elvish、Fish、PowerShell 和 Zsh。
630+
631+
### 低层 Tree API
632+
633+
不使用 `confique`,或者需要直接访问遍历结果时,可以使用 `load_config_tree`:
634+
635+
```rust
636+
use std::{fs, io, path::{Path, PathBuf}};
637+
638+
use rust_config_tree::{ConfigSource, load_config_tree};
639+
640+
fn load_source(path: &Path) -> io::Result<ConfigSource<String>> {
641+
let content = fs::read_to_string(path)?;
642+
let includes = content
643+
.lines()
644+
.filter_map(|line| line.strip_prefix("include: "))
645+
.map(PathBuf::from)
646+
.collect();
647+
648+
Ok(ConfigSource::new(content, includes))
649+
}
650+
651+
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
652+
let tree = load_config_tree("config.yaml", load_source)?;
653+
654+
for node in tree.nodes() {
655+
println!("{}", node.path().display());
656+
}
657+
658+
Ok(())
659+
}
660+
```
661+
662+
Tree API 会进行词法路径归一化、拒绝空 include path、检测递归 include 循环,
663+
并跳过已经从其他 include 分支加载过的文件。
664+
665+
### 许可证
666+
667+
按你的选择使用以下任一许可证:
668+
669+
- Apache License, Version 2.0
670+
- MIT license

manual/en/cli.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ loading path.
1414

1515
Flatten it into an application command enum:
1616

17+
1. Keep the application's own `Parser` type.
18+
2. Keep the application's own `Subcommand` enum.
19+
3. Add `#[command(flatten)] Config(ConfigCommand)` to that enum.
20+
4. Clap expands the flattened `ConfigCommand` variants into the same command
21+
level as the application's own variants.
22+
5. Match the `Config(command)` variant and pass it to `handle_config_command`.
23+
1724
```rust
1825
use std::path::PathBuf;
1926

manual/zh/cli.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313

1414
将它 flatten 到应用命令枚举中:
1515

16+
1. 保留应用自己的 `Parser` 类型。
17+
2. 保留应用自己的 `Subcommand` enum。
18+
3. 在这个 enum 里添加 `#[command(flatten)] Config(ConfigCommand)`
19+
4. Clap 会把 flattened `ConfigCommand` variants 展开到应用自己的同一层命令。
20+
5.`match` 里处理 `Config(command)` variant,并交给
21+
`handle_config_command`
22+
1623
```rust
1724
use std::path::PathBuf;
1825

0 commit comments

Comments
 (0)