1use nu_engine::command_prelude::*;
2use nu_glob::MatchOptions;
3use nu_path::expand_path_with;
4use nu_protocol::{
5 NuGlob,
6 shell_error::{self, generic::GenericError, 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", "rename", "ren"]
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::Generic(
113 GenericError::new("Missing file operand", "Missing file operand", call.head)
114 .with_help("Please provide source and destination paths"),
115 ));
116 }
117 if paths.len() == 1 {
118 return Err(ShellError::Generic(GenericError::new(
120 "Missing destination path",
121 format!(
122 "Missing destination path operand after {}",
123 expand_path_with(paths[0].item.as_ref(), cwd, paths[0].item.is_expand())
124 .to_string_lossy()
125 ),
126 paths[0].span,
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 let glob_options = if all {
138 None
139 } else {
140 let glob_options = MatchOptions {
141 require_literal_leading_dot: true,
142 ..Default::default()
143 };
144 Some(glob_options)
145 };
146 for mut p in paths {
147 p.item = p.item.strip_ansi_string_unlikely();
148 let exp_files: Vec<Result<PathBuf, ShellError>> = nu_engine::glob_from(
149 &p,
150 &cwd,
151 call.head,
152 glob_options,
153 engine_state.signals().clone(),
154 )
155 .map(|f| f.1)?
156 .collect();
157 if exp_files.is_empty() {
158 return Err(ShellError::Io(IoError::new(
159 shell_error::io::ErrorKind::FileNotFound,
160 p.span,
161 PathBuf::from(p.item.to_string()),
162 )));
163 };
164 let mut app_vals: Vec<PathBuf> = Vec::new();
165 for v in exp_files {
166 match v {
167 Ok(path) => {
168 app_vals.push(path);
169 }
170 Err(e) => return Err(e),
171 }
172 }
173 files.push((app_vals, p.item.is_expand()));
174 }
175
176 for (files, need_expand_tilde) in files.iter_mut() {
179 for src in files.iter_mut() {
180 if !src.is_absolute() {
181 *src = nu_path::expand_path_with(&*src, &cwd, *need_expand_tilde);
182 }
183 }
184 }
185 let mut files: Vec<PathBuf> = files.into_iter().flat_map(|x| x.0).collect();
186
187 let abs_target_path = expand_path_with(
189 nu_utils::strip_ansi_string_unlikely(spanned_target.item.to_string()),
190 &cwd,
191 matches!(spanned_target.item, NuGlob::Expand(..)),
192 );
193 files.push(abs_target_path.clone());
194 let files = files
195 .into_iter()
196 .map(|p| p.into_os_string())
197 .collect::<Vec<OsString>>();
198 let options = uu_mv::Options {
199 overwrite,
200 progress_bar: progress,
201 verbose,
202 suffix: String::from("~"),
203 backup: BackupMode::None,
204 update,
205 target_dir: None,
206 no_target_dir: false,
207 strip_slashes: false,
208 debug: false,
209 context: None,
210 };
211 if let Err(error) = uu_mv::mv(&files, &options) {
212 return Err(ShellError::Generic(GenericError::new_internal(
213 format!("{error}"),
214 translate!(&error.to_string()),
215 )));
216 }
217 Ok(PipelineData::empty())
218 }
219}