Skip to main content

dotfiles_processor/
known_directive.rs

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