https://github.com/ynqa/promkit
A toolkit for building interactive prompt in Rust
https://github.com/ynqa/promkit
cli interactive prompt prompt-toolkit readline rust terminal tui
Last synced: 1 day ago
JSON representation
A toolkit for building interactive prompt in Rust
- Host: GitHub
- URL: https://github.com/ynqa/promkit
- Owner: ynqa
- License: mit
- Created: 2022-05-01T04:49:46.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2025-04-09T14:11:10.000Z (6 days ago)
- Last Synced: 2025-04-09T15:25:35.312Z (6 days ago)
- Topics: cli, interactive, prompt, prompt-toolkit, readline, rust, terminal, tui
- Language: Rust
- Homepage: https://crates.io/crates/promkit
- Size: 1.02 MB
- Stars: 382
- Watchers: 4
- Forks: 9
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-rust - ynqa/promkit - line tools [](https://github.com/ynqa/promkit/actions/workflows/ci.yml) (Libraries / Command-line)
- awesome-rust-cn - ynqa/promkit
- awesome-rust - ynqa/promkit - A toolkit for building interactive command-line tools [](https://github.com/ynqa/promkit/actions/workflows/ci.yml) (Libraries / Command-line)
- fucking-awesome-rust - ynqa/promkit - A toolkit for building interactive command-line tools [](https://github.com/ynqa/promkit/actions/workflows/ci.yml) (Libraries / Command-line)
README
# promkit
[](https://github.com/ynqa/promkit/actions/workflows/ci.yml)
[](https://docs.rs/promkit)A toolkit for building your own interactive prompt in Rust.
## Getting Started
Put the package in your `Cargo.toml`.
```toml
[dependencies]
promkit = "0.9.1"
```## Features
- Cross-platform support for both UNIX and Windows utilizing [crossterm](https://github.com/crossterm-rs/crossterm)
- Modularized architecture
- [promkit-core](https://github.com/ynqa/promkit/tree/v0.9.1/promkit-core/)
- Core functionality for basic terminal operations and pane management
- [promkit-widgets](https://github.com/ynqa/promkit/tree/v0.9.1/promkit-widgets/)
- Various UI components (text, listbox, tree, etc.)
- [promkit](https://github.com/ynqa/promkit/tree/v0.9.1/promkit)
- High-level presets and user interfaces
- [promkit-derive](https://github.com/ynqa/promkit/tree/v0.9.1/promkit-derive/)
- A Derive macro that simplifies interactive form input
- Rich preset components
- [Readline](https://github.com/ynqa/promkit/tree/v0.9.1#readline) - Text input with auto-completion
- [Confirm](https://github.com/ynqa/promkit/tree/v0.9.1#confirm) - Yes/no confirmation prompt
- [Password](https://github.com/ynqa/promkit/tree/v0.9.1#password) - Password input with masking and validation
- [Form](https://github.com/ynqa/promkit/tree/v0.9.1#form) - Manage multiple text input fields
- [Listbox](https://github.com/ynqa/promkit/tree/v0.9.1#listbox) - Single selection interface from a list
- [QuerySelector](https://github.com/ynqa/promkit/tree/v0.9.1#queryselector) - Searchable selection interface
- [Checkbox](https://github.com/ynqa/promkit/tree/v0.9.1#checkbox) - Multiple selection checkbox interface
- [Tree](https://github.com/ynqa/promkit/tree/v0.9.1#tree) - Tree display for hierarchical data like file systems
- [JSON](https://github.com/ynqa/promkit/tree/v0.9.1#json) - Parse and interactively display JSON data
- [Text](https://github.com/ynqa/promkit/tree/v0.9.1#text) - Static text display## Concept
See [here](https://github.com/ynqa/promkit/tree/v0.9.1/Concept.md).
## Projects using *promkit*
- [ynqa/empiriqa](https://github.com/ynqa/empiriqa)
- [ynqa/jnv](https://github.com/ynqa/jnv)
- [ynqa/logu](https://github.com/ynqa/logu)
- [ynqa/sig](https://github.com/ynqa/sig)## Examples/Demos
*promkit* provides presets so that users can try prompts immediately without
having to build complex components for specific use cases.Show you commands, code, and actual demo screens for examples
that can be executed immediately below.### Readline
Command
```bash
cargo run --bin readline --manifest-path examples/readline/Cargo.toml
```Code
```rust,ignore
use promkit::{preset::readline::Readline, suggest::Suggest};fn main() -> anyhow::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(())
}
```
### Confirm
Command
```bash
cargo run --manifest-path examples/confirm/Cargo.toml
```Code
```rust,ignore
use promkit::preset::confirm::Confirm;fn main() -> anyhow::Result<()> {
let mut p = Confirm::new("Do you have a pet?").prompt()?;
println!("result: {:?}", p.run()?);
Ok(())
}
```
### Password
Command
```bash
cargo run --manifest-path examples/password/Cargo.toml
```Code
```rust,ignore
use promkit::preset::password::Password;fn main() -> anyhow::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(())
}
```
### Form
Command
```bash
cargo run --manifest-path examples/form/Cargo.toml
```Code
```rust,ignore
use promkit::{
crossterm::style::{Color, ContentStyle},
preset::form::Form,
promkit_widgets::text_editor,
};fn main() -> anyhow::Result<()> {
let mut p = Form::new([
text_editor::State {
prefix: String::from("❯❯ "),
prefix_style: ContentStyle {
foreground_color: Some(Color::DarkRed),
..Default::default()
},
active_char_style: ContentStyle {
background_color: Some(Color::DarkCyan),
..Default::default()
},
..Default::default()
},
text_editor::State {
prefix: String::from("❯❯ "),
prefix_style: ContentStyle {
foreground_color: Some(Color::DarkGreen),
..Default::default()
},
active_char_style: ContentStyle {
background_color: Some(Color::DarkCyan),
..Default::default()
},
..Default::default()
},
text_editor::State {
prefix: String::from("❯❯ "),
prefix_style: ContentStyle {
foreground_color: Some(Color::DarkBlue),
..Default::default()
},
active_char_style: ContentStyle {
background_color: Some(Color::DarkCyan),
..Default::default()
},
..Default::default()
},
])
.prompt()?;
println!("result: {:?}", p.run()?);
Ok(())
}
```
### Listbox
Command
```bash
cargo run --manifest-path examples/listbox/Cargo.toml
```Code
```rust,ignore
use promkit::preset::listbox::Listbox;fn main() -> anyhow::Result<()> {
let mut p = Listbox::new(0..100)
.title("What number do you like?")
.prompt()?;
println!("result: {:?}", p.run()?);
Ok(())
}
```
### QuerySelector
Command
```bash
cargo run --manifest-path examples/query_selector/Cargo.toml
```Code
```rust,ignore
use promkit::preset::query_selector::QuerySelector;fn main() -> anyhow::Result<()> {
let mut p = QuerySelector::new(0..100, |text, items| -> Vec {
text.parse::()
.map(|query| {
items
.iter()
.filter(|num| query <= num.parse::().unwrap_or_default())
.map(|num| num.to_string())
.collect::>()
})
.unwrap_or(items.clone())
})
.title("What number do you like?")
.listbox_lines(5)
.prompt()?;
println!("result: {:?}", p.run()?);
Ok(())
}
```
### Checkbox
Command
```bash
cargo run --manifest-path examples/checkbox/Cargo.toml
```Code
```rust,ignore
use promkit::preset::checkbox::Checkbox;fn main() -> anyhow::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(())
}
```
### Tree
Command
```bash
cargo run --manifest-path examples/tree/Cargo.toml
```Code
```rust,ignore
use promkit::{preset::tree::Tree, promkit_widgets::tree::node::Node};fn main() -> anyhow::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(())
}
```
### JSON
Command
```bash
cargo run --manifest-path examples/json/Cargo.toml
```Code
```rust,ignore
use promkit::{
preset::json::Json,
promkit_widgets::{
jsonstream::JsonStream,
serde_json::{self, Deserializer},
},
};fn main() -> anyhow::Result<()> {
let stream = JsonStream::new(
Deserializer::from_str(
r#"
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"annotations": {
"kubeadm.kubernetes.io/etcd.advertise-client-urls": "https://172.18.0.2:2379",
"kubernetes.io/config.hash": "9c4c3ba79af7ad68d939c568f053bfff",
"kubernetes.io/config.mirror": "9c4c3ba79af7ad68d939c568f053bfff",
"kubernetes.io/config.seen": "2024-10-12T12:53:27.751706220Z",
"kubernetes.io/config.source": "file"
},
"creationTimestamp": "2024-10-12T12:53:31Z",
"labels": {
"component": "etcd",
"tier": "control-plane"
},
"name": "etcd-kind-control-plane",
"namespace": "kube-system",
"ownerReferences": [
{
"apiVersion": "v1",
"controller": true,
"kind": "Node",
"name": "kind-control-plane",
"uid": "6cb2c3e5-1a73-4932-9cc5-6d69b80a9932"
}
],
"resourceVersion": "192988",
"uid": "77465839-5a58-43b1-b754-55deed66d5ca"
},
"spec": {
"containers": [
{
"command": [
"etcd",
"--advertise-client-urls=https://172.18.0.2:2379",
"--cert-file=/etc/kubernetes/pki/etcd/server.crt",
"--client-cert-auth=true",
"--data-dir=/var/lib/etcd",
"--experimental-initial-corrupt-check=true",
"--experimental-watch-progress-notify-interval=5s",
"--initial-advertise-peer-urls=https://172.18.0.2:2380",
"--initial-cluster=kind-control-plane=https://172.18.0.2:2380",
"--key-file=/etc/kubernetes/pki/etcd/server.key",
"--listen-client-urls=https://127.0.0.1:2379,https://172.18.0.2:2379",
"--listen-metrics-urls=http://127.0.0.1:2381",
"--listen-peer-urls=https://172.18.0.2:2380",
"--name=kind-control-plane",
"--peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt",
"--peer-client-cert-auth=true",
"--peer-key-file=/etc/kubernetes/pki/etcd/peer.key",
"--peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt",
"--snapshot-count=10000",
"--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt"
],
"image": "registry.k8s.io/etcd:3.5.15-0",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 8,
"httpGet": {
"host": "127.0.0.1",
"path": "/livez",
"port": 2381,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 15
},
"name": "etcd",
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"host": "127.0.0.1",
"path": "/readyz",
"port": 2381,
"scheme": "HTTP"
},
"periodSeconds": 1,
"successThreshold": 1,
"timeoutSeconds": 15
},
"resources": {
"requests": {
"cpu": "100m",
"memory": "100Mi"
}
},
"startupProbe": {
"failureThreshold": 24,
"httpGet": {
"host": "127.0.0.1",
"path": "/readyz",
"port": 2381,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 15
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/var/lib/etcd",
"name": "etcd-data"
},
{
"mountPath": "/etc/kubernetes/pki/etcd",
"name": "etcd-certs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"enableServiceLinks": true,
"hostNetwork": true,
"nodeName": "kind-control-plane",
"preemptionPolicy": "PreemptLowerPriority",
"priority": 2000001000,
"priorityClassName": "system-node-critical",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"terminationGracePeriodSeconds": 30,
"tolerations": [
{
"effect": "NoExecute",
"operator": "Exists"
}
],
"volumes": [
{
"hostPath": {
"path": "/etc/kubernetes/pki/etcd",
"type": "DirectoryOrCreate"
},
"name": "etcd-certs"
},
{
"hostPath": {
"path": "/var/lib/etcd",
"type": "DirectoryOrCreate"
},
"name": "etcd-data"
}
]
},
"status": {
"conditions": [
{
"lastProbeTime": null,
"lastTransitionTime": "2024-12-06T13:28:35Z",
"status": "True",
"type": "PodReadyToStartContainers"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2024-12-06T13:28:34Z",
"status": "True",
"type": "Initialized"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2024-12-06T13:28:50Z",
"status": "True",
"type": "Ready"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2024-12-06T13:28:50Z",
"status": "True",
"type": "ContainersReady"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2024-12-06T13:28:34Z",
"status": "True",
"type": "PodScheduled"
}
],
"containerStatuses": [
{
"containerID": "containerd://de0d57479a3ac10e213df6ea4fc1d648ad4d70d4ddf1b95a7999d0050171a41e",
"image": "registry.k8s.io/etcd:3.5.15-0",
"imageID": "sha256:27e3830e1402783674d8b594038967deea9d51f0d91b34c93c8f39d2f68af7da",
"lastState": {
"terminated": {
"containerID": "containerd://28d1a65bd9cfa40624a0c17979208f66a5cc7f496a57fa9a879907bb936f57b3",
"exitCode": 255,
"finishedAt": "2024-12-06T13:28:31Z",
"reason": "Unknown",
"startedAt": "2024-11-04T15:14:19Z"
}
},
"name": "etcd",
"ready": true,
"restartCount": 2,
"started": true,
"state": {
"running": {
"startedAt": "2024-12-06T13:28:35Z"
}
}
}
],
"hostIP": "172.18.0.2",
"hostIPs": [
{
"ip": "172.18.0.2"
}
],
"phase": "Running",
"podIP": "172.18.0.2",
"podIPs": [
{
"ip": "172.18.0.2"
}
],
"qosClass": "Burstable",
"startTime": "2024-12-06T13:28:34Z"
}
}
"#,
)
.into_iter::()
.filter_map(serde_json::Result::ok)
.collect::>()
.iter(),
);let mut p = Json::new(stream).title("JSON viewer").prompt()?;
println!("result: {:?}", p.run()?);
Ok(())
}
```
## License
This project is licensed under the MIT License.
See the [LICENSE](https://github.com/ynqa/promkit/blob/main/LICENSE)
file for details.## Stargazers over time
[](https://starchart.cc/ynqa/promkit)