use clap::Args;
use std::path::PathBuf;
#[derive(Args, Debug)]
pub struct ExportArgs {
#[arg(short, long, value_name = "FILE")]
pub output: Option<PathBuf>,
#[arg(long)]
pub gzip: bool,
#[arg(long, value_name = "LIST", value_delimiter = ',')]
pub tables: Option<Vec<String>>,
#[arg(long)]
pub no_history: bool,
#[arg(long)]
pub exclude_deleted: bool,
#[arg(long, value_name = "SIZE")]
pub compress_threshold: Option<String>,
}
impl ExportArgs {
pub fn tables_to_export(&self) -> Option<Vec<String>> {
if self.no_history {
if let Some(ref tables) = self.tables {
Some(
tables
.iter()
.filter(|t| *t != "task_sequence")
.cloned()
.collect(),
)
} else {
Some(vec![
"tasks".to_string(),
"dependencies".to_string(),
"attachments".to_string(),
"task_tags".to_string(),
"task_needed_tags".to_string(),
"task_wanted_tags".to_string(),
])
}
} else {
self.tables.clone()
}
}
pub fn compress_threshold_bytes(&self) -> Option<u64> {
self.compress_threshold.as_ref().and_then(|s| parse_size(s))
}
pub fn should_compress(&self, output_size: Option<u64>) -> bool {
if self.gzip {
return true;
}
if let Some(ref path) = self.output
&& path.extension().is_some_and(|ext| ext == "gz")
{
return true;
}
if let (Some(threshold), Some(size)) = (self.compress_threshold_bytes(), output_size) {
return size > threshold;
}
false
}
}
fn parse_size(s: &str) -> Option<u64> {
let s = s.trim().to_uppercase();
if let Some(num) = s.strip_suffix("GB") {
num.trim()
.parse::<u64>()
.ok()
.map(|n| n * 1024 * 1024 * 1024)
} else if let Some(num) = s.strip_suffix("MB") {
num.trim().parse::<u64>().ok().map(|n| n * 1024 * 1024)
} else if let Some(num) = s.strip_suffix("KB") {
num.trim().parse::<u64>().ok().map(|n| n * 1024)
} else if let Some(num) = s.strip_suffix('B') {
num.trim().parse::<u64>().ok()
} else {
s.parse::<u64>().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_size() {
assert_eq!(parse_size("100"), Some(100));
assert_eq!(parse_size("100B"), Some(100));
assert_eq!(parse_size("100KB"), Some(100 * 1024));
assert_eq!(parse_size("100kb"), Some(100 * 1024));
assert_eq!(parse_size("1MB"), Some(1024 * 1024));
assert_eq!(parse_size("1GB"), Some(1024 * 1024 * 1024));
assert_eq!(parse_size("invalid"), None);
}
#[test]
fn test_tables_to_export_no_history() {
let args = ExportArgs {
output: None,
gzip: false,
tables: None,
no_history: true,
exclude_deleted: false,
compress_threshold: None,
};
let tables = args.tables_to_export().unwrap();
assert!(!tables.contains(&"task_sequence".to_string()));
assert!(tables.contains(&"tasks".to_string()));
}
#[test]
fn test_should_compress() {
let args = ExportArgs {
output: None,
gzip: true,
tables: None,
no_history: false,
exclude_deleted: false,
compress_threshold: None,
};
assert!(args.should_compress(None));
let args = ExportArgs {
output: Some(PathBuf::from("snapshot.json.gz")),
gzip: false,
tables: None,
no_history: false,
exclude_deleted: false,
compress_threshold: None,
};
assert!(args.should_compress(None));
let args = ExportArgs {
output: None,
gzip: false,
tables: None,
no_history: false,
exclude_deleted: false,
compress_threshold: Some("100KB".to_string()),
};
assert!(!args.should_compress(Some(50 * 1024))); assert!(args.should_compress(Some(150 * 1024))); }
}