dotfiles_core/
directive.rs

1// Copyright (c) 2021-2022 Miguel Barreto and others
2//
3// Permission is hereby granted, free of charge, to any person obtaining
4// a copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to
8// permit persons to whom the Software is furnished to do so, subject to
9// the following conditions:
10//
11// The above copyright notice and this permission notice shall be
12// included in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22//! This module contains the base trait for all [Directive] and all
23//! necessary conveniences to allow for user-configuration of directive
24//! defaults.
25
26extern crate strict_yaml_rust;
27
28use crate::{
29  error::{DotfilesError, ErrorType},
30  settings::{parse_setting, Settings},
31  yaml_util::{fold_hash_until_first_err, get_setting_from_context},
32  Setting,
33};
34use getset::Getters;
35use strict_yaml_rust::StrictYaml;
36
37/// A struct that contains the default settings for a Directive and the
38/// name it takes in configuration sources. The name must be unique.
39///
40/// These default settings can be configured by the user as well.
41#[derive(Getters, Debug, Clone)]
42pub struct DirectiveData {
43  /// Unique name of this directive.
44  ///
45  /// This name will be used in configuration sources to instantiate actions
46  /// of this directive
47  #[getset(get = "pub")]
48  name: String,
49  /// Default settings for this directive.
50  ///
51  /// Any setting that is not in the defaults for a directive but is part of
52  /// the corresponding Action struct is considered to be mandatory.
53  ///
54  /// Since all configurable settings have a default, this can also be used to infer the data
55  /// types.
56  #[getset(get = "pub")]
57  defaults: Settings,
58}
59impl DirectiveData {
60  /// Constructs a new directive from a name and a set of default settings.
61  pub fn from(name: String, defaults: Settings) -> DirectiveData {
62    DirectiveData { name, defaults }
63  }
64
65  // Parses an individual setting named `name`'s value from a yaml containing the value, according
66  // to the type set in
67  /// `DirectiveData.setting_types`.
68  pub fn parse_setting_value(
69    &self,
70    name: &str,
71    yaml: &StrictYaml,
72  ) -> Result<Setting, DotfilesError> {
73    if let Some(setting_type) = self.defaults().get(name) {
74      parse_setting(setting_type, yaml)
75    } else {
76      Err(DotfilesError::from(
77        format!(
78          "Directive `{}` could not parse settings, unknown setting: {}",
79          self.name(),
80          name,
81        ),
82        ErrorType::InconsistentConfigurationError,
83      ))
84    }
85  }
86
87  /// Parses all settings for this directive from StrictYaml, checking the types correspond to
88  /// what's stored in `DirectiveData.setting_types`
89  pub fn parse_context_defaults(
90    &self,
91    yaml_settings: &StrictYaml,
92  ) -> Result<Settings, DotfilesError> {
93    fold_hash_until_first_err(
94      yaml_settings,
95      Ok(Settings::new()),
96      |name, value_yaml| {
97        self
98          .parse_setting_value(&name, value_yaml)
99          .map(|value| (name, value))
100      },
101      |mut settings, (name, val)| {
102        settings.try_insert(name.clone(), val).map_err(|_| {
103          DotfilesError::from(
104            format!(
105              "Directive {} configuration contains duplicated setting {}",
106              self.name(),
107              name
108            ),
109            ErrorType::InconsistentConfigurationError,
110          )
111        })?;
112        Ok(settings)
113      },
114    )
115  }
116}
117
118/// A trait for all directives, it is shared between [crate::action::ActionParser] and [Directive]
119pub trait HasDirectiveData<'a> {
120  /// Returns the directive data for this object
121  fn directive_data(&'a self) -> &'a DirectiveData;
122
123  /// Returns the name of the directive.
124  fn name(&'a self) -> &'a str {
125    self.directive_data().name()
126  }
127
128  /// Returns the default settings as configured.
129  fn defaults(&'a self) -> &'a Settings {
130    self.directive_data().defaults()
131  }
132}
133
134/// A parser for action steps, each directive represents a type of Action.
135pub trait Directive<'a>: HasDirectiveData<'a> {
136  /// Parse a particular setting with its correct type from yaml, fall back to context settings or
137  /// directive defaults if not found in yaml. Returns error if there is any kind of parsing or
138  /// typing error
139  fn get_setting_from_yaml_hash_or_from_context(
140    &'a self,
141    name: &str,
142    yaml: &StrictYaml,
143    context_settings: &Settings,
144  ) -> Result<Setting, DotfilesError> {
145    self
146      .get_setting_from_yaml_hash(name, yaml)
147      .or_else(|_| get_setting_from_context(name, context_settings, self.defaults()))
148  }
149
150  /// Parses an individual setting named `name` from a yaml hash using the type stored in
151  /// `DirectiveData.setting_types`.
152  fn get_setting_from_yaml_hash(
153    &'a self,
154    name: &str,
155    yaml: &StrictYaml,
156  ) -> Result<Setting, DotfilesError> {
157    if let Some(setting_type) = self.directive_data().defaults().get(name) {
158      crate::yaml_util::get_setting_from_yaml_hash(name, setting_type, yaml)
159    } else {
160      Err(DotfilesError::from(
161        format!(
162          "Directive `{}` could not parse settings, unknown setting: {}",
163          self.directive_data().name(),
164          name,
165        ),
166        ErrorType::InconsistentConfigurationError,
167      ))
168    }
169  }
170}