当前位置: 首页 > news >正文

[bat-cli] 输出处理 | `OutputType` 和 `OutputHandle`

第三章:输出处理

在上一章输入管理中,我们学习了 bat 如何巧妙地收集所有内容——无论是来自文件、标准输入还是字节字符串——并为其处理做好准备。

现在 bat 已经获取了我们的代码,控制器正准备将其美化,接下来的关键问题是:这些格式化的文本最终会去哪里

这就是输出处理的职责所在~

输出处理解决了什么问题?

想象一下,我们刚刚烹饪完一道美味佳肴(即高亮显示的代码)。我们不会把它留在厨房台面上,对吧?我们需要将其端给客人(也就是用户!)。但如何端上呢?

  • 是直接放在盘子里立即享用(直接显示到终端)?
  • 或者,如果是一道大餐,是否应该使用特殊的餐具,让客人可以轻松浏览并选择他们想要的部分(通过分页程序如 less 发送)?

输出处理就是 bat 的“上菜服务”。它的任务是决定如何将 bat 格式化的输出最佳地呈现给我们。它会判断是直接打印到屏幕还是使用外部工具来管理长输出,同时确保颜色和格式保持完美。

本章的目标是理解 bat 如何做出这一决策并管理输出的传递。

OutputTypeOutputHandle:输出选项

bat 提供了几种输出方式,每种适用于不同场景:

  1. 直接输出到终端(标准输出):对于较短的文件,bat 直接将所有内容打印到终端屏幕。这种方式快速且简单。
  2. 通过分页程序:对于较长的文件,或者如果我们有偏好,bat 可以将输出发送到一个称为“分页程序”的特殊程序。分页程序如 less(在 Linux/macOS 上非常常见)允许我们滚动浏览输出、搜索和轻松导航,而不会淹没终端历史记录。
  3. 写入自定义写入器/缓冲区:如果我们在自己的 Rust 程序中将 bat 作为库使用,可能希望将输出捕获到字符串或其他自定义数据结构中,而不是直接显示。

这里的核心概念是 OutputTypeOutputHandle

  • OutputType:这是一个内部组件,用于决定输出应该去哪里(例如直接到 stdout 还是到 Pager)。如果需要,它会管理分页程序的启动。
  • OutputHandle:这是一个通用接口,bat 的打印机使用它来实际写入格式化文本,无论最终传递到哪里。它可以是写入终端、分页程序的输入或字符串缓冲区。

让我们看看如何控制这一点。

PrettyPrinter 控制输出

我们可以通过 PrettyPrinter 的配置选项来影响输出处理。

use bat::{PrettyPrinter, PagingMode, WrappingMode};
use std::path::Path;fn main() {PrettyPrinter::new()// 1. 直接输出到终端(不使用分页程序).paging_mode(PagingMode::Never).input_file(Path::new("small_file.rs")).print().unwrap();PrettyPrinter::new()// 2. 始终使用分页程序,并专门配置 'less'.paging_mode(PagingMode::Always).pager("less -R -F -X") // 自定义分页程序命令.wrapping_mode(WrappingMode::NoWrapping(true)) // 告诉 less 不要换行.input_file(Path::new("long_file.rs")).print().unwrap();// 默认行为通常会智能地为大文件使用分页程序PrettyPrinter::new().input_file(Path::new("large_file.py")).print() // 可能会自动使用分页程序!.unwrap();
}

说明:

  1. paging_mode(PagingMode::Never):明确告诉 bat 不要使用分页程序,无论文件多长。输出将直接发送到终端。
  2. paging_mode(PagingMode::Always):强制 bat 使用分页程序,即使是短文件。
  3. pager("less -R -F -X"):设置 bat 用于启动分页程序的具体命令。-R 对颜色很重要,-F 如果文件适合一屏则退出,-X 禁用 less 的 termcap 初始化,有时可以解决问题。
  4. wrapping_mode(WrappingMode::NoWrapping(true)):配置 bat 本身,但也会影响 bat 如何设置分页程序(less)来处理长行。

运行这些命令时,bat 会:

  • 对于 small_file.rs:将其内容直接打印到终端。
  • 对于 long_file.rs:使用指定选项启动 less,并将高亮内容通过管道传递给它。然后我们可以使用 less 滚动浏览。
  • 对于 large_file.pybat 的默认 paging_modeQuitIfOneScreen,意味着只有在内容超出终端屏幕时才会启动分页程序。
输出到字符串缓冲区

如果我们将 bat 集成到另一个程序中,可能希望将其输出捕获到 String 而不是直接打印。OutputHandle::FmtWrite 选项允许这样做。

use bat::{assets::HighlightingAssets,config::Config,controller::Controller,output::OutputHandle, // 我们需要这个特定的 OutputHandleInput,
};fn main() {let mut buffer = String::new(); // 我们的目标字符串缓冲区let config = Config {colored_output: false, // 为了简化字符串输出..Default::default()};let assets = HighlightingAssets::from_binary();let controller = Controller::new(&config, &assets);let input = Input::from_bytes(b"fn test() { /* ... */ }").name("test.rs");controller.run(vec![input.into()],Some(OutputHandle::FmtWrite(&mut buffer)), // 将输出定向到缓冲区!).unwrap();println!("--- 捕获的输出 ---\n{buffer}--- 捕获结束 ---");
}

