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}