How to parse custom string with Clap derive - rust

I found a link for what i want here: Parse user input String with clap for command line programming
But it's not completely clear. I see lots of post using App::new() but i can't find any trace of it in clap documentation.
I want to parse a string inside my rust application (it doesn't come from the cmdline)
Currently i'm doing it like this:
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Optional name to operate on
test: String,
name: Option<String>,
}
pub fn test(mut to_parse: &String) {
let Ok(r) = shellwords::split(to_parse) else {
info!("error during process");
return;
};
let test = Cli::parse_from(r);
if let Some(name) = test.name.as_deref() {
println!("Value for name: {}", name);
}
}
And it kind of work, but maybe i don't understand enough how clap work. It only take positional argument like --test or --name. there must be a way to have a command before argument?
And the other question i have is : if i'm using a struct to define a command and use Cli::parse_from() to parse my string, how do i parse my string with multiple command (maybe unclear, but how do i use multiple commands?)
------EDIT
I've played with clap a little, and now i've something like this:
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Com,
}
/// Doc comment
#[derive(Subcommand)]
enum Com {
/// First command
Test(TestArg),
///Second command
SecondTest(OtherArg),
}
#[derive(Args)]
struct TestArg {
name: String,
}
#[derive(Args)]
struct OtherArg {
name: Option<String>,
and: Option<String>,
}
Nevertheless, when i enter a command in my in-game console (still parse with parse_from), nothing is recognize, i always have the error message.

Your question does not show what you pass in and what error you get, so it's rather hard to point to an issue.
I think you might be missing the first argument which clap (apparently) requires to work when parsing from an array of string slices, which is the binary name/path.
Using your code from above (2nd snippet), the snippet below does not work.
let cli = Cli::parse_from(["test", "asd"]);
dbg!(cli); // You need to add #[derive(Debug)] to your structs for it to work
as it ends with an error like
error: The subcommand 'asd' wasn't recognized
Usage: test <COMMAND>
For more information try '--help'
If you look closely, you'll see that the test argument, which we are trying to pass as a subcommand to our Cli struct, is recognized as the binary name.
If we add an additional argument at the beginning (the content does not matter, it can be even an empty string slice), it does work.
fn main() {
let cli = Cli::parse_from(["", "test", "asd"]);
dbg!(cli);
}
The above snippet prints successfully parsed arguments
[src/main.rs:32] cli = Cli {
command: Test(
TestArg {
name: "asd",
},
),
}
It does work also with the second sub-command SecondTest
let cli = Cli::parse_from(["", "second-test", "some name"]);
dbg!(cli);
Prints
[src/main.rs:35] cli = Cli {
command: SecondTest(
OtherArg {
name: Some(
"some name",
),
and: None,
},
),
}
Nevertheless, it seems like your code is missing some attribute macros in order to work as (I think) you expect it to work.
Assuming you want to parse it from a string like
mycmd test --name someName
your TestArg struct should look like this
#[derive(Args)]
struct TestArg {
#[clap(short, long)]
name: String,
}
now the name is taken not as a positional argument but as a flag(?) argument (required in this case), where the argument needs to be provided as --name <NAME> or -n <NAME>
Same for the second subcommand, you could declare it like this
#[derive(Args, Debug)]
struct OtherArg {
#[clap(long)]
name: Option<String>,
#[clap(long)]
and: Option<String>,
}
And then you need to pass the arguments with flags with long names, like so:
let cli = Cli::parse_from([
"",
"second-test",
"--name",
"some name",
"--and",
"other name",
]);
dbg!(cli);
which results in
[src/main.rs:41] cli = Cli {
command: SecondTest(
OtherArg {
name: Some(
"some name",
),
and: Some(
"other name",
),
},
),
}

Related

Flatbuffer multiple roots doesn't work as expected

