1#[allow(deprecated)]
2use nu_engine::{command_prelude::*, current_dir};
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 .rest(
72 "paths",
73 SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
74 "Rename SRC to DST, or move SRC to DIR.",
75 )
76 .allow_variants_without_examples(true)
77 .category(Category::FileSystem)
78 }
79
80 fn run(
81 &self,
82 engine_state: &EngineState,
83 stack: &mut Stack,
84 call: &Call,
85 _input: PipelineData,
86 ) -> Result<PipelineData, ShellError> {
87 let _ = localized_help_template("mv");
89
90 let interactive = call.has_flag(engine_state, stack, "interactive")?;
91 let no_clobber = call.has_flag(engine_state, stack, "no-clobber")?;
92 let progress = call.has_flag(engine_state, stack, "progress")?;
93 let verbose = call.has_flag(engine_state, stack, "verbose")?;
94 let overwrite = if no_clobber {
95 uu_mv::OverwriteMode::NoClobber
96 } else if interactive {
97 uu_mv::OverwriteMode::Interactive
98 } else {
99 uu_mv::OverwriteMode::Force
100 };
101 let update = if call.has_flag(engine_state, stack, "update")? {
102 UpdateMode::IfOlder
103 } else {
104 UpdateMode::All
105 };
106
107 #[allow(deprecated)]
108 let cwd = current_dir(engine_state, stack)?;
109 let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
110 if paths.is_empty() {
111 return Err(ShellError::GenericError {
112 error: "Missing file operand".into(),
113 msg: "Missing file operand".into(),
114 span: Some(call.head),
115 help: Some("Please provide source and destination paths".into()),
116 inner: Vec::new(),
117 });
118 }
119 if paths.len() == 1 {
120 return Err(ShellError::GenericError {
122 error: "Missing destination path".into(),
123 msg: format!(
124 "Missing destination path operand after {}",
125 expand_path_with(paths[0].item.as_ref(), cwd, paths[0].item.is_expand())
126 .to_string_lossy()
127 ),
128 span: Some(paths[0].span),
129 help: None,
130 inner: Vec::new(),
131 });
132 }
133
134 let spanned_target = paths.pop().ok_or(ShellError::NushellFailedSpanned {
136 msg: "Missing file operand".into(),
137 label: "Missing file operand".into(),
138 span: call.head,
139 })?;
140 let mut files: Vec<(Vec<PathBuf>, bool)> = Vec::new();
141 for mut p in paths {
142 p.item = p.item.strip_ansi_string_unlikely();
143 let exp_files: Vec<Result<PathBuf, ShellError>> =
144 nu_engine::glob_from(&p, &cwd, call.head, None, engine_state.signals().clone())
145 .map(|f| f.1)?
146 .collect();
147 if exp_files.is_empty() {
148 return Err(ShellError::Io(IoError::new(
149 shell_error::io::ErrorKind::FileNotFound,
150 p.span,
151 PathBuf::from(p.item.to_string()),
152 )));
153 };
154 let mut app_vals: Vec<PathBuf> = Vec::new();
155 for v in exp_files {
156 match v {
157 Ok(path) => {
158 app_vals.push(path);
159 }
160 Err(e) => return Err(e),
161 }
162 }
163 files.push((app_vals, p.item.is_expand()));
164 }
165
166 for (files, need_expand_tilde) in files.iter_mut() {
169 for src in files.iter_mut() {
170 if !src.is_absolute() {
171 *src = nu_path::expand_path_with(&*src, &cwd, *need_expand_tilde);
172 }
173 }
174 }
175 let mut files: Vec<PathBuf> = files.into_iter().flat_map(|x| x.0).collect();
176
177 let abs_target_path = expand_path_with(
179 nu_utils::strip_ansi_string_unlikely(spanned_target.item.to_string()),
180 &cwd,
181 matches!(spanned_target.item, NuGlob::Expand(..)),
182 );
183 files.push(abs_target_path.clone());
184 let files = files
185 .into_iter()
186 .map(|p| p.into_os_string())
187 .collect::<Vec<OsString>>();
188 let options = uu_mv::Options {
189 overwrite,
190 progress_bar: progress,
191 verbose,
192 suffix: String::from("~"),
193 backup: BackupMode::None,
194 update,
195 target_dir: None,
196 no_target_dir: false,
197 strip_slashes: false,
198 debug: false,
199 context: None,
200 };
201 if let Err(error) = uu_mv::mv(&files, &options) {
202 return Err(ShellError::GenericError {
203 error: format!("{error}"),
204 msg: translate!(&error.to_string()),
205 span: None,
206 help: None,
207 inner: Vec::new(),
208 });
209 }
210 Ok(PipelineData::empty())
211 }
212}