-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle parsing vector of custom structures #1704
Comments
I tried some prove of concept. Actually it is implemented with structopt, not clap. Just to give some idea. I found that structopt has method To split all parameters in such subgroups I used CodeHere is implementation with only 2 such sensor parameters // Standard modules paths
use std::env;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt()]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[structopt(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[structopt(long, default_value = "9898")]
socket_port: u16,
}
#[derive(StructOpt, Debug)]
#[structopt()]
pub struct OptSensor {
/// Number in decimal
#[structopt(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[structopt(long)]
sensor_i2c_address: Option<u8>,
}
#[derive(Debug)]
pub struct Config {
generic: OptGeneric,
sensors: Vec<OptSensor>,
}
impl Config {
pub fn parse(args: std::env::Args) -> Config {
// Split command line arguments into groups separated by '--sensor' delimiter
let arguments: Vec<String> = args.collect();
let mut sensor_parameters_groups = arguments.split(|argument| argument == "--sensor");
// First group of parameters is generic without any information for specific sensor
let generic_parameters = sensor_parameters_groups.next()
.unwrap();
// Parse generic parameters
let generic = OptGeneric::from_iter(generic_parameters);
// Create vector for sensors to be parsed
let mut sensors = Vec::new();
// Iterate over rest of groups each containing complete sensor information
for sensor_parameters in sensor_parameters_groups {
// OptSensor::from_iter is going to skip first item, so it have to be put anything to be skipped
// TODO maybe StructOpt::from_iter should not skip first item to not have to workaround it?
let array_with_empty_item = [String::from("")];
let iter_with_extra_first_item = array_with_empty_item.iter().chain(sensor_parameters);
// Parse each sensor information
let sensor = OptSensor::from_iter(iter_with_extra_first_item);
// Fill sensors vector
sensors.push(sensor);
}
// Return result
Config{generic, sensors}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = Config::parse(env::args());
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} RunInvocations are like that:
% cargo run --
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter`
Config {
generic: OptGeneric {
socket_interface: "0.0.0.0",
socket_port: 9898,
},
sensors: [],
}
% cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
`# 3 physical sensors located on physycial different i2c bus or address` \
--sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
--sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=2 \
--sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
>
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 --sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 --sensor --sensor-i2c-bus=0 --sensor-i2c-address=2 --sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
Config {
generic: OptGeneric {
socket_interface: "1.1.1.1",
socket_port: 10000,
},
sensors: [
OptSensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
1,
),
},
OptSensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
2,
),
},
OptSensor {
sensor_i2c_bus: 1,
sensor_i2c_address: Some(
1,
),
},
],
}
% cargo run -- --help
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --help`
prometheus_sensors_exporter 0.1.2
USAGE:
prometheus_sensors_exporter [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--socket-interface <socket-interface> Address of interface to bind [default: 0.0.0.0]
--socket-port <socket-port> Number in decimal [default: 9898] Questions
|
I have some another tries subcommand enumCode // Standard modules paths
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[structopt(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[structopt(long, default_value = "9898")]
socket_port: u16,
#[structopt(subcommand)]
sensor: OptSensor
}
#[derive(StructOpt, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[structopt(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[structopt(long)]
sensor_i2c_address: Option<u8>,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::from_args();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run with one sensor % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1`
OptGeneric {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensor: Sensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
1,
),
},
} Run with 2 sensors % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
>
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
error: Found argument 'sensor' which wasn't expected, or isn't valid in this context
USAGE:
prometheus_sensors_exporter sensor --sensor-i2c-address <sensor-i2c-address> --sensor-i2c-bus <sensor-i2c-bus>
For more information try --help subcommand vec enumCode // Standard modules paths
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[structopt(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[structopt(long, default_value = "9898")]
socket_port: u16,
#[structopt(subcommand)]
sensors: Vec<OptSensor>
}
#[derive(StructOpt, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[structopt(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[structopt(long)]
sensor_i2c_address: Option<u8>,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::from_args();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Cannot compile % cargo build
Compiling prometheus_sensors_exporter v0.1.2 (/home/k/project/prometheus_sensors_exporter)
error[E0277]: the trait bound `std::vec::Vec<OptSensor>: structopt::StructOptInternal` is not satisfied
--> src/main.rs:17:17
|
17 | #[structopt(subcommand)]
| ^^^^^^^^^^ the trait `structopt::StructOptInternal` is not implemented for `std::vec::Vec<OptSensor>`
|
= note: required by `structopt::StructOptInternal::from_subcommand`
error[E0277]: the trait bound `std::vec::Vec<OptSensor>: structopt::StructOptInternal` is not satisfied
--> src/main.rs:4:10
|
4 | #[derive(StructOpt, Debug)]
| ^^^^^^^^^ the trait `structopt::StructOptInternal` is not implemented for `std::vec::Vec<OptSensor>`
|
= note: required by `structopt::StructOptInternal::augment_clap`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.
error: could not compile `prometheus_sensors_exporter`.
To learn more, run the command again with --verbose. Should I implement StructOptInternal trait? Is it something recommended in this case? |
Can you try with the master branch of clap? We have |
@pksunkara, sure I would try it. |
Using master clap subcommand enumCode // Standard modules paths
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(subcommand)]
sensor: OptSensor
}
#[derive(Clap, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::parse();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run with one sensor is ok % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
Compiling prometheus_sensors_exporter v0.1.2 (/home/k/project/prometheus_sensors_exporter)
Finished dev [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1`
OptGeneric {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensor: Sensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
1,
),
},
} Run with 2 sensors is not allowed % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
error: Found argument 'sensor' which wasn't expected, or isn't valid in this context
If you tried to supply `sensor` as a PATTERN use `-- sensor`
USAGE:
prometheus_sensors_exporter sensor [OPTIONS]
For more information try --help subcommand vec enumCode // Standard modules paths
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(subcommand)]
sensors: Vec<OptSensor>
}
#[derive(Clap, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::parse();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Cannot compile. Error message is much better than structopt [Running 'cargo check']
Checking prometheus_sensors_exporter v0.1.2 (/home/k/project/prometheus_sensors_exporter)
error[E0277]: the trait bound `std::vec::Vec<OptSensor>: clap::derive::Subcommand` is not satisfied
--> src/main.rs:4:10
|
4 | #[derive(Clap, Debug)]
| ^^^^ the trait `clap::derive::Subcommand` is not implemented for `std::vec::Vec<OptSensor>`
|
= note: required by `clap::derive::Subcommand::augment_subcommands`
error[E0277]: the trait bound `std::vec::Vec<OptSensor>: clap::derive::Subcommand` is not satisfied
--> src/main.rs:17:12
|
17 | #[clap(subcommand)]
| ^^^^^^^^^^ the trait `clap::derive::Subcommand` is not implemented for `std::vec::Vec<OptSensor>`
|
= note: required by `clap::derive::Subcommand::from_subcommand`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.
error: could not compile `prometheus_sensors_exporter`.
To learn more, run the command again with --verbose.
[Finished running. Exit status: 101] |
Clap has
Maybe this is some solution? Could it be sub-sub-sub-...-commands also? Could it be wrapped into Option (with Box?) to brake infinite recursion? This try is interesting as it compiles ok, but overflow stack in runtime Code // Standard modules paths
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(subcommand)]
sensors: OptSensor
}
#[derive(Clap, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
#[clap(subcommand)]
sensors: Option<Box<OptSensor>>
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::parse();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
Compiling prometheus_sensors_exporter v0.1.2 (/home/k/project/prometheus_sensors_exporter)
Finished dev [unoptimized + debuginfo] target(s) in 0.62s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1`
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
[1] 9558 abort (core dumped) cargo run -- `# generic configuration` --socket-interface=1.1.1.1 sensor |
I see that there is some proper example of subcommands on some nested levels |
I see that example from documentation have specified tree of subcommands known ahead at compile time. Here is some another attempt not multipleCode // Standard modules paths
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(subcommand)]
sensor: OptSensor,
}
#[derive(Clap, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
#[clap()]
more_optional_sensors: Vec<String>,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::parse();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run help % cargo run -- help
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter help`
prometheus_sensors_exporter
USAGE:
prometheus_sensors_exporter [OPTIONS] <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--socket-interface <socket-interface> Address of interface to bind [default: 0.0.0.0]
--socket-port <socket-port> Number in decimal [default: 9898]
SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
sensor Run help for subcommand % cargo run -- help sensor
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter help sensor`
prometheus_sensors_exporter-sensor
USAGE:
prometheus_sensors_exporter sensor [OPTIONS] [more-optional-sensors]...
ARGS:
<more-optional-sensors>...
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--sensor-i2c-address <sensor-i2c-address> Number in hexadecimal
--sensor-i2c-bus <sensor-i2c-bus> Number in decimal [default: 1] Run with one sensor % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
>
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1`
OptGeneric {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensor: Sensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
1,
),
more_optional_sensors: [],
},
} Run with 2 sensors % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
\
sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
>
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
error: The argument '--sensor-i2c-bus <sensor-i2c-bus>' was provided more than once, but cannot be used multiple times
USAGE:
prometheus_sensors_exporter sensor [OPTIONS] [more-optional-sensors]...
For more information try --help multiple// Standard modules paths
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptGeneric {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(subcommand)]
sensor: OptSensor,
}
#[derive(Clap, Debug)]
pub enum OptSensor {
Sensor {
/// Number in decimal
#[clap(long, default_value = "1", multiple = true)]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long, multiple = true)]
sensor_i2c_address: Option<u8>,
#[clap()]
more_optional_sensors: Vec<String>,
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = OptGeneric::parse();
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run with 2 sensors % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
\
sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
OptGeneric {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensor: Sensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
1,
),
more_optional_sensors: [
"sensor",
],
},
} Parsing consumes all parameters overwriting previous ones and they are not available in more_optional_sensors strings vector. |
Here is best result I have so far structoptCode // Standard modules paths
use std::env;
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptBasic {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(flatten)]
sensor: OptSensor,
// Field is declared only for help message
// Vector is never filled
/// Each sensor definition should start with <sensor> keyword followed by its options
#[clap()]
more_optional_sensors: Vec<String>,
}
#[derive(Clap, Debug)]
pub struct OptSensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
}
#[derive(Debug)]
pub struct Config {
// Address of interface to bind
socket_interface: String,
// Number in decimal
socket_port: u16,
// All sensors
sensors: Vec<OptSensor>,
}
impl Config {
pub fn parse(args: std::env::Args) -> Self {
// Split command line arguments into groups separated by '--sensor' delimiter
let arguments: Vec<String> = args.collect();
let mut sensor_parameters_groups = arguments.split(|argument| argument == "sensor");
// First group of parameters is basic without any information for specific sensor
let basic_parameters = sensor_parameters_groups.next()
.unwrap();
// Parse basic parameters
let basic = OptBasic::from_iter(basic_parameters);
// Create vector to be filled with all parsed sensors
let mut sensors = Vec::new();
// Add already parsed basic sensor to list of all sensors
sensors.push(basic.sensor);
// Iterate over rest of groups each containing complete sensor information
for sensor_parameters in sensor_parameters_groups {
// OptSensor::from_iter is going to skip first item, so it have to be put anything to be skipped
// TODO maybe StructOpt::from_iter should not skip first item to not have to workaround it?
let array_with_empty_item = [String::from("")];
let iter_with_extra_first_item = array_with_empty_item.iter().chain(sensor_parameters);
// Parse each sensor information
let sensor = OptSensor::from_iter(iter_with_extra_first_item);
// Fill sensors vector
sensors.push(sensor);
}
// Return result
Config {
socket_interface: basic.socket_interface,
socket_port: basic.socket_port,
sensors,
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = Config::parse(env::args());
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run help % cargo run -- --help
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --help`
prometheus_sensors_exporter 0.1.2
USAGE:
prometheus_sensors_exporter [OPTIONS] [more-optional-sensors]...
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--sensor-i2c-address <sensor-i2c-address> Number in hexadecimal
--sensor-i2c-bus <sensor-i2c-bus> Number in decimal [default: 1]
--socket-interface <socket-interface> Address of interface to bind [default: 0.0.0.0]
--socket-port <socket-port> Number in decimal [default: 9898]
ARGS:
<more-optional-sensors>... Each sensor definition should start with <sensor> keyword followed by its options Run with one sensor % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
`# Basic sensor` \
--sensor-i2c-bus=4 \
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 --sensor-i2c-bus=4`
Config {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensors: [
OptSensor {
sensor_i2c_bus: 4,
sensor_i2c_address: None,
},
],
} Run with 3 sensors % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
`# Basic sensor` \
--sensor-i2c-bus=4 \
\
`# More optional sensors` \
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
\
sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 --sensor-i2c-bus=4 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
Config {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensors: [
OptSensor {
sensor_i2c_bus: 4,
sensor_i2c_address: None,
},
OptSensor {
sensor_i2c_bus: 0,
sensor_i2c_address: Some(
1,
),
},
OptSensor {
sensor_i2c_bus: 1,
sensor_i2c_address: Some(
1,
),
},
],
} clapCode // Standard modules paths
use std::env;
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptBasic {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(flatten)]
sensor: OptSensor,
// Field is declared only for help message
// Vector is never filled
/// Each sensor definition should start with <sensor> keyword followed by its options
#[clap()]
more_optional_sensors: Vec<String>,
}
#[derive(Clap, Debug)]
pub struct OptSensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
}
#[derive(Debug)]
pub struct Config {
// Address of interface to bind
socket_interface: String,
// Number in decimal
socket_port: u16,
// All sensors
sensors: Vec<OptSensor>,
}
impl Config {
pub fn parse(args: std::env::Args) -> Self {
// Split command line arguments into groups separated by '--sensor' delimiter
let arguments: Vec<String> = args.collect();
let mut sensor_parameters_groups = arguments.split(|argument| argument == "sensor");
// First group of parameters is basic without any information for specific sensor
let basic_parameters = sensor_parameters_groups.next()
.unwrap();
// Parse basic parameters
let basic = OptBasic::from_iter(basic_parameters);
// Create vector to be filled with all parsed sensors
let mut sensors = Vec::new();
// Add already parsed basic sensor to list of all sensors
sensors.push(basic.sensor);
// Iterate over rest of groups each containing complete sensor information
for sensor_parameters in sensor_parameters_groups {
// OptSensor::from_iter is going to skip first item, so it have to be put anything to be skipped
// TODO maybe StructOpt::from_iter should not skip first item to not have to workaround it?
let array_with_empty_item = [String::from("")];
let iter_with_extra_first_item = array_with_empty_item.iter().chain(sensor_parameters);
// Parse each sensor information
let sensor = OptSensor::from_iter(iter_with_extra_first_item);
// Fill sensors vector
sensors.push(sensor);
}
// Return result
Config {
socket_interface: basic.socket_interface,
socket_port: basic.socket_port,
sensors,
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = Config::parse(env::args());
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Cannot compile [Running 'cargo check']
Checking prometheus_sensors_exporter v0.1.2 (/home/k/project/prometheus_sensors_exporter)
error[E0599]: no function or associated item named `from_iter` found for type `OptBasic` in the current scope
--> src/main.rs:68:32
|
6 | pub struct OptBasic {
| ------------------- function or associated item `from_iter` not found for this
...
68 | let basic = OptBasic::from_iter(basic_parameters);
| ^^^^^^^^^ function or associated item not found in `OptBasic`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `from_iter`, perhaps you need to implement it:
candidate #1: `std::iter::FromIterator`
error[E0599]: no function or associated item named `from_iter` found for type `OptSensor` in the current scope
--> src/main.rs:85:38
|
30 | pub struct OptSensor {
| -------------------- function or associated item `from_iter` not found for this
...
85 | let sensor = OptSensor::from_iter(iter_with_extra_first_item);
| ^^^^^^^^^ function or associated item not found in `OptSensor`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `from_iter`, perhaps you need to implement it:
candidate #1: `std::iter::FromIterator`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0599`.
error: could not compile `prometheus_sensors_exporter`.
To learn more, run the command again with --verbose.
[Finished running. Exit status: 101] Is there any similar method to pass custom iterator for clap parser? |
Here is same thing as previous implemented with clap. clapCode // Standard modules paths
use std::env;
use clap::Clap;
#[derive(Clap, Debug)]
pub struct OptBasic {
// Socket
/// Address of interface to bind
#[clap(long, default_value = "0.0.0.0")]
socket_interface: String,
/// Number in decimal
#[clap(long, default_value = "9898")]
socket_port: u16,
#[clap(flatten)]
sensor: OptSensor,
// Field is declared only for help message
// Vector is never filled
/// Each sensor definition should start with <sensor> keyword followed by its options
#[clap()]
more_optional_sensors: Vec<String>,
}
#[derive(Clap, Debug)]
pub struct OptSensor {
/// Number in decimal
#[clap(long, default_value = "1")]
sensor_i2c_bus: usize,
/// Number in hexadecimal
#[clap(long)]
sensor_i2c_address: Option<u8>,
}
#[derive(Debug)]
pub struct Config {
// Address of interface to bind
socket_interface: String,
// Number in decimal
socket_port: u16,
// All sensors
sensors: Vec<OptSensor>,
}
impl Config {
pub fn parse(args: std::env::Args) -> Self {
// Split command line arguments into groups separated by '--sensor' delimiter
let arguments: Vec<String> = args.collect();
let mut sensor_parameters_groups = arguments.split(|argument| argument == "sensor");
// First group of parameters is basic without any information for specific sensor
let basic_parameters = sensor_parameters_groups.next()
.unwrap();
// Parse basic parameters
let basic = OptBasic::parse_from(basic_parameters);
// Create vector to be filled with all parsed sensors
let mut sensors = Vec::new();
// Add already parsed basic sensor to list of all sensors
sensors.push(basic.sensor);
// Iterate over rest of groups each containing complete sensor information
for sensor_parameters in sensor_parameters_groups {
// Parse each sensor information
let sensor = OptSensor::parse_from(sensor_parameters);
// Fill sensors vector
sensors.push(sensor);
}
// Return result
Config {
socket_interface: basic.socket_interface,
socket_port: basic.socket_port,
sensors,
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse configuration
let config = Config::parse(env::args());
// Debug purpose only
println!("{:#?}", config);
// Return success
Ok(())
} Run with some sensors % cargo run -- \
\
`# generic configuration` \
--socket-interface=1.1.1.1 \
--socket-port=10000 \
\
`# Basic sensor` \
--sensor-i2c-bus=4 \
\
`# More optional sensors` \
sensor \
--sensor-i2c-bus=0 \
--sensor-i2c-address=1 \
\
sensor \
--sensor-i2c-bus=1 \
--sensor-i2c-address=1 \
Compiling prometheus_sensors_exporter v0.1.2 (/home/k/project/prometheus_sensors_exporter)
Finished dev [unoptimized + debuginfo] target(s) in 0.65s
Running `target/debug/prometheus_sensors_exporter --socket-interface=1.1.1.1 --socket-port=10000 --sensor-i2c-bus=4 sensor --sensor-i2c-bus=0 --sensor-i2c-address=1 sensor --sensor-i2c-bus=1 --sensor-i2c-address=1`
Config {
socket_interface: "1.1.1.1",
socket_port: 10000,
sensors: [
OptSensor {
sensor_i2c_bus: 4,
sensor_i2c_address: None,
},
OptSensor {
sensor_i2c_bus: 1,
sensor_i2c_address: Some(
1,
),
},
OptSensor {
sensor_i2c_bus: 1,
sensor_i2c_address: Some(
1,
),
},
],
} Help % cargo run -- --help
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/prometheus_sensors_exporter --help`
prometheus_sensors_exporter
USAGE:
prometheus_sensors_exporter [OPTIONS] [more-optional-sensors]...
ARGS:
<more-optional-sensors>... Each sensor definition should start with <sensor> keyword followed by its options
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--sensor-i2c-address <sensor-i2c-address> Number in hexadecimal
--sensor-i2c-bus <sensor-i2c-bus> Number in decimal [default: 1]
--socket-interface <socket-interface> Address of interface to bind [default: 0.0.0.0]
--socket-port <socket-port> Number in decimal [default: 9898] It works fine. Clap and structopt are great :) Clap looks a little better. |
To summarize: I have parsing of command line arguments using this great library. For my case I had to split all commands to some smaller groups and run individual parsing for them. I'm wondering if it could be possible to express this logic by clap itself? Is such case worth to implement? Would it be more users interested in it? Or there are a low chance that anyone else would like to use clap in similar way? Is it useful to implement passing vector of enums? Something like #[clap(subcommand)]
// or #[clap(subcommands)] ?
field: Vec<UserEnum> Maybe there is some better way to model such command line arguments to fit into current clap implementation? |
@CreepySkeleton What do you think about this whole issue? It's a different way of doing things for sure. |
I'm afraid there's no way to implement this exact design with clap, but you can get pretty close. You see, You can overcome this limitation by applying some sort of external preprocessing, splitting the args into groups manually, as you do in the I can think about the alternative:
The idea is to threat each group as one option with multiple arguments, each parameter is a key-value pair. Unfortunately, parsing these values is up to you, and the auto generated help message leaves much to be desired.
Those are good questions, and whether we will consider adding support for this use case or not depends on the answers. This is the first such request, and it is also the first time I personally encounter this design.
Ideas are welcome! |
I found this issue trying to do the exact same thing. The case is that I want to pass multiple |
We've also talked some about this in #2683 |
I'd still love to see this, currently my CLI api is very much suffering from this not being implemented ^^' |
In the upcoming clap v4, we at least have a Builder example for position-sensitive options: https://github.com/clap-rs/clap/blob/master/examples/find.rs. That can be re-worked to handle grouping of arguments. The derive reference describes how to integrate sections of builder code into wider derive uses. |
b20a140 Fix some clippies.. (rajarshimaitra) 1e21cc7 Fix wasm (rajarshimaitra) ba1c165 Update cargo lock. (rajarshimaitra) 02e71b0 Reverse the recipient parsing string (rajarshimaitra) 95719b0 Remove vector of string from compact_filters options. (rajarshimaitra) 9fb651a Update wasm build to 1.57 in CI (rajarshimaitra) 7e87a65 update MSRV to 1.57 (rajarshimaitra) 3dcc269 Update Cargo lock (rajarshimaitra) f8c0f2e Update recipient parsing in handlers. (rajarshimaitra) 5bbc45a Move from structopt to clap (rajarshimaitra) Pull request description: <!-- You can erase any parts of this template not applicable to your Pull Request. --> ### Description Fixes #113. This is an attempt to migrate from `structopt` to `clap v0.3` which provides very similar kind of derives as `structopt`. Changes are straight forward. But this comes with few more problems. - with clap `v3.2.22` the MSRV pushes up to `1.57.0`.. The last clap of MSRV `1.56.0` was `clap 3.2.5`.. But even that doesn't seem to be working at MSRV `1.56.0` anymore, as bunch of underlying lib has upgraded. - `clap v3.0` doesn't seem to support custom vector parsing well, reported here clap-rs/clap#1704. This is required for `recipient` parsing which is a `Vec<(Script, u32)>`. Workaround for that is to use vecs of strings and parse them at runtime in `create_tx` handler. Included in the PR. ### Notes to the reviewers `structopt` is currently freezed at `clap 2.0` and doesn't seem to intend on updating and currently its has unmaintained vulnerability. And `clap v3.0` onward seems to replacing everything that `structopt` did before. So this means we should also look for ways to migrate from `strcutopt` to `clap`.. But `clap` seemed to have moved ahead than our MSRV `1.56.0`.. So we need to take up a call on that.. Opened this PR to facilitate that discussion.. This is draft until we figure what to do.. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing ACKs for top commit: notmandatory: ACK b20a140 Tree-SHA512: de3511c1531185064e3c328f0c966c3e87294b7c6d07a89ba9f64e49f7c8b8ccaef0915f49fc6721b738f91a0128b30eeb61147a2da174ccac9abab094ab6798
Has there been any changes with this, I've been struggling to implement it for a few hours but it doesn't seem to be possible. |
A thought I had: combining command-chaining (#2222) with subcommand flags would allow users to create something like this (when there is a struct-start argument) |
Uhh, fortunately I encountered this issue and found it opened here, unfortunately the solution is not yet in sight. I don't think command-chaining is a solution to this issue, it just seems like a workaround. An argument itself means a single value, but how a single value is parsed actually depends on the value parser of the argument itself. I expect |
At present, without affecting the structural design, I use |
What are you trying to do?
I have group of options which could be used multiple times. All such groups should be kept without overwriting previous one.
--sensor
specifies instance of new group. Then suboptions are only for current group.Ideally parsing would generate instance of Vector<Sensor> defined something like
Could it be possible?
Or maybe would you recommend to model it in some another way which could be easier to implement?
What have you tried already?
I tired few examples, but I didn't find anything similar so far.
The text was updated successfully, but these errors were encountered: