use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "midas_fetcher",
version,
about = "Download UK Met Office MIDAS Open dataset files efficiently",
long_about = "A high-performance tool for downloading weather data from the UK Met Office MIDAS Open dataset.
Features concurrent downloads, automatic retry logic, and comprehensive progress tracking."
)]
pub struct Cli {
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Args, Debug)]
pub struct GlobalArgs {
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(long, global = true)]
pub very_verbose: bool,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(long, global = true, value_name = "DIR")]
pub cache_dir: Option<PathBuf>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Download(DownloadArgs),
Manifest(ManifestArgs),
Auth(AuthArgs),
Cache(CacheArgs),
}
#[derive(Args, Debug, Clone)]
pub struct DownloadArgs {
#[arg(short, long)]
pub dataset: Option<String>,
#[arg(short, long)]
pub county: Option<String>,
#[arg(long = "quality-0")]
pub quality_zero: bool,
#[arg(short, long)]
pub limit: Option<usize>,
#[arg(short = 'w', long, default_value = "8")]
pub workers: usize,
#[arg(short, long)]
pub force: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(short, long)]
pub verbose: bool,
}
#[derive(Args, Debug)]
pub struct ManifestArgs {
#[command(subcommand)]
pub action: ManifestAction,
}
#[derive(Subcommand, Debug)]
pub enum ManifestAction {
Update {
#[arg(short, long)]
force: bool,
#[arg(long)]
verify: bool,
},
Info {
#[arg(value_name = "FILE")]
file: Option<PathBuf>,
},
List {
#[arg(long)]
datasets_only: bool,
#[arg(long)]
dataset: Option<String>,
},
Check {
#[arg(long)]
detailed: bool,
},
}
#[derive(Args, Debug)]
pub struct AuthArgs {
#[command(subcommand)]
pub action: AuthAction,
}
#[derive(Subcommand, Debug)]
pub enum AuthAction {
Setup {
#[arg(short, long)]
force: bool,
},
Verify,
Status,
Clear,
}
#[derive(Args, Debug)]
pub struct CacheArgs {
#[command(subcommand)]
pub action: CacheAction,
}
#[derive(Subcommand, Debug)]
pub enum CacheAction {
Verify {
#[arg(short, long)]
dataset: Option<String>,
},
Info,
Clean {
#[arg(long)]
all: bool,
#[arg(long)]
failed_only: bool,
},
}
impl Cli {
pub fn parse_args() -> Self {
Self::parse()
}
pub fn log_level(&self) -> tracing::Level {
if self.global.quiet {
tracing::Level::ERROR
} else if self.global.very_verbose {
tracing::Level::DEBUG
} else if self.global.verbose {
tracing::Level::INFO
} else {
tracing::Level::WARN
}
}
}
impl DownloadArgs {
pub fn validate(&self) -> Result<(), String> {
if self.workers == 0 {
return Err("Number of workers must be greater than 0".to_string());
}
Ok(())
}
pub fn quality_version(&self) -> crate::app::models::QualityControlVersion {
if self.quality_zero {
crate::app::models::QualityControlVersion::V0
} else {
crate::app::models::QualityControlVersion::V1
}
}
pub fn is_filtered(&self) -> bool {
self.dataset.is_some() || self.county.is_some() || self.quality_zero
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_download_args_validation() {
let mut args = DownloadArgs {
dataset: None,
county: None,
quality_zero: false,
limit: None,
workers: 8,
force: false,
dry_run: false,
verbose: false,
};
assert!(args.validate().is_ok());
args.workers = 0;
assert!(args.validate().is_err());
}
#[test]
fn test_quality_version_selection() {
let args_v1 = DownloadArgs {
dataset: None,
county: None,
quality_zero: false,
limit: None,
workers: 8,
force: false,
dry_run: false,
verbose: false,
};
let args_v0 = DownloadArgs {
quality_zero: true,
..args_v1.clone()
};
assert_eq!(
args_v1.quality_version(),
crate::app::models::QualityControlVersion::V1
);
assert_eq!(
args_v0.quality_version(),
crate::app::models::QualityControlVersion::V0
);
}
#[test]
fn test_filtering_detection() {
let base_args = DownloadArgs {
dataset: None,
county: None,
quality_zero: false,
limit: None,
workers: 8,
force: false,
dry_run: false,
verbose: false,
};
assert!(!base_args.is_filtered());
let filtered_args = DownloadArgs {
dataset: Some("uk-daily-temperature-obs".to_string()),
..base_args.clone()
};
assert!(filtered_args.is_filtered());
let quality_args = DownloadArgs {
quality_zero: true,
..base_args.clone()
};
assert!(quality_args.is_filtered());
}
#[test]
fn test_log_level() {
let cli_quiet = Cli {
global: GlobalArgs {
verbose: false,
very_verbose: false,
quiet: true,
config: None,
cache_dir: None,
},
command: Commands::Auth(AuthArgs {
action: AuthAction::Status,
}),
};
let cli_verbose = Cli {
global: GlobalArgs {
verbose: true,
very_verbose: false,
quiet: false,
config: None,
cache_dir: None,
},
command: Commands::Auth(AuthArgs {
action: AuthAction::Status,
}),
};
assert_eq!(cli_quiet.log_level(), tracing::Level::ERROR);
assert_eq!(cli_verbose.log_level(), tracing::Level::INFO);
}
}