dotfiles_actions/create/
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 [CreateAction] that creates a new directory
23//! when executed
24
25extern crate strict_yaml_rust;
26
27use derivative::Derivative;
28use dotfiles_core::action::Action;
29use dotfiles_core::error::DotfilesError;
30use dotfiles_core::path::convert_path_to_absolute;
31use dotfiles_core::path::process_home_dir_in_path;
32use dotfiles_core_macros::ConditionalAction;
33use filesystem::FakeFileSystem;
34use filesystem::FileSystem;
35use filesystem::OsFileSystem;
36
37use getset::Getters;
38use log::info;
39
40use std::io::ErrorKind;
41use std::path::Path;
42use std::path::PathBuf;
43
44/// [CreateAction] creates a new [directory](CreateAction::directory) when executed
45#[derive(Derivative, ConditionalAction, Getters)]
46#[derivative(Debug, PartialEq)]
47pub struct CreateAction<'a, F: FileSystem> {
48  /// Skips this action if it is running in a CI environment.
49  skip_in_ci: bool,
50  /// FileSystem to use to create the directory.
51  ///
52  /// Having a filesystem instance here allows us to use fakes/mocks to use
53  /// in unit tests.
54  #[derivative(Debug = "ignore", PartialEq = "ignore")]
55  fs: &'a F,
56  /// Directory to create. Can be absolute or relative.
57  #[get = "pub"]
58  directory: String,
59  /// Force creation of the directory and all its parents if they do not
60  /// exist already.
61  ///
62  /// Setting [`create_parent_dirs`](CreateAction::create_parent_dirs) to `true` is equivalent to
63  /// using the `-p` flag in `mkdir`.
64  #[get = "pub"]
65  create_parent_dirs: bool,
66  /// Current directory that will be used to determine relative file locations if necessary. It
67  /// must match the parent directory of the configuration file that declared this action.
68  #[get = "pub"]
69  current_dir: PathBuf,
70}
71
72/// A native create action that works on the real filesystem.
73pub type NativeCreateAction<'a> = CreateAction<'a, OsFileSystem>;
74/// A Fake create action that works on a fake test filesystem.
75pub type FakeCreateAction<'a> = CreateAction<'a, FakeFileSystem>;
76
77impl<'a, F: FileSystem> CreateAction<'a, F> {
78  /// Constructs a new instance of CreateAction
79  pub fn new(
80    fs: &'a F,
81    skip_in_ci: bool,
82    directory: String,
83    create_parent_dirs: bool,
84    current_dir: PathBuf,
85  ) -> Result<Self, DotfilesError> {
86    let action = CreateAction {
87      skip_in_ci,
88      fs,
89      directory,
90      create_parent_dirs,
91      current_dir,
92    };
93    log::trace!("Creating new {:?}", action);
94    Ok(action)
95  }
96}
97
98impl<F: FileSystem> Action<'_> for CreateAction<'_, F> {
99  /// Creates the [`directory`](CreateAction::directory).
100  ///
101  /// # Errors
102  /// - The parent directory does not exist and
103  ///   [`create_parent_dirs`](CreateAction::create_parent_dirs) is false.
104  /// - There is already a directory, file or symlink with the same name.
105  /// - Permission denied.
106  fn execute(&self) -> Result<(), DotfilesError> {
107    fn create_dir<F: FileSystem>(
108      fs: &'_ F,
109      directory: &str,
110      create_parent_dirs: bool,
111      current_dir: &Path,
112    ) -> Result<(), DotfilesError> {
113      let path = PathBuf::from(directory.to_owned());
114      let path = process_home_dir_in_path(&path);
115      let path = convert_path_to_absolute(&path, Some(current_dir))?;
116
117      if create_parent_dirs {
118        fs.create_dir_all(path)
119      } else {
120        fs.create_dir(path)
121      }
122      .or_else(|io_error| {
123        if let ErrorKind::AlreadyExists = io_error.kind() {
124          Ok(())
125        } else {
126          Err(DotfilesError::from_io_error(io_error))
127        }
128      })
129    }
130    create_dir(
131      self.fs,
132      &self.directory,
133      self.create_parent_dirs,
134      &self.current_dir,
135    )
136    .map(|_| {
137      info!("Created directory {}", &self.directory);
138    })
139  }
140}