grib_build/
cct_csv.rs

1use std::{collections::BTreeMap, error::Error, fmt, fs::File, path::Path};
2
3use serde::Deserialize;
4
5use crate::CodeTable;
6
7#[derive(Debug, Deserialize)]
8struct C00Record {
9    #[serde(rename = "GRIB version number")]
10    grib_version: String,
11    #[allow(dead_code)]
12    #[serde(rename = "BUFR version number")]
13    bufr_version: String,
14    #[allow(dead_code)]
15    #[serde(rename = "CREX version number")]
16    crex_version: String,
17    #[serde(rename = "Effective date")]
18    date: String,
19    #[allow(dead_code)]
20    #[serde(rename = "Status")]
21    status: String,
22}
23
24#[derive(Debug, Deserialize)]
25struct C11Record {
26    #[allow(dead_code)]
27    #[serde(rename = "CREX2")]
28    crex2: String,
29    #[serde(rename = "GRIB2_BUFR4")]
30    grib2_bufr4: String,
31    #[serde(rename = "OriginatingGeneratingCentre_en")]
32    center: String,
33    #[allow(dead_code)]
34    #[serde(rename = "Status")]
35    status: String,
36}
37
38pub struct CodeDB {
39    data: BTreeMap<u8, CodeTable>,
40}
41
42impl Default for CodeDB {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl CodeDB {
49    pub fn new() -> Self {
50        Self {
51            data: BTreeMap::new(),
52        }
53    }
54
55    pub fn load<P>(&mut self, path: P) -> Result<(), Box<dyn Error>>
56    where
57        P: AsRef<Path>,
58    {
59        let basename = path
60            .as_ref()
61            .file_stem()
62            .ok_or("unexpected path")?
63            .to_string_lossy();
64        match &*basename {
65            "C00" => {
66                self.data.insert(0, Self::parse_file_c00(path)?);
67            }
68            "C11" => {
69                self.data.insert(11, Self::parse_file_c11(path)?);
70            }
71            _ => {}
72        }
73
74        Ok(())
75    }
76
77    pub fn parse_file_c00<P>(path: P) -> Result<CodeTable, Box<dyn Error>>
78    where
79        P: AsRef<Path>,
80    {
81        let f = File::open(&path)?;
82        let mut reader = csv::Reader::from_reader(f);
83        let mut codetable = CodeTable::new("Common Code Table C-0".to_owned());
84
85        for record in reader.deserialize() {
86            let record: C00Record = record?;
87            codetable.data.push((record.grib_version, record.date));
88        }
89
90        Ok(codetable)
91    }
92
93    pub fn parse_file_c11<P>(path: P) -> Result<CodeTable, Box<dyn Error>>
94    where
95        P: AsRef<Path>,
96    {
97        let f = File::open(path)?;
98        let mut reader = csv::Reader::from_reader(f);
99        let mut codetable = CodeTable::new("Common Code Table C-11".to_owned());
100
101        for record in reader.deserialize() {
102            let record: C11Record = record?;
103            codetable.data.push((record.grib2_bufr4, record.center));
104        }
105
106        Ok(codetable)
107    }
108
109    pub fn export(&self, id: u8) -> String {
110        match self.get(id) {
111            Some(code_table) => {
112                let variable_name = self.get_variable_name(id);
113                code_table.export(&variable_name)
114            }
115            None => "[]".to_string(),
116        }
117    }
118
119    fn get_variable_name(&self, id: u8) -> String {
120        format!("COMMON_CODE_TABLE_{id:02}")
121    }
122
123    pub fn get(&self, id: u8) -> Option<&CodeTable> {
124        self.data.get(&id)
125    }
126}
127
128impl fmt::Display for CodeDB {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        let mut first = true;
131        for (id, code_table) in &self.data {
132            if first {
133                first = false;
134            } else {
135                write!(f, "\n\n")?;
136            }
137
138            let variable_name = self.get_variable_name(*id);
139            write!(f, "{}", code_table.export(&variable_name))?;
140        }
141        Ok(())
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    const PATH_STR_C00: &str = "testdata/C00.csv";
150    const PATH_STR_C11: &str = "testdata/C11.csv";
151
152    #[test]
153    fn parse_file_c00() {
154        let table = CodeDB::parse_file_c00(PATH_STR_C00).unwrap();
155        assert_eq!(table.desc, "Common Code Table C-0");
156        assert_eq!(
157            table.data,
158            vec![
159                ("0", "Experimental"),
160                ("", "1 January 1998"),
161                ("", "1 January 1999"),
162                ("1", "1 January 2000"),
163                ("2", "1 January 2001"),
164                ("3", "Pre-operational to be implemented by next amendment"),
165                ("4-254", "Future versions"),
166                ("255", "Missing"),
167            ]
168            .iter()
169            .map(|(a, b)| (a.to_string(), b.to_string()))
170            .collect::<Vec<_>>()
171        );
172    }
173
174    #[test]
175    fn parse_file_c11() {
176        let table = CodeDB::parse_file_c11(PATH_STR_C11).unwrap();
177        assert_eq!(table.desc, "Common Code Table C-11");
178        assert_eq!(
179            table.data,
180            vec![
181                ("0", "A"),
182                ("", "Comment"),
183                ("1", "B"),
184                ("2", ")"),
185                ("3", "C"),
186                ("4", "Reserved"),
187                ("5", "D"),
188                ("6", "Reserved for other centres"),
189                ("7-9", "E"),
190                ("10-14", "Reserved"),
191                ("15", "F"),
192                ("16-65534", "Reserved for other centres"),
193                ("65535", "Missing value"),
194                ("Not applicable", "Not used"),
195            ]
196            .iter()
197            .map(|(a, b)| (a.to_string(), b.to_string()))
198            .collect::<Vec<_>>()
199        );
200    }
201
202    #[test]
203    fn export_c00() {
204        let mut db = CodeDB::new();
205        db.load(PATH_STR_C00).unwrap();
206        assert_eq!(
207            db.export(0),
208            "\
209/// Common Code Table C-0
210const COMMON_CODE_TABLE_00: &[& str] = &[
211    \"Experimental\",
212    \"1 January 2000\",
213    \"1 January 2001\",
214    \"Pre-operational to be implemented by next amendment\",
215];"
216        );
217    }
218
219    #[test]
220    fn format() {
221        let mut db = CodeDB::new();
222        db.load(PATH_STR_C00).unwrap();
223        db.load(PATH_STR_C11).unwrap();
224        assert_eq!(
225            format!("{db}"),
226            "\
227/// Common Code Table C-0
228const COMMON_CODE_TABLE_00: &[& str] = &[
229    \"Experimental\",
230    \"1 January 2000\",
231    \"1 January 2001\",
232    \"Pre-operational to be implemented by next amendment\",
233];
234
235/// Common Code Table C-11
236const COMMON_CODE_TABLE_11: &[& str] = &[
237    \"A\",
238    \"B\",
239    \"\",
240    \"C\",
241    \"\",
242    \"D\",
243    \"\",
244    \"E\",
245    \"E\",
246    \"E\",
247    \"\",
248    \"\",
249    \"\",
250    \"\",
251    \"\",
252    \"F\",
253];"
254        );
255    }
256
257    #[test]
258    fn codetable_to_vec() {
259        let mut db = CodeDB::new();
260        db.load(PATH_STR_C00).unwrap();
261        db.load(PATH_STR_C11).unwrap();
262        assert_eq!(
263            db.get(0).unwrap().to_vec(),
264            vec![
265                "Experimental",
266                "1 January 2000",
267                "1 January 2001",
268                "Pre-operational to be implemented by next amendment",
269            ]
270        );
271        assert_eq!(
272            db.get(11).unwrap().to_vec(),
273            vec![
274                "A", "B", "", "C", "", "D", "", "E", "E", "E", "", "", "", "", "", "F",
275            ]
276        );
277    }
278}