use crate::stability::StabilityConfig;
use crate::webhook::WebhookClientConfig;
use camino::Utf8PathBuf;
use clap::{Args as ClapArgs, Parser};
use std::time::Duration;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[clap(flatten)]
pub watcher: WatcherArgs,
#[clap(flatten)]
pub stabilizer: StabilizerArgs,
#[clap(flatten)]
pub webhook: WebhookArgs,
}
#[derive(ClapArgs, Debug, Clone)]
pub struct WatcherArgs {
#[arg(env = "LYNCEUS_PATH")]
pub path: Utf8PathBuf,
#[arg(short, long, env = "LYNCEUS_PATTERN")]
pub pattern: Option<String>,
#[arg(
short,
long,
env = "LYNCEUS_INTERVAL",
default_value_t = humantime::Duration::from(Duration::from_secs(2))
)]
pub interval: humantime::Duration,
#[arg(
short,
long,
env = "LYNCEUS_DEBOUNCE",
default_value_t = humantime::Duration::from(Duration::from_secs(5))
)]
pub debounce: humantime::Duration,
}
#[derive(ClapArgs, Debug, Clone)]
pub struct StabilizerArgs {
#[arg(
short,
long,
env = "LYNCEUS_COOLDOWN",
default_value_t = humantime::Duration::from(StabilityConfig::default().cooldown)
)]
pub cooldown: humantime::Duration,
#[arg(
short,
long,
env = "LYNCEUS_STABLE_COUNT",
default_value_t = StabilityConfig::DEFAULT_STABLE_LIMIT
)]
pub stable_count: std::num::NonZeroUsize,
#[arg(
short,
long,
env = "LYNCEUS_ERROR_COUNT",
default_value_t = StabilityConfig::DEFAULT_ERROR_LIMIT
)]
pub error_count: std::num::NonZeroUsize,
}
#[derive(ClapArgs, Debug, Clone)]
pub struct WebhookArgs {
#[arg(short, long, env = "LYNCEUS_WEBHOOK_URL")]
pub webhook_url: Option<String>,
#[arg(
long,
env = "LYNCEUS_WEBHOOK_TEMPLATE",
value_parser = parse_json,
default_value = WebhookClientConfig::DEFAULT_TEMPLATE
)]
pub webhook_template: serde_json::Value,
#[arg(
long,
env = "LYNCEUS_WEBHOOK_RETRIES",
default_value_t = WebhookClientConfig::DEFAULT_RETRIES
)]
pub webhook_retries: usize,
}
fn parse_json(s: &str) -> Result<serde_json::Value, String> {
serde_json::from_str(s).map_err(|e| format!("invalid JSON: {}", e))
}
impl std::fmt::Display for WatcherArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"path={:?} interval={} debounce={}",
self.path, self.interval, self.debounce
)?;
if let Some(ref pattern) = self.pattern {
write!(f, " pattern={:?}", pattern)?;
}
Ok(())
}
}
impl std::fmt::Display for StabilizerArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"cooldown={} stable_count={} error_count={}",
self.cooldown, self.stable_count, self.error_count
)
}
}
impl std::fmt::Display for WebhookArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(ref webhook_url) = self.webhook_url {
write!(
f,
"url={:?} retries={} template={}",
webhook_url, self.webhook_retries, self.webhook_template
)
} else {
write!(f, "None")
}
}
}
impl std::fmt::Display for Args {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"watcher={{{}}} stabilizer={{{}}} webhook={{{}}}",
self.watcher, self.stabilizer, self.webhook
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_args_display() {
let args = Args {
watcher: WatcherArgs {
path: Utf8PathBuf::from("/tmp"),
pattern: Some("**/*.rs".to_string()),
interval: humantime::Duration::from(std::time::Duration::from_secs(2)),
debounce: humantime::Duration::from(std::time::Duration::from_secs(5)),
},
stabilizer: StabilizerArgs {
cooldown: humantime::Duration::from(std::time::Duration::from_secs(10)),
stable_count: std::num::NonZeroUsize::new(3).unwrap(),
error_count: std::num::NonZeroUsize::new(5).unwrap(),
},
webhook: WebhookArgs {
webhook_url: Some("http://localhost".to_string()),
webhook_template: serde_json::json!({"path": "{{path}}"}),
webhook_retries: 3,
},
};
let formatted = format!("{}", args);
assert_eq!(
formatted,
"watcher={path=\"/tmp\" interval=2s debounce=5s pattern=\"**/*.rs\"} stabilizer={cooldown=10s stable_count=3 error_count=5} webhook={url=\"http://localhost\" retries=3 template={\"path\":\"{{path}}\"}}"
);
}
}