datui_lib/home/
codebook.rs1use crate::home::catalog::Dataset;
8use std::collections::BTreeMap;
9
10#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct Column {
13 pub description: String,
14 pub unit: String,
15 pub values: BTreeMap<String, String>,
17}
18
19impl Column {
20 pub fn about(&self) -> String {
22 match (self.description.is_empty(), self.unit.is_empty()) {
23 (false, false) => format!("{} ({})", self.description, self.unit),
24 (false, true) => self.description.clone(),
25 (true, false) => self.unit.clone(),
26 (true, true) => String::new(),
27 }
28 }
29
30 pub fn meaning(&self, value: Option<&str>) -> Option<(&str, &str)> {
33 let code = value.map(str::trim).unwrap_or("");
34 self.values
35 .get_key_value(code)
36 .map(|(key, meaning)| (key.as_str(), meaning.as_str()))
37 }
38
39 pub fn legend_line(&self, value: Option<&str>) -> Option<String> {
42 let (code, meaning) = self.meaning(value)?;
43 let code = if code.is_empty() { "blank" } else { code };
44 Some(format!("{code} = {meaning}"))
45 }
46}
47
48#[derive(Debug, Clone, Default, PartialEq, Eq)]
50pub struct Codebook {
51 pub source: String,
53 pub columns: BTreeMap<String, Column>,
54}
55
56impl Codebook {
57 pub fn of(dataset: &Dataset) -> Option<Self> {
59 if dataset.columns.is_empty() {
60 return None;
61 }
62 Some(Self {
63 source: dataset.documentation.clone(),
64 columns: dataset
65 .columns
66 .iter()
67 .map(|(name, note)| {
68 (
69 name.clone(),
70 Column {
71 description: note.description.clone(),
72 unit: note.unit.clone(),
73 values: note.values.iter().cloned().collect(),
74 },
75 )
76 })
77 .collect(),
78 })
79 }
80
81 pub fn column(&self, name: &str) -> Option<&Column> {
82 self.columns.get(name)
83 }
84
85 pub fn covers<'a>(&self, mut names: impl Iterator<Item = &'a str>) -> bool {
87 names.any(|name| self.columns.contains_key(name))
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn flag() -> Column {
96 Column {
97 description: "Quality flag".to_string(),
98 unit: String::new(),
99 values: [
100 ("", "did not fail any quality assurance check"),
101 ("S", "failed spatial consistency check"),
102 ]
103 .into_iter()
104 .map(|(k, v)| (k.to_string(), v.to_string()))
105 .collect(),
106 }
107 }
108
109 #[test]
110 fn a_code_reads_its_meaning_and_blank_reads_the_empty_entry() {
111 let column = flag();
112 assert_eq!(
113 column.legend_line(Some("S")).as_deref(),
114 Some("S = failed spatial consistency check")
115 );
116 assert_eq!(column.legend_line(Some("s")), None);
117 assert_eq!(
118 column.legend_line(None).as_deref(),
119 Some("blank = did not fail any quality assurance check")
120 );
121 assert_eq!(
122 column.legend_line(Some(" ")).as_deref(),
123 Some("blank = did not fail any quality assurance check")
124 );
125 assert_eq!(column.legend_line(Some("Q")), None);
126 assert_eq!(column.about(), "Quality flag");
127 }
128}