use clap::{Args, Subcommand, ValueEnum, ValueHint};
use std::time::Duration;
use twinleaf::device::DeviceRoute;
use crate::{parse_device_route, TioOpts};
#[derive(Args, Debug)]
#[command(args_conflicts_with_subcommands = true)]
pub struct LogCli {
#[command(flatten)]
pub tio: TioOpts,
#[command(subcommand)]
pub subcommands: Option<LogSubcommands>,
#[arg(short = 'f', default_value_t = default_log_path())]
pub file: String,
#[arg(short = 'u')]
pub unbuffered: bool,
#[arg(long)]
pub raw: bool,
#[arg(long = "depth")]
pub depth: Option<usize>,
#[arg(long, value_parser = humantime::parse_duration)]
pub duration: Option<Duration>,
}
#[derive(Subcommand, Debug)]
pub enum LogSubcommands {
#[command(args_conflicts_with_subcommands = true)]
Meta {
#[command(flatten)]
tio: TioOpts,
#[command(subcommand)]
subcommands: Option<MetaSubcommands>,
#[arg(short = 'f', default_value = "meta.tio")]
file: String,
},
Dump {
#[arg(value_hint = ValueHint::FilePath, required = true, num_args = 1..)]
files: Vec<String>,
#[arg(short = 'd', long = "data")]
data: bool,
#[arg(short = 'm', long = "meta")]
meta: bool,
#[arg(short = 's', long = "sensor", default_value = "/", value_parser = parse_device_route)]
sensor: DeviceRoute,
#[arg(long = "depth")]
depth: Option<usize>,
},
Inspect {
#[arg(value_hint = ValueHint::FilePath, required = true, num_args = 1..)]
files: Vec<String>,
},
Csv {
#[arg(value_hint = ValueHint::FilePath)]
args: Vec<String>,
#[arg(short = 's', value_parser = parse_device_route)]
sensor: Option<DeviceRoute>,
#[arg(short = 'o')]
output: Option<String>,
#[arg(short = 'f', long)]
force: bool,
},
#[command(alias = "hdf5")]
Hdf {
#[arg(value_hint = ValueHint::FilePath, required = true, num_args = 1..)]
files: Vec<String>,
#[arg(short = 'o')]
output: Option<String>,
#[arg(short = 'g', long = "glob")]
filter: Option<String>,
#[arg(short = 'c', long = "compress")]
compress: bool,
#[arg(short = 'd', long)]
debug: bool,
#[arg(short = 'l', long = "split", default_value = "none")]
split_level: SplitLevel,
#[arg(short = 'p', long = "policy", default_value = "continuous")]
split_policy: SplitPolicy,
},
}
#[derive(Subcommand, Debug)]
pub enum MetaSubcommands {
Reroute {
#[arg(value_hint = ValueHint::FilePath)]
input: String,
#[arg(short = 's', long = "sensor", value_parser = parse_device_route)]
route: DeviceRoute,
#[arg(short = 'o', long = "output")]
output: Option<String>,
},
}
#[derive(Clone, Debug)]
pub enum StreamSel {
Id(u8),
Name(String),
}
#[derive(Clone, Debug)]
pub struct CsvTarget {
pub route: Option<DeviceRoute>,
pub stream: StreamSel,
}
pub fn parse_csv_target(s: &str) -> Result<CsvTarget, String> {
if s.is_empty() {
return Err("empty stream selector".into());
}
let last = s.rsplit('/').next().unwrap_or(s);
if last.is_empty() {
return Err(format!("missing stream name or id in {s:?}"));
}
let stream = match last.parse::<u8>() {
Ok(id) => StreamSel::Id(id),
Err(_) => StreamSel::Name(last.to_string()),
};
let route = if s.contains('/') {
let prefix = s[..s.len() - last.len()].trim_end_matches('/');
Some(parse_device_route(prefix)?)
} else {
None
};
Ok(CsvTarget { route, stream })
}
fn default_log_path() -> String {
chrono::Local::now()
.format("log.%Y%m%d-%H%M%S.tio")
.to_string()
}
#[derive(ValueEnum, Clone, Debug, Default)]
pub enum SplitPolicy {
#[default]
Continuous,
Monotonic,
}
#[cfg(feature = "hdf5")]
impl From<SplitPolicy> for twinleaf::data::export::SplitPolicy {
fn from(policy: SplitPolicy) -> Self {
match policy {
SplitPolicy::Continuous => Self::Continuous,
SplitPolicy::Monotonic => Self::Monotonic,
}
}
}
#[derive(ValueEnum, Clone, Debug, Default)]
pub enum SplitLevel {
#[default]
None,
Stream,
Device,
Global,
}
#[cfg(feature = "hdf5")]
impl From<SplitLevel> for twinleaf::data::export::RunSplitLevel {
fn from(level: SplitLevel) -> Self {
match level {
SplitLevel::None => Self::None,
SplitLevel::Stream => Self::PerStream,
SplitLevel::Device => Self::PerDevice,
SplitLevel::Global => Self::Global,
}
}
}