Skip to main content

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}