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