10 releases (5 breaking)
| 0.6.4 | Sep 9, 2025 |
|---|---|
| 0.6.3 | Sep 9, 2025 |
| 0.5.3 | Sep 7, 2025 |
| 0.5.0 | May 1, 2025 |
| 0.1.0 | Apr 10, 2025 |
#479 in Asynchronous
446 downloads per month
215KB
5K
SLoC
ModCLI is a lightweight, modular CLI framework for Rust. A fully customizable and feature-rich system for registering commands, styling output, and running interactive shells with zero bloat. Built to speed up CLI development and get powerful tools running fast—without rewriting the basics.
Key Features
Installation
Add the library to your Cargo.toml:
[dependencies]
mod-cli = "0.6.4"
Add the library to your Cargo.toml with features:
[dependencies]
mod-cli = { version = "0.6.4", features = ["gradients", "table-presets"] }
Feature Flags
| Feature | Description |
|---|---|
internal-commands |
Built-in helper commands like help, ping, etc. |
custom-commands |
Ergonomic helpers for user-defined commands. |
tracing-logs |
Emit tracing events via output::hook alongside console output. |
dispatch-cache |
Single-entry dispatch cache to speed repeated invocations. |
gradients |
Named gradient helpers (24‑bit RGB) with zero extra deps. |
layouts |
Lightweight layout engine for terminal rows/columns. |
table-presets |
Convenience presets for TableStyle (ASCII, Rounded, Heavy). |
progress-presets |
Convenience constructors for ProgressStyle (compact, heavy). |
theme-config |
Enable theme config serialization (serde/serde_json). |
images |
Optional image support (png/jpeg) via the image crate. |
Usage
Basic Usage
use modcli::ModCli;
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let mut cli = ModCli::new();
cli.run(args);
}
Set a custom prefix
use modcli::ModCli;
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let mut cli = ModCli::new();
// Set a custom prefix
cli.set_prefix("myCLI");
cli.run(args);
}
Using named colors
let teal = colors::get("teal"); // always returns a Color (or fallback)
let demo = build()
.part("Color Demo:").space()
.part("Teal").color(teal).bold().get();
print::line(&demo, 0);
Using Gradients
Two-color gradient:
use modcli::output::{
gradient,
print,
RED, ORANGE
};
let gradient_text = gradient::two_color("Two color gradient", RED, ORANGE);
print::line(&gradient_text);
Three-color gradient:
use modcli::output::{
gradient,
print,
BLUE, GREEN, YELLOW
};
let gradient_text = gradient::three_color("Three color gradient", BLUE, GREEN, YELLOW);
print::line(&gradient_text);
Multi-color gradient:
use modcli::output::{
gradient,
print,
RED, ORANGE, YELLOW, GREEN, BLUE
};
let gradient_text = gradient::multi_color("Multi-color gradient", vec![RED, ORANGE, YELLOW, GREEN, BLUE]);
print::line(&gradient_text);
Using RGB with gradients:
use modcli::output::{
gradient,
print
};
let gradient_text = gradient::two_color(
"Gradient Output",
Color::Rgb { r: 255, g: 0, b: 0 },
Color::Rgb { r: 0, g: 0, b: 255 },
);
print::line(&gradient_text);
Output Styles
use modcli::output::{
print,
build,
BLUE
};
// 📦 Progress Bar Demo
let testing = build()
.part("Testing")
.color(BLUE)
.bold()
.get();
print::line(&testing);
// Outputs "Testing" in bold/blue.
Multiple Styles:
use modcli::output::{
gradient,
print,
build,
BLUE, LIGHT_BLUE
};
// 📦 Progress Bar Demo
let testing = build()
.part("Label:").color(BLUE).bold().space()
.part("This content has").space()
.part("multiple").color(LIGHT_BLUE).bold().space()
.part("styles").underline().space()
.part("and").italic().space()
.part("colors").underline().space()
.part("!")
.get();
print::line(&testing);
Style + Gradients:
use modcli::output::{
print,
build,
BLUE, GREEN
};
let gradient_text = gradient::two_color("Gradient Output", BLUE, GREEN);
let testing = build()
.part(&gradient_text).bold().space()
.part("+ Styled!")
.get();
print::line(&testing);
Progress Bar & Animated Loaders
Auto Progress:
use modcli::output::{
progress::{
show_progress_bar,
}
};
show_progress_bar("Testing", 45, 1500);
Displays
Label [#############################################] 100% Done!
Manual control:
use modcli::output::{
build,
progress::{
ProgressBar,
ProgressStyle,
},
LIGHT_BLUE
};
// Progress Bar Demo
let label = build()
.part("Loading")
.color(LIGHT_BLUE)
.bold()
.get();
let mut bar = ProgressBar::new(30, ProgressStyle {
fill: '■',
done_label: "Complete!",
color: Some(LIGHT_BLUE),
..Default::default()
});
bar.set_label(&label);
bar.start_auto(2000); // auto-fill in 2 seconds
Manual .tick() control (like during a loop):
use std::time::Duration;
use modcli::output::{
progress::{
ProgressBar,
ProgressStyle
},
ORANGE
};
use modcli::console::run_shell;
let mut bar = ProgressBar::new(10, ProgressStyle {
fill: '■',
done_label: "Done!",
color: Some(ORANGE),
..Default::default()
});
bar.set_label("Syncing");
for _ in 0..10 {
bar.tick();
std::thread::sleep(Duration::from_millis(200));
}
println!(" {}", bar.style.done_label);
Animated Spinner (Loading/Waiting):
use modcli::output::{
progress::{
show_spinner
}
};
show_spinner("Loading", 20, 100);
Animated Percentage Loader:
use std::thread::sleep;
use std::time::Duration;
use modcli::output::{
progress::{
show_percent_progress
}
};
for i in (0..=100).step_by(10) {
show_percent_progress("Loading", i);
sleep(Duration::from_millis(100));
}
println!();
Tables
Table Example: Flex Width, Heavy Borders
use crate::output::table::{render_table, TableMode, TableStyle};
let headers = ["Name", "Age", "Role"];
let rows = vec![
vec!["Alice", "29", "Engineer"],
vec!["Bob", "35", "Manager"],
vec!["Charlie", "41", "CTO"],
];
render_table(&headers, &rows, TableMode::Flex, TableStyle::Heavy);
Outputs
┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃Name ┃Age ┃Role ┃
┣━━━━━━━━╋━━━━━━━━╋━━━━━━━━┫
┃Alice ┃29 ┃Engineer┃
┃Bob ┃35 ┃Manager ┃
┃Charlie ┃41 ┃CTO ┃
┗━━━━━━━━┻━━━━━━━━┻━━━━━━━━┛
Table Example: Fixed Width, Rounded Borders
use crate::output::table::{render_table, TableMode, TableStyle};
let headers = ["Name", "Age", "Role"];
let rows = vec![
vec!["Alice", "29", "Engineer"],
vec!["Bob", "35", "Manager"],
vec!["Charlie", "41", "CTO"],
];
render_table(&headers, &rows, TableMode::Fixed(15), TableStyle::Rounded);
Outputs
╭───────────────┬───────────────┬───────────────╮
│Name │Age │Role │
├───────────────┼───────────────┼───────────────┤
│Alice │29 │Engineer │
│Bob │35 │Manager │
│Charlie │41 │CTO │
╰───────────────┴───────────────┴───────────────╯
Table Example: Fixed Width, Ascii Borders
use crate::output::table::{render_table, TableMode, TableStyle};
let headers = ["Name", "Age", "Role"];
let rows = vec![
vec!["Alice", "29", "Engineer"],
vec!["Bob", "35", "Manager"],
vec!["Charlie", "41", "CTO"],
];
render_table(&headers, &rows, TableMode::Fixed(15), TableStyle::Ascii);
Outputs
+---------------+---------------+---------------+
|Name |Age |Role |
+---------------+---------------+---------------+
|Alice |29 |Engineer |
|Bob |35 |Manager |
|Charlie |41 |CTO |
+---------------+---------------+---------------+
Creating Custom Commands
File Structure
my_project/
├── src/
│ ├── commands/
│ │ └── greet.rs ← define `GreetCommand` here
Create a commands folder in src/, then put the command in its own file:
Custom Command File
use modcli::command::Command;
pub struct GreetCommand;
impl Command for GreetCommand {
fn name(&self) -> &str {
"greet"
}
fn aliases(&self) -> &[&str] {
&["hi"]
}
fn help(&self) -> Option<&str> {
Some("Greets the user.")
}
fn validate(&self, _args: &[String]) -> Result<(), String> {
Ok(())
}
fn execute(&self, _args: &[String]) {
println!("Greetings!");
}
}
greet.rs
Register your command in main.rs, tool.rs, etc.
mod commands;
use modcli::ModCli;
use commands::greet::GreetCommand;
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let mut cli = ModCli::new();
// Register function
cli.registry.register(Box::new(GreetCommand));
cli.run(args);
}
Test Command
$ myCLI greet
Greetings!
$ myCLI help
List of available commands...
Interactive Shell
ModCLI supports an interactive console mode (like a REPL):
use modcli::config::CliConfig;
use modcli::console::run_shell;
fn main() {
let config = CliConfig::load(None);
run_shell(config);
}
Adding a custom console command for shell:
use modcli::ModCli;
use modcli::shell_commands::{register, ShellCommand};
fn greet_handler(_input: &str) -> bool {
println!("👋 Hello from shell command!");
true
}
fn main() {
register(ShellCommand {
name: "greet",
aliases: &["hi", "wave"],
help: "Greets the user with a friendly hello",
handler: greet_handler,
});
}
Config File Example (config.json)
{
"modcli": {
"name" : "mod-cli",
"prefix": "mod",
"banner": "Welcome to ModCLI",
"delay" : 0,
"theme" : "default",
"strict": false,
"force_shell": false,
"shell": {
"prompt": "Tool >",
"welcome": ["Welcome to the console."],
"goodbye": ["Bye!"]
},
"messages": {
"no_command": "No command provided.",
"not_found": "Command not found."
}
}
}
Default location:
project_root/config.json
Manually set the config path (if not project root)
use modcli::config;
fn main() {
config::set_path(("my/custom/config.json");
Warning
Pre-release: This project is in active development. The core is stable but features are evolving. Production use is possible, but interfaces may still evolve until 1.0.
📌 License
Licensed under the Apache License, version 2.0 (the "License"); you may not use this software, including, but not limited to the source code, media files, ideas, techniques, or any other associated property or concept belonging to, associated with, or otherwise packaged with this software except in compliance with the License.
You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the LICENSE file included with this project for the specific language governing permissions and limitations under the License.
COPYRIGHT © 2025 JAMES GOBER.
Dependencies
~5–21MB
~256K SLoC