use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Duration;
use clap::{ArgMatches, Command, FromArgMatches, Subcommand};
use clap_complete::engine::ArgValueCandidates;
use cerulion_cli_engine::message_text::VerbError;
use cerulion_cli_engine::topic_cmd::{publish, snapshot, TopicScope};
use super::NestedVerbs;
use crate::cli::VerbLogClass;
use crate::completion;
use crate::network_switch::{self, NetworkRefusal, SwitchEnv};
const PARENT: &str = "topic";
#[derive(Subcommand)]
enum TopicMessageAction {
Pub {
#[arg(long)]
local: bool,
#[arg(long, value_name = "NAME", conflicts_with = "local",
add = ArgValueCandidates::new(completion::robot_names))]
robot: Option<String>,
#[arg(long, value_name = "N", default_value_t = 1,
value_parser = clap::value_parser!(u64).range(1..))]
times: u64,
#[arg(long, value_name = "HZ")]
rate: Option<f64>,
#[arg(long, value_name = "SECS", default_value_t = publish::DEFAULT_WAIT_SUBSCRIBERS.as_secs_f64())]
wait_subscribers: f64,
#[arg(add = ArgValueCandidates::new(completion::topics))]
topic: String,
#[arg(value_name = "TYPE", add = ArgValueCandidates::new(completion::schema_names))]
type_name: String,
message: String,
},
Snapshot {
#[arg(long)]
local: bool,
#[arg(long, value_name = "NAME", conflicts_with = "local",
add = ArgValueCandidates::new(completion::robot_names))]
robot: Option<String>,
#[arg(short = 'o', long = "output", value_name = "PATH")]
output: Option<PathBuf>,
#[arg(long, value_name = "SECS", default_value_t = snapshot::DEFAULT_SNAPSHOT_TIMEOUT.as_secs_f64())]
timeout: f64,
#[arg(long, value_name = "Q", default_value_t = snapshot::DEFAULT_JPEG_QUALITY,
value_parser = clap::value_parser!(u8).range(1..=100))]
quality: u8,
#[arg(add = ArgValueCandidates::new(completion::topics))]
topic: String,
},
}
pub(crate) struct TopicMessage;
impl NestedVerbs for TopicMessage {
fn parent(&self) -> &'static str {
PARENT
}
fn commands(&self) -> Vec<Command> {
super::derived_verbs::<TopicMessageAction>()
}
fn log_class(&self, _matches: &ArgMatches) -> VerbLogClass {
VerbLogClass::OneShot
}
fn network_refusal(&self, matches: &ArgMatches, env: &SwitchEnv) -> Option<NetworkRefusal> {
use cerulion_cli_engine::robot_act::{TOPIC_PUBLISH_VERB, TOPIC_SNAPSHOT_VERB};
let (robot, verb) = match TopicMessageAction::from_arg_matches(matches).ok()? {
TopicMessageAction::Pub { robot, .. } => (robot, TOPIC_PUBLISH_VERB),
TopicMessageAction::Snapshot { robot, .. } => (robot, TOPIC_SNAPSHOT_VERB),
};
network_switch::robot_refusal(robot.as_deref(), verb, env)
}
fn needs_identity(&self, _matches: &ArgMatches) -> bool {
true
}
fn run(&self, matches: &ArgMatches) -> ExitCode {
let action = TopicMessageAction::from_arg_matches(matches).unwrap_or_else(|e| e.exit());
verb_exit_code(run(action))
}
}
pub(crate) fn verb_exit_code(result: Result<String, VerbError>) -> ExitCode {
match result {
Ok(line) => {
println!("{line}");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("Error: {e}");
ExitCode::from(e.exit_code())
}
}
}
pub(crate) fn seconds(flag: &str, secs: f64) -> Result<Duration, VerbError> {
Duration::try_from_secs_f64(secs).map_err(|_| {
VerbError::Usage(format!(
"{flag} must be a number of seconds, 0 or more, got {secs}"
))
})
}
fn run(action: TopicMessageAction) -> Result<String, VerbError> {
let ws = crate::discover_optional_workspace()?;
let schemas_dir = ws.as_ref().map(|w| w.schemas_dir.as_path());
let running = crate::setup_ctrlc_handler()?;
match action {
TopicMessageAction::Pub {
local,
robot,
times,
rate,
wait_subscribers,
topic,
type_name,
message,
} => {
let req = publish::PubRequest {
topic: &topic,
type_name: &type_name,
message: &message,
times,
rate_hz: rate,
wait_subscribers: seconds("--wait-subscribers", wait_subscribers)?,
scope: TopicScope::from_local(local),
};
match robot {
Some(robot) => cerulion_cli_engine::robot_act::robot_pub(&robot, &req),
None => publish::topic_pub(&req, schemas_dir, &running).map(|r| r.line()),
}
}
TopicMessageAction::Snapshot {
local,
robot,
output,
timeout,
quality,
topic,
} => {
let req = snapshot::SnapshotRequest {
topic: &topic,
output: output.as_deref(),
timeout: seconds("--timeout", timeout)?,
quality,
scope: TopicScope::from_local(local),
};
match robot {
Some(robot) => cerulion_cli_engine::robot_act::robot_snapshot(&robot, &req),
None => {
snapshot::topic_snapshot(&req, schemas_dir, &running).map(|r| r.json_line())
}
}
}
}
}