nushell/src/shell/filesystem_shell.rs

1137 lines
45 KiB
Rust
Raw Normal View History

use crate::commands::command::EvaluatedWholeStreamCommandArgs;
2019-08-21 19:03:59 +02:00
use crate::commands::cp::CopyArgs;
use crate::commands::mkdir::MkdirArgs;
use crate::commands::mv::MoveArgs;
use crate::commands::rm::RemoveArgs;
use crate::data::dir_entry_dict;
2019-08-07 19:49:11 +02:00
use crate::prelude::*;
2019-08-09 07:36:43 +02:00
use crate::shell::completer::NuCompleter;
2019-08-07 19:49:11 +02:00
use crate::shell::shell::Shell;
2019-08-21 19:03:59 +02:00
use crate::utils::FileStructure;
2019-08-09 07:36:43 +02:00
use rustyline::completion::FilenameCompleter;
2019-08-07 19:49:11 +02:00
use rustyline::hint::{Hinter, HistoryHinter};
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
2019-10-19 22:52:39 +02:00
use trash as SendToTrash;
2019-08-22 07:15:14 +02:00
2019-08-07 19:49:11 +02:00
pub struct FilesystemShell {
pub(crate) path: String,
pub(crate) last_path: String,
2019-08-07 19:49:11 +02:00
completer: NuCompleter,
hinter: HistoryHinter,
}
impl std::fmt::Debug for FilesystemShell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FilesystemShell @ {}", self.path)
}
}
2019-08-07 19:49:11 +02:00
impl Clone for FilesystemShell {
fn clone(&self) -> Self {
FilesystemShell {
path: self.path.clone(),
last_path: self.path.clone(),
2019-08-07 19:49:11 +02:00
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
2019-08-10 07:02:15 +02:00
commands: self.completer.commands.clone(),
2019-08-07 19:49:11 +02:00
},
hinter: HistoryHinter {},
}
}
}
impl FilesystemShell {
2019-08-10 07:02:15 +02:00
pub fn basic(commands: CommandRegistry) -> Result<FilesystemShell, std::io::Error> {
2019-08-07 19:49:11 +02:00
let path = std::env::current_dir()?;
Ok(FilesystemShell {
path: path.to_string_lossy().to_string(),
last_path: path.to_string_lossy().to_string(),
2019-08-07 19:49:11 +02:00
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
2019-08-10 07:02:15 +02:00
commands,
2019-08-07 19:49:11 +02:00
},
hinter: HistoryHinter {},
})
}
2019-08-10 07:02:15 +02:00
pub fn with_location(
path: String,
commands: CommandRegistry,
) -> Result<FilesystemShell, std::io::Error> {
let last_path = path.clone();
2019-08-07 19:49:11 +02:00
Ok(FilesystemShell {
path,
last_path,
2019-08-07 19:49:11 +02:00
completer: NuCompleter {
file_completer: FilenameCompleter::new(),
2019-08-10 07:02:15 +02:00
commands,
2019-08-07 19:49:11 +02:00
},
hinter: HistoryHinter {},
})
}
}
impl Shell for FilesystemShell {
fn name(&self) -> String {
2019-08-07 19:49:11 +02:00
"filesystem".to_string()
}
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
fn homedir(&self) -> Option<PathBuf> {
dirs::home_dir()
}
fn ls(
&self,
pattern: Option<Tagged<PathBuf>>,
context: &RunnableContext,
full: bool,
) -> Result<OutputStream, ShellError> {
2019-08-23 06:51:43 +02:00
let cwd = self.path();
let mut full_path = PathBuf::from(self.path());
match &pattern {
Some(value) => full_path.push((*value).as_ref()),
2019-08-07 19:49:11 +02:00
_ => {}
}
let ctrl_c = context.ctrl_c.clone();
let name_tag = context.name.clone();
//If it's not a glob, try to display the contents of the entry if it's a directory
let lossy_path = full_path.to_string_lossy();
if !lossy_path.contains("*") && !lossy_path.contains("?") {
let entry = Path::new(&full_path);
if entry.is_dir() {
let entries = std::fs::read_dir(&entry);
let entries = match entries {
Err(e) => {
if let Some(s) = pattern {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
s.tag(),
));
} else {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
));
}
}
Ok(o) => o,
};
let stream = async_stream! {
for entry in entries {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
if let Ok(entry) = entry {
let filepath = entry.path();
if let Ok(metadata) = std::fs::symlink_metadata(&filepath) {
let filename = if let Ok(fname) = filepath.strip_prefix(&cwd) {
fname
} else {
Path::new(&filepath)
};
let value = dir_entry_dict(filename, &metadata, &name_tag, full)?;
yield ReturnSuccess::value(value);
}
}
}
};
return Ok(stream.to_output_stream());
}
}
let entries = match glob::glob(&full_path.to_string_lossy()) {
Ok(files) => files,
2019-08-23 06:51:43 +02:00
Err(_) => {
if let Some(source) = pattern {
2019-08-23 06:51:43 +02:00
return Err(ShellError::labeled_error(
"Invalid pattern",
"Invalid pattern",
source.tag(),
2019-08-23 06:51:43 +02:00
));
} else {
Overhaul the coloring system This commit replaces the previous naive coloring system with a coloring system that is more aligned with the parser. The main benefit of this change is that it allows us to use parsing rules to decide how to color tokens. For example, consider the following syntax: ``` $ ps | where cpu > 10 ``` Ideally, we could color `cpu` like a column name and not a string, because `cpu > 10` is a shorthand block syntax that expands to `{ $it.cpu > 10 }`. The way that we know that it's a shorthand block is that the `where` command declares that its first parameter is a `SyntaxShape::Block`, which allows the shorthand block form. In order to accomplish this, we need to color the tokens in a way that corresponds to their expanded semantics, which means that high-fidelity coloring requires expansion. This commit adds a `ColorSyntax` trait that corresponds to the `ExpandExpression` trait. The semantics are fairly similar, with a few differences. First `ExpandExpression` consumes N tokens and returns a single `hir::Expression`. `ColorSyntax` consumes N tokens and writes M `FlatShape` tokens to the output. Concretely, for syntax like `[1 2 3]` - `ExpandExpression` takes a single token node and produces a single `hir::Expression` - `ColorSyntax` takes the same token node and emits 7 `FlatShape`s (open delimiter, int, whitespace, int, whitespace, int, close delimiter) Second, `ColorSyntax` is more willing to plow through failures than `ExpandExpression`. In particular, consider syntax like ``` $ ps | where cpu > ``` In this case - `ExpandExpression` will see that the `where` command is expecting a block, see that it's not a literal block and try to parse it as a shorthand block. It will successfully find a member followed by an infix operator, but not a following expression. That means that the entire pipeline part fails to parse and is a syntax error. - `ColorSyntax` will also try to parse it as a shorthand block and ultimately fail, but it will fall back to "backoff coloring mode", which parsing any unidentified tokens in an unfallible, simple way. In this case, `cpu` will color as a string and `>` will color as an operator. Finally, it's very important that coloring a pipeline infallibly colors the entire string, doesn't fail, and doesn't get stuck in an infinite loop. In order to accomplish this, this PR separates `ColorSyntax`, which is infallible from `FallibleColorSyntax`, which might fail. This allows the type system to let us know if our coloring rules bottom out at at an infallible rule. It's not perfect: it's still possible for the coloring process to get stuck or consume tokens non-atomically. I intend to reduce the opportunity for those problems in a future commit. In the meantime, the current system catches a number of mistakes (like trying to use a fallible coloring rule in a loop without thinking about the possibility that it will never terminate).
2019-10-06 22:22:50 +02:00
return Err(ShellError::untagged_runtime_error("Invalid pattern."));
2019-08-23 06:51:43 +02:00
}
}
};
2019-08-07 19:49:11 +02:00
// Enumerate the entries from the glob and add each
let stream = async_stream! {
for entry in entries {
if ctrl_c.load(Ordering::SeqCst) {
break;
}
if let Ok(entry) = entry {
if let Ok(metadata) = std::fs::symlink_metadata(&entry) {
let filename = if let Ok(fname) = entry.strip_prefix(&cwd) {
fname
} else {
Path::new(&entry)
};
if let Ok(value) = dir_entry_dict(filename, &metadata, &name_tag, full) {
yield ReturnSuccess::value(value);
}
}
}
2019-08-07 19:49:11 +02:00
}
};
Ok(stream.to_output_stream())
2019-08-07 19:49:11 +02:00
}
2019-08-15 07:02:02 +02:00
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
2019-08-09 21:42:23 +02:00
let path = match args.nth(0) {
2019-08-07 19:49:11 +02:00
None => match dirs::home_dir() {
Some(o) => o,
_ => {
return Err(ShellError::labeled_error(
"Can not change to home directory",
"can not go to home",
&args.call_info.name_tag,
2019-08-07 19:49:11 +02:00
))
}
},
Some(v) => {
Add support for ~ expansion This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
2019-08-26 21:21:03 +02:00
let target = v.as_path()?;
2019-09-08 11:55:49 +02:00
if PathBuf::from("-") == target {
PathBuf::from(&self.last_path)
} else {
let path = PathBuf::from(self.path());
if target.exists() && !target.is_dir() {
return Err(ShellError::labeled_error(
"Can not change to directory",
"is not a directory",
v.tag().clone(),
));
}
2019-09-08 11:55:49 +02:00
match dunce::canonicalize(path.join(&target)) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
v.tag().clone(),
2019-09-08 11:55:49 +02:00
))
}
2019-08-07 19:49:11 +02:00
}
}
}
};
let mut stream = VecDeque::new();
2019-09-08 11:55:49 +02:00
2019-09-11 16:36:50 +02:00
stream.push_back(ReturnSuccess::change_cwd(
2019-08-07 19:49:11 +02:00
path.to_string_lossy().to_string(),
));
2019-08-07 19:49:11 +02:00
Ok(stream.into())
}
2019-08-21 19:03:59 +02:00
fn cp(
&self,
CopyArgs {
src,
dst,
recursive,
}: CopyArgs,
name: Tag,
path: &str,
2019-08-24 21:36:19 +02:00
) -> Result<OutputStream, ShellError> {
let name_tag = name;
2019-08-21 19:09:23 +02:00
let mut source = PathBuf::from(path);
let mut destination = PathBuf::from(path);
2019-08-21 19:09:23 +02:00
source.push(&src.item);
destination.push(&dst.item);
2019-08-21 19:03:59 +02:00
let sources: Vec<_> = match glob::glob(&source.to_string_lossy()) {
Ok(files) => files.collect(),
Err(_) => {
return Err(ShellError::labeled_error(
"Invalid pattern.",
"Invalid pattern.",
src.tag,
))
}
};
if sources.len() == 1 {
if let Ok(entry) = &sources[0] {
if entry.is_dir() && !recursive.item {
return Err(ShellError::labeled_error(
"is a directory (not copied). Try using \"--recursive\".",
"is a directory (not copied). Try using \"--recursive\".",
src.tag,
));
}
let mut sources: FileStructure = FileStructure::new();
sources.walk_decorate(&entry)?;
if entry.is_file() {
let strategy = |(source_file, _depth_level)| {
if destination.exists() {
let mut new_dst = dunce::canonicalize(destination.clone())?;
if let Some(name) = entry.file_name() {
new_dst.push(name);
}
Ok((source_file, new_dst))
} else {
Ok((source_file, destination.clone()))
}
};
let sources = sources.paths_applying_with(strategy)?;
for (ref src, ref dst) in sources {
if src.is_file() {
match std::fs::copy(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
}
}
if entry.is_dir() {
if !destination.exists() {
match std::fs::create_dir_all(&destination) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
let strategy = |(source_file, depth_level)| {
let mut new_dst = destination.clone();
let path = dunce::canonicalize(&source_file)?;
let mut comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
comps.reverse();
for fragment in comps.iter() {
new_dst.push(fragment);
}
Ok((PathBuf::from(&source_file), PathBuf::from(new_dst)))
};
let sources = sources.paths_applying_with(strategy)?;
for (ref src, ref dst) in sources {
if src.is_dir() {
if !dst.exists() {
match std::fs::create_dir_all(dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
}
if src.is_file() {
match std::fs::copy(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
}
} else {
match entry.file_name() {
Some(name) => destination.push(name),
None => {
return Err(ShellError::labeled_error(
"Copy aborted. Not a valid path",
"Copy aborted. Not a valid path",
name_tag,
2019-08-21 19:03:59 +02:00
))
}
}
match std::fs::create_dir_all(&destination) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
let strategy = |(source_file, depth_level)| {
let mut new_dst = dunce::canonicalize(&destination)?;
let path = dunce::canonicalize(&source_file)?;
let mut comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
comps.reverse();
for fragment in comps.iter() {
new_dst.push(fragment);
}
Ok((PathBuf::from(&source_file), PathBuf::from(new_dst)))
};
let sources = sources.paths_applying_with(strategy)?;
for (ref src, ref dst) in sources {
if src.is_dir() {
if !dst.exists() {
match std::fs::create_dir_all(dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
}
if src.is_file() {
match std::fs::copy(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
}
}
}
}
} else {
if destination.exists() {
if !sources.iter().all(|x| match x {
Ok(f) => f.is_file(),
Err(_) => false,
}) {
return Err(ShellError::labeled_error(
"Copy aborted (directories found). Recursive copying in patterns not supported yet (try copying the directory directly)",
"Copy aborted (directories found). Recursive copying in patterns not supported yet (try copying the directory directly)",
src.tag,
));
}
for entry in sources {
if let Ok(entry) = entry {
let mut to = PathBuf::from(&destination);
match entry.file_name() {
Some(name) => to.push(name),
None => {
return Err(ShellError::labeled_error(
"Copy aborted. Not a valid path",
"Copy aborted. Not a valid path",
name_tag,
2019-08-21 19:03:59 +02:00
))
}
}
if entry.is_file() {
match std::fs::copy(&entry, &to) {
Err(e) => {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
src.tag,
));
}
Ok(o) => o,
};
}
}
}
} else {
let destination_file_name = {
match destination.file_name() {
Some(name) => PathBuf::from(name),
None => {
return Err(ShellError::labeled_error(
"Copy aborted. Not a valid destination",
"Copy aborted. Not a valid destination",
name_tag,
2019-08-21 19:03:59 +02:00
))
}
}
};
return Err(ShellError::labeled_error(
format!("Copy aborted. (Does {:?} exist?)", destination_file_name),
format!("Copy aborted. (Does {:?} exist?)", destination_file_name),
dst.tag(),
2019-08-21 19:03:59 +02:00
));
}
}
2019-08-24 21:36:19 +02:00
Ok(OutputStream::empty())
2019-08-21 19:03:59 +02:00
}
fn mkdir(
&self,
MkdirArgs { rest: directories }: MkdirArgs,
name: Tag,
path: &str,
2019-08-24 21:36:19 +02:00
) -> Result<OutputStream, ShellError> {
let full_path = PathBuf::from(path);
2019-08-21 19:03:59 +02:00
if directories.len() == 0 {
return Err(ShellError::labeled_error(
"mkdir requires directory paths",
"needs parameter",
name,
));
}
for dir in directories.iter() {
let create_at = {
let mut loc = full_path.clone();
loc.push(&dir.item);
loc
};
match std::fs::create_dir_all(create_at) {
Err(reason) => {
return Err(ShellError::labeled_error(
reason.to_string(),
reason.to_string(),
dir.tag(),
2019-08-21 19:03:59 +02:00
))
}
Ok(_) => {}
2019-08-21 19:03:59 +02:00
}
}
2019-08-24 21:36:19 +02:00
Ok(OutputStream::empty())
2019-08-21 19:03:59 +02:00
}
fn mv(
&self,
MoveArgs { src, dst }: MoveArgs,
name: Tag,
path: &str,
2019-08-24 21:36:19 +02:00
) -> Result<OutputStream, ShellError> {
let name_tag = name;
2019-08-21 19:09:23 +02:00
let mut source = PathBuf::from(path);
let mut destination = PathBuf::from(path);
2019-08-21 19:09:23 +02:00
source.push(&src.item);
destination.push(&dst.item);
2019-08-21 19:03:59 +02:00
let sources: Vec<_> = match glob::glob(&source.to_string_lossy()) {
Ok(files) => files.collect(),
Err(_) => {
return Err(ShellError::labeled_error(
"Invalid pattern.",
"Invalid pattern.",
src.tag,
))
}
};
let destination_file_name = {
match destination.file_name() {
Some(name) => PathBuf::from(name),
None => {
return Err(ShellError::labeled_error(
"Rename aborted. Not a valid destination",
"Rename aborted. Not a valid destination",
dst.tag(),
2019-08-21 19:03:59 +02:00
))
}
}
};
if sources.len() == 1 {
if let Ok(entry) = &sources[0] {
let entry_file_name = match entry.file_name() {
Some(name) => name,
None => {
return Err(ShellError::labeled_error(
"Rename aborted. Not a valid entry name",
"Rename aborted. Not a valid entry name",
name_tag,
2019-08-21 19:03:59 +02:00
))
}
};
if destination.exists() && destination.is_dir() {
destination = match dunce::canonicalize(&destination) {
Ok(path) => path,
Err(e) => {
return Err(ShellError::labeled_error(
format!("Rename aborted. {:}", e.to_string()),
format!("Rename aborted. {:}", e.to_string()),
name_tag,
2019-08-21 19:03:59 +02:00
))
}
};
destination.push(entry_file_name);
}
if entry.is_file() {
#[cfg(not(windows))]
{
match std::fs::rename(&entry, &destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(o) => o,
};
}
#[cfg(windows)]
{
match std::fs::copy(&entry, &destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(_) => match std::fs::remove_file(&entry) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(o) => o,
},
};
}
2019-08-21 19:03:59 +02:00
}
if entry.is_dir() {
match std::fs::create_dir_all(&destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
#[cfg(not(windows))]
{
match std::fs::rename(&entry, &destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
#[cfg(windows)]
{
let mut sources: FileStructure = FileStructure::new();
sources.walk_decorate(&entry)?;
let strategy = |(source_file, depth_level)| {
let mut new_dst = destination.clone();
let path = dunce::canonicalize(&source_file)?;
let mut comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
comps.reverse();
for fragment in comps.iter() {
new_dst.push(fragment);
}
Ok((PathBuf::from(&source_file), PathBuf::from(new_dst)))
};
let sources = sources.paths_applying_with(strategy)?;
for (ref src, ref dst) in sources {
if src.is_dir() {
if !dst.exists() {
match std::fs::create_dir_all(dst) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
}
}
}
if src.is_file() {
match std::fs::copy(&src, &dst) {
2019-08-21 19:03:59 +02:00
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
src,
2019-08-21 19:03:59 +02:00
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
src,
2019-08-21 19:03:59 +02:00
destination_file_name,
e.to_string(),
),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(_) => match std::fs::remove_file(&src) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(o) => o,
},
};
2019-08-21 19:03:59 +02:00
}
}
match std::fs::remove_dir_all(entry) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
2019-08-21 19:03:59 +02:00
));
}
Ok(o) => o,
};
}
}
}
} else {
if destination.exists() {
if !sources.iter().all(|x| {
if let Ok(entry) = x.as_ref() {
entry.is_file()
} else {
false
}
}) {
return Err(ShellError::labeled_error(
"Rename aborted (directories found). Renaming in patterns not supported yet (try moving the directory directly)",
"Rename aborted (directories found). Renaming in patterns not supported yet (try moving the directory directly)",
src.tag,
));
}
for entry in sources {
if let Ok(entry) = entry {
let entry_file_name = match entry.file_name() {
Some(name) => name,
None => {
return Err(ShellError::labeled_error(
"Rename aborted. Not a valid entry name",
"Rename aborted. Not a valid entry name",
name_tag,
2019-08-21 19:03:59 +02:00
))
}
};
let mut to = PathBuf::from(&destination);
to.push(entry_file_name);
if entry.is_file() {
#[cfg(not(windows))]
{
match std::fs::rename(&entry, &to) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(o) => o,
};
}
#[cfg(windows)]
{
match std::fs::copy(&entry, &to) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(_) => match std::fs::remove_file(&entry) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Remove {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
format!(
"Remove {:?} to {:?} aborted. {:}",
entry_file_name,
destination_file_name,
e.to_string(),
),
name_tag,
));
}
Ok(o) => o,
},
};
}
2019-08-21 19:03:59 +02:00
}
}
}
} else {
return Err(ShellError::labeled_error(
format!("Rename aborted. (Does {:?} exist?)", destination_file_name),
format!("Rename aborted. (Does {:?} exist?)", destination_file_name),
dst.tag(),
2019-08-21 19:03:59 +02:00
));
}
}
2019-08-24 21:36:19 +02:00
Ok(OutputStream::empty())
2019-08-21 19:03:59 +02:00
}
fn rm(
&self,
2019-10-19 22:52:39 +02:00
RemoveArgs {
target,
recursive,
trash,
}: RemoveArgs,
name: Tag,
path: &str,
2019-08-24 21:36:19 +02:00
) -> Result<OutputStream, ShellError> {
let name_tag = name;
2019-08-21 19:09:23 +02:00
2019-08-22 06:23:57 +02:00
if target.item.to_str() == Some(".") || target.item.to_str() == Some("..") {
2019-08-21 19:03:59 +02:00
return Err(ShellError::labeled_error(
"Remove aborted. \".\" or \"..\" may not be removed.",
"Remove aborted. \".\" or \"..\" may not be removed.",
target.tag(),
2019-08-21 19:03:59 +02:00
));
}
2019-08-22 06:23:57 +02:00
let mut path = PathBuf::from(path);
path.push(&target.item);
let file = path.to_string_lossy();
2019-08-21 19:03:59 +02:00
let entries: Vec<_> = match glob::glob(&path.to_string_lossy()) {
Ok(files) => files.collect(),
Err(_) => {
return Err(ShellError::labeled_error(
"Invalid pattern.",
"Invalid pattern.",
target.tag,
))
}
};
if entries.len() == 1 {
if let Ok(entry) = &entries[0] {
if entry.is_dir() {
let mut source_dir: FileStructure = FileStructure::new();
source_dir.walk_decorate(&entry)?;
if source_dir.contains_files() && !recursive.item {
return Err(ShellError::labeled_error(
format!("{:?} is a directory. Try using \"--recursive\".", file),
format!("{:?} is a directory. Try using \"--recursive\".", file),
target.tag(),
2019-08-21 19:03:59 +02:00
));
}
}
}
}
for entry in entries {
match entry {
Ok(path) => {
let path_file_name = {
match path.file_name() {
Some(name) => PathBuf::from(name),
None => {
return Err(ShellError::labeled_error(
"Remove aborted. Not a valid path",
"Remove aborted. Not a valid path",
name_tag,
2019-08-21 19:03:59 +02:00
))
}
}
};
let mut source_dir: FileStructure = FileStructure::new();
source_dir.walk_decorate(&path)?;
if source_dir.contains_more_than_one_file() && !recursive.item {
return Err(ShellError::labeled_error(
format!(
"Directory {:?} found somewhere inside. Try using \"--recursive\".",
path_file_name
),
format!(
"Directory {:?} found somewhere inside. Try using \"--recursive\".",
path_file_name
),
target.tag(),
2019-08-21 19:03:59 +02:00
));
}
if trash.item {
SendToTrash::remove(path).unwrap();
} else if path.is_dir() {
2019-08-21 19:03:59 +02:00
std::fs::remove_dir_all(&path)?;
} else if path.is_file() {
std::fs::remove_file(&path)?;
2019-08-21 19:03:59 +02:00
}
}
Err(e) => {
return Err(ShellError::labeled_error(
format!("Remove aborted. {:}", e.to_string()),
format!("Remove aborted. {:}", e.to_string()),
name_tag,
2019-08-21 19:03:59 +02:00
))
}
}
}
2019-08-24 21:36:19 +02:00
Ok(OutputStream::empty())
2019-08-21 19:03:59 +02:00
}
2019-08-07 19:49:11 +02:00
fn path(&self) -> String {
self.path.clone()
}
fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let path = PathBuf::from(self.path());
let p = match dunce::canonicalize(path.as_path()) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::labeled_error(
"unable to show current directory",
"pwd command failed",
&args.call_info.name_tag,
));
}
};
let mut stream = VecDeque::new();
stream.push_back(ReturnSuccess::value(
Value::Primitive(Primitive::String(p.to_string_lossy().to_string()))
.tagged(&args.call_info.name_tag),
));
Ok(stream.into())
}
2019-08-07 19:49:11 +02:00
fn set_path(&mut self, path: String) {
2019-08-08 02:52:29 +02:00
let pathbuf = PathBuf::from(&path);
let path = match dunce::canonicalize(pathbuf.as_path()) {
Ok(path) => {
let _ = std::env::set_current_dir(&path);
path
}
_ => {
// TODO: handle the case where the path cannot be canonicalized
pathbuf
}
};
self.last_path = self.path.clone();
2019-08-08 02:52:29 +02:00
self.path = path.to_string_lossy().to_string();
2019-08-07 19:49:11 +02:00
}
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
2019-08-09 21:42:23 +02:00
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
2019-08-07 19:49:11 +02:00
self.completer.complete(line, pos, ctx)
}
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx)
}
}