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}