#user-input #prompt #command-line #preset #toolkit #interactive #ui

promkit

用于构建您自己的交互式命令行工具的库

16 个不稳定版本 (3 个破坏性更新)

0.4.5 2024年8月5日
0.4.4 2024年6月7日
0.4.3 2024年5月23日
0.3.3 2024年3月27日
0.1.1 2022年6月26日

#18 in 文本编辑器

Download history 79/week @ 2024-05-04 70/week @ 2024-05-11 688/week @ 2024-05-18 408/week @ 2024-05-25 325/week @ 2024-06-01 160/week @ 2024-06-08 68/week @ 2024-06-15 43/week @ 2024-06-22 19/week @ 2024-06-29 35/week @ 2024-07-06 34/week @ 2024-07-13 8/week @ 2024-07-20 103/week @ 2024-07-27 219/week @ 2024-08-03 292/week @ 2024-08-10 163/week @ 2024-08-17

777 每月下载次数
6 crates 中使用

MIT 许可证

265KB
5.5K SLoC

promkit

ci docs.rs

用于在 Rust 中构建您自己的交互式提示的库。

入门

将包放入您的 Cargo.toml 文件中。

[dependencies]
promkit = "0.4.5"

特性

  • 由于使用了 crossterm,支持跨平台(UNIX 和 Windows)
  • 多种构建方法
  • 灵活的定制能力
    • 提示外观设计的主题
      • 例如,光标、文本和提示字符串
    • 用户输入验证和错误信息构建
    • 可自定义的按键映射
  • 鼠标支持(部分)
    • 允许使用鼠标滚轮滚动列表

使用 promkit 的项目

示例/演示

promkit 提供预设,以便用户可以在不构建特定用例的复杂组件的情况下立即尝试提示。

以下将展示可立即执行的示例的命令、代码和实际演示屏幕。

Readline

命令
cargo run --example readline
代码
use promkit::{preset::readline::Readline, suggest::Suggest, Result};