说明:

这里,我们向 controller.run() 方法提供了 OutputHandle::FmtWrite(&mut buffer)

这告诉控制器将所有格式化输出定向到我们的 buffer 字符串,而不是终端或分页程序。

这对于测试或基于 bat 构建工具非常有用。

输出处理的内部工作原理

当控制器准备显示内容时,它需要设置输出通道。

以下是简化的步骤分解:

  1. 控制器询问输出类型:控制器首先从配置中获取 PagingMode(例如 AlwaysNeverQuitIfOneScreen)。
  2. OutputType 决策基于 PagingModepager 命令(如果指定),OutputType 模块决定是使用分页程序还是 stdout。它会检查终端是否支持分页程序、指定的分页程序是否存在,以及传递哪些参数(如 less-R 颜色支持)。
  3. 分页程序设置(如果选择):如果选择了分页程序,OutputType 会实际启动分页程序(如 less)作为子进程。然后将 bat 的输出重定向到分页程序的标准输入。这使得分页程序显示 bat 的内容。
  4. 创建 OutputHandle:无论是否使用分页程序,控制器都会创建一个 OutputHandle。这个句柄要么是直接到 stdout 或分页程序 stdinio::Write,要么是如果我们提供了自定义缓冲区时的 fmt::Write
  5. 打印机写入:打印机模块然后使用这个 OutputHandle 写入所有格式化行,而无需知道数据最终去哪里。它只是写入,OutputHandle 确保它到达正确的目的地。

以下是描述这一流程的序列图:

在这里插入图片描述

深入代码:src/output.rssrc/pager.rs

在这里插入图片描述

让我们看看 src/output.rssrc/pager.rs 中管理这一功能的核心结构。

首先,src/output.rs 中的 OutputType 枚举定义了两个主要目的地:

// src/output.rs
#[derive(Debug)]
pub enum OutputType {#[cfg(feature = "paging")]Pager(Child), // 使用分页程序时,保存子进程Stdout(io::Stdout), // 输出直接到标准输出
}impl OutputType {#[cfg(feature = "paging")]pub fn from_mode(paging_mode: PagingMode,wrapping_mode: WrappingMode,pager: Option<&str>,) -> Result<Self> {// ... 决定使用分页程序还是标准输出的逻辑 ...// 调用 try_pager 或 stdout()}/// 尝试启动分页程序。如果出错则回退到标准输出。#[cfg(feature = "paging")]fn try_pager( /* ... */ ) -> Result<Self> {// ... 构造并启动分页程序 Command 的逻辑 ...// 返回 OutputType::Pager(child_process) 或出错时返回 OutputType::stdout()}pub(crate) fn stdout() -> Self {OutputType::Stdout(io::stdout())}pub fn handle(&mut self) -> Result<&mut dyn Write> {// 返回对底层写入器的可变引用(分页程序的 stdin 或标准输出)match *self {#[cfg(feature = "paging")]OutputType::Pager(ref mut command) => command.stdin.as_mut().ok_or("无法为分页程序打开 stdin")?,OutputType::Stdout(ref mut handle) => handle,}}
}

说明:

  • OutputType 是一个 enum,捕获状态:bat 要么启动了 Pager(并保存其 Child 进程句柄),要么正在写入 Stdout
  • from_mode:控制器调用此函数,根据配置中的 paging_modewrapping_mode 和特定 pager 命令决定输出类型。
  • try_pager:这个私有辅助函数尝试启动分页程序进程。它处理设置 less 特定参数如 -R(颜色)和 -F(一屏退出)。如果启动分页程序失败,则回退到 OutputType::stdout()
  • stdout():一个简单的构造函数,用于输出直接到终端的情况。
  • handle():这是关键方法,提供通用 &mut dyn Write 接口。打印机使用它来写入,而无需知道是写入 stdout 还是分页程序的 stdin

接下来,OutputHandle 提供了写入的最终抽象层:

// src/output.rs
pub enum OutputHandle<'a> {IoWrite(&'a mut dyn io::Write), // 用于实际 I/O 流写入FmtWrite(&'a mut dyn fmt::Write), // 用于实现 fmt::Write 的任何对象(如 String)
}impl OutputHandle<'_> {pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> {match self {Self::IoWrite(handle) => handle.write_fmt(args).map_err(Into::into),Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into),}}
}

说明:

  • OutputHandle 是另一个 enum,包装了 io::Write(用于实际 I/O,如文件或网络流,包括 stdout 和分页程序 stdin)或 fmt::Write(用于写入 String 缓冲区等)。
  • write_fmt:此方法允许 bat 将格式化字符串(如 writeln! 创建的)写入任一类型的句柄。

