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