nu_command/filesystem/
umv.rs

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            // expand path for better error message
114            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        // Do not glob target
128        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        // Make sure to send absolute paths to avoid uu_cp looking for cwd in std::env which is not
160        // supported in Nushell
161        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        // Add back the target after globbing
171        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}