use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
#[default]
Text,
Json,
Csv,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
pub enum ReportFormat {
#[default]
Text,
Json,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
pub enum StyleMode {
Minimal,
#[default]
Rich,
Plain,
}
#[derive(Debug, Clone, Args)]
pub struct DeviceArgs {
#[arg(short, long, env = "ARANET_DEVICE")]
pub device: Option<String>,
#[arg(short = 'T', long)]
pub timeout: Option<u64>,
}
#[derive(Debug, Clone, Args)]
pub struct MultiDeviceArgs {
#[arg(short, long, value_delimiter = ',', env = "ARANET_DEVICE")]
pub device: Vec<String>,
#[arg(short = 'T', long)]
pub timeout: Option<u64>,
}
#[derive(Debug, Clone, Args)]
pub struct OutputArgs {
#[arg(short, long, value_enum)]
pub format: Option<OutputFormat>,
#[arg(long, conflicts_with = "celsius")]
pub fahrenheit: bool,
#[arg(long, conflicts_with = "fahrenheit")]
pub celsius: bool,
#[arg(long, conflicts_with = "pci")]
pub bq: bool,
#[arg(long, conflicts_with = "bq")]
pub pci: bool,
#[arg(long, conflicts_with = "hpa")]
pub inhg: bool,
#[arg(long, conflicts_with = "inhg")]
pub hpa: bool,
#[arg(long)]
pub no_header: bool,
}
impl OutputArgs {
pub fn resolve_fahrenheit(&self, config_fahrenheit: bool) -> bool {
if self.fahrenheit {
true
} else if self.celsius {
false
} else {
config_fahrenheit
}
}
pub fn resolve_bq(&self, config_bq: bool) -> bool {
if self.bq {
true
} else if self.pci {
false
} else {
config_bq
}
}
pub fn resolve_inhg(&self, config_inhg: bool) -> bool {
if self.inhg {
true
} else if self.hpa {
false
} else {
config_inhg
}
}
}
#[derive(Debug, Clone, Args, Default)]
pub struct ReportOutputArgs {
#[arg(long, conflicts_with = "celsius")]
pub fahrenheit: bool,
#[arg(long, conflicts_with = "fahrenheit")]
pub celsius: bool,
#[arg(long, conflicts_with = "hpa")]
pub inhg: bool,
#[arg(long, conflicts_with = "inhg")]
pub hpa: bool,
#[arg(long, conflicts_with = "pci")]
pub bq: bool,
#[arg(long, conflicts_with = "bq")]
pub pci: bool,
}
impl ReportOutputArgs {
pub fn resolve_fahrenheit(&self, config_fahrenheit: bool) -> bool {
if self.fahrenheit {
true
} else if self.celsius {
false
} else {
config_fahrenheit
}
}
pub fn resolve_inhg(&self, config_inhg: bool) -> bool {
if self.inhg {
true
} else if self.hpa {
false
} else {
config_inhg
}
}
pub fn resolve_bq(&self, config_bq: bool) -> bool {
if self.bq {
true
} else if self.pci {
false
} else {
config_bq
}
}
}
#[derive(Parser)]
#[command(name = "aranet")]
#[command(
author,
version,
about = "CLI for Aranet environmental sensors",
long_about = "CLI for Aranet environmental sensors\n\nMade with ❤️ by Cameron Rye\nhttps://rye.dev"
)]
pub struct Cli {
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true)]
pub json: bool,
#[arg(long, global = true)]
pub compact: bool,
#[arg(long, global = true)]
pub no_color: bool,
#[arg(
long,
global = true,
value_enum,
default_value = "rich",
env = "ARANET_STYLE"
)]
pub style: StyleMode,
#[arg(short, long, global = true)]
pub output: Option<PathBuf>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
Scan {
#[arg(short, long)]
timeout: Option<u64>,
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,
#[arg(long)]
no_header: bool,
#[arg(short, long)]
alias: bool,
},
Read {
#[command(flatten)]
device: MultiDeviceArgs,
#[command(flatten)]
output: OutputArgs,
#[arg(long)]
passive: bool,
},
Status {
#[command(flatten)]
device: DeviceArgs,
#[command(flatten)]
output: OutputArgs,
#[arg(long)]
brief: bool,
},
History {
#[command(flatten)]
device: DeviceArgs,
#[command(flatten)]
output: OutputArgs,
#[arg(short, long, default_value = "0")]
count: u32,
#[arg(long)]
since: Option<String>,
#[arg(long)]
until: Option<String>,
#[arg(long)]
cache: bool,
},
Info {
#[command(flatten)]
device: DeviceArgs,
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,
#[arg(long)]
no_header: bool,
},
Set {
#[command(flatten)]
device: DeviceArgs,
#[arg(long, short = 'f')]
force: bool,
#[command(subcommand)]
setting: DeviceSetting,
},
Watch {
#[command(flatten)]
device: DeviceArgs,
#[command(flatten)]
output: OutputArgs,
#[arg(short, long, default_value = "60")]
interval: u64,
#[arg(short = 'n', long, default_value = "0")]
count: u32,
#[arg(long)]
passive: bool,
},
Config {
#[command(subcommand)]
action: ConfigAction,
},
Alias {
#[command(subcommand)]
action: AliasSubcommand,
},
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
Doctor,
Examples,
Sync {
#[command(flatten)]
device: DeviceArgs,
#[arg(short, long, value_enum)]
format: Option<OutputFormat>,
#[arg(long)]
full: bool,
#[arg(long, conflicts_with = "device")]
all: bool,
},
Cache {
#[command(subcommand)]
action: CacheAction,
},
Report {
#[arg(short, long)]
device: Option<String>,
#[arg(long, conflicts_with = "device")]
all: bool,
#[arg(short, long, value_enum, default_value = "daily")]
period: ReportPeriod,
#[arg(short, long, value_enum)]
format: Option<ReportFormat>,
#[command(flatten)]
output: ReportOutputArgs,
},
Server {
#[arg(short, long)]
config: Option<std::path::PathBuf>,
#[arg(short, long)]
bind: Option<String>,
#[arg(long)]
database: Option<std::path::PathBuf>,
#[arg(long)]
no_collector: bool,
#[arg(long)]
daemon: bool,
},
#[cfg(feature = "tui")]
Tui,
#[cfg(feature = "gui")]
Gui,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
pub enum ReportPeriod {
#[default]
Daily,
Weekly,
Monthly,
}
#[derive(Debug, Clone, Subcommand)]
pub enum CacheAction {
Devices,
Stats {
#[arg(short, long)]
device: Option<String>,
},
History {
#[arg(short, long)]
device: String,
#[arg(short, long, default_value = "100")]
count: u32,
#[arg(long)]
since: Option<String>,
#[arg(long)]
until: Option<String>,
#[command(flatten)]
output: OutputArgs,
},
Aggregate {
#[arg(short, long)]
device: String,
#[arg(long)]
since: Option<String>,
#[arg(long)]
until: Option<String>,
#[arg(short, long, value_enum, default_value = "text")]
format: OutputFormat,
},
Export {
#[arg(short, long)]
device: String,
#[arg(short, long, value_enum, default_value = "csv")]
format: ExportFormat,
#[arg(short, long)]
output: Option<std::path::PathBuf>,
#[arg(long)]
since: Option<String>,
#[arg(long)]
until: Option<String>,
},
Prune {
#[arg(long, value_name = "DURATION")]
older_than: String,
#[arg(long)]
history_only: bool,
#[arg(long, short = 'f')]
force: bool,
#[arg(long)]
vacuum: bool,
},
Info,
Import {
#[arg(short, long, value_enum, default_value = "csv")]
format: ExportFormat,
#[arg(short, long)]
input: Option<std::path::PathBuf>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum ExportFormat {
Csv,
Json,
}
#[derive(Debug, Clone, Subcommand)]
pub enum AliasSubcommand {
List,
Set {
name: String,
address: String,
},
#[command(alias = "rm")]
Remove {
name: String,
},
}
#[derive(Debug, Clone, Subcommand)]
pub enum DeviceSetting {
Interval {
#[arg(value_parser = parse_interval)]
minutes: u8,
},
Range {
#[arg(value_enum)]
range: BluetoothRangeSetting,
},
SmartHome {
#[arg(value_parser = parse_bool_arg)]
enabled: bool,
},
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum BluetoothRangeSetting {
Standard,
Extended,
}
fn parse_interval(s: &str) -> Result<u8, String> {
let minutes: u8 = s
.parse()
.map_err(|_| format!("'{}' is not a valid number", s))?;
match minutes {
1 | 2 | 5 | 10 => Ok(minutes),
_ => Err(format!(
"Invalid interval '{}'. Valid values: 1, 2, 5, 10 minutes",
minutes
)),
}
}
fn parse_bool_arg(s: &str) -> Result<bool, String> {
match s.to_lowercase().as_str() {
"true" | "yes" | "on" | "1" | "enable" | "enabled" => Ok(true),
"false" | "no" | "off" | "0" | "disable" | "disabled" => Ok(false),
_ => Err(format!(
"Invalid boolean value '{}'. Use: true/false, yes/no, on/off, 1/0",
s
)),
}
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ConfigKey {
Device,
Format,
Timeout,
NoColor,
Fahrenheit,
Inhg,
Bq,
}
#[derive(Subcommand)]
pub enum ConfigAction {
Show,
Get {
#[arg(value_enum)]
key: ConfigKey,
},
Set {
#[arg(value_enum)]
key: ConfigKey,
value: String,
},
Unset {
#[arg(value_enum)]
key: ConfigKey,
},
Path,
Init,
}