1#![cfg(unix)]
26use crate::install_command::InstallCommand;
27use dotfiles_core::action::Action;
28use dotfiles_core::error::DotfilesError;
29use dotfiles_core_macros::ConditionalAction;
30use getset::Getters;
31#[cfg(target_os = "macos")]
32use log::info;
33#[cfg(target_os = "macos")]
34use std::fmt::Display;
35use std::marker::PhantomData;
36use subprocess::Exec;
37#[cfg(target_os = "macos")]
38#[derive(Getters, Eq, PartialEq, Debug, Clone)]
39pub struct MacAppStoreItem {
41 #[getset(get)]
42 id: i64,
44 #[getset(get)]
45 name: String,
47}
48
49#[cfg(target_os = "macos")]
50impl From<(i64, String)> for MacAppStoreItem {
51 fn from(value: (i64, String)) -> Self {
52 MacAppStoreItem {
53 id: value.0,
54 name: value.1,
55 }
56 }
57}
58
59#[cfg(target_os = "macos")]
60impl Display for MacAppStoreItem {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "id: {}, name: {}", self.id, self.name)
63 }
64}
65
66#[cfg(target_os = "macos")]
67#[derive(Eq, PartialEq, Debug, Clone)]
68pub struct MacAppStoreCommand {
70 items: Vec<MacAppStoreItem>,
71 args: Vec<String>,
72}
73
74#[cfg(target_os = "macos")]
75impl From<Vec<MacAppStoreItem>> for MacAppStoreCommand {
76 fn from(items: Vec<MacAppStoreItem>) -> Self {
77 let mut args: Vec<String> = items.iter().map(|it| it.id().to_string()).collect();
78 args.insert(0, "install".into());
79 MacAppStoreCommand { items, args }
80 }
81}
82
83#[cfg(target_os = "macos")]
84impl InstallCommand<MacAppStoreItem> for MacAppStoreCommand {
85 fn base_command(&self) -> Exec {
86 Exec::cmd("mas")
87 }
88
89 fn args(&self) -> &Vec<String> {
90 &self.args
91 }
92
93 fn action_description(&self) -> &str {
94 "Installing from Mac App Store"
95 }
96
97 fn items(&self) -> &Vec<MacAppStoreItem> {
98 &self.items
99 }
100
101 fn action_name(&self) -> &str {
102 "mas"
103 }
104
105 fn execute(&self) -> Result<(), DotfilesError> {
106 let item_list: String = self
107 .items()
108 .iter()
109 .map(|it| format!("{}", it))
110 .collect::<Vec<String>>()
111 .join(", ");
112 info!("{} {}", self.action_description(), item_list);
113 let mut cmd = self.base_command();
114 for arg in self.args().iter() {
115 cmd = cmd.arg(arg);
116 }
117 dotfiles_core::exec_wrapper::execute_commands(
118 vec![cmd],
119 format!("Couldn't {} {}", self.action_name(), item_list).as_str(),
120 format!(
121 "Unexpected error while {} {}",
122 self.action_description(),
123 &item_list
124 )
125 .as_str(),
126 )
127 }
128}
129
130struct BrewCommand {
131 items: Vec<String>,
132 args: Vec<String>,
133 action_name: String,
134 action_description: String,
135}
136
137impl InstallCommand<String> for BrewCommand {
138 fn base_command(&self) -> Exec {
139 Exec::cmd("brew")
140 }
141
142 fn args(&self) -> &Vec<String> {
143 &self.args
144 }
145
146 fn action_description(&self) -> &str {
147 &self.action_description
148 }
149
150 fn items(&self) -> &Vec<String> {
151 &self.items
152 }
153
154 fn action_name(&self) -> &str {
155 &self.action_name
156 }
157}
158impl BrewCommand {
159 fn tap(tap: &str) -> BrewCommand {
160 BrewCommand {
161 items: vec![tap.into()],
162 args: vec!["tap".into(), tap.into()],
163 action_name: "tap".into(),
164 action_description: "tapping".into(),
165 }
166 }
167
168 fn install_formulae(items: &Vec<String>) -> BrewCommand {
169 let mut args: Vec<String> = items.clone();
170 args.insert(0, "install".into());
171 BrewCommand {
172 items: items.clone(),
173 args: args,
174 action_name: "install formula".into(),
175 action_description: "installing formula".into(),
176 }
177 }
178
179 fn install_casks(items: &Vec<String>, force: &bool, adopt: &bool) -> BrewCommand {
180 let mut args = vec!["install".into(), "--cask".into()];
181 if *force {
182 args.push("--force".into())
183 }
184 if *adopt {
185 args.push("--adopt".into())
186 }
187 {
188 let mut items = items.clone();
189 args.append(&mut items)
190 }
191 let args = args;
192 let items = items.clone();
193 BrewCommand {
194 items,
195 args,
196 action_name: "install cask".into(),
197 action_description: "installing cask".into(),
198 }
199 }
200}
201
202#[derive(Eq, PartialEq, Debug, ConditionalAction, Getters)]
204pub struct BrewAction<'a> {
205 #[get = "pub"]
207 skip_in_ci: bool,
208 #[get = "pub"]
210 force_casks: bool,
211 #[get = "pub"]
214 adopt_casks: bool,
215 #[get = "pub"]
217 taps: Vec<String>,
218 #[get = "pub"]
220 formulae: Vec<String>,
221
222 #[get = "pub"]
225 casks: Vec<String>,
226
227 #[cfg(target_os = "macos")]
228 #[get = "pub"]
230 mas_apps: Vec<MacAppStoreItem>,
231 phantom_data: PhantomData<&'a String>,
232}
233impl<'a> BrewAction<'a> {
234 pub fn new(
236 skip_in_ci: bool,
237 force_casks: bool,
238 adopt_casks: bool,
239 taps: Vec<String>,
240 formulae: Vec<String>,
241 casks: Vec<String>,
242 #[cfg(target_os = "macos")] mas_apps: Vec<MacAppStoreItem>,
243 ) -> Self {
244 let action = BrewAction {
245 skip_in_ci,
246 force_casks,
247 adopt_casks,
248 taps,
249 formulae,
250 casks,
251 #[cfg(target_os = "macos")]
252 mas_apps,
253 phantom_data: PhantomData,
254 };
255 log::trace!("Creating new {:?}", action);
256 action
257 }
258}
259
260impl Action<'_> for BrewAction<'_> {
261 fn execute(&self) -> Result<(), DotfilesError> {
262 for tap in &self.taps {
263 BrewCommand::tap(tap).execute()?;
264 }
265 if !self.formulae.is_empty() {
266 BrewCommand::install_formulae(&self.formulae).execute()?;
267 }
268 if !self.casks.is_empty() {
269 BrewCommand::install_casks(&self.casks, self.force_casks(), self.adopt_casks()).execute()?;
270 }
271 Ok(())
272 }
273}