use crate::Args; use crate::compression_engine::CompressionType; use crate::meta_plugin::MetaPluginType; use clap::Command; use clap::error::ErrorKind; use log::debug; use prettytable::format::TableFormat; use regex::Regex; use std::collections::HashMap; use std::env; use std::str::FromStr; use strum::IntoEnumIterator; use serde::{Deserialize, Serialize}; pub fn get_meta_from_env() -> HashMap { debug!("COMMON: Getting meta from KEEP_META_*"); let re = Regex::new(r"^KEEP_META_(.+)$").unwrap(); let mut meta_env: HashMap = HashMap::new(); for (key, value) in env::vars() { if let Some(meta_name_caps) = re.captures(key.as_str()) { let name = String::from(meta_name_caps.get(1).unwrap().as_str()); // Ignore KEEP_META_PLUGINS if name != "PLUGINS" { debug!("COMMON: Found meta: {}={}", name.clone(), value.clone()); meta_env.insert(name, value.clone()); } } } meta_env } pub fn format_size_human_readable(size: u64) -> String { const UNITS: &[&str] = &["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"]; const THRESHOLD: u64 = 1024; if size == 0 { return "0".to_string(); } let mut size_f = size as f64; let mut unit_index = 0; while size_f >= THRESHOLD as f64 && unit_index < UNITS.len() - 1 { size_f /= THRESHOLD as f64; unit_index += 1; } if unit_index == 0 { format!("{}", size) } else if size_f.fract() == 0.0 { format!("{}{}", size_f as u64, UNITS[unit_index]) } else { format!("{:.1}{}", size_f, UNITS[unit_index]) } } pub fn format_size(size: u64, human_readable: bool) -> String { match human_readable { true => format_size_human_readable(size), false => size.to_string(), } } pub fn string_column(s: String, column_width: usize) -> String { if column_width > 0 { match s.char_indices().nth(column_width) { None => s.to_string(), Some((idx, _)) => s[..idx].to_string(), } } else { s.to_string() } } pub fn size_column(size: u64, human_readable: bool, column_width: usize) -> String { string_column(format_size(size, human_readable), column_width) } #[derive(Debug, Eq, PartialEq, Clone, strum::EnumIter, strum::Display, strum::EnumString)] #[strum(ascii_case_insensitive)] pub enum ColumnType { Id, Time, Size, Compression, FileSize, FilePath, Tags, Meta, } impl ColumnType { /// Returns a Result with error message if the string is not a valid ColumnType pub fn from_str(s: &str) -> anyhow::Result { Ok(Self::try_from(s)?) } } // impl TryFrom<&str> for ColumnType is already implemented by strum_macros // so we remove this conflicting implementation pub fn get_format_box_chars_no_border_line_separator() -> TableFormat { prettytable::format::FormatBuilder::new() .column_separator('│') .borders('│') .separators( &[prettytable::format::LinePosition::Top], prettytable::format::LineSeparator::new('─', '┬', '┌', '┐'), ) .separators( &[prettytable::format::LinePosition::Title], prettytable::format::LineSeparator::new('─', '┼', '├', '┤'), ) .separators( &[prettytable::format::LinePosition::Bottom], prettytable::format::LineSeparator::new('─', '┴', '└', '┘'), ) .padding(1, 1) .build() } pub fn get_digest_type_meta(digest_type: MetaPluginType) -> String { format!("digest_{}", digest_type.to_string().to_lowercase()) } pub fn cmd_args_digest_type(cmd: &mut Command, args: &Args) -> MetaPluginType { let digest_name = args .item .digest .clone() .unwrap_or(MetaPluginType::DigestSha256.to_string()); let digest_type_opt = MetaPluginType::from_str(&digest_name); if digest_type_opt.is_err() { cmd.error( ErrorKind::InvalidValue, format!("Invalid digest algorithm '{}'. Use 'sha256' or 'md5'", digest_name), ) .exit(); } digest_type_opt.unwrap() } pub fn cmd_args_compression_type(cmd: &mut Command, args: &Args) -> CompressionType { let compression_name = args .item .compression .clone() .unwrap_or(CompressionType::LZ4.to_string()); let compression_type_opt = CompressionType::from_str(&compression_name); if compression_type_opt.is_err() { cmd.error( ErrorKind::InvalidValue, format!("Invalid compression algorithm '{}'. Supported algorithms: lz4, gzip, xz, zstd", compression_name), ) .exit(); } compression_type_opt.unwrap() } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum OutputFormat { Table, Json, Yaml, } impl FromStr for OutputFormat { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "table" => Ok(OutputFormat::Table), "json" => Ok(OutputFormat::Json), "yaml" => Ok(OutputFormat::Yaml), _ => Err(anyhow::anyhow!("Invalid output format. Supported formats: table, json, yaml")), } } } pub fn get_output_format(args: &Args) -> OutputFormat { args.options.output_format .as_ref() .and_then(|s| OutputFormat::from_str(s).ok()) .unwrap_or(OutputFormat::Table) } pub fn cmd_args_meta_plugin_types(cmd: &mut Command, args: &Args) -> Vec { let mut meta_plugin_types = Vec::new(); // Handle comma-separated values in each meta_plugins argument for meta_plugin_names_str in &args.item.meta_plugins { let meta_plugin_names: Vec<&str> = meta_plugin_names_str.split(',').collect(); for name in meta_plugin_names { let trimmed_name = name.trim(); if trimmed_name.is_empty() { continue; } // Try to find the MetaPluginType by meta name let mut found = false; for meta_plugin_type in MetaPluginType::iter() { let mut meta_plugin = crate::meta_plugin::get_meta_plugin(meta_plugin_type.clone()); if meta_plugin.meta_name() == trimmed_name { meta_plugin_types.push(meta_plugin_type); found = true; break; } } if !found { cmd.error( ErrorKind::InvalidValue, format!("Unknown meta plugin type: {}", trimmed_name), ) .exit(); } } } meta_plugin_types }