1use 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}