OpenCode 实践课:4 分钟用 Rust 写出一个命令行文本统计分析工具

项目介绍

在日常开发中,经常需要快速了解一个文本文件的基本信息——比如 README 写了多少字、日志文件有多少行、某个模块的代码量等。今天我们用 Rust 写一个命令行文本统计分析工具,输入一个文件路径,输出字符数、词数、行数、句子数、段落数、字节数、预估阅读时间,以及高频词排名。

技术栈:Rust + 标准库(零外部依赖)

最终效果

═══════════════════════════════
  📊 文本统计报告
═══════════════════════════════
  文件:     README.md
  ─────────────────────────
  字符数:   3421
  字节数:   3580
  行数:     87
  词数:     512
  句子数:   45
  段落数:   18
  ─────────────────────────
  预估阅读: 3 分钟 (250词/分钟)
═══════════════════════════════
  高频词 Top 10:
   1. opencode            23 次
   2. 项目                 15 次
   3. 使用                 12 次
  ...
═══════════════════════════════

下面我们来看看如何用 OpenCode 一步步完成这个工具,全程只写提示词,不手写一行代码。

准备工作

  • 安装 Rust 开发环境
  • 注册并安装 OpenCode(opencode.ai
  • 在终端中打开项目目录

实践过程

第 1 步:创建项目骨架

我对 OpenCode 说:

> 帮我用 cargo 新建一个 Rust 项目,名字叫 textstat。然后实现一个命令行工具:接收一个文件路径作为参数,读取文件内容,统计总行数、字符数、词数、字节数并打印出来。只用标准库,不要加任何外部依赖。

OpenCode 的响应:

OpenCode 先后执行了 cargo new textstat 创建项目,然后直接生成了 src/main.rs。它用 std::fs::read_to_string 读取文件,用 .lines().count() 统计行数,.len() 统计字节数,.chars().count() 统计字符数,.split_whitespace().count() 统计词数,最后用 println! 格式化输出。整个过程只用了一条提示词,项目骨架就搭好了。

关键要点:OpenCode 理解"零外部依赖"这个约束,全程使用标准库,Cargo.toml[dependencies] 保持为空。

第 2 步:添加词频统计

我对 OpenCode 说:

> 增加词频统计功能。把每个词转小写,过滤掉长度小于 2 的纯标点或单字符,用 HashMap 计数,按频率降序排列,输出前 10 个高频词。

OpenCode 的响应:

OpenCode 在代码中引入 HashMap,对 split_whitespace() 得到的每个词做清洗——用 .chars().filter(|c| c.is_alphanumeric()) 过滤标点,转小写后只保留长度 ≥ 2 的词。然后将 HashMap 转为 Vec 并按值降序排序,取前 10 个输出。OpenCode 还主动处理了边界情况:如果词汇不满 10 个,就只输出实际数量。

第 3 步:扩展统计维度

我对 OpenCode 说:

> 再加入三个统计:1)段落数,按连续两个换行符(空行)分隔;2)句子数,按中英文句号、问号、感叹号分隔;3)预估阅读时间,按每分钟 250 词计算(向上取整)。

OpenCode 的响应:

OpenCode 准确地实现了段落统计(.split("\n\n").filter(|p| !p.trim().is_empty()))和句子统计(使用 .split() 的闭包参数,匹配 .!?)。阅读时间用 (word_count as f64 / 250.0).ceil() as u64 计算。三个新指标无缝集成到输出中,整个过程没有破坏已有的统计逻辑。

第 4 步:美化输出 + 可配置参数

我对 OpenCode 说:

> 添加一个 --top N 命令行参数,让用户可以自定义显示多少个高频词,默认 10。另外用 ════─── 分隔线美化输出格式,让报告看起来更专业。

OpenCode 的响应:

OpenCode 在 args 解析中加入了 --top 的处理逻辑,用迭代器的 position() 找到 --top 的位置,读取下一个参数作为 N 值。输出格式换成了带分隔线的布局,层次分明。整个修改只涉及参数解析和 print 语句,对核心逻辑零侵入。

第 5 步:编译测试

我对 OpenCode 说:

> 帮我编译项目,然后用项目自身的 README.md(如果存在的话)做一次测试运行,看看输出效果。

OpenCode 的响应:

OpenCode 执行 cargo build --release 完成编译(约 3 秒),然后用 target/release/textstat README.md 运行测试,打印出完整的统计报告。所有指标计算正确,输出格式对齐美观。

完整代码

最终 src/main.rs68 行,零外部依赖:

use std::collections::HashMap;
use std::env;
use std::fs;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        eprintln!("用法: {} <文件路径> [--top N]", args[0]);
        return;
    }

    let path = &args[1];
    let top_n: usize = args.iter()
        .position(|a| a == "--top")
        .and_then(|i| args.get(i + 1))
        .and_then(|n| n.parse().ok())
        .unwrap_or(10);

    let content = fs::read_to_string(path)
        .expect("无法读取文件,请确认路径正确");

    let lines = content.lines().count();
    let chars = content.chars().count();
    let bytes = content.len();
    let paragraphs = content.split("\n\n")
        .filter(|p| !p.trim().is_empty())
        .count();
    let sentences = content.split(|c: char| {
        c == '.' || c == '!' || c == '?' || c == '。' || c == '!' || c == '?'
    }).filter(|s| !s.trim().is_empty()).count();

    let words: Vec<&str> = content.split_whitespace().collect();
    let word_count = words.len();

    let mut freq: HashMap<String, usize> = HashMap::new();
    for w in &words {
        let cleaned: String = w.chars()
            .filter(|c| c.is_alphanumeric())
            .collect::<String>()
            .to_lowercase();
        if cleaned.len() >= 2 {
            *freq.entry(cleaned).or_insert(0) += 1;
        }
    }

    let mut freq_vec: Vec<(String, usize)> = freq.into_iter().collect();
    freq_vec.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));

    let reading_minutes = (word_count as f64 / 250.0).ceil() as u64;

    println!("═══════════════════════════════");
    println!("  📊 文本统计报告");
    println!("═══════════════════════════════");
    println!("  文件:     {}", path);
    println!("  ─────────────────────────");
    println!("  字符数:   {}", chars);
    println!("  字节数:   {}", bytes);
    println!("  行数:     {}", lines);
    println!("  词数:     {}", word_count);
    println!("  句子数:   {}", sentences);
    println!("  段落数:   {}", paragraphs);
    println!("  ─────────────────────────");
    println!("  预估阅读: {} 分钟 (250词/分钟)", reading_minutes);
    println!("═══════════════════════════════");
    println!("  高频词 Top {}:", top_n.min(freq_vec.len()));
    for (i, (word, count)) in freq_vec.iter().take(top_n).enumerate() {
        println!("  {:2}. {:<18} {} 次", i + 1, word, count);
    }
    println!("═══════════════════════════════");
}

小结

这个工具从零到可用,全程只用了 5 条提示词、不到 4 分钟。OpenCode 表现出了三个突出优势:

精准理解约束:说"零外部依赖",它就用标准库;说"默认 10 个",它就设 unwrap_or(10)

增量开发无缝:每次追加新功能,OpenCode 都做到对已有逻辑零破坏,修改精准到函数和变量级别

主动处理边界:词数不满 N 个、空段落、空句子等边界情况,OpenCode 都自动加了过滤条件

对比传统开发方式——查 API 文档、写代码、处理边界、调整格式——至少需要 15-20 分钟。AI 编程助手省去的不是"打字时间",而是"决策和查阅时间"。你只需要想清楚"要什么",剩下的交给 OpenCode。