12 个版本 (重大变更)
0.9.0 | 2024 年 1 月 9 日 |
---|---|
0.8.0 | 2023 年 5 月 30 日 |
0.7.0 | 2022 年 11 月 13 日 |
0.6.0 | 2021 年 9 月 25 日 |
0.5.1 | 2021 年 7 月 25 日 |
#129 in 命令行界面
3,874 每月下载量
用于 2 crates
52KB
957 行
Shellfish
Shellfish 是一个库,可以在程序中包含交互式壳。当构建需要持久状态的终端应用程序时,这可能很有用;但基本 CLI 不够用;而完整的 TUI 超出了项目的范围。Shellfish 提供了一条中间道路,允许交互式命令编辑,同时保存所有命令都可以访问的状态。
壳
默认情况下,壳只包含 3 个内置命令
help
- 显示帮助信息。quit
- 退出壳。exit
- 退出壳。
后两个是相同的,只是名称不同。
当用户添加命令时(见下文),会自动生成并显示帮助。请注意,此帮助应保持相对简短,任何额外的帮助都应通过专用帮助选项提供。
特性
以下特性可用
rustyline
,用于更好的输入。这提供了一个InputHandler
app
,用于命令行参数解析。async
,用于异步。这可以与tokio
或async_std
配合使用clap
,用于与clap
库集成。
示例
以下代码创建了一个基本的壳,并添加了以下命令
greet
,问候用户。echo
,回显输入。count
,增加计数器。cat
,它是 cat。
此外,如果带有参数运行,则壳将非交互式运行。
use std::error::Error;
use std::fmt;
use std::ops::AddAssign;
use async_std::prelude::*;
use rustyline::DefaultEditor;
use shellfish::{app, handler::DefaultAsyncHandler, Command, Shell};
#[macro_use]
extern crate shellfish;
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Define a shell
let mut shell = Shell::new_with_async_handler(
0_u64,
"<[Shellfish Example]>-$ ",
DefaultAsyncHandler::default(),
DefaultEditor::new()?,
);
// Add some commands
shell
.commands
.insert("greet", Command::new("greets you.".to_string(), greet));
shell
.commands
.insert("echo", Command::new("prints the input.".to_string(), echo));
shell.commands.insert(
"count",
Command::new("increments a counter.".to_string(), count),
);
shell.commands.insert(
"cat",
Command::new_async(
"Displays a plaintext file.".to_string(),
async_fn!(u64, cat),
),
);
// Check if we have > 2 args, if so no need for interactive shell
let mut args = std::env::args();
if args.nth(1).is_some() {
// Create the app from the shell.
let mut app = app::App::try_from_async(shell)?;
// Set the binary name
app.handler.proj_name = Some("shellfish-example".to_string());
app.load_cache()?;
// Run it
app.run_args_async().await?;
} else {
// Run the shell
shell.run_async().await?;
}
Ok(())
}
/// Greets the user
fn greet(_state: &mut u64, args: Vec<String>) -> Result<(), Box<dyn Error>> {
let arg = args.get(1).ok_or_else(|| Box::new(GreetingError))?;
println!("Greetings {}, my good friend.", arg);
Ok(())
}
/// Echos the input
fn echo(_state: &mut u64, args: Vec<String>) -> Result<(), Box<dyn Error>> {
let mut args = args.iter();
args.next();
for arg in args {
print!("{} ", arg);
}
println!();
Ok(())
}
/// Acts as a counter
fn count(state: &mut u64, _args: Vec<String>) -> Result<(), Box<dyn Error>> {
state.add_assign(1);
println!("You have used this counter {} times", state);
Ok(())
}
/// Asynchronously reads a file
async fn cat(
_state: &mut u64,
args: Vec<String>,
) -> Result<(), Box<dyn Error>> {
use async_std::fs;
if let Some(file) = args.get(1) {
let mut contents = String::new();
let mut file = fs::File::open(file).await?;
file.read_to_string(&mut contents).await?;
println!("{}", contents);
}
Ok(())
}
/// Greeting error
#[derive(Debug)]
pub struct GreetingError;
impl fmt::Display for GreetingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "No name specified")
}
}
impl Error for GreetingError {}
Clap 支持
clap
允许更干净、更简单地处理命令行参数,如下所示
// ... imports ...
/// Simple command to greet a person
///
/// This command will greet the person based of a multitide
/// of option flags, see below.
#[derive(Parser, Debug)]
#[clap(author, version, about)]
struct GreetArgs {
/// Name of the person to greet
name: String,
/// Age of the person to greet
#[clap(short, long)]
age: Option<u8>,
/// Whether to be formal or note
#[clap(short, long)]
formal: bool,
}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Define a shell
let mut shell = Shell::new_with_async_handler(
(),
"<[Shellfish Example]>-$ ",
DefaultAsyncHandler::default(),
DefaultEditor::new()?,
);
shell
.commands
.insert("greet", clap_command!((), GreetArgs, greet));
shell.run_async().await?;
Ok(())
}
fn greet(
_state: &mut (),
args: GreetArgs,
) -> Result<(), Box<dyn std::error::Error>> {
// .. snip .. /
Ok(())
}
对于大型项目,建议使用 clap 来减少样板代码。
依赖关系
~3–18MB
~196K SLoC