5 个版本
0.0.5 | 2024年1月14日 |
---|---|
0.0.4 | 2024年1月14日 |
0.0.3 | 2024年1月8日 |
0.0.2 | 2024年1月8日 |
0.0.1 | 2024年1月8日 |
#143 in 命令行界面
104 次每月下载
在 4 crates 中使用
1.5MB
3K SLoC
Promptuity
Promptuity = 提示 + 智慧
Promptuity 是一个提供交互式提示的库。它高度可扩展,允许您从头开始构建您自己的提示。它为各种项目带来了 智慧。
目录
概念
- ⚡ 不易,但简单
- 避免具有隐性行为的 API,旨在提供尽可能透明的 API。
- 启动提示所需的代码量可能比其他库多。
- 🔨 可扩展
- 您可以自定义内置提示或从头开始构建提示。
- 内置提示是最小的,假设提示需求因项目而异。
- 💅 美观
- 提供两种内置主题。
- 主题也可以完全自定义,以适应您的理想。
快速开始
以下是一些基本用法。
use promptuity::prompts::{Confirm, Input, Select, SelectOption};
use promptuity::themes::FancyTheme;
use promptuity::{Error, Promptuity, Term};
fn main() -> Result<(), Error> {
let mut term = Term::default();
let mut theme = FancyTheme::default();
let mut p = Promptuity::new(&mut term, &mut theme);
p.term().clear()?;
p.with_intro("Survey").begin()?;
let name = p.prompt(Input::new("Please enter your username").with_placeholder("username"))?;
let _ = p.prompt(Confirm::new("Are you a full-time software developer?").with_default(true))?;
let _ = p.prompt(
Select::new(
"Select your primary programming language",
vec![
SelectOption::new("Rust", "rust"),
SelectOption::new("Go", "go"),
SelectOption::new("C++", "cpp"),
SelectOption::new("C", "c"),
SelectOption::new("TypeScript", "typescript"),
SelectOption::new("JavaScript", "javascript"),
SelectOption::new("Deno", "deno"),
SelectOption::new("Python", "python"),
SelectOption::new("Java", "java"),
SelectOption::new("Dart", "dart"),
SelectOption::new("Other", "other"),
],
)
.with_hint("Submit with Space or Enter."),
)?;
p.with_outro(format!("Thank you for your response, {}!", name))
.finish()?;
Ok(())
}
示例
如果您想看到更多示例,请参阅 示例 目录。
文档
请参阅 文档。
提示
promptuity::prompts
提供五个内置提示。
要实现您自己的提示,请参阅 构建您自己的提示 部分。
输入
用于一般文本输入的提示。
let name = p.prompt(
Input::new("What is your accout name?")
.with_placeholder("username")
.with_hint("Only alphanumeric characters are allowed.")
.with_validator(|value: &String| {
if value.chars().all(|c| c.is_alphanumeric()) {
Ok(())
} else {
Err("Invalid format".into())
}
}),
)?;
密码
不显示输入的文本输入提示。
let secret = p.prompt(
Password::new("Set a password for your account")
.with_hint("Please enter more than 6 alphanumeric characters.")
.with_validator(|value: &String| {
if value.len() < 6 {
Err("Password must be at least 6 characters long".into())
} else {
Ok(())
}
}),
)?;
数字
仅输入整数值的提示。
let age = p.prompt(Number::new("How old are you?").with_min(0).with_max(120))?;
选择
从选项列表中选择单个元素的提示。
let color = p.prompt(
Select::new(
"What is your favorite color?",
vec![
SelectOption::new("Red", "#ff0000"),
SelectOption::new("Green", "#00ff00").with_hint("recommended"),
SelectOption::new("Blue", "#0000ff"),
],
)
.as_mut(),
)?;
多选
从选项列表中选择多个元素的提示。
let color = p.prompt(
MultiSelect::new(
"What are your favorite colors?",
vec![
MultiSelectOption::new("Red", "#ff0000"),
MultiSelectOption::new("Green", "#00ff00").with_hint("recommended"),
MultiSelectOption::new("Blue", "#0000ff"),
],
)
.as_mut(),
)?;
确认
输入 Yes/No 选择的提示。
let like = p.prompt(
Confirm::new("Do you like dogs?")
.with_hint("This is just a sample prompt :)")
.with_default(true),
)?;
自动完成
[注意] 自动补全不是一个内置功能。这是因为模糊匹配和快捷键的行为因项目而异。
虽然不是内置功能,但提供了一个参考实现,位于 examples/autocomplete.rs。请根据您项目的需求进行修改。
主题
Promptuity提供了两种不同的内置主题。
要实现您自己的主题,请参阅构建自己的主题部分。
MinimalTheme
MinimalTheme与Inquirer类似。它提供了一种紧凑的用户界面。
use promptuity::themes::MinimalTheme;
fn main() {
let mut theme = MinimalTheme::default();
// ...
}
FancyTheme
FancyTheme与clack类似。它提供了一种丰富的用户界面。
use promptuity::themes::FancyTheme;
fn main() {
let mut theme = FancyTheme::default();
// ...
}
自定义
本节提供了如何构建原始提示和主题的指导。
构建您自己的提示
通过实现Prompt
特质,可以创建原始提示。通过实现三个生命周期方法,您可以使用Promptuity::prompt
构建可用的提示。
Promptuity提示由以下元素组成
项目 | 描述 |
---|---|
信息 | 显示提示的问题内容。 |
输入 | 单行项目,接受用户按键输入。 |
正文 | 多行项目,接受用户按键输入。 |
提示 | 显示帮助提示信息。 |
- 接受单行输入的提示,如
Input
或Password
,不使用正文。 - 不接受输入的提示,如
Select
或MultiSelect
,不使用输入。
在构建您的提示时请注意这些要点。
0. 设置自定义提示
让我们以与Confirm
类似的自定义提示的实现为例。
use promptuity::Prompt;
struct CustomConfirm {
message: String,
hint: Option<String>,
value: bool,
}
impl Prompt for CustomConfirm {
type Output = bool;
// TODO
}
定义一个包含消息、提示和值的结构。在Output
中指定最终结果类型。
首先,让我们实现按键输入的接收。
1. 接收按键输入
在Prompt::handle
方法中处理按键输入。
例如,让我们实现它,以便按下y为是,按下n为否,最终确定结果。
use promptuity::event::{KeyCode, KeyModifiers};
use promptuity::{Prompt, PromptState};
// ...
impl Prompt for CustomConfirm {
// ...
fn handle(&mut self, code: KeyCode, modifiers: KeyModifiers) -> PromptState {
match (code, modifiers) {
(KeyCode::Enter, _) => PromptState::Submit,
(KeyCode::Esc, _) | (KeyCode::Char('c'), KeyModifiers::CONTROL) => PromptState::Cancel,
(KeyCode::Char('y'), KeyModifiers::NONE) | (KeyCode::Char('Y'), KeyModifiers::NONE) => {
self.value = true;
PromptState::Submit
}
(KeyCode::Char('n'), KeyModifiers::NONE) | (KeyCode::Char('N'), KeyModifiers::NONE) => {
self.value = false;
PromptState::Submit
}
_ => PromptState::Active,
}
}
}
您可以自由组合按键码和修饰符,以构建符合特定要求的复杂提示。
[重要] 通常,提示会被Ctrl + C中断,但Promptuity不会自动处理。
如果省略了实现,则会导致提示无法中断,从而影响可用性。因此,在构建原始提示时,您必须自己明确实现中断过程。
2. 渲染提示
在Prompt::render
方法中构建渲染内容。以下是一个仅使用Input
而没有Body
的简单示例。
use promptuity::event::{KeyCode, KeyModifiers};
use promptuity::{Prompt, PromptState, RenderPayload};
// ...
impl Prompt for CustomConfirm {
// ...
fn render(&mut self, state: &PromptState) -> Result<RenderPayload, String> {
let payload = RenderPayload::new(self.message.clone(), self.hint.clone(), None);
match state {
PromptState::Submit => {
let raw = if self.value { "Yes" } else { "No" };
Ok(payload.input(PromptInput::Raw(raw.into())))
}
PromptState::Cancel => Ok(payload),
_ => Ok(payload.input(PromptInput::Raw("Y/n"))),
}
}
}
根据Prompt::handle
返回的PromptState
确定适当的渲染内容。上述实现实现了以下要求
- 结果显示
Yes
或No
。 - 如果提示被中断,只显示消息。
- 在用户输入接收期间,它显示
Y/n
。
3. 返回提交结果
这是构建自定义提示的最后一步。
实现 Prompt::submit
方法,该方法返回接收到的键入的最终值。
impl Prompt for CustomConfirm {
// ...
fn submit(&mut self) -> Self::Output {
self.value
}
}
Prompt::submit
是一个生命周期方法,在 Prompt::handle
返回 PromptState::Submit
后立即调用。
处理键入和根据输入状态进行渲染是构建提示的基础。
对于构建更复杂的提示,可以参考 examples/autocomplete.rs。
构建您自己的主题
就像提示一样,你可以通过实现 Theme
特性来构建一个原始的主题。
有关完整示例,请参阅 examples/custom_theme.rs。
错误处理
所有错误都汇总到 promptuity::Error
。
在许多情况下,需要单独处理提示中断。中断通常发生在用户输入接收期间,通常通过像 Ctrl + C 或 ESC 这样的输入。
use promptuity::prompts::Input;
use promptuity::themes::MinimalTheme;
use promptuity::{Error, Promptuity, Term};
fn ask() -> Result<String, Error> {
let mut term = Term::default();
let mut theme = MinimalTheme::default();
let mut p = Promptuity::new(&mut term, &mut theme);
p.begin()?;
let name = p.prompt(Input::new("Please enter your username").with_placeholder("username"))?;
p.finish()?;
Ok(name)
}
fn main() {
match ask() {
Ok(name) => println!("Hello, {}!", name),
Err(Error::Cancel) => {}
Err(e) => eprintln!("Error: {}", e),
}
}
提示中断可以处理为 Error::Cancel
。在上面的示例中,发生中断时不会显示任何消息。
测试
通常,涉及用户输入的验证是昂贵的。由于 Promptuity 将终端行为作为 Terminal
特性实现,因此很容易用假值替换。
用于 Promptuity 集成测试的模拟键入输入的 Terminal
,可以在 Term
中参考。
以下是使用假 Terminal
测试提示的示例。
#[test]
fn test_prompts() {
let mut term = fake_term::Term::new(&[
(KeyCode::Char('a'), KeyModifiers::NONE),
(KeyCode::Char('b'), KeyModifiers::NONE),
(KeyCode::Char('c'), KeyModifiers::NONE),
(KeyCode::Enter, KeyModifiers::NONE),
]);
let mut theme = MinimalTheme::default();
let result = {
let mut p = Promptuity::new(&mut term, &mut theme);
p.prompt(Input::new("Input Message").as_mut()).unwrap()
};
let output = term.output();
assert_eq!(result, String::from("abc"));
// This is an example of performing snapshots on outputs.
insta::with_settings!({ omit_expression => true }, {
insta::assert_snapshot!(output);
});
}
替代方案
Rust 生态系统包含许多优秀的crate。
灵感来源
Promptuity 的各种提示和设计受到了这些项目的极大启发。我们对他们的开发表示衷心的感谢。
贡献
请参阅 CONTRIBUTING.md。
变更日志
请参阅 CHANGELOG.md。
许可证
依赖项
~1.6–7MB
~41K SLoC