1extern crate strict_yaml_rust;
24
25use std::{path::Path, str::FromStr, vec};
26
27use crate::{
28 error::{fold_until_first_err, DotfilesError, ErrorType},
29 settings::{parse_setting, Setting, Settings},
30};
31use strict_yaml_rust::{StrictYaml, StrictYamlLoader};
32
33pub fn process_yaml_hash_until_first_err<F>(
37 yaml_hash: &StrictYaml,
38 mut process_function: F,
39) -> Result<(), DotfilesError>
40where
41 F: FnMut(String, &StrictYaml) -> Result<(), DotfilesError>,
42{
43 if let StrictYaml::Hash(hash) = yaml_hash {
44 hash.into_iter().try_for_each(|(key, value)| {
45 parse_as_string(key)
46 .map(|key_str| (key_str, value))
47 .and_then(|(key, val)| process_function(key, val))
48 })
49 } else {
50 Err(DotfilesError::from_wrong_yaml(
51 "Expected a yaml hash, got something else".to_owned(),
52 yaml_hash.to_owned(),
53 StrictYaml::Hash(Default::default()),
54 ))
55 }
56}
57
58pub fn process_value_from_yaml_hash<T, F>(
69 key: &str,
70 yaml_hash: &StrictYaml,
71 mut process: F,
72) -> Result<T, DotfilesError>
73where
74 F: FnMut(&StrictYaml) -> Result<T, DotfilesError>,
75{
76 if let StrictYaml::Hash(inner_hash) = yaml_hash {
77 match inner_hash.get(&StrictYaml::String(key.into())) {
78 Some(yaml) => process(yaml),
79 None => Err(DotfilesError::from(
80 format!("Hash does not contain key {}", key.to_owned()),
81 ErrorType::IncompleteConfigurationError {
82 missing_field: key.into(),
83 },
84 )),
85 }
86 } else {
87 Err(DotfilesError::from_wrong_yaml(
88 "process_value_from_yaml_hash expects a hash, but got something else".into(),
89 yaml_hash.to_owned(),
90 StrictYaml::Hash(Default::default()),
91 ))
92 }
93}
94
95pub fn map_yaml_array<T, F>(yaml_array: &StrictYaml, process: F) -> Result<Vec<T>, DotfilesError>
103where
104 F: FnMut(&StrictYaml) -> Result<T, DotfilesError>,
105{
106 if let StrictYaml::Array(inner_vec) = yaml_array {
107 inner_vec.iter().map(process).collect()
108 } else {
109 Err(DotfilesError::from_wrong_yaml(
110 "map_yaml_array expects a yaml array, but got something else".into(),
111 yaml_array.to_owned(),
112 StrictYaml::Array(vec![]),
113 ))
114 }
115}
116
117pub fn get_setting_from_yaml_hash(
125 name: &str,
126 setting_type: &Setting,
127 yaml: &StrictYaml,
128) -> Result<Setting, DotfilesError> {
129 process_value_from_yaml_hash(name, yaml, |value_for_name| {
130 parse_setting(setting_type, value_for_name)
131 })
132}
133pub fn get_boolean_from_yaml_hash(name: &str, yaml: &StrictYaml) -> Result<bool, DotfilesError> {
141 process_value_from_yaml_hash(name, yaml, |value_for_name| {
142 parse_as_boolean(value_for_name)
143 })
144}
145
146pub fn get_integer_from_yaml_hash(name: &str, yaml: &StrictYaml) -> Result<i64, DotfilesError> {
154 process_value_from_yaml_hash(name, yaml, |value_for_name| {
155 parse_as_integer(value_for_name)
156 })
157}
158
159pub fn get_string_from_yaml_hash(name: &str, yaml: &StrictYaml) -> Result<String, DotfilesError> {
167 process_value_from_yaml_hash(name, yaml, parse_as_string)
168}
169
170pub fn get_string_array_from_yaml_hash(
179 name: &str,
180 yaml: &StrictYaml,
181) -> Result<Vec<String>, DotfilesError> {
182 process_value_from_yaml_hash(name, yaml, |value_for_name| {
183 parse_as_string_array(value_for_name)
184 })
185}
186
187pub fn get_optional_string_array_from_yaml_hash(
196 name: &str,
197 yaml: &StrictYaml,
198) -> Result<Vec<String>, DotfilesError> {
199 get_string_array_from_yaml_hash(name, yaml).or_else(|err| {
200 if err.is_missing_config(name) {
201 Ok(vec![])
202 } else {
203 Err(err)
204 }
205 })
206}
207
208pub fn get_boolean_setting_from_context(
215 name: &str,
216 context_settings: &Settings,
217 directive_defaults: &Settings,
218) -> Result<bool, DotfilesError> {
219 if let Setting::Boolean(b) = get_setting_from_context(name, context_settings, directive_defaults)?
220 {
221 Ok(b)
222 } else {
223 Err(DotfilesError::from(
224 format!("Setting {name} was found in directive defaults but is not boolean",),
225 ErrorType::CoreError,
226 ))
227 }
228}
229
230pub fn get_string_setting(
237 name: &str,
238 context_settings: &Settings,
239 directive_defaults: &Settings,
240) -> Result<String, DotfilesError> {
241 if let Setting::String(s) = get_setting_from_context(name, context_settings, directive_defaults)?
242 {
243 Ok(s)
244 } else {
245 Err(DotfilesError::from(
246 format!("Setting {name} was found in directive defaults but is not a string",),
247 ErrorType::CoreError,
248 ))
249 }
250}
251
252pub fn get_integer_setting(
259 name: &str,
260 context_settings: &Settings,
261 directive_defaults: &Settings,
262) -> Result<i64, DotfilesError> {
263 if let Setting::Integer(x) = get_setting_from_context(name, context_settings, directive_defaults)?
264 {
265 Ok(x)
266 } else {
267 Err(DotfilesError::from(
268 format!("Setting {name} was found in directive defaults but is not an integer",),
269 ErrorType::CoreError,
270 ))
271 }
272}
273
274pub fn get_setting_from_context(
281 name: &str,
282 context_settings: &Settings,
283 directive_defaults: &Settings,
284) -> Result<Setting, DotfilesError> {
285 if let Some(setting) = context_settings.get(name) {
286 Ok(setting.clone())
287 } else if let Some(setting) = directive_defaults.get(name) {
288 Ok(setting.clone())
289 } else {
290 Err(DotfilesError::from(
291 format!("Setting {name} couldn't be found in context or defaults"),
292 ErrorType::CoreError,
293 ))
294 }
295}
296
297pub fn get_setting_from_yaml_hash_or_from_context(
299 name: &str,
300 yaml: &StrictYaml,
301 context_settings: &Settings,
302 directive_defaults: &Settings,
303) -> Result<Setting, DotfilesError> {
304 if let Some(setting_type) = directive_defaults.get(name) {
305 get_setting_from_yaml_hash(name, setting_type, yaml)
306 .or_else(|_| get_setting_from_context(name, context_settings, directive_defaults))
307 } else {
308 Err(DotfilesError::from(
309 format!("Unknown setting: {}", name),
310 ErrorType::InconsistentConfigurationError,
311 ))
312 }
313}
314
315pub fn get_boolean_setting_from_yaml_or_context(
324 name: &str,
325 yaml: &StrictYaml,
326 context_settings: &Settings,
327 directive_defaults: &Settings,
328) -> Result<bool, DotfilesError> {
329 get_boolean_from_yaml_hash(name, yaml)
330 .or_else(|_| get_boolean_setting_from_context(name, context_settings, directive_defaults))
331}
332
333pub fn get_integer_setting_from_yaml_or_context(
342 name: &str,
343 yaml: &StrictYaml,
344 context_settings: &Settings,
345 directive_defaults: &Settings,
346) -> Result<i64, DotfilesError> {
347 get_integer_from_yaml_hash(name, yaml)
348 .or_else(|_| get_integer_setting(name, context_settings, directive_defaults))
349}
350
351pub fn get_string_setting_from_yaml_or_context(
360 name: &str,
361 yaml: &StrictYaml,
362 context_settings: &Settings,
363 directive_defaults: &Settings,
364) -> Result<String, DotfilesError> {
365 process_value_from_yaml_hash(name, yaml, parse_as_string)
366 .or_else(|_| get_string_setting(name, context_settings, directive_defaults))
367}
368
369pub fn get_string_content_or_keyed_value(
380 yaml: &StrictYaml,
381 key: Option<&str>,
382) -> Result<String, DotfilesError> {
383 parse_as_string(yaml).or_else(|err| {
384 if let Some(key_str) = key {
385 get_string_from_yaml_hash(key_str, yaml)
386 } else {
387 Err(err)
388 }
389 })
390}
391
392pub fn parse_as_string_array(yaml: &StrictYaml) -> Result<Vec<String>, DotfilesError> {
395 map_yaml_array(yaml, parse_as_string)
396}
397
398pub fn parse_as_string(yaml_to_parse: &StrictYaml) -> Result<String, DotfilesError> {
404 match yaml_to_parse {
405 StrictYaml::String(s) => Ok(s.to_owned()),
406 _ => Err(DotfilesError::from_wrong_yaml(
407 "Expected StrictYaml String and got something else".into(),
408 yaml_to_parse.clone(),
409 StrictYaml::String("".into()),
410 )),
411 }
412}
413
414pub fn parse_as_boolean(yaml: &StrictYaml) -> Result<bool, DotfilesError> {
419 if let StrictYaml::String(b) = yaml {
420 FromStr::from_str(b).map_err(|_| {
421 DotfilesError::from_wrong_yaml(
422 format!("Got a Yaml String that can't be parsed as boolean: `{b}`"),
423 yaml.to_owned(),
424 StrictYaml::String("true".into()),
425 )
426 })
427 } else {
428 Err(DotfilesError::from_wrong_yaml(
429 "Expected StrictYaml string containing a boolean and got something else".into(),
430 yaml.clone(),
431 StrictYaml::String("false".into()),
432 ))
433 }
434}
435pub fn parse_as_integer(yaml: &StrictYaml) -> Result<i64, DotfilesError> {
440 if let StrictYaml::String(i) = yaml {
441 FromStr::from_str(i).map_err(|_| {
442 DotfilesError::from_wrong_yaml(
443 format!("Got a Yaml String that can't be parsed as integer: `{i}`"),
444 yaml.to_owned(),
445 StrictYaml::String("11111".into()),
446 )
447 })
448 } else {
449 Err(DotfilesError::from_wrong_yaml(
450 "Expected StrictYaml String and got something else".into(),
451 yaml.clone(),
452 StrictYaml::String("0".into()),
453 ))
454 }
455}
456
457pub fn parse_as_array(yaml: &StrictYaml) -> Result<Vec<StrictYaml>, DotfilesError> {
462 if let Some(v) = yaml.as_vec() {
463 Ok(v.to_owned())
464 } else {
465 Err(DotfilesError::from_wrong_yaml(
466 "Expected StrictYaml Array and got something else".into(),
467 yaml.clone(),
468 StrictYaml::Array(vec![]),
469 ))
470 }
471}
472
473pub fn read_yaml_file(file: &Path) -> Result<Vec<StrictYaml>, DotfilesError> {
475 let contents = std::fs::read_to_string(file).map_err(DotfilesError::from_io_error)?;
476 StrictYamlLoader::load_from_str(&contents).map_err(|err| {
477 DotfilesError::from(
478 format!("yaml syntax error in file `{:?}`", file.as_os_str()),
479 ErrorType::YamlParseError { scan_error: err },
480 )
481 })
482}
483
484pub fn fold_hash_until_first_err<T, P, Processed, F>(
495 yaml: &StrictYaml,
496 init: Result<T, DotfilesError>,
497 mut process_function: P,
498 fold_function: F,
499) -> Result<T, DotfilesError>
500where
501 P: FnMut(String, &StrictYaml) -> Result<Processed, DotfilesError>,
502 F: FnMut(T, Processed) -> Result<T, DotfilesError>,
503{
504 if let StrictYaml::Hash(hash) = yaml {
505 fold_until_first_err(
506 hash,
507 init,
508 |(yaml_key, yaml_value)| process_function(parse_as_string(yaml_key)?, yaml_value),
509 fold_function,
510 )
511 } else {
512 Err(DotfilesError::from_wrong_yaml(
513 "Expected StrictYaml Hash, got wrong type".to_owned(),
514 yaml.to_owned(),
515 StrictYaml::Hash(Default::default()),
516 ))
517 }
518}
519
520pub fn parse_context_defaults(
522 name: &str,
523 default_settings: &Settings,
524 yaml_settings: &StrictYaml,
525) -> Result<Settings, DotfilesError> {
526 fold_hash_until_first_err(
527 yaml_settings,
528 Ok(Settings::new()),
529 |setting_name, value_yaml| {
530 if let Some(setting_type) = default_settings.get(&setting_name) {
531 parse_setting(setting_type, value_yaml).map(|value| (setting_name, value))
532 } else {
533 Err(DotfilesError::from(
534 format!(
535 "Action type `{}` could not parse settings, unknown setting: {}",
536 name, setting_name,
537 ),
538 ErrorType::InconsistentConfigurationError,
539 ))
540 }
541 },
542 |mut settings, (setting_name, val)| {
543 settings
544 .try_insert(setting_name.clone(), val)
545 .map_err(|_| {
546 DotfilesError::from(
547 format!(
548 "Action type {} configuration contains duplicated setting {}",
549 name, setting_name
550 ),
551 ErrorType::InconsistentConfigurationError,
552 )
553 })?;
554 Ok(settings)
555 },
556 )
557}