最后,src/pager.rs 模块包含查找和配置分页程序的逻辑:

// src/pager.rs
#[derive(Debug)]
pub(crate) struct Pager {pub bin: String,      // 分页程序二进制文件(如 "less")pub args: Vec<String>, // 分页程序参数(如 ["-R", "-F"])pub kind: PagerKind,   // 已知分页程序的枚举(Less、More、Unknown 等)pub source: PagerSource, // 分页程序配置来源(Config、EnvVarPager)
}#[derive(Debug, PartialEq)]
pub(crate) enum PagerKind 
{Bat, // 防止递归如果 PAGER 设置为 'bat'Less,More,Most,Unknown,
}#[derive(Debug, PartialEq)]
pub(crate) enum PagerSource 
{Config,EnvVarBatPager, // 来自 BAT_PAGER 环境变量EnvVarPager,    // 来自 PAGER 环境变量Default,
}pub(crate) fn get_pager(config_pager: Option<&str>) -> Result<Option<Pager>, ParseError> 
{// ... 检查 config_pager、BAT_PAGER、PAGER 环境变量的逻辑 ...// ... 然后解析命令并创建 Pager 结构体 ...// ... 有时将 'more' 或 'most' 替换为 'less' 以支持颜色 ...// 返回 Option<Pager>
}

说明:

  • Pager:此结构体保存所选分页程序的所有详细信息,包括其二进制路径、参数以及 bat 找到其配置的位置。
  • PagerKind:枚举用于识别常用分页程序,允许 bat 应用特定配置(如为 less 添加 -R)。
  • PagerSource:枚举跟踪分页程序如何被指定(通过 --pager 选项、BAT_PAGER 环境变量、PAGER 环境变量或 bat 默认值)。这帮助 bat 决定何时覆盖参数。
  • get_pager:此函数由 OutputType::try_pager 调用。
    • 它负责从各种来源(--pager 命令行选项、BAT_PAGER 环境变量、PAGER 环境变量)解析分页程序命令,并创建 Pager 结构体。
    • 它还包含逻辑,如果分页程序来自通用 PAGER 环境变量且不支持颜色(如 more),则替换为 less

这些组件共同确保 bat 精美的输出总是传递到正确的地方,以正确的方式,无论是直接到终端、通过智能分页程序,还是到自定义缓冲区。

结论

在本章中,我们揭示了 bat输出处理机制。

我们学习了 bat 如何智能地决定将其格式化文本发送到哪里——直接到终端或通过强大的分页程序如 less

  • 我们还看到了如何通过 PrettyPrinter 选项显式控制此行为或将输出重定向到 String 缓冲区。

  • 在内部,OutputType 做出关键决策并管理分页程序进程,而 OutputHandle 为打印机提供统一的方式来写入内容,确保为我们的代码提供流畅灵活的输出服务。

现在输出已经处理完毕,下一步自然是深入了解 bat 本身的配置。在下一章中,我们将探索配置,学习所有这些偏好和设置如何存储和管理。

下一章:配置

http://www.xdnf.cn/news/1465651.html

相关文章:

  • 基于华为云平台的STM32F103C8T6工业生产线温湿度监控系统
  • 深度学习书籍推荐
  • LangChain: Models, Prompts 模型和提示词
  • UE4 Mac构建编译报错 no member named “disjunction” in namespace “std”
  • 企业为何仍困在“数据孤岛”?——从iPaaS重构信息流的实践路径
  • 一个专为地图制图和数据可视化设计的在线配色网站,可以助你制作漂亮的地图!
  • Leetcode—2749. 得到整数零需要执行的最少操作数【中等】(__builtin_popcountl)
  • 嵌入式系统学习Day31(多路IO复用)
  • Android Studio新版本编译release版本apk实现
  • 在Ubuntu 20.04的服务器上查找的服务器的IP地址
  • 2025最全的软件测试面试八股文(含答案+文档)
  • 属性关键字
  • Kubernetes(k8s) po 配置持久化挂载(nfs)
  • Ansible 角色使用指南
  • js设计模式-状态模式
  • 腾讯最新开源HunyuanVideo-Foley本地部署教程:端到端TV2A框架,REPA策略+MMDiT架构,重新定义视频音效新SOTA!
  • 2025精选5款AI视频转文字工具,高效转录秒变文字!
  • MySQL集群——主从复制
  • MongoDB 源码编译与调试:深入理解存储引擎设计
  • solidity的高阶语法
  • 【Linux】网络安全管理:SELinux 和 防火墙联合使用 | Redhat
  • 红黑树 + 双链表最小调度器原型
  • 【JMeter】分布式集群压测
  • 解锁上下文的力量:大型语言模型中的上下文工程全解析
  • Java基础篇02:基本语法
  • CAD:修改
  • 23.【C++进阶】异常(try、catch、throw)
  • SQL表一共有几种写入方式
  • 零基础入门AI: YOLOv5 详解与项目实战
  • 数据库存储大量的json文件怎么样高效的读取和分页,利用文件缓存办法不占用内存