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