1use std::{borrow::Cow, fmt, fs, io, path::Path};
2
3use log::warn;
4use once_cell::sync::OnceCell;
5use serde::{
6 de::{self, Unexpected},
7 Deserialize, Deserializer,
8};
9
10static SETTINGS: OnceCell<Vec<Setting>> = OnceCell::new();
11
12fn bundled_settings() -> Vec<Setting> {
13 let settings_yaml = include_str!(concat!(env!("OUT_DIR"), "/settings.yaml"));
14 serde_yaml::from_str(settings_yaml).expect("could not parse settings.yaml")
15}
16
17pub fn load_from_path(path: impl AsRef<Path>) -> Result<(), LoadFromPathError> {
18 let file = fs::File::open(path).map_err(LoadFromPathError::Io)?;
19 let settings: Vec<serde_yaml::Mapping> =
20 serde_yaml::from_reader(file).map_err(LoadFromPathError::Serde)?;
21 let type_key = serde_yaml::Value::String("type".into());
22 let settings = settings
23 .into_iter()
24 .map(|mut map| {
25 if map.contains_key(&type_key) {
26 serde_yaml::from_value(map.into())
27 } else {
28 map.insert(type_key.clone(), serde_yaml::Value::String("string".into()));
29 let setting: Setting = serde_yaml::from_value(map.into())?;
30 warn!(
31 "Missing `type` field for {} -> {}",
32 setting.group, setting.name
33 );
34 Ok(setting)
35 }
36 })
37 .collect::<Result<Vec<_>, _>>()
38 .map_err(LoadFromPathError::Serde)?;
39 load(settings).map_err(|_| LoadFromPathError::AlreadySet)
40}
41
42pub fn load(settings: Vec<Setting>) -> Result<(), Vec<Setting>> {
43 SETTINGS.set(settings)
44}
45
46#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
47pub struct Setting {
48 pub name: String,
49
50 pub group: String,
51
52 #[serde(rename = "type")]
53 pub kind: SettingKind,
54
55 #[serde(deserialize_with = "deserialize_bool", default)]
56 pub expert: bool,
57
58 #[serde(deserialize_with = "deserialize_bool", default)]
59 pub readonly: bool,
60
61 #[serde(rename = "Description")]
62 pub description: Option<String>,
63
64 #[serde(
65 alias = "default value",
66 deserialize_with = "deserialize_string",
67 default
68 )]
69 pub default_value: Option<String>,
70
71 #[serde(rename = "Notes")]
72 pub notes: Option<String>,
73
74 #[serde(deserialize_with = "deserialize_string", default)]
75 pub units: Option<String>,
76
77 #[serde(
78 rename = "enumerated possible values",
79 deserialize_with = "deserialize_string",
80 default
81 )]
82 pub enumerated_possible_values: Option<String>,
83
84 pub digits: Option<String>,
85}
86
87impl Setting {
88 pub fn all() -> &'static [Setting] {
89 let settings = SETTINGS.get_or_init(bundled_settings);
90 settings.as_slice()
91 }
92
93 pub fn find(group: impl AsRef<str>, name: impl AsRef<str>) -> Option<&'static Setting> {
94 let group = group.as_ref();
95 let name = name.as_ref();
96 Setting::all()
97 .iter()
98 .find(|s| s.group == group && s.name == name)
99 }
100
101 pub(crate) fn new(group: impl AsRef<str>, name: impl AsRef<str>) -> Cow<'static, Setting> {
102 Setting::find(&group, &name).map_or_else(
103 || {
104 let group = group.as_ref().to_owned();
105 let name = name.as_ref().to_owned();
106 warn!("No documentation entry setting {} -> {}", group, name);
107 Cow::Owned(Setting {
108 group,
109 name,
110 ..Default::default()
111 })
112 },
113 Cow::Borrowed,
114 )
115 }
116
117 pub(crate) fn with_fmt_type(
118 group: impl AsRef<str>,
119 name: impl AsRef<str>,
120 fmt_type: impl AsRef<str>,
121 ) -> Cow<'static, Setting> {
122 let mut setting = Setting::new(group, name);
123 if setting.kind == SettingKind::Enum {
124 let mut parts = fmt_type.as_ref().splitn(2, ':');
125 let possible_values = parts.nth(1);
126 if let Some(p) = possible_values {
127 setting.to_mut().enumerated_possible_values = Some(p.to_owned());
128 }
129 }
130 setting
131 }
132}
133
134#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Default)]
135pub enum SettingKind {
136 #[serde(rename = "integer", alias = "int")]
137 Integer,
138
139 #[serde(rename = "boolean", alias = "bool")]
140 Boolean,
141
142 #[serde(rename = "float")]
143 Float,
144
145 #[serde(rename = "double", alias = "Double")]
146 Double,
147
148 #[serde(rename = "string")]
149 #[default]
150 String,
151
152 #[serde(rename = "enum")]
153 Enum,
154
155 #[serde(rename = "packed bitfield")]
156 PackedBitfield,
157}
158
159impl SettingKind {
160 pub fn to_str(&self) -> &'static str {
161 match self {
162 SettingKind::Integer => "integer",
163 SettingKind::Boolean => "boolean",
164 SettingKind::Float => "float",
165 SettingKind::Double => "double",
166 SettingKind::String => "string",
167 SettingKind::Enum => "enum",
168 SettingKind::PackedBitfield => "packed bitfield",
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq)]
174pub enum SettingValue {
175 Integer(i64),
176 Boolean(bool),
177 Float(f32),
178 Double(f64),
179 String(String),
180}
181
182impl SettingValue {
183 pub fn parse(v: &str, kind: SettingKind) -> Option<Self> {
184 if v.is_empty() {
185 return None;
186 }
187 match kind {
188 SettingKind::Integer => v.parse().ok().map(SettingValue::Integer),
189 SettingKind::Boolean if v == "True" => Some(SettingValue::Boolean(true)),
190 SettingKind::Boolean if v == "False" => Some(SettingValue::Boolean(false)),
191 SettingKind::Float => v.parse().ok().map(SettingValue::Float),
192 SettingKind::Double => v.parse().ok().map(SettingValue::Double),
193 SettingKind::String | SettingKind::Enum | SettingKind::PackedBitfield => {
194 Some(SettingValue::String(v.to_owned()))
195 }
196 _ => None,
197 }
198 }
199}
200
201impl fmt::Display for SettingValue {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match self {
204 SettingValue::Integer(s) => s.fmt(f),
205 SettingValue::Boolean(true) => write!(f, "True"),
208 SettingValue::Boolean(false) => write!(f, "False"),
209 SettingValue::Float(s) => s.fmt(f),
210 SettingValue::Double(s) => s.fmt(f),
211 SettingValue::String(s) => s.fmt(f),
212 }
213 }
214}
215
216fn deserialize_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
217where
218 D: Deserializer<'de>,
219{
220 struct BoolVisitor;
221
222 impl<'de> de::Visitor<'de> for BoolVisitor {
223 type Value = bool;
224
225 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
226 formatter.write_str("a bool or a string containing a bool")
227 }
228
229 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
230 where
231 E: de::Error,
232 {
233 Ok(v)
234 }
235
236 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
237 where
238 E: de::Error,
239 {
240 match v {
241 "True" | "true" => Ok(true),
242 "False" | "false" => Ok(false),
243 other => Err(de::Error::invalid_value(
244 Unexpected::Str(other),
245 &"True or False",
246 )),
247 }
248 }
249 }
250
251 deserializer.deserialize_any(BoolVisitor)
252}
253
254fn deserialize_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
255where
256 D: Deserializer<'de>,
257{
258 struct StringVisitor;
259
260 impl<'de> de::Visitor<'de> for StringVisitor {
261 type Value = Option<String>;
262
263 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
264 formatter.write_str("an optional string")
265 }
266
267 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
268 where
269 E: de::Error,
270 {
271 match v {
272 "N/A" | "" => Ok(None),
273 _ => Ok(Some(v.to_owned())),
274 }
275 }
276
277 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
278 where
279 E: de::Error,
280 {
281 Ok(Some(v.to_string()))
282 }
283
284 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
285 where
286 E: de::Error,
287 {
288 Ok(Some(v.to_string()))
289 }
290
291 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
292 where
293 E: de::Error,
294 {
295 Ok(Some(v.to_string()))
296 }
297
298 fn visit_unit<E>(self) -> Result<Self::Value, E>
299 where
300 E: de::Error,
301 {
302 Ok(None)
303 }
304 }
305
306 deserializer.deserialize_any(StringVisitor)
307}
308
309#[derive(Debug)]
310pub enum LoadFromPathError {
311 AlreadySet,
312 Io(io::Error),
313 Serde(serde_yaml::Error),
314}
315
316impl fmt::Display for LoadFromPathError {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 match self {
319 LoadFromPathError::AlreadySet => write!(f, "settings have already been loaded"),
320 LoadFromPathError::Io(e) => write!(f, "{}", e),
321 LoadFromPathError::Serde(e) => write!(f, "{}", e),
322 }
323 }
324}
325
326impl std::error::Error for LoadFromPathError {}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_find_setting() {
334 assert_eq!(
335 Setting::find("solution", "soln_freq"),
336 Some(&Setting {
337 name: "soln_freq".to_string(),
338 group: "solution".to_string(),
339 kind: SettingKind::Integer,
340 expert: false,
341 readonly: false,
342 description: Some("The frequency at which GNSS navigation solution is computed.\n".to_string()),
343 default_value: Some("10".to_string()),
344 notes: Some("Minimum is 1 Hz. Maximum is 10 Hz for RTK positioning with a maximum of 15 used satellites.\nAt 5 Hz and lower the maximum number of used satellites is 22. 20 Hz is an absolute maximum with\na limit of 5 used satellites.\n\nSystem with inertial fusion (Duro Inertial, Piksi Multi Inertial) can output position at a higher rate\nthan the GNSS-only solution. See fused_soln_freq in the INS group.\n".to_string()),
345 units: Some("Hz".to_string()),
346 enumerated_possible_values: None,
347 digits: None
348 })
349 );
350
351 assert_eq!(Setting::find("solution", "froo_froo"), None);
352 }
353
354 #[test]
355 fn test_na_is_none() {
356 let setting = Setting::find("tcp_server0", "enabled_sbp_messages").unwrap();
357 assert_eq!(setting.units, None);
358 }
359
360 #[test]
361 fn test_bool_display() {
362 assert_eq!(SettingValue::Boolean(true).to_string(), "True");
363 assert_eq!(SettingValue::Boolean(false).to_string(), "False");
364 }
365}