dotfiles_actions/exec/action.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 [ExecAction] that executes a command in the shell
23
24use std::marker::PhantomData;
25use std::path::Path;
26
27use dotfiles_core::error::execution_error;
28use dotfiles_core::error::DotfilesError;
29use dotfiles_core_macros::ConditionalAction;
30use subprocess::Exec;
31
32use dotfiles_core::Action;
33
34/// [ExecAction] Installs software using homebrew.
35#[derive(Eq, PartialEq, Debug, ConditionalAction)]
36pub struct ExecAction<'a> {
37 /// Skips this action if it is running in a CI environment.
38 skip_in_ci: bool,
39 /// Command to run
40 command: String,
41 /// Description
42 description: Option<String>,
43 /// Whether to print out the command for clarity.
44 echo: bool,
45 phantom_data: PhantomData<&'a String>,
46}
47
48impl<'a> ExecAction<'a> {
49 /// Create a new Exec Action that will run from the parent directory of the config file
50 pub fn new(
51 skip_in_ci: bool,
52 command: String,
53 description: Option<String>,
54 echo: bool,
55 current_dir: &Path,
56 ) -> Result<Self, DotfilesError> {
57 let action = ExecAction {
58 skip_in_ci,
59 command: format!(
60 "cd \"{}\" && {}",
61 current_dir.as_os_str().to_str().unwrap(),
62 command
63 ),
64 description,
65 echo,
66 phantom_data: PhantomData,
67 };
68 log::trace!("Creating new {:?}", action);
69 Ok(action)
70 }
71 /// The command to run
72 pub fn command(&self) -> &str {
73 self.command.as_str()
74 }
75
76 /// Whether to print out the command for clarity.
77 pub fn echo(&self) -> bool {
78 self.echo
79 }
80
81 /// Description for the command to run.
82 pub fn description(&self) -> Option<&String> {
83 self.description.as_ref()
84 }
85}
86
87impl<'a> Action<'a> for ExecAction<'a> {
88 fn execute(&self) -> Result<(), DotfilesError> {
89 if let Some(description) = self.description.as_ref() {
90 log::info!("{}", description);
91 }
92 if self.echo {
93 log::info!("Running command: {0}", self.command);
94 }
95 Exec::shell(self.command()).join().map_or_else(
96 |err| {
97 Err(DotfilesError::from(
98 format!(
99 "Couldn't run command `{0}`, failed with error {1}",
100 self.command(),
101 err
102 ),
103 execution_error(Some(err), None),
104 ))
105 },
106 |status| match status.success() {
107 true => Ok(()),
108 false => Err(DotfilesError::from(
109 format!(
110 "Command `{0}` failed with error code {1}",
111 self.command(),
112 status.code().unwrap()
113 ),
114 execution_error(None, Some(status)),
115 ))
116 },
117 )
118 }
119}