use crate::transport::factory::TransportOptions;
use std::time::Duration;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgType {
String,
Bool,
Integer,
MultiString,
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct ArgSpec {
pub name: &'static str,
pub help: &'static str,
pub arg_type: ArgType,
pub default: Option<&'static str>,
pub possible_values: Option<&'static [&'static str]>,
}
impl ArgSpec {
#[cfg(feature = "clap")]
pub fn to_clap_arg(&self) -> clap::Arg {
use clap::{Arg, ArgAction};
let mut arg = Arg::new(self.name).long(self.name).help(self.help);
match self.arg_type {
ArgType::Bool => {
arg = arg.action(ArgAction::SetTrue);
}
ArgType::MultiString => {
arg = arg.action(ArgAction::Append);
}
ArgType::String | ArgType::Integer => {
arg = arg.action(ArgAction::Set);
}
}
if let Some(default) = self.default {
arg = arg.default_value(default);
}
if let Some(values) = self.possible_values {
arg = arg.value_parser(values.to_vec());
}
arg
}
}
pub static COMMON_ARG_SPECS: &[ArgSpec] = &[
ArgSpec {
name: "can-disable-brs",
help: "Disable CAN-FD bit rate switching",
arg_type: ArgType::Bool,
default: None,
possible_values: None,
},
ArgSpec {
name: "force-transport",
help: "Force specific transport type",
arg_type: ArgType::String,
default: None,
possible_values: None,
},
ArgSpec {
name: "timeout-ms",
help: "Communication timeout in milliseconds",
arg_type: ArgType::Integer,
default: Some("100"),
possible_values: None,
},
];
pub fn transport_arg_specs() -> Vec<ArgSpec> {
use super::factory::get_factories;
let mut specs: Vec<ArgSpec> = COMMON_ARG_SPECS.to_vec();
for factory in get_factories() {
specs.extend(factory.arg_specs());
}
specs
}
impl TransportOptions {
#[cfg(feature = "clap")]
pub fn from_arg_matches(matches: &clap::ArgMatches) -> std::result::Result<Self, String> {
let mut opts = TransportOptions::new();
if let Some(values) = matches.get_many::<String>("fdcanusb") {
opts.fdcanusb_paths = values.cloned().collect();
}
if let Some(values) = matches.get_many::<String>("can-chan") {
opts.socketcan_interfaces = values.cloned().collect();
}
if matches.get_flag("can-disable-brs") {
opts.disable_brs = true;
}
if let Some(value) = matches.get_one::<String>("force-transport") {
opts.force_transport = Some(value.clone());
}
if let Some(value) = matches.get_one::<String>("timeout-ms") {
let ms: u32 = value
.parse()
.map_err(|_| format!("invalid timeout: {}", value))?;
opts.timeout = Duration::from_millis(ms as u64);
}
for factory in super::factory::get_factories() {
for spec in factory.arg_specs() {
if matches!(spec.name, "fdcanusb" | "can-chan") {
continue;
}
match spec.arg_type {
ArgType::MultiString => {
if let Some(values) = matches.get_many::<String>(spec.name) {
let vals: Vec<String> = values.cloned().collect();
if !vals.is_empty() {
opts.extra.insert(spec.name.to_string(), vals);
}
}
}
ArgType::Bool => {
if matches.get_flag(spec.name) {
opts.extra
.insert(spec.name.to_string(), vec!["true".to_string()]);
}
}
ArgType::String | ArgType::Integer => {
if let Some(value) = matches.get_one::<String>(spec.name) {
opts.extra
.insert(spec.name.to_string(), vec![value.clone()]);
}
}
}
}
}
Ok(opts)
}
}
#[cfg(feature = "clap")]
pub fn add_transport_args(mut cmd: clap::Command) -> clap::Command {
for spec in transport_arg_specs() {
cmd = cmd.arg(spec.to_clap_arg());
}
cmd
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "clap", derive(clap::Args))]
pub struct TransportArgs {
#[cfg_attr(feature = "clap", arg(long = "fdcanusb", action = clap::ArgAction::Append))]
pub fdcanusb: Vec<String>,
#[cfg_attr(feature = "clap", arg(long = "can-chan", action = clap::ArgAction::Append))]
pub can_chan: Vec<String>,
#[cfg_attr(feature = "clap", arg(long = "can-disable-brs"))]
pub can_disable_brs: bool,
#[cfg_attr(feature = "clap", arg(long = "force-transport"))]
pub force_transport: Option<String>,
#[cfg_attr(feature = "clap", arg(long = "timeout-ms", default_value = "100"))]
pub timeout_ms: u32,
}
impl TransportArgs {
pub fn new() -> Self {
Self {
timeout_ms: 100,
..Default::default()
}
}
pub fn into_options(self) -> TransportOptions {
TransportOptions {
fdcanusb_paths: self.fdcanusb,
socketcan_interfaces: self.can_chan,
disable_brs: self.can_disable_brs,
force_transport: self.force_transport,
timeout: Duration::from_millis(self.timeout_ms as u64),
extra: Default::default(),
}
}
}
impl From<TransportArgs> for TransportOptions {
fn from(args: TransportArgs) -> Self {
args.into_options()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transport_args_default() {
let args = TransportArgs::new();
assert_eq!(args.timeout_ms, 100);
assert!(args.fdcanusb.is_empty());
assert!(args.can_chan.is_empty());
assert!(!args.can_disable_brs);
assert!(args.force_transport.is_none());
}
#[test]
fn test_transport_args_to_options() {
let args = TransportArgs {
fdcanusb: vec!["/dev/ttyACM0".to_string()],
can_chan: vec!["can0".to_string(), "can1".to_string()],
can_disable_brs: true,
force_transport: Some("socketcan".to_string()),
timeout_ms: 200,
};
let opts: TransportOptions = args.into();
assert_eq!(opts.fdcanusb_paths, vec!["/dev/ttyACM0"]);
assert_eq!(opts.socketcan_interfaces, vec!["can0", "can1"]);
assert!(opts.disable_brs);
assert_eq!(opts.force_transport, Some("socketcan".to_string()));
assert_eq!(opts.timeout, Duration::from_millis(200));
}
#[test]
fn test_common_arg_specs_complete() {
let names: Vec<_> = COMMON_ARG_SPECS.iter().map(|s| s.name).collect();
assert!(names.contains(&"can-disable-brs"));
assert!(names.contains(&"force-transport"));
assert!(names.contains(&"timeout-ms"));
}
#[test]
fn test_transport_arg_specs_includes_factory_args() {
let specs = transport_arg_specs();
let names: Vec<_> = specs.iter().map(|s| s.name).collect();
assert!(names.contains(&"can-disable-brs"));
assert!(names.contains(&"force-transport"));
assert!(names.contains(&"timeout-ms"));
assert!(names.contains(&"fdcanusb"));
assert!(names.contains(&"can-chan"));
}
#[test]
fn test_arg_specs_types() {
let specs = transport_arg_specs();
for spec in &specs {
match spec.name {
"fdcanusb" | "can-chan" => {
assert_eq!(spec.arg_type, ArgType::MultiString);
}
"can-disable-brs" => {
assert_eq!(spec.arg_type, ArgType::Bool);
}
"force-transport" => {
assert_eq!(spec.arg_type, ArgType::String);
}
"timeout-ms" => {
assert_eq!(spec.arg_type, ArgType::Integer);
assert_eq!(spec.default, Some("100"));
}
_ => {} }
}
}
}