1use nu_engine::command_prelude::*;
2use nu_protocol::{NuGlob, shell_error::generic::GenericError};
3use uu_mkdir::mkdir;
4use uucore::{localized_help_template, translate};
5
6#[derive(Clone)]
7pub struct UMkdir;
8
9const IS_RECURSIVE: bool = true;
10const DEFAULT_MODE: u32 = 0o777;
11
12#[cfg(target_family = "unix")]
13fn get_mode() -> u32 {
14 !nu_system::get_umask() & DEFAULT_MODE
15}
16
17#[cfg(not(target_family = "unix"))]
18fn get_mode() -> u32 {
19 DEFAULT_MODE
20}
21
22impl Command for UMkdir {
23 fn name(&self) -> &str {
24 "mkdir"
25 }
26
27 fn description(&self) -> &str {
28 "Create directories, with intermediary directories if required using uutils/coreutils mkdir."
29 }
30
31 fn search_terms(&self) -> Vec<&str> {
32 vec![
33 "directory",
34 "folder",
35 "create",
36 "make_dirs",
37 "coreutils",
38 "md",
39 ]
40 }
41
42 fn signature(&self) -> Signature {
43 Signature::build("mkdir")
44 .input_output_types(vec![
45 (Type::Nothing, Type::Nothing),
46 (
47 Type::Nothing,
48 Type::Table(
49 [
50 ("path".to_string(), Type::String),
51 ("created".to_string(), Type::Bool),
52 (
53 "error".to_string(),
54 Type::OneOf([Type::Nothing, Type::String].into()),
55 ),
56 ]
57 .into(),
58 ),
59 ),
60 ])
61 .rest(
62 "rest",
63 SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Directory]),
64 "The name(s) of the path(s) to create.",
65 )
66 .switch(
67 "verbose",
68 "Print a message for each created directory.",
69 Some('v'),
70 )
71 .category(Category::FileSystem)
72 }
73
74 fn run(
75 &self,
76 engine_state: &EngineState,
77 stack: &mut Stack,
78 call: &Call,
79 _input: PipelineData,
80 ) -> Result<PipelineData, ShellError> {
81 let _ = localized_help_template("mkdir");
83
84 let cwd = engine_state.cwd(Some(stack))?.into_std_path_buf();
85 let mut directories = call
86 .rest::<Spanned<NuGlob>>(engine_state, stack, 0)?
87 .into_iter()
88 .map(|dir| {
89 (
90 nu_path::expand_path_with(dir.item.as_ref(), &cwd, dir.item.is_expand()),
91 dir.span,
92 )
93 })
94 .peekable();
95
96 let is_verbose = call.has_flag(engine_state, stack, "verbose")?;
97
98 if directories.peek().is_none() {
99 return Err(ShellError::MissingParameter {
100 param_name: "requires directory paths".to_string(),
101 span: call.head,
102 });
103 }
104
105 let config = uu_mkdir::Config {
106 recursive: IS_RECURSIVE,
107 mode: get_mode(),
108 verbose: false,
109 set_security_context: false,
110 context: None,
111 };
112
113 let mut verbose_out = Vec::new();
114 let mut err = None;
115 for (dir, dir_span) in directories {
116 if let Err(error) = mkdir(&dir, &config) {
117 let shell_error = ShellError::Generic(GenericError::new(
118 format!("{error}"),
119 translate!(&error.to_string()),
120 dir_span,
121 ));
122
123 if is_verbose {
124 verbose_out.push(
125 record! {
126 "path" => Value::string(dir.display().to_string(), call.head),
127 "created" => Value::bool(false, call.head),
128 "error" => Value::string(format!("{error}"), call.head),
129 }
130 .into_value(call.head),
131 )
132 } else {
133 err = Some(shell_error);
134 }
135 } else if is_verbose {
136 verbose_out.push(
137 record! {
138 "path" => Value::string(dir.display().to_string(), call.head),
139 "created" => Value::bool(true, call.head),
140 "error" => Value::nothing(call.head),
141 }
142 .into_value(call.head),
143 );
144 }
145 }
146
147 if is_verbose {
148 Ok(PipelineData::value(
149 Value::list(verbose_out, call.head),
150 None,
151 ))
152 } else if let Some(err) = err {
153 Err(err)
154 } else {
155 Ok(PipelineData::empty())
156 }
157 }
158
159 fn examples(&self) -> Vec<Example<'_>> {
160 vec![
161 Example {
162 description: "Make a directory named foo.",
163 example: "mkdir foo",
164 result: None,
165 },
166 Example {
167 description: "Make multiple directories and show the paths created.",
168 example: "mkdir -v foo/bar foo2",
169 result: Some(Value::test_list(vec![
170 Value::record(
171 record! {
172 "path" => Value::string("foo/bar".to_string(), Span::test_data()),
173 "created" => Value::bool(true, Span::test_data()),
174 "error" => Value::nothing(Span::test_data()),
175 },
176 Span::test_data(),
177 ),
178 Value::record(
179 record! {
180 "path" => Value::string("foo2".to_string(), Span::test_data()),
181 "created" => Value::bool(true, Span::test_data()),
182 "error" => Value::nothing(Span::test_data()),
183 },
184 Span::test_data(),
185 ),
186 ])),
187 },
188 ]
189 }
190}