use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnMetadata {
pub name: String,
pub description: Option<String>,
pub unit: Option<String>,
pub datatype: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct MetadataFile {
column: Vec<ColumnMetadata>,
}
pub fn print_metadata(metadata: &HashMap<String, ColumnMetadata>) {
println!(
"\n{:<25} {:<15} {:<60}",
"Column Name", "Unit", "Description"
);
println!("{}", "=".repeat(100));
let mut sorted_keys: Vec<_> = metadata.keys().collect();
sorted_keys.sort();
for key in sorted_keys {
if let Some(meta) = metadata.get(key) {
let unit = meta.unit.as_deref().unwrap_or("-");
let desc = meta.description.as_deref().unwrap_or("-");
println!("{:<25} {:<15} {:<60}", meta.name, unit, desc);
}
}
println!("\nTotal columns: {}\n", metadata.len());
}
pub fn get_columns_metadata(
all_metadata: &HashMap<String, ColumnMetadata>,
column_names: &[&str],
) -> HashMap<String, String> {
let mut result = HashMap::new();
for &col_name in column_names {
if let Some(meta) = all_metadata.get(col_name) {
let mut description = meta.description.clone().unwrap_or_default();
if let Some(unit) = &meta.unit
&& !description.contains('[')
&& !unit.is_empty()
{
description = format!("{} [{}]", description, unit);
}
result.insert(col_name.to_string(), description);
}
}
result
}
pub fn save_metadata_toml(
metadata: &HashMap<String, ColumnMetadata>,
output_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let mut columns: Vec<ColumnMetadata> = metadata.values().cloned().collect();
columns.sort_by(|a, b| a.name.cmp(&b.name));
let metadata_file = MetadataFile { column: columns };
let toml_string = toml::to_string_pretty(&metadata_file)?;
fs::write(output_path, toml_string)?;
Ok(())
}
pub fn load_metadata_toml(
path: &Path,
) -> Result<HashMap<String, ColumnMetadata>, Box<dyn std::error::Error>> {
let contents = fs::read_to_string(path)?;
let metadata_file: MetadataFile = toml::from_str(&contents)?;
let metadata_map: HashMap<String, ColumnMetadata> = metadata_file
.column
.into_iter()
.map(|m| (m.name.clone(), m))
.collect();
Ok(metadata_map)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_columns_metadata() {
let mut all_meta = HashMap::new();
all_meta.insert(
"pl_name".to_string(),
ColumnMetadata {
name: "pl_name".to_string(),
description: Some("Planet Name".to_string()),
unit: None,
datatype: "char".to_string(),
},
);
all_meta.insert(
"pl_orbper".to_string(),
ColumnMetadata {
name: "pl_orbper".to_string(),
description: Some("Orbital Period".to_string()),
unit: Some("day".to_string()),
datatype: "double".to_string(),
},
);
let result = get_columns_metadata(&all_meta, &["pl_name", "pl_orbper"]);
assert_eq!(result.len(), 2);
assert_eq!(result.get("pl_name").unwrap(), "Planet Name");
assert!(result.get("pl_orbper").unwrap().contains("Orbital Period"));
assert!(result.get("pl_orbper").unwrap().contains("[day]"));
}
#[test]
fn test_save_and_load_metadata_toml() {
use std::env;
let mut metadata = HashMap::new();
metadata.insert(
"hostname".to_string(),
ColumnMetadata {
name: "hostname".to_string(),
description: Some("Host Name".to_string()),
unit: None,
datatype: "char".to_string(),
},
);
metadata.insert(
"dec".to_string(),
ColumnMetadata {
name: "dec".to_string(),
description: None,
unit: Some("deg".to_string()),
datatype: "double".to_string(),
},
);
let temp_dir = env::temp_dir();
let temp_file = temp_dir.join("test_metadata.toml");
save_metadata_toml(&metadata, &temp_file)
.expect("Failed to save metadata");
let loaded_metadata =
load_metadata_toml(&temp_file).expect("Failed to load metadata");
assert_eq!(loaded_metadata.len(), 2);
assert_eq!(loaded_metadata.get("hostname").unwrap().name, "hostname");
assert_eq!(
loaded_metadata.get("hostname").unwrap().description,
Some("Host Name".to_string())
);
assert_eq!(
loaded_metadata.get("dec").unwrap().unit,
Some("deg".to_string())
);
let _ = std::fs::remove_file(&temp_file);
}
}