I'm evaluating/learning flatbuffers and I've written a schema and some basic code. The schema contains two root tables but when I try to convert a wrong root it doesn't fail. Is this expected behavior?
schema.fbs:
table Weapon {
name:string;
damage:short;
two_handed:bool;
}
root_type Weapon;
table Shield {
name:string;
damage:short;
}
root_type Shield;
main.rs:
use flatbuffers;
// import the generated code
#[allow(dead_code, unused_imports)]
#[path = "./schema_generated.rs"]
mod schema;
fn main() {
let mut sword_builder = flatbuffers::FlatBufferBuilder::new();
let sword_name = sword_builder.create_string("Sword");
let sword = schema::Weapon::create(
&mut sword_builder,
&schema::WeaponArgs {
name: Some(sword_name),
damage: 10,
two_handed: false,
},
);
sword_builder.finish(sword, None);
let sword_buffer = sword_builder.finished_data();
let mut shield_builder = flatbuffers::FlatBufferBuilder::new();
let shield_name = shield_builder.create_string("Shield");
let shield = schema::Weapon::create(
&mut shield_builder,
&schema::WeaponArgs {
name: Some(shield_name),
damage: 2,
two_handed: true,
},
);
shield_builder.finish(shield, None);
let shield_buffer = shield_builder.finished_data();
// Lets decode our buffers
let sword_decoded = flatbuffers::root::<schema::Weapon>(&sword_buffer).unwrap();
println!("{:#?}", sword_decoded);
let shield_decoded = flatbuffers::root::<schema::Shield>(&shield_buffer).unwrap();
println!("{:#?}", shield_decoded);
// This should fail:
let sword_decoded_failure = flatbuffers::root::<schema::Weapon>(&shield_buffer).unwrap();
println!("{:#?}", sword_decoded_failure);
}
output:
Weapon {
name: Some(
"Sword",
),
damage: 10,
two_handed: false,
}
Shield {
name: Some(
"Shield",
),
damage: 2,
}
Weapon {
name: Some(
"Shield",
),
damage: 2,
two_handed: true,
}
github link: https://github.com/ic3man5/fb_test
Documentation for root:
Gets the root of the Flatbuffer, verifying it first with default options. Note that verification is an experimental feature and may not be maximally performant or catch every error (though that is the goal). See the _unchecked variants for previous behavior.
I would expect it to be able to catch a basic error like this? If so I can see two work arounds, one to prepend a header in front of the bytes to identify the table or using a flatbuffer union (I don't want to do this).
Flatbuffers only allows one root type per schema and one instance of it per buffer. So your schema needs to change to reflect this.
As for there being no error, a verifier takes a binary buffer of bytes and checks that it can be safely accessed according to the current schema. There is no type information in the binary bytes, so if the bytes happen to be safely accessible by another schema, it may succeed. To force it to not succeed, you could add a file_identifier to your schema, which if the Rust verifier checks it, would cause it to fail for the wrong schema.

How to refer to the program name in help strings?

In my CLI program the usage examples are provided as part of the help message. Using the clap derive interface I can do the following
#[derive(Parser, Debug, Default)]
#[clap( after_help = "EXAMPLES:\n $ foo abc.txt")]
pub struct CmdLine {...}
The program name foo is hard coded in the literal string above.
How can I avoid hard-coding the program name and get it dynamically; for example, from std::env::args[0] or clap::App:get_bin_name() ?
clap provides a macro called crate_name! that will take the name from your cargo.toml.
For example, suppose you have this in your cargo.toml.
[package]
name = "myapp"
description = "myapp description"
version = "0.1.0"
edition = "2021"
authors = [ "John Doe" ]
Then, in your application, you can fetch these values using the macros, like this:
let matches = Command::new(clap::crate_name!())
.version(clap::crate_version!())
.author(clap::crate_authors!())
.about(clap::crate_description!())
//
// abbreviated
//
The section below is appended to respond to the original poster's specific question. See the comments below for context. Also, including some learnings as well.
Appended per the discussion in comments.
Based on the comments/discussion below, initial thought is just to stuff the binary name from the arguments into a string and pass into the after_help() function. For example, something like this:
let bin_name = std::env:args().into_iter().next().unwrap();
let matches = Command::new(bin_name)
.after_help(format!("Text that includes {}", bin_name)) // This won't compile
.get_matches();
Taking this approach, you quickly run into a lifetime requirement in the function signature for after_help(). From clap's repo:
pub fn after_help<S: Into<&'help str>>(mut self, help: S)
In fact, if you look, there are many fields in the Command struct that have the lifetime annotation (&'help) on them. The Command::new() method doesn't have this lifetime annotation so it worked fine to just pass it bin_name as shown above.
Below is an abbreviated solution that dynamically generates after-help text in a manner that adheres to the lifetime requirements. Assuming a clean binary (application), called "foo", add the following code:
cargo.toml
[package]
name = "foo"
version = "0.1.0"
description = "A foo cli application"
authors = [ "John Doe" ]
edition = "2021"
[dependencies]
clap = { version = "3.1.6", features = ["cargo"] }
main.rs
fn main() {
// Get the binary name from the command line
let bin_name = std::env::args().into_iter().next().unwrap();
// Construct text that will be used in after_help.
let after_help_text = format!(
"Some after-help text that includes the binary name: {}",
bin_name
);
// clap, by default, will reference the name of your package. So, if you're
// doing the above, you might as well override the usage text too so you're
// being consistent.
let usage_text = format!("{}", bin_name);
if let Err(e) = foo::get_args(bin_name, after_help_text, usage_text).and_then(foo::run) {
eprintln!("{e}");
std::process::exit(1);
}
}
lib.rs
use clap::{ArgMatches, Command};
pub fn get_args(
bin_name: String,
after_help_text: String,
usage_text: String,
) -> std::io::Result<ArgMatches> {
let matches = Command::new(bin_name)
.override_usage(usage_text.as_str())
.version(clap::crate_version!())
.after_help(after_help_text.as_str())
.author(clap::crate_authors!())
.about(clap::crate_description!())
// add and configure args...
.get_matches();
Result::Ok(matches)
}
pub fn run(matches: ArgMatches) -> std::io::Result<()> {
// Do your CLI logic here based on matches.
Ok(())
}
Running the solution ( cargo run -- --help ) will produce the following output:
./foo 0.1.0
John Doe
A foo cli application
USAGE:
./foo
OPTIONS:
-h, --help Print help information
-V, --version Print version information
Some after-help text that includes the binary name: ./foo

How to use mail filter context data?

I am trying to write a mail filter in Rust using the milter crate. I built the example on a Linux VM and it all works fine. However, the example is using u32 as the type of context injected into their handlers, a quite simple example. I instead need to store a string from the handle_header callback through to the handle_eom handler so I can use an incoming header to set the envelope from.
If I log the value of the header in handle_header to console, it writes correctly but by the time it arrives in handle_eom, it has been corrupted/overwritten whatever. I thought that context was supposed to be specifically for this scenario but it seems weird that it uses type inference rather than e.g. a pointer to an object that you can just assign whatever you want to it.
Is my understanding of context wrong or is the code incorrect?
I tried using value and &value in handle_header and it behaves the same way.
use milter::*;
fn main() {
Milter::new("inet:3000#localhost")
.name("BounceRewriteFilter")
.on_header(header_callback)
.on_eom(eom_callback)
.on_abort(abort_callback)
.actions(Actions::ADD_HEADER | Actions::REPLACE_SENDER)
.run()
.expect("milter execution failed");
}
#[on_header(header_callback)]
fn handle_header<'a>(mut context: Context<&'a str>, header: &str, value: &'a str) -> milter::Result<Status> {
if header == "Set-Return-Path" {
match context.data.borrow_mut() {
Some(retpath) => *retpath = &value,
None => {
context.data.replace(value)?;
}
}
}
Ok(Status::Continue)
}
#[on_eom(eom_callback)]
fn handle_eom(mut context: Context<&str>) -> milter::Result<Status> {
match context.data.take() {
Ok(result) => {
println!("Set-return-path header is {}", result.unwrap());
context.api.replace_sender(result.unwrap(), None::<&str>)?;
}
Err(_error) => {}
}
Ok(Status::Continue)
}
Thanks to glts on Github, the author of the crate, the problem was that the string slices passed into the handle_header method were not borrowed by the external code that stores the data pointer so by the time that handle_eom is called, the memory has been reused for something else.
All I had to do was change Context<&str> to Context<String> and convert the strings using mystr.to_owned() and in the reverse direction val = &*mystring

StructOpt: How to use subcommand enum's fields in clap::arg attribute methods?

I have the following setup:
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
struct CliArgs {
#[structopt(short, long)]
aisle: Option<String>,
#[structopt(short, long)]
shelf: Option<String>,
#[structopt(subcommand)]
operation: Option<Ops>,
}
#[derive(Debug, StructOpt)]
enum Ops {
Add {
isbn: Option<String>,
pin: Option<String>
},
Remove {
isbn: Option<String>,
},
Status,
}
In this scenario, I'd like a condition where either aisle and shelf are together specified or isbn alone is specified. I found both raw clap methods required_unless and conflicts_with, and their variants required_unless_all and conflicts_with_all together could be of use.
Maybe using them like such:
...
#[structopt(short, long, required_unless = "isbn", conflicts_with = "isbn")]
aisle: Option<String>,
#[structopt(short, long, required_unless = "isbn", conflicts_with = "isbn")]
shelf: Option<String>,
...
and
...
Add {
#[structopt(required_unless_all = &["aisle", "shelf"], conflicts_with_all = &["aisle", "shelf"])]
isbn: Option<String>,
pin: Option<String>
}
...
But it results in the following
~❯ RUST_BACKTRACE=full cargo run -q -- --aisle A1 --shelf X3 add 2441
error: The following required arguments were not provided:
<pin>
Which is right because passed pin is used as isbn instead.
~❯ RUST_BACKTRACE=full cargo run -q -- add A1/X3 2441
error: The following required arguments were not provided:
--aisle <aisle>
--shelf <shelf>
~❯ RUST_BACKTRACE=full cargo run -q -- --aisle B1 --shelf Y3 add A1/X3 2441
<It works>
~❯ RUST_BACKTRACE=full cargo run -q -- add
error: The following required arguments were not provided:
<isbn>
<pin>
Which doesn't work for obvious reasons. I think the main issue with my problem is that the fields can't reference each other if put under a sub-command. Am I doing something wrong with the current approach? Would there be a better alternative to clap::arg methods in this case? Or is there a way of communicating between sub-commands?

Idiomatic rust way to properly parse Clap ArgMatches

I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).
Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file.
Now I don't know how to do this in an idiomatic way.
fn main() {
let matches = App::new("findx")
.version(crate_version!())
.author(crate_authors!())
.about("find + directory operations utility")
.arg(
Arg::with_name("paths")
...
)
.arg(
Arg::with_name("patterns")
...
)
.arg(
Arg::with_name("operation")
...
)
.get_matches();
let paths;
let patterns;
let operation;
if matches.is_present("patterns") && matches.is_present("operation") {
patterns = matches.values_of("patterns").unwrap().collect();
paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
operation = match matches.value_of("operation").unwrap() { // I dont like this
"Append" => Operation::Append,
"Prepend" => Operation::Prepend,
"Rename" => Operation::Rename,
_ => {
print!("Operation unsupported");
process::exit(1);
}
};
}else if Path::new("findx.yml").is_file(){
//TODO: try load from config file
}else{
eprintln!("Command line parameters or findx.yml file must be provided");
process::exit(1);
}
if let Err(e) = findx::run(Config {
paths: paths,
patterns: patterns,
operation: operation,
}) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.
Can someone help me with this, or point me to the right direction?
Best Regards
Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.
There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.
From the clap documentation
//sets value of arg "other" to "default" if value of "--opt" is "special"
let m = App::new("prog")
.arg(Arg::with_name("opt")
.takes_value(true)
.long("opt"))
.arg(Arg::with_name("other")
.long("other")
.default_value_if("opt", Some("special"), "default"))
.get_matches_from(vec![
"prog", "--opt", "special"
]);
assert_eq!(m.value_of("other"), Some("default"));
In addition you can add a validator to your operation OR convert your valid operation values into flags.
Here's an example converting your match arm values into individual flags (smaller example for clarity).
extern crate clap;
use clap::{Arg,App};
fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
//Sets the command line interface of the program.
App::new("something")
.version("0.1")
.arg(Arg::with_name("rename")
.help("renames something")
.short("r")
.long("rename"))
.arg(Arg::with_name("prepend")
.help("prepends something")
.short("p")
.long("prepend"))
.arg(Arg::with_name("append")
.help("appends something")
.short("a")
.long("append"))
.get_matches()
}
#[derive(Debug)]
enum Operation {
Rename,
Append,
Prepend,
}
fn main() {
let matches = command_line_interface();
let operation = if matches.is_present("rename") {
Operation::Rename
} else if matches.is_present("prepend"){
Operation::Prepend
} else {
//DEFAULT
Operation::Append
};
println!("Value of operation is {:?}",operation);
}
I hope this helps!
EDIT:
You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.
let app_m = App::new("git")
.subcommand(SubCommand::with_name("clone"))
.subcommand(SubCommand::with_name("push"))
.subcommand(SubCommand::with_name("commit"))
.get_matches();
match app_m.subcommand() {
("clone", Some(sub_m)) => {}, // clone was used
("push", Some(sub_m)) => {}, // push was used
("commit", Some(sub_m)) => {}, // commit was used
_ => {}, // Either no subcommand or one not tested for...
}

Resources