1use nu_engine::command_prelude::*;
2use nu_glob::MatchOptions;
3use nu_path::expand_path_with;
4use nu_protocol::{
5 NuGlob,
6 shell_error::{self, io::IoError},
7};
8use std::{ffi::OsString, path::PathBuf};
9use uu_mv::{BackupMode, UpdateMode};
10use uucore::{localized_help_template, translate};
11
12#[derive(Clone)]
13pub struct UMv;
14
15impl Command for UMv {
16 fn name(&self) -> &str {
17 "mv"
18 }
19
20 fn description(&self) -> &str {
21 "Move files or directories using uutils/coreutils mv."
22 }
23
24 fn examples(&self) -> Vec<Example<'_>> {
25 vec![
26 Example {
27 description: "Rename a file",
28 example: "mv before.txt after.txt",
29 result: None,
30 },
31 Example {
32 description: "Move a file into a directory",
33 example: "mv test.txt my/subdirectory",
34 result: None,
35 },
36 Example {
37 description: "Move only if source file is newer than target file",
38 example: "mv -u new/test.txt old/",
39 result: None,
40 },
41 Example {
42 description: "Move many files into a directory",
43 example: "mv *.txt my/subdirectory",
44 result: None,
45 },
46 Example {
47 description: r#"Move a file into the "my" directory two levels up in the directory tree"#,
48 example: "mv test.txt .../my/",
49 result: None,
50 },
51 ]
52 }
53
54 fn search_terms(&self) -> Vec<&str> {
55 vec!["move", "file", "files", "coreutils"]
56 }
57
58 fn signature(&self) -> nu_protocol::Signature {
59 Signature::build("mv")
60 .input_output_types(vec![(Type::Nothing, Type::Nothing)])
61 .switch("force", "do not prompt before overwriting", Some('f'))
62 .switch("verbose", "explain what is being done.", Some('v'))
63 .switch("progress", "display a progress bar", Some('p'))
64 .switch("interactive", "prompt before overwriting", Some('i'))
65 .switch(
66 "update",
67 "move and overwrite only when the SOURCE file is newer than the destination file or when the destination file is missing",
68 Some('u')
69 )
70 .switch("no-clobber", "do not overwrite an existing file", Some('n'))
71 .switch("all", "move hidden files if '*' is provided", Some('a'))
72 .rest(
73 "paths",
74 SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
75 "Rename SRC to DST, or move SRC to DIR.",
76 )
77 .allow_variants_without_examples(true)
78 .category(Category::FileSystem)
79 }
80
81 fn run(
82 &self,
83 engine_state: &EngineState,
84 stack: &mut Stack,
85 call: &Call,
86 _input: PipelineData,
87 ) -> Result<PipelineData, ShellError> {
88 let _ = localized_help_template("mv");
90
91 let interactive = call.has_flag(engine_state, stack, "interactive")?;
92 let no_clobber = call.has_flag(engine_state, stack, "no-clobber")?;
93 let progress = call.has_flag(engine_state, stack, "progress")?;
94 let verbose = call.has_flag(engine_state, stack, "verbose")?;
95 let all = call.has_flag(engine_state, stack, "all")?;
96 let overwrite = if no_clobber {
97 uu_mv::OverwriteMode::NoClobber
98 } else if interactive {
99 uu_mv::OverwriteMode::Interactive
100 } else {
101 uu_mv::OverwriteMode::Force
102 };
103 let update = if call.has_flag(engine_state, stack, "update")? {
104 UpdateMode::IfOlder
105 } else {
106 UpdateMode::All
107 };
108
109 let cwd = engine_state.cwd(Some(stack))?.into_std_path_buf();
110 let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
111 if paths.is_empty() {
112 return Err(ShellError::GenericError {
113 error: "Missing file operand".into(),
114 msg: "Missing file operand".into(),
115 span: Some(call.head),
116 help: Some("Please provide source and destination paths".into()),
117 inner: Vec::new(),
118 });
119 }
120 if paths.len() == 1 {
121 return Err(ShellError::GenericError {
123 error: "Missing destination path".into(),
124 msg: format!(
125 "Missing destination path operand after {}",
126 expand_path_with(paths[0].item.as_ref(), cwd, paths[0].item.is_expand())
127 .to_string_lossy()
128 ),
129 span: Some(paths[0].span),
130 help: None,
131 inner: Vec::new(),
132 });
133 }
134
135 let spanned_target = paths.pop().ok_or(ShellError::NushellFailedSpanned {
137 msg: "Missing file operand".into(),
138 label: "Missing file operand".into(),
139 span: call.head,
140 })?;
141 let mut files: Vec<(Vec<PathBuf>, bool)> = Vec::new();
142 let glob_options = if all {
143 None
144 } else {
145 let glob_options = MatchOptions {
146 require_literal_leading_dot: true,
147 ..Default::default()
148 };
149 Some(glob_options)
150 };
151 for mut p in paths {
152 p.item = p.item.strip_ansi_string_unlikely();
153 let exp_files: Vec<Result<PathBuf, ShellError>> = nu_engine::glob_from(
154 &p,
155 &cwd,
156 call.head,
157 glob_options,
158 engine_state.signals().clone(),
159 )
160 .map(|f| f.1)?
161 .collect();
162 if exp_files.is_empty() {
163 return Err(ShellError::Io(IoError::new(
164 shell_error::io::ErrorKind::FileNotFound,
165 p.span,
166 PathBuf::from(p.item.to_string()),
167 )));
168 };
169 let mut app_vals: Vec<PathBuf> = Vec::new();
170 for v in exp_files {
171 match v {
172 Ok(path) => {
173 app_vals.push(path);
174 }
175 Err(e) => return Err(e),
176 }
177 }
178 files.push((app_vals, p.item.is_expand()));
179 }
180
181 for (files, need_expand_tilde) in files.iter_mut() {
184 for src in files.iter_mut() {
185 if !src.is_absolute() {
186 *src = nu_path::expand_path_with(&*src, &cwd, *need_expand_tilde);
187 }
188 }
189 }
190 let mut files: Vec<PathBuf> = files.into_iter().flat_map(|x| x.0).collect();
191
192 let abs_target_path = expand_path_with(
194 nu_utils::strip_ansi_string_unlikely(spanned_target.item.to_string()),
195 &cwd,
196 matches!(spanned_target.item, NuGlob::Expand(..)),
197 );
198 files.push(abs_target_path.clone());
199 let files = files
200 .into_iter()
201 .map(|p| p.into_os_string())
202 .collect::<Vec<OsString>>();
203 let options = uu_mv::Options {
204 overwrite,
205 progress_bar: progress,
206 verbose,
207 suffix: String::from("~"),
208 backup: BackupMode::None,
209 update,
210 target_dir: None,
211 no_target_dir: false,
212 strip_slashes: false,
213 debug: false,
214 context: None,
215 };
216 if let Err(error) = uu_mv::mv(&files, &options) {
217 return Err(ShellError::GenericError {
218 error: format!("{error}"),
219 msg: translate!(&error.to_string()),
220 span: None,
221 help: None,
222 inner: Vec::new(),
223 });
224 }
225 Ok(PipelineData::empty())
226 }
227}