fn main() -> Result {
    let mut p = Readline::default()
        .title("Hi!")
        .enable_suggest(Suggest::from_iter([
            "apple",
            "applet",
            "application",
            "banana",
        ]))
        .validator(
            |text| text.len() > 10,
            |text| format!("Length must be over 10 but got {}", text.len()),
        )
        .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

readline

确认

命令
cargo run --example confirm
代码
use promkit::{preset::confirm::Confirm, Result};

fn main() -> Result {
    let mut p = Confirm::new("Do you have a pet?").prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

confirm

密码

命令
cargo run --example password
代码
use promkit::{preset::password::Password, Result};

fn main() -> Result {
    let mut p = Password::default()
        .title("Put your password")
        .validator(
            |text| 4 < text.len() && text.len() < 10,
            |text| format!("Length must be over 4 and within 10 but got {}", text.len()),
        )
        .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

password

表单

命令
cargo run --example form
代码
use promkit::{crossterm::style::Color, preset::form::Form, style::StyleBuilder, text_editor};

fn main() -> anyhow::Result<()> {
    let mut p = Form::new([
        text_editor::State {
            texteditor: Default::default(),
            history: Default::default(),
            prefix: String::from("❯❯ "),
            mask: Default::default(),
            prefix_style: StyleBuilder::new().fgc(Color::DarkRed).build(),
            active_char_style: StyleBuilder::new().bgc(Color::DarkCyan).build(),
            inactive_char_style: StyleBuilder::new().build(),
            edit_mode: Default::default(),
            word_break_chars: Default::default(),
            lines: Default::default(),
        },
        text_editor::State {
            texteditor: Default::default(),
            history: Default::default(),
            prefix: String::from("❯❯ "),
            mask: Default::default(),
            prefix_style: StyleBuilder::new().fgc(Color::DarkGreen).build(),
            active_char_style: StyleBuilder::new().bgc(Color::DarkCyan).build(),
            inactive_char_style: StyleBuilder::new().build(),
            edit_mode: Default::default(),
            word_break_chars: Default::default(),
            lines: Default::default(),
        },
        text_editor::State {
            texteditor: Default::default(),
            history: Default::default(),
            prefix: String::from("❯❯ "),
            mask: Default::default(),
            prefix_style: StyleBuilder::new().fgc(Color::DarkBlue).build(),
            active_char_style: StyleBuilder::new().bgc(Color::DarkCyan).build(),
            inactive_char_style: StyleBuilder::new().build(),
            edit_mode: Default::default(),
            word_break_chars: Default::default(),
            lines: Default::default(),
        },
    ])
    .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

form

列表框

命令
cargo run --example listbox
代码
use promkit::{preset::listbox::Listbox, Result};

fn main() -> Result {
    let mut p = Listbox::new(0..100)
        .title("What number do you like?")
        .listbox_lines(5)
        .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

listbox

查询选择器

命令
cargo run --example query_selector
代码
use promkit::{preset::query_selector::QuerySelector, Result};

fn main() -> Result {
    let mut p = QuerySelector::new(0..100, |text, items| -> Vec<String> {
        text.parse::<usize>()
            .map(|query| {
                items
                    .iter()
                    .filter(|num| query <= num.parse::<usize>().unwrap_or_default())
                    .map(|num| num.to_string())
                    .collect::<Vec<String>>()
            })
            .unwrap_or(items.clone())
    })
    .title("What number do you like?")
    .listbox_lines(5)
    .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

query_selector

复选框

命令
cargo run --example checkbox
代码
use promkit::{preset::checkbox::Checkbox, Result};

fn main() -> Result {
    let mut p = Checkbox::new(vec![
        "Apple",
        "Banana",
        "Orange",
        "Mango",
        "Strawberry",
        "Pineapple",
        "Grape",
        "Watermelon",
        "Kiwi",
        "Pear",
    ])
    .title("What are your favorite fruits?")
    .checkbox_lines(5)
    .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

checkbox

命令
cargo run --example tree
代码
use promkit::{preset::tree::Tree, tree::Node, Result};

fn main() -> Result {
    let mut p = Tree::new(Node::try_from(&std::env::current_dir()?.join("src"))?)
        .title("Select a directory or file")
        .tree_lines(10)
        .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

tree

JSON

命令
cargo run --example json
代码
use promkit::{json::JsonStream, preset::json::Json, serde_json::Deserializer, Result};

fn main() -> Result {
    let stream = JsonStream::new(
        Deserializer::from_str(
            r#"{
              "number": 9,
              "map": {
                "entry1": "first",
                "entry2": "second"
              },
              "list": [
                "abc",
                "def"
              ]
            }"#,
        )
        .into_iter::<serde_json::Value>()
        .filter_map(serde_json::Result::ok),
        None,
    );

    let mut p = Json::new(stream)
        .title("JSON viewer")
        .json_lines(5)
        .prompt()?;
    println!("result: {:?}", p.run()?);
    Ok(())
}

json

为什么选择 promkit

此类别中的相关库包括以下内容

promkit 相比这些库有以下优势

UI 组件的统一接口方法

promkit 采用统一的方法,使其所有组件继承相同的 Renderer 特性。这种设计选择使用户能够无缝地支持他们自己的数据结构进行显示,类似于在 TUI 项目(如 ratatui-org/ratatuiEdJoPaTo/tui-rs-tree-widget)中看到的关系。换句话说,任何人都可以通过 promkit 中的小部件轻松显示自己的数据结构。
相比之下,其他库倾向于将每个提示视为一个主要独立的实体。如果您想显示新的数据结构,通常必须从头开始构建 UI,这可能是一个耗时且不太灵活的过程。

pub trait Renderer: AsAny + Finalizer {
    /// Creates a collection of panes based on the specified width.
    ///
    /// This method is responsible for generating the layout of the UI components
    /// that will be displayed in the prompt. The width parameter allows the layout
    /// to adapt to the current terminal width.
    ///
    /// # Parameters
    ///
    /// * `width`: The width of the terminal in characters.
    ///
    /// # Returns
    ///
    /// Returns a vector of `Pane` objects that represent the layout of the UI components.
    fn create_panes(&self, width: u16) -> Vec<Pane>;

    /// Evaluates an event and determines the next action for the prompt.
    ///
    /// This method is called whenever an event occurs (e.g., user input). It allows
    /// the renderer to react to the event and decide whether the prompt should continue
    /// running or quit.
    ///
    /// # Parameters
    ///
    /// * `event`: A reference to the event that occurred.
    ///
    /// # Returns
    ///
    /// Returns a `Result` containing a `PromptSignal`. `PromptSignal::Continue` indicates
    /// that the prompt should continue running, while `PromptSignal::Quit` indicates that
    /// the prompt should terminate its execution.
    fn evaluate(&mut self, event: &Event) -> anyhow::Result<PromptSignal>;
}

丰富的预构建 UI 预设组件

选择 promkit 的一个令人信服的理由是其广泛的预构建 UI 预设组件。这些预设允许开发人员快速实现各种交互式提示,而无需从头设计和构建每个组件。这些预设的可用性不仅加快了开发过程,还确保了不同应用程序之间的一致性和可靠性。以下是可用的预设组件,请参阅示例

对终端尺寸调整的弹性

在执行涉及在一个面板中执行命令的同时打开新面板的操作是常见的。在这些操作期间,如果由于调整终端尺寸而导致 UI 损坏,可能会对用户体验产生不利影响。
其他库在终端尺寸调整时可能会遇到困难,使得打字和交互变得困难或不可能。例如

promkit 引入了一个步骤,在渲染前将数据与屏幕大小对齐。这种方法确保即使在终端尺寸变化时,UI 元素的一致性,从而提供更流畅的用户体验。

许可证

本项目受 MIT 许可证的许可。有关详细信息,请参阅LICENSE 文件。

随时间推移的 Star 数

Stargazers over time

依赖关系

~3–9MB
~76K SLoC