26 个版本
0.1.26 | 2024 年 4 月 16 日 |
---|---|
0.1.25 | 2024 年 1 月 15 日 |
0.1.23 | 2023 年 12 月 23 日 |
0.1.21 | 2023 年 10 月 22 日 |
0.1.13 | 2023 年 9 月 24 日 |
#71 在 命令行界面
8,154 每月下载量
在 5 个 Crates 中使用 (直接使用 3 个)
340KB
5K SLoC
r3bl_tuify
什么是 r3bl_tuify?
r3bl_tuify 是一个 Rust crate,允许您为您的 CLI 应用添加简单的交互功能。
r3bl_tuify crate 可以以两种方式使用
- 作为库。如果您想在用 Rust 编写的 CLI 应用中添加简单的交互功能,这将很有用。您可以在
examples
文件夹中的main_interactive.rs
文件中看到示例。您可以使用cargo run --example main_interactive
或使用 nunu run examples
来运行它。 - 作为二进制文件。如果您想将此 crate 作为命令行工具使用,这将很有用。二进制目标名为
rt
。
如何将其作为库使用?
这是此 crate 的库目标的演示。
https://github.com/r3bl-org/r3bl-open-core/assets/22040032/46850043-4973-49fa-9824-58f32f21e96e
要将 crate 作为库安装,请将以下内容添加到您的 Cargo.toml
文件中
[dependencies]
r3bl_tuify = "0.1.24" # Get the latest version at the time you get this.
r3bl_rs_utils_core = "0.9.11" # Get the latest version at the time you get this.
以下示例说明了如何将其作为库使用。执行UI渲染工作的函数称为select_from_list
。它接受一个项目列表,并返回选定的项目或项目(取决于选择模式)。如果用户没有选择任何内容,它将返回None
。该函数还接受显示的最大高度和宽度以及选择模式(单选或多选)。
它在 macOS、Linux 和 Windows 上运行,并且了解每个终端颜色输出限制。例如,在 Windows 上,它使用 Windows API 进行键盘输入。在 macOS Terminal.app 中,它将颜色输出限制为 256 色调板。
APIs
我们提供了 2 个 API
select_from_list
:如果您想显示具有单行标题的项目列表,请使用此 API。select_from_list_with_multi_line_header
:如果您想显示具有多行标题的项目列表,请使用此 API。
select_from_list
使用此 API 如果您想显示具有单行标题的项目列表。
select_from_list
签名
fn select_from_list(
header: String,
items: Vec<String>,
max_height_row_count: usize,
// If you pass 0, then the width of your terminal gets set as max_width_col_count.
max_width_col_count: usize,
selection_mode: SelectionMode,
style: StyleSheet,
) -> Option<Vec<String>>
use r3bl_rs_utils_core::*;
use r3bl_tuify::*;
use std::io::Result;
fn main() -> Result<()> {
// Get display size.
let max_height_row_count: usize = 5;
let user_input = select_from_list(
"Select an item".to_string(),
[
"item 1", "item 2", "item 3", "item 4", "item 5", "item 6", "item 7", "item 8",
"item 9", "item 10",
]
.iter()
.map(|it| it.to_string())
.collect(),
max_height_row_count,
0,
SelectionMode::Single,
StyleSheet::default(),
);
match &user_input {
Some(it) => {
println!("User selected: {:?}", it);
}
None => println!("User did not select anything"),
}
Ok(())
}
select_from_list_with_multi_line_header
如果您想显示具有多行标题的项目列表,请使用select_from_list_with_multi_line_header
API。前 5 行都是多行标题的一部分。
select_from_list_with_multi_line_header
签名
fn select_from_list_with_multi_line_header(
multi_line_header: Vec<Vec<AnsiStyledText<'_>>>,
items: Vec<String>,
maybe_max_height_row_count: Option<usize>,
// If you pass None, then the width of your terminal gets used.
maybe_max_width_col_count: Option<usize>,
selection_mode: SelectionMode,
style: StyleSheet,
) -> Option<Vec<String>>
use std::{io::Result, vec};
use r3bl_ansi_color::{AnsiStyledText, Color, Style as RStyle};
use r3bl_rs_utils_core::*;
use r3bl_tuify::{
components::style::StyleSheet,
select_from_list_with_multi_line_header,
SelectionMode,
};
fn multi_select_instructions() -> Vec<Vec<AnsiStyledText<'static>>> {
let up_and_down = AnsiStyledText {
text: " Up or down:",
style: &[
RStyle::Foreground(Color::Rgb(9, 238, 211)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let navigate = AnsiStyledText {
text: " navigate",
style: &[
RStyle::Foreground(Color::Rgb(94, 103, 111)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let line_1 = vec![up_and_down, navigate];
let space = AnsiStyledText {
text: " Space:",
style: &[
RStyle::Foreground(Color::Rgb(255, 216, 9)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let select = AnsiStyledText {
text: " select or deselect item",
style: &[
RStyle::Foreground(Color::Rgb(94, 103, 111)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let line_2 = vec![space, select];
let esc = AnsiStyledText {
text: " Esc or Ctrl+C:",
style: &[
RStyle::Foreground(Color::Rgb(255, 132, 18)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let exit = AnsiStyledText {
text: " exit program",
style: &[
RStyle::Foreground(Color::Rgb(94, 103, 111)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let line_3 = vec![esc, exit];
let return_key = AnsiStyledText {
text: " Return:",
style: &[
RStyle::Foreground(Color::Rgb(234, 0, 196)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let confirm = AnsiStyledText {
text: " confirm selection",
style: &[
RStyle::Foreground(Color::Rgb(94, 103, 111)),
RStyle::Background(Color::Rgb(14, 17, 23)),
],
};
let line_4 = vec![return_key, confirm];
vec![line_1, line_2, line_3, line_4]
}
fn main() -> Result<()> {
let header = AnsiStyledText {
text: " Please select one or more items. This is a really long heading that just keeps going and if your terminal viewport is small enough, this heading will be clipped",
style: &[
RStyle::Foreground(Color::Rgb(171, 204, 242)),
RStyle::Background(Color::Rgb(31, 36, 46)),
],
};
let mut instructions_and_header: Vec<Vec<AnsiStyledText>> = multi_select_instructions();
instructions_and_header.push(vec![header]);
let user_input = select_from_list_with_multi_line_header(
instructions_and_header,
[
"item 1 of 13",
"item 2 of 13",
"item 3 of 13",
"item 4 of 13",
"item 5 of 13",
"item 6 of 13",
"item 7 of 13",
"item 8 of 13",
"item 9 of 13",
"item 10 of 13",
"item 11 of 13",
"item 12 of 13",
"item 13 of 13",
]
.iter()
.map(|it| it.to_string())
.collect(),
Some(6),
None,
SelectionMode::Multiple,
StyleSheet::default(),
);
match &user_input {
Some(it) => {
println!("User selected: {:?}", it);
}
None => println!("User did not select anything"),
}
Ok(())
}
如何将其作为二进制文件使用?
以下是一个展示此crate二进制目标效果的演示。
您可以使用以下命令安装二进制文件:使用cargo install r3bl_tuify
(来自crates.io)。或者,从源代码安装:cargo install --path .
。rt
是一个命令行工具,允许您从通过stdin
传入它的列表中选择一个选项。它支持stdin
和stdout
管道。
以下是可以接受的命令行参数
-s
或--selection-mode
- 允许您选择选择模式。有两种选项:single
和multiple
。-c
或--command-to-run-with-selection
- 允许您指定与选定项目一起运行的命令。例如,"echo foo \'%\'"
将简单地打印每个选定的项目。-t
或--tui-height
- 可选地允许您设置 TUI 的高度。默认为 5。
交互式用户体验
通常,CLI 应用程序不是交互式的。您可以将其传递命令、子命令、选项和参数,但如果您出错,则会出现错误,并且必须从头开始。这种“对话”风格的界面可能需要大量的尝试和错误才能得到所需的结果。
以下是一个使用许多子命令、选项和参数的示例。
cat TODO.todo | cargo run -- select-from-list \
--selection-mode single \
--command-to-run-with-each-selection "echo %"
以下是一个展示其效果的演示视频。
https://github.com/r3bl-org/r3bl-open-core/assets/2966499/c9b49bfb-b811-460e-a844-fe260eaa860a
这是做什么的?
cat TODO.todo
- 将TODO.todo
文件的 内容打印到stdout
。|
- 将上一个命令的输出传递到下一个命令,即rt
(即,此crate的二进制目标)。cargo run --
- 在目标文件夹中运行rt
调试二进制。select-from-list
- 使用select-from-list
子命令运行rt
二进制。此子命令需要两个参数:--selection-mode
和--command-to-run-with-each-selection
。哇!这有点长!--selection-mode single
- 将选择模式设置为single
。这意味着用户只能从列表中选择一个项目。哪个列表?就是从上一个命令管道传递进来的列表(即,cat TODO.todo
)。--command-to-run-with-each-selection "echo %"
- 设置与每个选择一起运行的命令。在这种情况下,它是echo %
。这里的%
是占位符,用于表示所选的项目。所以如果用户选择了item 1
,那么将运行的命令是echo item 1
。echo
命令只是将所选的项目打印到stdout
。
这要记住的东西确实很多。使用 clap
提供友好的命令行帮助很有用,但你还是需要正确设置一些东西才能使该命令正常工作。
不一定要这样。二进制可以交互式地使用 clap
指定一些子命令和参数。这不一定是全有或全无的方法。我们可以两者兼得。以下视频展示了当
-
--selection-mode
和--command-to-run-with-each-selection
未通过命令行传递时会发生什么。cat TODO.todo | cargo run -- select-from-list
以下是可能出现的三种情况:
-
用户首先选择
single
选择模式(使用列表选择组件),然后在终端中键入echo %
作为每个选择要运行的命令。这是一个交互式场景,因为用户必须提供两件信息:选择模式和每个选择要运行的命令。他们在运行命令时没有提前提供这些信息。https://github.com/r3bl-org/r3bl-open-core/assets/2966499/51de8867-513b-429f-aff2-63dd25d71c82
-
另一个场景是用户甚至在交互式提示时也没有提供所需的信息。在这种情况下,程序会以错误和帮助信息退出。
在这种情况下,他们没有提供他们想要的
selection-mode
。他们也没有提供他们想要的command-to-run-with-each-selection
。没有这些信息,程序无法继续,因此它退出并提供一些帮助信息。https://github.com/r3bl-org/r3bl-open-core/assets/2966499/664d0367-90fd-4f0a-ad87-3f4745642ad0
-
-
--selection-mode
未通过命令行传递。因此,它只交互式地提示用户提供此信息。同样,如果用户不提供此信息,则应用程序退出并提供帮助信息。cat TODO.todo | cargo run -- select-from-list --command-to-run-with-each-selection "echo %"
https://github.com/r3bl-org/r3bl-open-core/assets/2966499/be65d9b2-575b-47c0-8291-110340bd2fe7
-
--command-to-run-with-each-selection
不会在命令行中传递。因此,它只交互式地提示用户输入此信息。同样,如果用户没有提供此信息,应用程序会退出并显示帮助信息。cat TODO.todo | cargo run -- select-from-list --selection-mode single
https://github.com/r3bl-org/r3bl-open-core/assets/2966499/d8d7d419-c85e-4c10-bea5-345aa31a92a3
路径
您可以使用这个相对简单的程序进行许多不同的执行路径。以下是一个列表。
-
快乐路径
rt
- 打印帮助信息。cat Cargo.toml | rt -s single -c "echo foo \'%\'"
- 从stdin
中读取输入,并将其打印到stdout
。cat Cargo.toml | rt -s multiple -c "echo foo \'%\'"
- 从stdin
中读取输入,并将其打印到stdout
。
-
不快乐的路径(
stdin
未连接到管道,或stdout
连接到管道)rt -s single
- 期望stdin
连接到管道,并打印帮助信息。rt -s multiple
- 期望stdin
连接到管道,并打印帮助信息。ls -la | rt -s single | xargs -0
- 不期望stdout
连接到管道,并打印帮助信息。ls -la | rt -s multiple | xargs -0
- 不期望stdout
连接到管道,并打印帮助信息。
由于 Unix 管道的实现方式,无法将此命令的
stdout
连接到其他任何东西。Unix 管道是非阻塞的。因此,无法在管道“中途”停止管道。这就是为什么当stdout
连接到管道时,rt
会显示错误。无法将rt
的stdout
连接到另一个命令。相反,rt
二进制文件简单地接受一个将在用户做出选择后运行的命令。使用所选项并将它们应用于此命令。
组件样式
选择 3 个内置样式之一
内置样式称为 default
、sea_foam_style
和 hot_pink_style
。您可以在 style.rs
文件(tuify/src/components/style.rs)中找到它们。
默认样式
sea_foam_style
hot_pink_style
要使用内置样式之一,只需将其作为参数传递给 select_from_list
函数。
use r3bl_rs_utils_core::*;
use r3bl_tuify::*;
use std::io::Result;
fn main() -> Result<()> {
// 🎨 Uncomment the lines below to choose the other 2 built-in styles.
// let default_style = StyleSheet::default();
// let hot_pink_style = StyleSheet::hot_pink_style();
let sea_foam_style = StyleSheet::sea_foam_style();
let max_width_col_count: usize = get_size().map(|it| it.col_count).unwrap_or(ch!(80)).into();
let max_height_row_count: usize = 5;
let user_input = select_from_list(
"Select an item".to_string(),
[
"item 1", "item 2", "item 3", "item 4", "item 5", "item 6", "item 7", "item 8",
"item 9", "item 10",
]
.iter()
.map(|it| it.to_string())
.collect(),
max_height_row_count,
max_width_col_count,
SelectionMode::Single,
sea_foam_style, // 🖌️ or default_style or hot_pink_style
);
match &user_input {
Some(it) => {
println!("User selected: {:?}", it);
}
None => println!("User did not select anything"),
}
Ok(())
}
创建您的样式
要创建您的样式,您需要创建一个 StyleSheet
结构体,并将其作为参数传递给 select_from_list
函数。
use std::io::Result;
use r3bl_ansi_color::{AnsiStyledText, Color, Style as RStyle};
use r3bl_tuify::{components::style::{Style, StyleSheet},
select_from_list,
SelectionMode};
fn main() -> Result<()> {
// This is how you can define your custom style.
// For each Style struct, you can define different style overrides.
// Please take a look at the Style struct to see what you can override.
let my_custom_style = StyleSheet {
focused_and_selected_style: Style {
fg_color: Color::Rgb(255, 244, 0),
bg_color: Color::Rgb(15, 32, 66),
..Style::default()
},
focused_style: Style {
fg_color: Color::Rgb(255, 244, 0),
..Style::default()
},
unselected_style: Style { ..Style::default() },
selected_style: Style {
fg_color: Color::Rgb(203, 170, 250),
bg_color: Color::Rgb(15, 32, 66),
..Style::default()
},
header_style: Style {
fg_color: Color::Rgb(171, 204, 242),
bg_color: Color::Rgb(31, 36, 46),
..Style::default()
},
};
// Then pass `my_custom_style` as the last argument to the `select_from_list` function.
let user_input = select_from_list(
"Multiple select".to_string(),
["item 1 of 3", "item 2 of 3", "item 3 of 3"]
.iter()
.map(|it| it.to_string())
.collect(),
6, // max_height_row_count
80, // max_width_col_count
SelectionMode::Multiple,
my_custom_style,
);
match &user_input {
Some(it) => {
println!("User selected: {:?}", it);
}
None => println!("User did not select anything"),
}
Ok(())
}
构建、运行、测试任务
先决条件
🌠 为了使这些功能正常工作,您需要在您的系统上安装 Rust 工具链、nu
、cargo-watch
、bat
和 flamegraph
。以下是说明
- 使用
rustup
安装 Rust 工具链,按照 这里 的说明进行。 - 使用以下命令安装
cargo-watch
: - 使用以下命令安装
flamegraph
: - 使用以下命令安装
bat
: - 使用以下命令在您的系统上安装
nu
shell:。它支持 Linux、macOS 和 Windows。
Nu 脚本来构建、运行、测试等。
进入 tuify
文件夹,并运行以下命令。这些命令在 ./run
文件夹中定义。
命令 | 描述 |
---|---|
nu run examples |
运行 ./examples 文件夹中的示例 |
nu run piped |
运行具有管道输入的二进制文件 |
nu run build |
构建 |
nu run clean |
清理 |
nu run all |
全部 |
nu run examples-与-flamegraph-分析 |
使用 flamegraph 分析运行示例 |
nu run test |
运行测试 |
nu run clippy |
运行 clippy |
nu run docs |
构建文档 |
nu run serve-文档 |
通过 VSCode Remote SSH 会话提供文档服务 |
nu run upgrade-依赖 |
升级依赖 |
nu run rustfmt |
运行 rustfmt |
以下命令将监视源文件夹中的更改并重新运行
命令 | 描述 |
---|---|
nu run watch-examples |
监视运行示例 |
nu run watch-all-tests |
监视所有测试 |
nu run watch-one-test<test_name> |
监视单个测试 |
nu run watch-clippy |
监视 clippy |
nu run watch-macro-expansion-one-test<test_name> |
监视单个测试的宏展开 |
在存储库的顶层文件夹中还有一个 run
脚本。它旨在在 CI/CD 环境中使用,或者以交互模式使用,用户将被提示输入。
命令 | 描述 |
---|---|
nu run all |
一次性运行所有测试、代码质量检查、格式化等。用于 CI/CD。 |
nu run build-full |
这将构建 Rust 工作空间中的所有 crate。它将安装所有必需的先决工具(如 install-cargo-tools 所做的)并清除 cargo 缓存,清理并执行一个真正干净的构建。 |
nu run install-cargo-tools |
这将安装所有必需的先决工具,以用于此 crate(例如,cargo-deny 和 flamegraph 将一次性安装) |
nu run check-licenses |
使用 cargo-deny 检查 Rust 工作空间中使用的所有许可证 |
参考
CLI 用户体验指南
- https://rust-cli-recommendations.sunshowers.io/handling-arguments.html
- https://rust-cli-recommendations.sunshowers.io/configuration.html
- https://rust-cli-recommendations.sunshowers.io/hierarchical-config.html
- https://rust-cli-recommendations.sunshowers.io/hierarchical-config.html
- https://docs.rs/clap/latest/clap/_derive/#overview
- https://clig.dev/#foreword
ANSI 转义代码
- https://notes.burke.libbey.me/ansi-escape-codes/
- https://en.wikipedia.org/wiki/ANSI_escape_code
- https://www.asciitable.com/
- https://commons.wikimedia.org/wiki/File:Xterm_256color_chart.svg
- https://www.ditig.com/256-colors-cheat-sheet
- https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
- https://www.compuphase.com/cmetric.htm
依赖项
~17–29MB
~352K SLoC