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 ArgGroup
s 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_args
directly. It's whatArgs::parse
does, too. - Use
let args = match args { Ok(args) => args, Err(e) => exit_on_error(e) };
(Must change the type ofexit_on_error
for this.) - Make
exit_on_error
have typefn(Result<Args, String>) -> Args
and move thematch
into it. - Make
main
returnResult<(), Box<dyn std::error::Error>>
so you can use?
to unwrapResult
s (though you would need to choose a different error type with a niceDisplay
implementation type instead ofString
. Might as well use theanyhow
crate.) - Use
error.as_ref().err().unwrap()
(You could even addOption::cloned
to 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.