dotfiles_processor/
known_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
22use std::convert::TryFrom;
23use std::path::PathBuf;
24
25#[cfg(target_os = "linux")]
26use dotfiles_actions::apt::{action::AptAction, directive::AptDirective};
27#[cfg(any(target_os = "linux", target_os = "macos"))]
28use dotfiles_actions::brew::{action::BrewAction, directive::BrewDirective};
29use dotfiles_actions::create::{action::NativeCreateAction, directive::NativeCreateDirective};
30use dotfiles_actions::exec::{action::ExecAction, directive::ExecDirective};
31#[cfg(unix)]
32use dotfiles_actions::link::{action::NativeLinkAction, directive::NativeLinkDirective};
33use dotfiles_core::action::ActionParser;
34use dotfiles_core::action::ConditionalAction;
35use dotfiles_core::directive::{DirectiveData, HasDirectiveData};
36use dotfiles_core::error::{DotfilesError, ErrorType};
37use dotfiles_core::yaml_util::map_yaml_array;
38use dotfiles_core::Settings;
39use once_cell::sync::Lazy;
40use strict_yaml_rust::StrictYaml;
41
42use crate::context::Context;
43
44#[cfg(target_os = "linux")]
45static APT: Lazy<AptDirective<'static>> = Lazy::new(Default::default);
46#[cfg(any(target_os = "linux", target_os = "macos"))]
47static BREW: Lazy<BrewDirective<'static>> = Lazy::new(Default::default);
48static CREATE: Lazy<NativeCreateDirective<'static>> = Lazy::new(Default::default);
49static EXEC: Lazy<ExecDirective<'static>> = Lazy::new(Default::default);
50#[cfg(unix)]
51static LINK: Lazy<NativeLinkDirective<'static>> = Lazy::new(Default::default);
52static SUBCONFIG_DIRECTIVE_DATA: Lazy<DirectiveData> = Lazy::new(subconfig_directive_data);
53
54fn subconfig_directive_data() -> DirectiveData {
55  DirectiveData::from("subconfig".into(), Default::default())
56}
57
58#[derive(Clone)]
59pub enum KnownDirective {
60  #[cfg(target_os = "linux")]
61  Apt,
62  #[cfg(any(target_os = "linux", target_os = "macos"))]
63  Brew,
64  Create,
65  Exec,
66  #[cfg(unix)]
67  Link,
68  Subconfig,
69}
70
71pub enum KnownAction<'a> {
72  #[cfg(target_os = "linux")]
73  Apt(AptAction<'a>),
74  #[cfg(any(target_os = "linux", target_os = "macos"))]
75  Brew(BrewAction<'a>),
76  Create(NativeCreateAction<'a>),
77  Exec(ExecAction<'a>),
78  #[cfg(unix)]
79  Link(NativeLinkAction<'a>),
80  Subconfig(Context),
81}
82
83#[cfg(target_os = "linux")]
84impl<'a> From<AptAction<'a>> for KnownAction<'a> {
85  fn from(value: AptAction<'a>) -> Self {
86    KnownAction::Apt(value)
87  }
88}
89
90#[cfg(any(target_os = "linux", target_os = "macos"))]
91impl<'a> From<BrewAction<'a>> for KnownAction<'a> {
92  fn from(value: BrewAction<'a>) -> Self {
93    KnownAction::Brew(value)
94  }
95}
96
97impl<'a> From<NativeCreateAction<'a>> for KnownAction<'a> {
98  fn from(value: NativeCreateAction<'a>) -> Self {
99    KnownAction::Create(value)
100  }
101}
102
103impl<'a> From<ExecAction<'a>> for KnownAction<'a> {
104  fn from(value: ExecAction<'a>) -> Self {
105    KnownAction::Exec(value)
106  }
107}
108
109#[cfg(unix)]
110impl<'a> From<NativeLinkAction<'a>> for KnownAction<'a> {
111  fn from(value: NativeLinkAction<'a>) -> Self {
112    KnownAction::Link(value)
113  }
114}
115
116impl<'a> From<Context> for KnownAction<'a> {
117  fn from(value: Context) -> Self {
118    KnownAction::Subconfig(value)
119  }
120}
121
122impl<'a> KnownAction<'a> {
123  pub fn execute(self) -> Result<(), DotfilesError> {
124    match self {
125      #[cfg(target_os = "linux")]
126      KnownAction::Apt(action) => action.check_conditions_and_execute().map_err(|mut err| {
127        err.add_message_prefix("Executing apt action".into());
128        err
129      }),
130      #[cfg(any(target_os = "linux", target_os = "macos"))]
131      KnownAction::Brew(action) => action.check_conditions_and_execute().map_err(|mut err| {
132        err.add_message_prefix("Executing brew action".into());
133        err
134      }),
135      KnownAction::Create(action) => action.check_conditions_and_execute().map_err(|mut err| {
136        err.add_message_prefix("Executing create action".into());
137        err
138      }),
139      KnownAction::Exec(action) => action.check_conditions_and_execute().map_err(|mut err| {
140        err.add_message_prefix("Executing exec action".into());
141        err
142      }),
143      #[cfg(unix)]
144      KnownAction::Link(action) => action.check_conditions_and_execute().map_err(|mut err| {
145        err.add_message_prefix("Executing link action".into());
146        err
147      }),
148      KnownAction::Subconfig(subcontext) => Context::run_actions(subcontext),
149    }
150  }
151}
152
153impl KnownDirective {
154  pub fn data(&self) -> &DirectiveData {
155    match self {
156      #[cfg(target_os = "linux")]
157      KnownDirective::Apt => APT.directive_data(),
158      #[cfg(any(target_os = "linux", target_os = "macos"))]
159      KnownDirective::Brew => BREW.directive_data(),
160      KnownDirective::Create => CREATE.directive_data(),
161      KnownDirective::Exec => EXEC.directive_data(),
162      #[cfg(unix)]
163      KnownDirective::Link => LINK.directive_data(),
164      KnownDirective::Subconfig => &SUBCONFIG_DIRECTIVE_DATA,
165    }
166  }
167  pub fn parse_context_defaults(
168    &self,
169    defaults: &strict_yaml_rust::StrictYaml,
170  ) -> Result<(String, Settings), DotfilesError> {
171    Ok((
172      self.data().name().clone(),
173      self.data().parse_context_defaults(defaults)?,
174    ))
175  }
176
177  pub fn parse_action_list<'a>(
178    directive: KnownDirective,
179    context_settings: &Settings,
180    actions: &strict_yaml_rust::StrictYaml,
181    context: &Context,
182  ) -> Result<Vec<KnownAction<'a>>, DotfilesError> {
183    let file = context.file();
184    let current_dir = file.parent().ok_or_else(||
185      DotfilesError::from(
186        format!(
187          "{} doesn't seem to have a parent dir to use as a current directory to parse actions, this makes no sense and should never happen",
188          file.to_str().unwrap()) ,
189      ErrorType::CoreError))?;
190    match directive {
191      #[cfg(target_os = "linux")]
192      KnownDirective::Apt => APT
193        .parse_action_list(context_settings, actions, current_dir)
194        .map(|list| list.into_iter().map(KnownAction::from).collect()),
195      #[cfg(any(target_os = "linux", target_os = "macos"))]
196      KnownDirective::Brew => BREW
197        .parse_action_list(context_settings, actions, current_dir)
198        .map(|list| list.into_iter().map(KnownAction::from).collect()),
199      KnownDirective::Create => CREATE
200        .parse_action_list(context_settings, actions, current_dir)
201        .map(|list| list.into_iter().map(KnownAction::from).collect()),
202      KnownDirective::Exec => EXEC
203        .parse_action_list(context_settings, actions, current_dir)
204        .map(|list| list.into_iter().map(KnownAction::from).collect()),
205      #[cfg(unix)]
206      KnownDirective::Link => LINK
207        .parse_action_list(context_settings, actions, current_dir)
208        .map(|list| list.into_iter().map(KnownAction::from).collect()),
209      KnownDirective::Subconfig => map_yaml_array(actions, |file_yaml| {
210        file_yaml
211          .clone()
212          .into_string()
213          .ok_or(DotfilesError::from_wrong_yaml(
214            "Subconfig: Expected a file name".to_owned(),
215            actions.clone(),
216            StrictYaml::String("".into()),
217          ))
218          .and_then(|filename| {
219            let path = PathBuf::from(&filename);
220            let mut subcontext = context.subcontext(&path)?;
221            subcontext.parse_file()?;
222            Ok(KnownAction::from(subcontext))
223          })
224      }),
225    }
226  }
227}
228
229impl TryFrom<&str> for KnownDirective {
230  type Error = DotfilesError;
231
232  fn try_from(value: &str) -> Result<Self, Self::Error> {
233    match value {
234      #[cfg(target_os = "linux")]
235      "apt" => Ok(KnownDirective::Apt),
236      #[cfg(any(target_os = "linux", target_os = "macos"))]
237      "brew" => Ok(KnownDirective::Brew),
238      "create" => Ok(KnownDirective::Create),
239      "exec" => Ok(KnownDirective::Exec),
240      "link" => Ok(KnownDirective::Link),
241      "subconfig" => Ok(KnownDirective::Subconfig),
242      _ => Err(DotfilesError::from(
243        format!("Configuration refers to unknown directive `{value}`"),
244        ErrorType::InconsistentConfigurationError,
245      )),
246    }
247  }
248}