Issue
I'm trying to develop a simple cli bill manager in rust using clap. I want to let the user add new bills by entering its name and value, remove some bills, and I also want to add undo and redo features. As it is my first project in rust, I called it p1. In the terminal, the user must do like this:
p1 -n bill1 -v 50
And then, the bill will be added or
p1 undo 5
Then, the last 5 actions will be undone. But, because of the usual functioning of clap, at least as far as I understood, this behavior is also accepted:
p1 -n bill2 -v 30 redo 30
And I don't want to allow it. I don't want to let the user use flags and subcommands at the same time. So I made some validation. To make it easier for you to help me I will show the pertinent part of the code.
use clap::{Parser, Subcommand};
use std::{collections::HashMap, path::PathBuf};
use home::home_dir;
use std::fs;
#[derive(Parser, Debug)]
struct Args {
/// The name of the bill
#[clap(short, long, value_parser)]
name: Option<String>,
/// The value of the bill
#[clap(short, long, value_parser)]
value: Option<u32>,
#[clap(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Undo
Undo { undo: Option<u32> },
/// Redo
Redo { redo: Option<u32> },
/// Remove
Remove { remove: Option<String> },
}
fn validate_args(args: Args) -> Result<Args, String> {
match (&args.name, &args.value, &args.command) {
(Some(_), Some(_), None) => Ok(args),
(None, None, Some(_)) => Ok(args),
(None, None, None) => Ok(args),
_ => Err("You can't use options and subcommands at the same time".to_string())
}
}
fn exit_on_error(error: &Result<Args, String>) {
println!("{:?}", error);
panic!("aaaaaaaaaaaaaaaaaa");
}
fn main() {
let args: Result<Args, String> = validate_args(Args::parse());
match args {
Ok(_) => (),
Err(_) => exit_on_error(&args)
};
...
}
Another thing I need help is. When the user do not insert neither flags nor subcommands, just typing "p1" in the terminal, I want to redirect him to the help subcommand as if he had typed
p1 help
How can I do it?
And also, I'm still not used to the rust style of handle with variable possession. The "exit_on_error" function can only receive borrowed results because, apparently, strings cannot implement Copy. This prevents me to unwrap the Err before printing it, which makes it appears with quotation marks in the terminal. What should I do to workaround It?
Please help me and let me know if something isn't clear in my question.
Solution
I'd agree with @SirDarius that you probably shouldn't do this, but eh, doesn't mean it hurts to know how you could do it:
When the user do not insert neither flags nor subcommands, just typing "p1" in the terminal, I want to redirect him to the help subcommand as if he had typed
p1 help
If knowing whether any arguments were passed at all from the parsed Args is difficult, you can for example sidestep clap and check the argument count from std::env::args()
if std::env::args().count() <= 1 {
Args::parse_from(&[
// Argument 0 is always the program name.
// Could just use "p1" here, but this is more generic:
std::env::args()
.next()
.as_ref()
.map(String::as_str)
.unwrap_or(env!("CARGO_CRATE_NAME")),
// as if help was typed:
"help",
]);
unreachable!("Should print help and exit");
}
I think you can also use clap's ArgGroups to achieve this kind of behaviour, but I find them to be clunky.
And also, I'm still not used to the rust style of handle with variable possession.
This is a classic Rust problem, and there's so many things you could do:
- Just exit from
validate_argsdirectly. It's whatArgs::parsedoes, too. - Use
let args = match args { Ok(args) => args, Err(e) => exit_on_error(e) };(Must change the type ofexit_on_errorfor this.) - Make
exit_on_errorhave typefn(Result<Args, String>) -> Argsand move thematchinto it. - Make
mainreturnResult<(), Box<dyn std::error::Error>>so you can use?to unwrapResults (though you would need to choose a different error type with a niceDisplayimplementation type instead ofString. Might as well use theanyhowcrate.) - Use
error.as_ref().err().unwrap()(You could even addOption::clonedto have an owned copy of the error string.)
Lastly: One question per question on StackOverflow, please.
Answered By - Caesar Answer Checked By - Pedro (PHPFixing Volunteer)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.