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}