mirror of
https://github.com/nushell/nushell.git
synced 2025-08-11 01:05:56 +02:00
Refactor I/O Errors (#14927)
<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx
you can also mention related issues, PRs or discussions!
-->
# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.
Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->
As mentioned in #10698, we have too many `ShellError` variants, with
some even overlapping in meaning. This PR simplifies and improves I/O
error handling by restructuring `ShellError` related to I/O issues.
Previously, `ShellError::IOError` only contained a message string,
making it convenient but overly generic. It was widely used without
providing spans (#4323).
This PR introduces a new `ShellError::Io` variant that consolidates
multiple I/O-related errors (except for `ShellError::NetworkFailure`,
which remains distinct for now). The new `ShellError::Io` variant
replaces the following:
- `FileNotFound`
- `FileNotFoundCustom`
- `IOInterrupted`
- `IOError`
- `IOErrorSpanned`
- `NotADirectory`
- `DirectoryNotFound`
- `MoveNotPossible`
- `CreateNotPossible`
- `ChangeAccessTimeNotPossible`
- `ChangeModifiedTimeNotPossible`
- `RemoveNotPossible`
- `ReadingFile`
## The `IoError`
`IoError` includes the following fields:
1. **`kind`**: Extends `std::io::ErrorKind` to specify the type of I/O
error without needing new `ShellError` variants. This aligns with the
approach used in `std::io::Error`. This adds a second dimension to error
reporting by combining the `kind` field with `ShellError` variants,
making it easier to describe errors in more detail. As proposed by
@kubouch in [#design-discussion on
Discord](https://discord.com/channels/601130461678272522/615329862395101194/1323699197165178930),
this helps reduce the number of `ShellError` variants. In the error
report, the `kind` field is displayed as the "source" of the error,
e.g., "I/O error," followed by the specific kind of I/O error.
2. **`span`**: A non-optional field to encourage providing spans for
better error reporting (#4323).
3. **`path`**: Optional `PathBuf` to give context about the file or
directory involved in the error (#7695). If provided, it’s shown as a
help entry in error reports.
4. **`additional_context`**: Allows adding custom messages when the
span, kind, and path are insufficient. This is rendered in the error
report at the labeled span.
5. **`location`**: Sometimes, I/O errors occur in the engine itself and
are not caused directly by user input. In such cases, if we don’t have a
span and must set it to `Span::unknown()`, we need another way to
reference the error. For this, the `location` field uses the new
`Location` struct, which records the Rust file and line number where the
error occurred. This ensures that we at least know the Rust code
location that failed, helping with debugging. To make this work, a new
`location!` macro was added, which retrieves `file!`, `line!`, and
`column!` values accurately. If `Location::new` is used directly, it
issues a warning to remind developers to use the macro instead, ensuring
consistent and correct usage.
### Constructor Behavior
`IoError` provides five constructor methods:
- `new` and `new_with_additional_context`: Used for errors caused by
user input and require a valid (non-unknown) span to ensure precise
error reporting.
- `new_internal` and `new_internal_with_path`: Used for internal errors
where a span is not available. These methods require additional context
and the `Location` struct to pinpoint the source of the error in the
engine code.
- `factory`: Returns a closure that maps an `std::io::Error` to an
`IoError`. This is useful for handling multiple I/O errors that share
the same span and path, streamlining error handling in such cases.
## New Report Look
This is simulation how the I/O errors look like (the `open crates` is
simulated to show how internal errors are referenced now):

## `Span::test_data()`
To enable better testing, `Span::test_data()` now returns a value
distinct from `Span::unknown()`. Both `Span::test_data()` and
`Span::unknown()` refer to invalid source code, but having a separate
value for test data helps identify issues during testing while keeping
spans unique.
## Cursed Sneaky Error Transfers
I removed the conversions between `std::io::Error` and `ShellError` as
they often removed important information and were used too broadly to
handle I/O errors. This also removed the problematic implementation
found here:
7ea4895513/crates/nu-protocol/src/errors/shell_error.rs (L1534-L1583)
which hid some downcasting from I/O errors and made it hard to trace
where `ShellError` was converted into `std::io::Error`. To address this,
I introduced a new struct called `ShellErrorBridge`, which explicitly
defines this transfer behavior. With `ShellErrorBridge`, we can now
easily grep the codebase to locate and manage such conversions.
## Miscellaneous
- Removed the OS error added in #14640, as it’s no longer needed.
- Improved error messages in `glob_from` (#14679).
- Trying to open a directory with `open` caused a permissions denied
error (it's just what the OS provides). I added a `is_dir` check to
provide a better error in that case.
# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->
- Error outputs now include more detailed information and are formatted
differently, including updated error codes.
- The structure of `ShellError` has changed, requiring plugin authors
and embedders to update their implementations.
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
I updated tests to account for the new I/O error structure and
formatting changes.
# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This PR closes #7695 and closes #14892 and partially addresses #4323 and
#10698.
---------
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, BufRead},
|
||||
@ -76,7 +77,7 @@ impl Command for BytesEndsWith {
|
||||
Ok(&[]) => break,
|
||||
Ok(buf) => buf,
|
||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(e.into_spanned(span).into()),
|
||||
Err(e) => return Err(IoError::new(e.kind(), span, None).into()),
|
||||
};
|
||||
let len = buf.len();
|
||||
if len >= cap {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use std::io::Read;
|
||||
|
||||
struct Arguments {
|
||||
@ -71,7 +72,7 @@ impl Command for BytesStartsWith {
|
||||
reader
|
||||
.take(pattern.len() as u64)
|
||||
.read_to_end(&mut start)
|
||||
.err_span(span)?;
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
|
||||
Ok(Value::bool(start == pattern, head).into_pipeline_data())
|
||||
} else {
|
||||
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{into_code, Config};
|
||||
use nu_protocol::{shell_error::into_code, Config};
|
||||
use nu_utils::get_system_locale;
|
||||
use num_format::ToFormattedString;
|
||||
|
||||
|
@ -2,7 +2,10 @@ use super::definitions::{
|
||||
db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey,
|
||||
db_index::DbIndex, db_table::DbTable,
|
||||
};
|
||||
use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Signals, Span, Spanned, Value};
|
||||
use nu_protocol::{
|
||||
shell_error::io::IoError, CustomValue, PipelineData, Record, ShellError, Signals, Span,
|
||||
Spanned, Value,
|
||||
};
|
||||
use rusqlite::{
|
||||
types::ValueRef, Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement,
|
||||
ToSql,
|
||||
@ -38,24 +41,22 @@ impl SQLiteDatabase {
|
||||
}
|
||||
|
||||
pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result<Self, ShellError> {
|
||||
let mut file = File::open(path).map_err(|e| ShellError::ReadingFile {
|
||||
msg: e.to_string(),
|
||||
span,
|
||||
})?;
|
||||
let mut file =
|
||||
File::open(path).map_err(|e| IoError::new(e.kind(), span, PathBuf::from(path)))?;
|
||||
|
||||
let mut buf: [u8; 16] = [0; 16];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|e| ShellError::ReadingFile {
|
||||
msg: e.to_string(),
|
||||
span,
|
||||
})
|
||||
.map_err(|e| ShellError::Io(IoError::new(e.kind(), span, PathBuf::from(path))))
|
||||
.and_then(|_| {
|
||||
if buf == SQLITE_MAGIC_BYTES {
|
||||
Ok(SQLiteDatabase::new(path, signals))
|
||||
} else {
|
||||
Err(ShellError::ReadingFile {
|
||||
msg: "Not a SQLite file".into(),
|
||||
span,
|
||||
Err(ShellError::GenericError {
|
||||
error: "Not a SQLite file".into(),
|
||||
msg: format!("Could not read '{}' as SQLite file", path.display()),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
15
crates/nu-command/src/env/config/config_.rs
vendored
15
crates/nu-command/src/env/config/config_.rs
vendored
@ -60,6 +60,8 @@ pub(super) fn start_editor(
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// Find the editor executable.
|
||||
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
|
||||
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
@ -99,13 +101,22 @@ pub(super) fn start_editor(
|
||||
// Spawn the child process. On Unix, also put the child process to
|
||||
// foreground if we're in an interactive session.
|
||||
#[cfg(windows)]
|
||||
let child = ForegroundChild::spawn(command)?;
|
||||
let child = ForegroundChild::spawn(command);
|
||||
#[cfg(unix)]
|
||||
let child = ForegroundChild::spawn(
|
||||
command,
|
||||
engine_state.is_interactive,
|
||||
&engine_state.pipeline_externals_state,
|
||||
)?;
|
||||
);
|
||||
|
||||
let child = child.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
call.head,
|
||||
None,
|
||||
"Could not spawn foreground child",
|
||||
)
|
||||
})?;
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?;
|
||||
|
47
crates/nu-command/src/env/config/config_reset.rs
vendored
47
crates/nu-command/src/env/config/config_reset.rs
vendored
@ -1,8 +1,9 @@
|
||||
use chrono::Local;
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use nu_utils::{get_scaffold_config, get_scaffold_env};
|
||||
use std::io::Write;
|
||||
use std::{io::Write, path::PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigReset;
|
||||
@ -58,19 +59,23 @@ impl Command for ConfigReset {
|
||||
"oldconfig-{}.nu",
|
||||
Local::now().format("%F-%H-%M-%S"),
|
||||
));
|
||||
if std::fs::rename(nu_config.clone(), backup_path).is_err() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "config.nu could not be backed up".into(),
|
||||
if let Err(err) = std::fs::rename(nu_config.clone(), &backup_path) {
|
||||
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
});
|
||||
PathBuf::from(backup_path),
|
||||
"config.nu could not be backed up",
|
||||
)));
|
||||
}
|
||||
}
|
||||
if let Ok(mut file) = std::fs::File::create(nu_config) {
|
||||
if writeln!(&mut file, "{config_file}").is_err() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "config.nu could not be written to".into(),
|
||||
if let Ok(mut file) = std::fs::File::create(&nu_config) {
|
||||
if let Err(err) = writeln!(&mut file, "{config_file}") {
|
||||
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
});
|
||||
PathBuf::from(nu_config),
|
||||
"config.nu could not be written to",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,19 +86,23 @@ impl Command for ConfigReset {
|
||||
if !no_backup {
|
||||
let mut backup_path = config_path.clone();
|
||||
backup_path.push(format!("oldenv-{}.nu", Local::now().format("%F-%H-%M-%S"),));
|
||||
if std::fs::rename(env_config.clone(), backup_path).is_err() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "env.nu could not be backed up".into(),
|
||||
if let Err(err) = std::fs::rename(env_config.clone(), &backup_path) {
|
||||
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
});
|
||||
PathBuf::from(backup_path),
|
||||
"env.nu could not be backed up",
|
||||
)));
|
||||
}
|
||||
}
|
||||
if let Ok(mut file) = std::fs::File::create(env_config) {
|
||||
if writeln!(&mut file, "{config_file}").is_err() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "env.nu could not be written to".into(),
|
||||
if let Ok(mut file) = std::fs::File::create(&env_config) {
|
||||
if let Err(err) = writeln!(&mut file, "{config_file}") {
|
||||
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
});
|
||||
PathBuf::from(env_config),
|
||||
"env.nu could not be written to",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
crates/nu-command/src/env/source_env.rs
vendored
11
crates/nu-command/src/env/source_env.rs
vendored
@ -2,7 +2,7 @@ use nu_engine::{
|
||||
command_prelude::*, find_in_dirs_env, get_dirs_var_from_call, get_eval_block_with_early_return,
|
||||
redirect_env,
|
||||
};
|
||||
use nu_protocol::{engine::CommandType, BlockId};
|
||||
use nu_protocol::{engine::CommandType, shell_error::io::IoError, BlockId};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Source a file for environment variables.
|
||||
@ -65,10 +65,11 @@ impl Command for SourceEnv {
|
||||
)? {
|
||||
PathBuf::from(&path)
|
||||
} else {
|
||||
return Err(ShellError::FileNotFound {
|
||||
file: source_filename.item,
|
||||
span: source_filename.span,
|
||||
});
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
source_filename.span,
|
||||
PathBuf::from(source_filename.item),
|
||||
)));
|
||||
};
|
||||
|
||||
if let Some(parent) = file_path.parent() {
|
||||
|
@ -1,4 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::{self, io::IoError};
|
||||
use nu_utils::filesystem::{have_permission, PermissionResult};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -77,25 +80,39 @@ impl Command for Cd {
|
||||
if physical {
|
||||
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::NotADirectory { span: v.span });
|
||||
return Err(shell_error::io::IoError::new(
|
||||
shell_error::io::ErrorKind::NotADirectory,
|
||||
v.span,
|
||||
None,
|
||||
)
|
||||
.into());
|
||||
};
|
||||
path
|
||||
} else {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path_no_whitespace.to_string(),
|
||||
span: v.span,
|
||||
});
|
||||
return Err(shell_error::io::IoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
v.span,
|
||||
PathBuf::from(path_no_whitespace),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
} else {
|
||||
let path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
|
||||
if !path.exists() {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path_no_whitespace.to_string(),
|
||||
span: v.span,
|
||||
});
|
||||
return Err(shell_error::io::IoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
v.span,
|
||||
PathBuf::from(path_no_whitespace),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::NotADirectory { span: v.span });
|
||||
return Err(shell_error::io::IoError::new(
|
||||
shell_error::io::ErrorKind::NotADirectory,
|
||||
v.span,
|
||||
path,
|
||||
)
|
||||
.into());
|
||||
};
|
||||
path
|
||||
}
|
||||
@ -117,13 +134,9 @@ impl Command for Cd {
|
||||
stack.set_cwd(path)?;
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
|
||||
msg: format!(
|
||||
"Cannot change directory to {}: {}",
|
||||
path.to_string_lossy(),
|
||||
reason
|
||||
),
|
||||
}),
|
||||
PermissionResult::PermissionDenied(_) => {
|
||||
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use nu_engine::glob_from;
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use nu_glob::MatchOptions;
|
||||
use nu_path::{expand_path_with, expand_to_real_path};
|
||||
use nu_protocol::{DataSource, NuGlob, PipelineMetadata, Signals};
|
||||
use nu_protocol::{shell_error::io::IoError, DataSource, NuGlob, PipelineMetadata, Signals};
|
||||
use pathdiff::diff_paths;
|
||||
use rayon::prelude::*;
|
||||
#[cfg(unix)]
|
||||
@ -254,10 +254,12 @@ fn ls_for_one_pattern(
|
||||
if let Some(path) = pattern_arg {
|
||||
// it makes no sense to list an empty string.
|
||||
if path.item.as_ref().is_empty() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "empty string('') directory or file does not exist".to_string(),
|
||||
span: path.span,
|
||||
});
|
||||
return Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
std::io::ErrorKind::NotFound,
|
||||
path.span,
|
||||
PathBuf::from(path.item.to_string()),
|
||||
"empty string('') directory or file does not exist",
|
||||
)));
|
||||
}
|
||||
match path.item {
|
||||
NuGlob::DoNotExpand(p) => Some(Spanned {
|
||||
@ -283,10 +285,7 @@ fn ls_for_one_pattern(
|
||||
nu_path::expand_path_with(pat.item.as_ref(), &cwd, pat.item.is_expand());
|
||||
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
|
||||
if !directory && tmp_expanded.is_dir() {
|
||||
if read_dir(&tmp_expanded, p_tag, use_threads)?
|
||||
.next()
|
||||
.is_none()
|
||||
{
|
||||
if read_dir(tmp_expanded, p_tag, use_threads)?.next().is_none() {
|
||||
return Ok(Value::test_nothing().into_pipeline_data());
|
||||
}
|
||||
just_read_dir = !(pat.item.is_expand() && nu_glob::is_glob(pat.item.as_ref()));
|
||||
@ -305,7 +304,7 @@ fn ls_for_one_pattern(
|
||||
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
|
||||
if directory {
|
||||
(NuGlob::Expand(".".to_string()), false)
|
||||
} else if read_dir(&cwd, p_tag, use_threads)?.next().is_none() {
|
||||
} else if read_dir(cwd.clone(), p_tag, use_threads)?.next().is_none() {
|
||||
return Ok(Value::test_nothing().into_pipeline_data());
|
||||
} else {
|
||||
(NuGlob::Expand("*".to_string()), false)
|
||||
@ -318,7 +317,7 @@ fn ls_for_one_pattern(
|
||||
let path = pattern_arg.into_spanned(p_tag);
|
||||
let (prefix, paths) = if just_read_dir {
|
||||
let expanded = nu_path::expand_path_with(path.item.as_ref(), &cwd, path.item.is_expand());
|
||||
let paths = read_dir(&expanded, p_tag, use_threads)?;
|
||||
let paths = read_dir(expanded.clone(), p_tag, use_threads)?;
|
||||
// just need to read the directory, so prefix is path itself.
|
||||
(Some(expanded), paths)
|
||||
} else {
|
||||
@ -350,7 +349,16 @@ fn ls_for_one_pattern(
|
||||
let signals_clone = signals.clone();
|
||||
|
||||
let pool = if use_threads {
|
||||
let count = std::thread::available_parallelism()?.get();
|
||||
let count = std::thread::available_parallelism()
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
call_span,
|
||||
None,
|
||||
"Could not get available parallelism",
|
||||
)
|
||||
})?
|
||||
.get();
|
||||
create_pool(count)?
|
||||
} else {
|
||||
create_pool(1)?
|
||||
@ -910,14 +918,12 @@ mod windows_helper {
|
||||
&mut find_data,
|
||||
) {
|
||||
Ok(_) => Ok(find_data),
|
||||
Err(e) => Err(ShellError::ReadingFile {
|
||||
msg: format!(
|
||||
"Could not read metadata for '{}':\n '{}'",
|
||||
filename.to_string_lossy(),
|
||||
e
|
||||
),
|
||||
Err(e) => Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
std::io::ErrorKind::Other,
|
||||
span,
|
||||
}),
|
||||
PathBuf::from(filename),
|
||||
format!("Could not read metadata: {e}"),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -950,28 +956,17 @@ mod windows_helper {
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn read_dir(
|
||||
f: &Path,
|
||||
f: PathBuf,
|
||||
span: Span,
|
||||
use_threads: bool,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<PathBuf, ShellError>> + Send>, ShellError> {
|
||||
let items = f
|
||||
.read_dir()
|
||||
.map_err(|error| {
|
||||
if error.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
return ShellError::GenericError {
|
||||
error: "Permission denied".into(),
|
||||
msg: "The permissions may not allow access for this user".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
};
|
||||
}
|
||||
|
||||
error.into()
|
||||
})?
|
||||
.map(|d| {
|
||||
.map_err(|err| IoError::new(err.kind(), span, f.clone()))?
|
||||
.map(move |d| {
|
||||
d.map(|r| r.path())
|
||||
.map_err(|e| ShellError::IOError { msg: e.to_string() })
|
||||
.map_err(|err| IoError::new(err.kind(), span, f.clone()))
|
||||
.map_err(ShellError::from)
|
||||
});
|
||||
if !use_threads {
|
||||
let mut collected = items.collect::<Vec<_>>();
|
||||
|
@ -106,14 +106,10 @@ impl Command for Mktemp {
|
||||
};
|
||||
|
||||
let res = match uu_mktemp::mktemp(&options) {
|
||||
Ok(res) => {
|
||||
res.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|e| ShellError::IOErrorSpanned {
|
||||
msg: e.to_string_lossy().to_string(),
|
||||
span,
|
||||
})?
|
||||
}
|
||||
Ok(res) => res
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| ShellError::NonUtf8 { span })?,
|
||||
Err(e) => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("{}", e),
|
||||
|
@ -1,7 +1,11 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
|
||||
use nu_protocol::{ast, DataSource, NuGlob, PipelineMetadata};
|
||||
use std::path::Path;
|
||||
use nu_protocol::{
|
||||
ast,
|
||||
shell_error::{self, io::IoError},
|
||||
DataSource, NuGlob, PipelineMetadata,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use crate::database::SQLiteDatabase;
|
||||
@ -87,25 +91,10 @@ impl Command for Open {
|
||||
|
||||
for path in nu_engine::glob_from(&path, &cwd, call_span, None)
|
||||
.map_err(|err| match err {
|
||||
ShellError::DirectoryNotFound { span, .. } => ShellError::FileNotFound {
|
||||
file: path.item.to_string(),
|
||||
span,
|
||||
},
|
||||
// that particular error in `nu_engine::glob_from` doesn't have a span attached
|
||||
// to it, so let's add it
|
||||
ShellError::GenericError {
|
||||
error,
|
||||
msg,
|
||||
span: _,
|
||||
help,
|
||||
inner,
|
||||
} if error.as_str() == "Permission denied" => ShellError::GenericError {
|
||||
error,
|
||||
msg,
|
||||
span: Some(arg_span),
|
||||
help,
|
||||
inner,
|
||||
},
|
||||
ShellError::Io(mut err) => {
|
||||
err.span = arg_span;
|
||||
err.into()
|
||||
}
|
||||
_ => err,
|
||||
})?
|
||||
.1
|
||||
@ -114,24 +103,26 @@ impl Command for Open {
|
||||
let path = Path::new(&path);
|
||||
|
||||
if permission_denied(path) {
|
||||
let err = IoError::new(
|
||||
std::io::ErrorKind::PermissionDenied,
|
||||
arg_span,
|
||||
PathBuf::from(path),
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
let error_msg = match path.metadata() {
|
||||
Ok(md) => format!(
|
||||
"The permissions of {:o} does not allow access for this user",
|
||||
md.permissions().mode() & 0o0777
|
||||
),
|
||||
Err(e) => e.to_string(),
|
||||
let err = {
|
||||
let mut err = err;
|
||||
err.additional_context = Some(match path.metadata() {
|
||||
Ok(md) => format!(
|
||||
"The permissions of {:o} does not allow access for this user",
|
||||
md.permissions().mode() & 0o0777
|
||||
),
|
||||
Err(e) => e.to_string(),
|
||||
});
|
||||
err
|
||||
};
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let error_msg = String::from("Permission denied");
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Permission denied".into(),
|
||||
msg: error_msg,
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
return Err(err.into());
|
||||
} else {
|
||||
#[cfg(feature = "sqlite")]
|
||||
if !raw {
|
||||
@ -147,18 +138,18 @@ impl Command for Open {
|
||||
}
|
||||
}
|
||||
|
||||
let file = match std::fs::File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Permission denied".into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
};
|
||||
if path.is_dir() {
|
||||
// At least under windows this check ensures that we don't get a
|
||||
// permission denied error on directories
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
shell_error::io::ErrorKind::IsADirectory,
|
||||
arg_span,
|
||||
PathBuf::from(path),
|
||||
)));
|
||||
}
|
||||
|
||||
let file = std::fs::File::open(path)
|
||||
.map_err(|err| IoError::new(err.kind(), arg_span, PathBuf::from(path)))?;
|
||||
|
||||
// No content_type by default - Is added later if no converter is found
|
||||
let stream = PipelineData::ByteStream(
|
||||
|
@ -3,7 +3,11 @@ use super::util::try_interaction;
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use nu_glob::MatchOptions;
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{report_shell_error, NuGlob};
|
||||
use nu_protocol::{
|
||||
report_shell_error,
|
||||
shell_error::{self, io::IoError},
|
||||
NuGlob,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::FileTypeExt;
|
||||
use std::{
|
||||
@ -299,9 +303,17 @@ fn rm(
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// glob_from may canonicalize path and return `DirectoryNotFound`
|
||||
// glob_from may canonicalize path and return an error when a directory is not found
|
||||
// nushell should suppress the error if `--force` is used.
|
||||
if !(force && matches!(e, ShellError::DirectoryNotFound { .. })) {
|
||||
if !(force
|
||||
&& matches!(
|
||||
e,
|
||||
ShellError::Io(IoError {
|
||||
kind: shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotFound),
|
||||
..
|
||||
})
|
||||
))
|
||||
{
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
@ -413,8 +425,7 @@ fn rm(
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
let msg = format!("Could not delete {:}: {e:}", f.to_string_lossy());
|
||||
Err(ShellError::RemoveNotPossible { msg, span })
|
||||
Err(ShellError::Io(IoError::new(e.kind(), span, f)))
|
||||
} else if verbose {
|
||||
let msg = if interactive && !confirmed {
|
||||
"not deleted"
|
||||
|
@ -4,8 +4,8 @@ use nu_engine::get_eval_block;
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast, byte_stream::copy_with_signals, process::ChildPipe, ByteStreamSource, DataSource, OutDest,
|
||||
PipelineMetadata, Signals,
|
||||
ast, byte_stream::copy_with_signals, process::ChildPipe, shell_error::io::IoError,
|
||||
ByteStreamSource, DataSource, OutDest, PipelineMetadata, Signals,
|
||||
};
|
||||
use std::{
|
||||
fs::File,
|
||||
@ -86,6 +86,7 @@ impl Command for Save {
|
||||
span: arg.span,
|
||||
});
|
||||
|
||||
let from_io_error = IoError::factory(span, path.item.as_path());
|
||||
match input {
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?;
|
||||
@ -129,7 +130,7 @@ impl Command for Save {
|
||||
io::copy(&mut tee, &mut io::stderr())
|
||||
}
|
||||
}
|
||||
.err_span(span)?;
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -153,7 +154,7 @@ impl Command for Save {
|
||||
)
|
||||
})
|
||||
.transpose()
|
||||
.err_span(span)?;
|
||||
.map_err(&from_io_error)?;
|
||||
|
||||
let res = match stdout {
|
||||
ChildPipe::Pipe(pipe) => {
|
||||
@ -203,15 +204,10 @@ impl Command for Save {
|
||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||
for val in ls {
|
||||
file.write_all(&value_to_bytes(val)?)
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: err.to_string(),
|
||||
})?;
|
||||
file.write_all("\n".as_bytes())
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: err.to_string(),
|
||||
})?;
|
||||
.map_err(&from_io_error)?;
|
||||
file.write_all("\n".as_bytes()).map_err(&from_io_error)?;
|
||||
}
|
||||
file.flush()?;
|
||||
file.flush().map_err(&from_io_error)?;
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
@ -232,11 +228,8 @@ impl Command for Save {
|
||||
// Only open file after successful conversion
|
||||
let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?;
|
||||
|
||||
file.write_all(&bytes).map_err(|err| ShellError::IOError {
|
||||
msg: err.to_string(),
|
||||
})?;
|
||||
|
||||
file.flush()?;
|
||||
file.write_all(&bytes).map_err(&from_io_error)?;
|
||||
file.flush().map_err(&from_io_error)?;
|
||||
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
@ -420,33 +413,27 @@ fn prepare_path(
|
||||
}
|
||||
|
||||
fn open_file(path: &Path, span: Span, append: bool) -> Result<File, ShellError> {
|
||||
let file = match (append, path.exists()) {
|
||||
(true, true) => std::fs::OpenOptions::new().append(true).open(path),
|
||||
let file: Result<File, nu_protocol::shell_error::io::ErrorKind> = match (append, path.exists())
|
||||
{
|
||||
(true, true) => std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(path)
|
||||
.map_err(|err| err.kind().into()),
|
||||
_ => {
|
||||
// This is a temporary solution until `std::fs::File::create` is fixed on Windows (rust-lang/rust#134893)
|
||||
// A TOCTOU problem exists here, which may cause wrong error message to be shown
|
||||
#[cfg(target_os = "windows")]
|
||||
if path.is_dir() {
|
||||
// It should be `io::ErrorKind::IsADirectory` but it's not available in stable yet (1.83)
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"Is a directory (os error 21)",
|
||||
))
|
||||
Err(nu_protocol::shell_error::io::ErrorKind::IsADirectory)
|
||||
} else {
|
||||
std::fs::File::create(path)
|
||||
std::fs::File::create(path).map_err(|err| err.kind().into())
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
std::fs::File::create(path)
|
||||
std::fs::File::create(path).map_err(|err| err.kind().into())
|
||||
}
|
||||
};
|
||||
|
||||
file.map_err(|e| ShellError::GenericError {
|
||||
error: format!("Problem with [{}], Permission denied", path.display()),
|
||||
msg: e.to_string(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
file.map_err(|err_kind| ShellError::Io(IoError::new(err_kind, span, PathBuf::from(path))))
|
||||
}
|
||||
|
||||
/// Get output file and optional stderr file
|
||||
@ -493,6 +480,9 @@ fn stream_to_file(
|
||||
span: Span,
|
||||
progress: bool,
|
||||
) -> Result<(), ShellError> {
|
||||
// TODO: maybe we can get a path in here
|
||||
let from_io_error = IoError::factory(span, None);
|
||||
|
||||
// https://github.com/nushell/nushell/pull/9377 contains the reason for not using `BufWriter`
|
||||
if progress {
|
||||
let mut bytes_processed = 0;
|
||||
@ -512,7 +502,7 @@ fn stream_to_file(
|
||||
match reader.fill_buf() {
|
||||
Ok(&[]) => break Ok(()),
|
||||
Ok(buf) => {
|
||||
file.write_all(buf).err_span(span)?;
|
||||
file.write_all(buf).map_err(&from_io_error)?;
|
||||
let len = buf.len();
|
||||
reader.consume(len);
|
||||
bytes_processed += len as u64;
|
||||
@ -530,9 +520,9 @@ fn stream_to_file(
|
||||
if let Err(err) = res {
|
||||
let _ = file.flush();
|
||||
bar.abandoned_msg("# Error while saving #".to_owned());
|
||||
Err(err.into_spanned(span).into())
|
||||
Err(from_io_error(err).into())
|
||||
} else {
|
||||
file.flush().err_span(span)?;
|
||||
file.flush().map_err(&from_io_error)?;
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_protocol::NuGlob;
|
||||
use nu_protocol::{shell_error::io::IoError, NuGlob};
|
||||
use std::path::PathBuf;
|
||||
use uu_cp::{BackupMode, CopyMode, UpdateMode};
|
||||
|
||||
@ -197,10 +197,11 @@ impl Command for UCp {
|
||||
.map(|f| f.1)?
|
||||
.collect();
|
||||
if exp_files.is_empty() {
|
||||
return Err(ShellError::FileNotFound {
|
||||
file: p.item.to_string(),
|
||||
span: p.span,
|
||||
});
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
p.span,
|
||||
PathBuf::from(p.item.to_string()),
|
||||
)));
|
||||
};
|
||||
let mut app_vals: Vec<PathBuf> = Vec::new();
|
||||
for v in exp_files {
|
||||
|
@ -1,7 +1,7 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
use nu_protocol::{shell_error::io::IoError, NuGlob};
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
use uu_mv::{BackupMode, UpdateMode};
|
||||
|
||||
@ -138,10 +138,11 @@ impl Command for UMv {
|
||||
.map(|f| f.1)?
|
||||
.collect();
|
||||
if exp_files.is_empty() {
|
||||
return Err(ShellError::FileNotFound {
|
||||
file: p.item.to_string(),
|
||||
span: p.span,
|
||||
});
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
p.span,
|
||||
PathBuf::from(p.item.to_string()),
|
||||
)));
|
||||
};
|
||||
let mut app_vals: Vec<PathBuf> = Vec::new();
|
||||
for v in exp_files {
|
||||
|
@ -3,8 +3,8 @@ use filetime::FileTime;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_glob::{glob, is_glob};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
use std::{io::ErrorKind, path::PathBuf};
|
||||
use nu_protocol::{shell_error::io::IoError, NuGlob};
|
||||
use std::path::PathBuf;
|
||||
use uu_touch::{error::TouchError, ChangeTimes, InputFile, Options, Source};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -225,20 +225,12 @@ impl Command for UTouch {
|
||||
},
|
||||
TouchError::ReferenceFileInaccessible(reference_path, io_err) => {
|
||||
let span = reference_span.expect("touch should've been given a reference file");
|
||||
if io_err.kind() == ErrorKind::NotFound {
|
||||
ShellError::FileNotFound {
|
||||
span,
|
||||
file: reference_path.display().to_string(),
|
||||
}
|
||||
} else {
|
||||
ShellError::GenericError {
|
||||
error: io_err.to_string(),
|
||||
msg: format!("Failed to read metadata of {}", reference_path.display()),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
}
|
||||
}
|
||||
ShellError::Io(IoError::new_with_additional_context(
|
||||
io_err.kind(),
|
||||
span,
|
||||
reference_path,
|
||||
"failed to read metadata",
|
||||
))
|
||||
}
|
||||
_ => ShellError::GenericError {
|
||||
error: err.to_string(),
|
||||
|
@ -9,6 +9,7 @@ use nu_engine::{command_prelude::*, ClosureEval};
|
||||
use nu_protocol::{
|
||||
engine::{Closure, StateWorkingSet},
|
||||
format_shell_error,
|
||||
shell_error::io::IoError,
|
||||
};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
@ -83,11 +84,12 @@ impl Command for Watch {
|
||||
|
||||
let path = match nu_path::canonicalize_with(path_no_whitespace, cwd) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path_no_whitespace.to_string(),
|
||||
span: path_arg.span,
|
||||
})
|
||||
Err(err) => {
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
err.kind(),
|
||||
path_arg.span,
|
||||
PathBuf::from(path_no_whitespace),
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
@ -151,14 +153,22 @@ impl Command for Watch {
|
||||
let mut debouncer = match new_debouncer(debounce_duration, None, tx) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
return Err(ShellError::IOError {
|
||||
msg: format!("Failed to create watcher: {e}"),
|
||||
})
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Failed to create watcher".to_string(),
|
||||
msg: e.to_string(),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
};
|
||||
if let Err(e) = debouncer.watcher().watch(&path, recursive_mode) {
|
||||
return Err(ShellError::IOError {
|
||||
msg: format!("Failed to create watcher: {e}"),
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Failed to create watcher".to_string(),
|
||||
msg: e.to_string(),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
// need to cache to make sure that rename event works.
|
||||
@ -249,13 +259,21 @@ impl Command for Watch {
|
||||
}
|
||||
}
|
||||
Ok(Err(_)) => {
|
||||
return Err(ShellError::IOError {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Receiving events failed".to_string(),
|
||||
msg: "Unexpected errors when receiving events".into(),
|
||||
})
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
return Err(ShellError::IOError {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Disconnected".to_string(),
|
||||
msg: "Unexpected disconnect from file watcher".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ListStream;
|
||||
use nu_protocol::{shell_error::io::IoError, ListStream};
|
||||
use std::{
|
||||
io::{BufRead, Cursor, ErrorKind},
|
||||
num::NonZeroUsize,
|
||||
@ -119,6 +119,7 @@ pub fn chunks(
|
||||
chunk_size: NonZeroUsize,
|
||||
span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let from_io_error = IoError::factory(span, None);
|
||||
match input {
|
||||
PipelineData::Value(Value::List { vals, .. }, metadata) => {
|
||||
let chunks = ChunksIter::new(vals, chunk_size, span);
|
||||
@ -136,7 +137,7 @@ pub fn chunks(
|
||||
};
|
||||
let value_stream = chunk_read.map(move |chunk| match chunk {
|
||||
Ok(chunk) => Value::binary(chunk, span),
|
||||
Err(e) => Value::error(e.into(), span),
|
||||
Err(e) => Value::error(from_io_error(e).into(), span),
|
||||
});
|
||||
let pipeline_data_with_metadata = value_stream.into_pipeline_data_with_metadata(
|
||||
span,
|
||||
@ -155,7 +156,7 @@ pub fn chunks(
|
||||
};
|
||||
let value_stream = chunk_read.map(move |chunk| match chunk {
|
||||
Ok(chunk) => Value::binary(chunk, span),
|
||||
Err(e) => Value::error(e.into(), span),
|
||||
Err(e) => Value::error(from_io_error(e).into(), span),
|
||||
});
|
||||
value_stream.into_pipeline_data_with_metadata(
|
||||
span,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use std::io::Read;
|
||||
|
||||
pub fn empty(
|
||||
@ -41,7 +42,12 @@ pub fn empty(
|
||||
let span = stream.span();
|
||||
match stream.reader() {
|
||||
Some(reader) => {
|
||||
let is_empty = reader.bytes().next().transpose().err_span(span)?.is_none();
|
||||
let is_empty = reader
|
||||
.bytes()
|
||||
.next()
|
||||
.transpose()
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?
|
||||
.is_none();
|
||||
if negate {
|
||||
Ok(Value::bool(!is_empty, head).into_pipeline_data())
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Signals;
|
||||
use nu_protocol::{shell_error::io::IoError, Signals};
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -180,7 +180,11 @@ fn first_helper(
|
||||
if return_single_element {
|
||||
// Take a single byte
|
||||
let mut byte = [0u8];
|
||||
if reader.read(&mut byte).err_span(span)? > 0 {
|
||||
if reader
|
||||
.read(&mut byte)
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?
|
||||
> 0
|
||||
{
|
||||
Ok(Value::int(byte[0] as i64, head).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::AccessEmptyContent { span: head })
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::{command_prelude::*, ClosureEvalOnce};
|
||||
use nu_protocol::engine::Closure;
|
||||
use nu_protocol::{engine::Closure, shell_error::io::IoError};
|
||||
use std::{sync::mpsc, thread};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -137,10 +137,7 @@ interleave
|
||||
}
|
||||
})
|
||||
.map(|_| ())
|
||||
.map_err(|err| ShellError::IOErrorSpanned {
|
||||
msg: err.to_string(),
|
||||
span: head,
|
||||
})
|
||||
.map_err(|err| IoError::new(err.kind(), head, None).into())
|
||||
})
|
||||
})?;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use std::{collections::VecDeque, io::Read};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -165,7 +166,7 @@ impl Command for Last {
|
||||
let mut buf = VecDeque::with_capacity(rows + TAKE as usize);
|
||||
loop {
|
||||
let taken = std::io::copy(&mut (&mut reader).take(TAKE), &mut buf)
|
||||
.err_span(span)?;
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
if buf.len() > rows {
|
||||
buf.drain(..(buf.len() - rows));
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
|
||||
#[cfg(feature = "os")]
|
||||
use nu_protocol::process::ChildPipe;
|
||||
use nu_protocol::{
|
||||
byte_stream::copy_with_signals, engine::Closure, report_shell_error, ByteStream,
|
||||
ByteStreamSource, OutDest, PipelineMetadata, Signals,
|
||||
byte_stream::copy_with_signals, engine::Closure, report_shell_error, shell_error::io::IoError,
|
||||
ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals,
|
||||
};
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
@ -82,6 +82,7 @@ use it in your pipeline."#
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let from_io_error = IoError::factory(head, None);
|
||||
let use_stderr = call.has_flag(engine_state, stack, "stderr")?;
|
||||
|
||||
let closure: Spanned<Closure> = call.req(engine_state, stack, 0)?;
|
||||
@ -263,7 +264,7 @@ use it in your pipeline."#
|
||||
let input = rx.into_pipeline_data_with_metadata(span, signals, metadata_clone);
|
||||
eval_block(input)
|
||||
})
|
||||
.err_span(call.head)?
|
||||
.map_err(&from_io_error)?
|
||||
.map(move |result| result.unwrap_or_else(|err| Value::error(err, closure_span)))
|
||||
.into_pipeline_data_with_metadata(
|
||||
span,
|
||||
@ -278,7 +279,7 @@ use it in your pipeline."#
|
||||
tee_once(engine_state_arc, move || {
|
||||
eval_block(value_clone.into_pipeline_data_with_metadata(metadata_clone))
|
||||
})
|
||||
.err_span(call.head)?;
|
||||
.map_err(&from_io_error)?;
|
||||
Ok(value.into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
}
|
||||
@ -439,7 +440,9 @@ fn spawn_tee(
|
||||
);
|
||||
eval_block(PipelineData::ByteStream(stream, info.metadata))
|
||||
})
|
||||
.err_span(info.span)?;
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(err.kind(), info.span, None, "Could not spawn tee")
|
||||
})?;
|
||||
|
||||
Ok(TeeThread { sender, thread })
|
||||
}
|
||||
@ -478,7 +481,15 @@ fn copy_on_thread(
|
||||
copy_with_signals(src, dest, span, &signals)?;
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| e.into_spanned(span).into())
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
None,
|
||||
"Could not spawn stderr copier",
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "os")]
|
||||
@ -521,7 +532,12 @@ fn tee_forwards_errors_back_immediately() {
|
||||
use std::time::Duration;
|
||||
let slow_input = (0..100).inspect(|_| std::thread::sleep(Duration::from_millis(1)));
|
||||
let iter = tee(slow_input, |_| {
|
||||
Err(ShellError::IOError { msg: "test".into() })
|
||||
Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
std::io::ErrorKind::Other,
|
||||
Span::test_data(),
|
||||
None,
|
||||
"test",
|
||||
)))
|
||||
})
|
||||
.expect("io error");
|
||||
for result in iter {
|
||||
@ -548,7 +564,12 @@ fn tee_waits_for_the_other_thread() {
|
||||
let iter = tee(0..100, move |_| {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
waited_clone.store(true, Ordering::Relaxed);
|
||||
Err(ShellError::IOError { msg: "test".into() })
|
||||
Err(ShellError::Io(IoError::new_with_additional_context(
|
||||
std::io::ErrorKind::Other,
|
||||
Span::test_data(),
|
||||
None,
|
||||
"test",
|
||||
)))
|
||||
})
|
||||
.expect("io error");
|
||||
let last = iter.last();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::io::{BufRead, Cursor};
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ListStream, Signals};
|
||||
use nu_protocol::{shell_error::io::IoError, ListStream, Signals};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FromJson;
|
||||
@ -134,7 +134,7 @@ fn read_json_lines(
|
||||
.lines()
|
||||
.filter(|line| line.as_ref().is_ok_and(|line| !line.trim().is_empty()) || line.is_err())
|
||||
.map(move |line| {
|
||||
let line = line.err_span(span)?;
|
||||
let line = line.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
if strict {
|
||||
convert_string_to_value_strict(&line, span)
|
||||
} else {
|
||||
|
@ -1,16 +1,14 @@
|
||||
use csv::WriterBuilder;
|
||||
use nu_cmd_base::formats::to::delimited::merge_descriptors;
|
||||
use nu_protocol::{
|
||||
ByteStream, ByteStreamType, Config, PipelineData, ShellError, Signals, Span, Spanned, Value,
|
||||
shell_error::io::IoError, ByteStream, ByteStreamType, Config, PipelineData, ShellError,
|
||||
Signals, Span, Spanned, Value,
|
||||
};
|
||||
use std::{iter, sync::Arc};
|
||||
|
||||
fn make_csv_error(error: csv::Error, format_name: &str, head: Span) -> ShellError {
|
||||
if let csv::ErrorKind::Io(error) = error.kind() {
|
||||
ShellError::IOErrorSpanned {
|
||||
msg: error.to_string(),
|
||||
span: head,
|
||||
}
|
||||
IoError::new(error.kind(), head, None).into()
|
||||
} else {
|
||||
ShellError::GenericError {
|
||||
error: format!("Failed to generate {format_name} data"),
|
||||
|
@ -5,7 +5,7 @@ use std::io;
|
||||
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ast::PathMember, Signals, Spanned};
|
||||
use nu_protocol::{ast::PathMember, shell_error::io::IoError, Signals, Spanned};
|
||||
use rmp::encode as mp;
|
||||
|
||||
/// Max recursion depth
|
||||
@ -138,7 +138,7 @@ impl From<WriteError> for ShellError {
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
WriteError::Io(err, span) => err.into_spanned(span).into(),
|
||||
WriteError::Io(err, span) => ShellError::Io(IoError::new(err.kind(), span, None)),
|
||||
WriteError::Shell(err) => *err,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::io::Write;
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
|
||||
use super::msgpack::write_value;
|
||||
|
||||
@ -80,7 +81,8 @@ impl Command for ToMsgpackz {
|
||||
);
|
||||
|
||||
write_value(&mut out, &value, 0)?;
|
||||
out.flush().err_span(call.head)?;
|
||||
out.flush()
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
drop(out);
|
||||
|
||||
Ok(Value::binary(out_buf, call.head).into_pipeline_data())
|
||||
|
@ -1,6 +1,8 @@
|
||||
use chrono_humanize::HumanTime;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{format_duration, ByteStream, Config, PipelineMetadata};
|
||||
use nu_protocol::{
|
||||
format_duration, shell_error::io::IoError, ByteStream, Config, PipelineMetadata,
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
|
||||
@ -72,6 +74,7 @@ impl Command for ToText {
|
||||
}
|
||||
PipelineData::ListStream(stream, meta) => {
|
||||
let span = stream.span();
|
||||
let from_io_error = IoError::factory(head, None);
|
||||
let stream = if no_newline {
|
||||
let mut first = true;
|
||||
let mut iter = stream.into_inner();
|
||||
@ -87,7 +90,7 @@ impl Command for ToText {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
write!(buf, "{LINE_ENDING}").err_span(head)?;
|
||||
write!(buf, "{LINE_ENDING}").map_err(&from_io_error)?;
|
||||
}
|
||||
// TODO: write directly into `buf` instead of creating an intermediate
|
||||
// string.
|
||||
@ -98,7 +101,7 @@ impl Command for ToText {
|
||||
&config,
|
||||
serialize_types,
|
||||
);
|
||||
write!(buf, "{str}").err_span(head)?;
|
||||
write!(buf, "{str}").map_err(&from_io_error)?;
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{engine::CommandType, BlockId};
|
||||
use nu_protocol::{engine::CommandType, shell_error::io::IoError, BlockId};
|
||||
|
||||
/// Source a file for environment variables.
|
||||
#[derive(Clone)]
|
||||
@ -55,11 +55,8 @@ impl Command for Source {
|
||||
let cwd = engine_state.cwd_as_string(Some(stack))?;
|
||||
let pb = std::path::PathBuf::from(block_id_name);
|
||||
let parent = pb.parent().unwrap_or(std::path::Path::new(""));
|
||||
let file_path =
|
||||
canonicalize_with(pb.as_path(), cwd).map_err(|err| ShellError::FileNotFoundCustom {
|
||||
msg: format!("Could not access file '{}': {err}", pb.as_path().display()),
|
||||
span: Span::unknown(),
|
||||
})?;
|
||||
let file_path = canonicalize_with(pb.as_path(), cwd)
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, pb.clone()))?;
|
||||
|
||||
// Note: We intentionally left out PROCESS_PATH since it's supposed to
|
||||
// to work like argv[0] in C, which is the name of the program being executed.
|
||||
|
@ -6,10 +6,11 @@ use base64::{
|
||||
};
|
||||
use multipart_rs::MultipartWriter;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ByteStream, LabeledError, Signals};
|
||||
use nu_protocol::{shell_error::io::IoError, ByteStream, LabeledError, Signals};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error::Error as StdError,
|
||||
io::Cursor,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
@ -184,6 +185,7 @@ pub fn request_add_authorization_header(
|
||||
request
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ShellErrorOrRequestError {
|
||||
ShellError(ShellError),
|
||||
@ -372,10 +374,10 @@ fn send_multipart_request(
|
||||
Value::Record { val, .. } => {
|
||||
let mut builder = MultipartWriter::new();
|
||||
|
||||
let err = |e| {
|
||||
ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
||||
msg: format!("failed to build multipart data: {}", e),
|
||||
})
|
||||
let err = |e: std::io::Error| {
|
||||
ShellErrorOrRequestError::ShellError(
|
||||
IoError::new_with_additional_context(e.kind(), span, None, e).into(),
|
||||
)
|
||||
};
|
||||
|
||||
for (col, val) in val.into_owned() {
|
||||
@ -464,6 +466,14 @@ fn send_cancellable_request(
|
||||
let ret = request_fn();
|
||||
let _ = tx.send(ret); // may fail if the user has cancelled the operation
|
||||
})
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
None,
|
||||
"Could not spawn HTTP requester",
|
||||
)
|
||||
})
|
||||
.map_err(ShellError::from)?;
|
||||
|
||||
// ...and poll the channel for responses
|
||||
@ -519,6 +529,14 @@ fn send_cancellable_request_bytes(
|
||||
// may fail if the user has cancelled the operation
|
||||
let _ = tx.send(ret);
|
||||
})
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
span,
|
||||
None,
|
||||
"Could not spawn HTTP requester",
|
||||
)
|
||||
})
|
||||
.map_err(ShellError::from)?;
|
||||
|
||||
// ...and poll the channel for responses
|
||||
@ -618,27 +636,56 @@ pub fn request_add_custom_headers(
|
||||
|
||||
fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -> ShellError {
|
||||
match response_err {
|
||||
Error::Status(301, _) => ShellError::NetworkFailure { msg: format!("Resource moved permanently (301): {requested_url:?}"), span },
|
||||
Error::Status(400, _) => {
|
||||
ShellError::NetworkFailure { msg: format!("Bad request (400) to {requested_url:?}"), span }
|
||||
}
|
||||
Error::Status(403, _) => {
|
||||
ShellError::NetworkFailure { msg: format!("Access forbidden (403) to {requested_url:?}"), span }
|
||||
}
|
||||
Error::Status(404, _) => ShellError::NetworkFailure { msg: format!("Requested file not found (404): {requested_url:?}"), span },
|
||||
Error::Status(408, _) => {
|
||||
ShellError::NetworkFailure { msg: format!("Request timeout (408): {requested_url:?}"), span }
|
||||
}
|
||||
Error::Status(_, _) => ShellError::NetworkFailure { msg: format!(
|
||||
Error::Status(301, _) => ShellError::NetworkFailure {
|
||||
msg: format!("Resource moved permanently (301): {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(400, _) => ShellError::NetworkFailure {
|
||||
msg: format!("Bad request (400) to {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(403, _) => ShellError::NetworkFailure {
|
||||
msg: format!("Access forbidden (403) to {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(404, _) => ShellError::NetworkFailure {
|
||||
msg: format!("Requested file not found (404): {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(408, _) => ShellError::NetworkFailure {
|
||||
msg: format!("Request timeout (408): {requested_url:?}"),
|
||||
span,
|
||||
},
|
||||
Error::Status(_, _) => ShellError::NetworkFailure {
|
||||
msg: format!(
|
||||
"Cannot make request to {:?}. Error is {:?}",
|
||||
requested_url,
|
||||
response_err.to_string()
|
||||
), span },
|
||||
|
||||
Error::Transport(t) => match t {
|
||||
t if t.kind() == ErrorKind::ConnectionFailed => ShellError::NetworkFailure { msg: format!("Cannot make request to {requested_url}, there was an error establishing a connection.",), span },
|
||||
t => ShellError::NetworkFailure { msg: t.to_string(), span },
|
||||
),
|
||||
span,
|
||||
},
|
||||
|
||||
Error::Transport(t) => {
|
||||
let generic_network_failure = || ShellError::NetworkFailure {
|
||||
msg: t.to_string(),
|
||||
span,
|
||||
};
|
||||
match t.kind() {
|
||||
ErrorKind::ConnectionFailed => ShellError::NetworkFailure { msg: format!("Cannot make request to {requested_url}, there was an error establishing a connection.",), span },
|
||||
ErrorKind::Io => 'io: {
|
||||
let Some(source) = t.source() else {
|
||||
break 'io generic_network_failure();
|
||||
};
|
||||
|
||||
let Some(io_error) = source.downcast_ref::<std::io::Error>() else {
|
||||
break 'io generic_network_failure();
|
||||
};
|
||||
|
||||
ShellError::Io(IoError::new(io_error.kind(), span, None))
|
||||
}
|
||||
_ => generic_network_failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener};
|
||||
|
||||
@ -61,12 +62,14 @@ fn get_free_port(
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let from_io_error = IoError::factory(call.head, None);
|
||||
|
||||
let start_port: Option<Spanned<usize>> = call.opt(engine_state, stack, 0)?;
|
||||
let end_port: Option<Spanned<usize>> = call.opt(engine_state, stack, 1)?;
|
||||
|
||||
let listener = if start_port.is_none() && end_port.is_none() {
|
||||
// get free port from system.
|
||||
TcpListener::bind("127.0.0.1:0")?
|
||||
TcpListener::bind("127.0.0.1:0").map_err(&from_io_error)?
|
||||
} else {
|
||||
let (start_port, start_span) = match start_port {
|
||||
Some(p) => (p.item, Some(p.span)),
|
||||
@ -118,20 +121,25 @@ fn get_free_port(
|
||||
});
|
||||
}
|
||||
|
||||
// try given port one by one.
|
||||
match (start_port..=end_port)
|
||||
.map(|port| SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)))
|
||||
.find_map(|addr| TcpListener::bind(addr).ok())
|
||||
{
|
||||
Some(listener) => listener,
|
||||
None => {
|
||||
return Err(ShellError::IOError {
|
||||
msg: "Every port has been tried, but no valid one was found".to_string(),
|
||||
})
|
||||
'search: {
|
||||
let mut last_err = None;
|
||||
for port in start_port..=end_port {
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port));
|
||||
match TcpListener::bind(addr) {
|
||||
Ok(listener) => break 'search Ok(listener),
|
||||
Err(err) => last_err = Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(IoError::new_with_additional_context(
|
||||
last_err.expect("range not empty, validated before").kind(),
|
||||
range_span,
|
||||
None,
|
||||
"Every port has been tried, but no valid one was found",
|
||||
))
|
||||
}?
|
||||
};
|
||||
|
||||
let free_port = listener.local_addr()?.port();
|
||||
let free_port = listener.local_addr().map_err(&from_io_error)?.port();
|
||||
Ok(Value::int(free_port as i64, call.head).into_pipeline_data())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use super::PathSubcommandArguments;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, current_dir_const};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
struct Arguments {
|
||||
@ -140,7 +140,7 @@ fn exists(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
// symlink_metadata returns true if the file/folder exists
|
||||
// whether it is a symbolic link or not. Sorry, but returns Err
|
||||
// in every other scenario including the NotFound
|
||||
std::fs::symlink_metadata(path).map_or_else(
|
||||
std::fs::symlink_metadata(&path).map_or_else(
|
||||
|e| match e.kind() {
|
||||
std::io::ErrorKind::NotFound => Ok(false),
|
||||
_ => Err(e),
|
||||
@ -153,15 +153,7 @@ fn exists(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
Value::bool(
|
||||
match exists {
|
||||
Ok(exists) => exists,
|
||||
Err(err) => {
|
||||
return Value::error(
|
||||
ShellError::IOErrorSpanned {
|
||||
msg: err.to_string(),
|
||||
span,
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
Err(err) => return Value::error(IoError::new(err.kind(), span, path).into(), span),
|
||||
},
|
||||
span,
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -54,23 +54,25 @@ impl Command for SubCommand {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let path: Option<String> = call.opt_const(working_set, 0)?;
|
||||
let cwd = working_set.permanent_state.cwd(None)?;
|
||||
let current_file =
|
||||
working_set
|
||||
.files
|
||||
.top()
|
||||
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
||||
msg: "Couldn't find current file".into(),
|
||||
span: call.head,
|
||||
})?;
|
||||
let current_file = working_set.files.top().ok_or_else(|| {
|
||||
IoError::new_with_additional_context(
|
||||
std::io::ErrorKind::NotFound,
|
||||
call.head,
|
||||
None,
|
||||
"Couldn't find current file",
|
||||
)
|
||||
})?;
|
||||
|
||||
let out = if let Some(path) = path {
|
||||
let dir = expand_path_with(
|
||||
current_file
|
||||
.parent()
|
||||
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
||||
msg: "Couldn't find current file's parent.".into(),
|
||||
span: call.head,
|
||||
})?,
|
||||
current_file.parent().ok_or_else(|| {
|
||||
IoError::new_with_additional_context(
|
||||
std::io::ErrorKind::NotFound,
|
||||
call.head,
|
||||
current_file.to_owned(),
|
||||
"Couldn't find current file's parent.",
|
||||
)
|
||||
})?,
|
||||
&cwd,
|
||||
true,
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::PathSubcommandArguments;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_path::AbsolutePathBuf;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError};
|
||||
use std::{io, path::Path};
|
||||
|
||||
struct Arguments {
|
||||
@ -108,7 +108,7 @@ fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
match path.symlink_metadata() {
|
||||
Ok(metadata) => Value::string(get_file_type(&metadata), span),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span),
|
||||
Err(err) => Value::error(err.into_spanned(span).into(), span),
|
||||
Err(err) => Value::error(IoError::new(err.kind(), span, None).into(), span),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ use crossterm::{
|
||||
QueueableCommand,
|
||||
};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
@ -41,19 +42,27 @@ impl Command for Clear {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let from_io_error = IoError::factory(call.head, None);
|
||||
match call.has_flag(engine_state, stack, "keep-scrollback")? {
|
||||
true => {
|
||||
std::io::stdout()
|
||||
.queue(MoveTo(0, 0))?
|
||||
.queue(ClearCommand(ClearType::All))?
|
||||
.flush()?;
|
||||
.queue(MoveTo(0, 0))
|
||||
.map_err(&from_io_error)?
|
||||
.queue(ClearCommand(ClearType::All))
|
||||
.map_err(&from_io_error)?
|
||||
.flush()
|
||||
.map_err(&from_io_error)?;
|
||||
}
|
||||
_ => {
|
||||
std::io::stdout()
|
||||
.queue(MoveTo(0, 0))?
|
||||
.queue(ClearCommand(ClearType::All))?
|
||||
.queue(ClearCommand(ClearType::Purge))?
|
||||
.flush()?;
|
||||
.queue(MoveTo(0, 0))
|
||||
.map_err(&from_io_error)?
|
||||
.queue(ClearCommand(ClearType::All))
|
||||
.map_err(&from_io_error)?
|
||||
.queue(ClearCommand(ClearType::Purge))
|
||||
.map_err(&from_io_error)?
|
||||
.flush()
|
||||
.map_err(&from_io_error)?;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use filesize::file_real_size_fast;
|
||||
use nu_glob::Pattern;
|
||||
use nu_protocol::{record, ShellError, Signals, Span, Value};
|
||||
use nu_protocol::{record, shell_error::io::IoError, ShellError, Signals, Span, Value};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -77,7 +77,7 @@ impl FileInfo {
|
||||
long,
|
||||
})
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
Err(e) => Err(IoError::new(e.kind(), tag, path).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,6 +91,7 @@ impl DirInfo {
|
||||
signals: &Signals,
|
||||
) -> Result<Self, ShellError> {
|
||||
let path = path.into();
|
||||
let from_io_error = IoError::factory(span, path.as_path());
|
||||
|
||||
let mut s = Self {
|
||||
dirs: Vec::new(),
|
||||
@ -99,7 +100,7 @@ impl DirInfo {
|
||||
size: 0,
|
||||
blocks: 0,
|
||||
tag: params.tag,
|
||||
path,
|
||||
path: path.clone(),
|
||||
long: params.long,
|
||||
};
|
||||
|
||||
@ -108,7 +109,7 @@ impl DirInfo {
|
||||
s.size = d.len(); // dir entry size
|
||||
s.blocks = file_real_size_fast(&s.path, &d).ok().unwrap_or(0);
|
||||
}
|
||||
Err(e) => s = s.add_error(e.into()),
|
||||
Err(e) => s = s.add_error(from_io_error(e).into()),
|
||||
};
|
||||
|
||||
match std::fs::read_dir(&s.path) {
|
||||
@ -122,13 +123,13 @@ impl DirInfo {
|
||||
s = s.add_dir(i.path(), depth, params, span, signals)?
|
||||
}
|
||||
Ok(_t) => s = s.add_file(i.path(), params),
|
||||
Err(e) => s = s.add_error(e.into()),
|
||||
Err(e) => s = s.add_error(from_io_error(e).into()),
|
||||
},
|
||||
Err(e) => s = s.add_error(e.into()),
|
||||
Err(e) => s = s.add_error(from_io_error(e).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => s = s.add_error(e.into()),
|
||||
Err(e) => s = s.add_error(from_io_error(e).into()),
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use crossterm::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
|
||||
use std::{io::Write, time::Duration};
|
||||
|
||||
@ -69,6 +70,8 @@ impl Command for Input {
|
||||
span: call.head,
|
||||
});
|
||||
|
||||
let from_io_error = IoError::factory(call.head, None);
|
||||
|
||||
if numchar.item < 1 {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "Number of characters to read has to be positive".to_string(),
|
||||
@ -89,11 +92,11 @@ impl Command for Input {
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
crossterm::terminal::enable_raw_mode().map_err(&from_io_error)?;
|
||||
// clear terminal events
|
||||
while crossterm::event::poll(Duration::from_secs(0))? {
|
||||
while crossterm::event::poll(Duration::from_secs(0)).map_err(&from_io_error)? {
|
||||
// If there's an event, read it to remove it from the queue
|
||||
let _ = crossterm::event::read()?;
|
||||
let _ = crossterm::event::read().map_err(&from_io_error)?;
|
||||
}
|
||||
|
||||
loop {
|
||||
@ -110,10 +113,14 @@ impl Command for Input {
|
||||
|| k.modifiers == KeyModifiers::CONTROL
|
||||
{
|
||||
if k.modifiers == KeyModifiers::CONTROL && c == 'c' {
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
return Err(ShellError::IOError {
|
||||
msg: "SIGINT".to_string(),
|
||||
});
|
||||
crossterm::terminal::disable_raw_mode()
|
||||
.map_err(&from_io_error)?;
|
||||
return Err(IoError::new(
|
||||
std::io::ErrorKind::Interrupted,
|
||||
call.head,
|
||||
None,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -138,8 +145,8 @@ impl Command for Input {
|
||||
},
|
||||
Ok(_) => continue,
|
||||
Err(event_error) => {
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
return Err(event_error.into());
|
||||
crossterm::terminal::disable_raw_mode().map_err(&from_io_error)?;
|
||||
return Err(from_io_error(event_error).into());
|
||||
}
|
||||
}
|
||||
if !suppress_output {
|
||||
@ -148,16 +155,18 @@ impl Command for Input {
|
||||
std::io::stdout(),
|
||||
terminal::Clear(ClearType::CurrentLine),
|
||||
cursor::MoveToColumn(0),
|
||||
)?;
|
||||
)
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
if let Some(prompt) = &prompt {
|
||||
execute!(std::io::stdout(), Print(prompt.to_string()))?;
|
||||
execute!(std::io::stdout(), Print(prompt.to_string()))
|
||||
.map_err(&from_io_error)?;
|
||||
}
|
||||
execute!(std::io::stdout(), Print(buf.to_string()))?;
|
||||
execute!(std::io::stdout(), Print(buf.to_string())).map_err(&from_io_error)?;
|
||||
}
|
||||
}
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
crossterm::terminal::disable_raw_mode().map_err(&from_io_error)?;
|
||||
if !suppress_output {
|
||||
std::io::stdout().write_all(b"\n")?;
|
||||
std::io::stdout().write_all(b"\n").map_err(&from_io_error)?;
|
||||
}
|
||||
match default_val {
|
||||
Some(val) if buf.is_empty() => Ok(Value::string(val, call.head).into_pipeline_data()),
|
||||
|
@ -5,6 +5,7 @@ use crossterm::event::{
|
||||
use crossterm::{execute, terminal};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use num_traits::AsPrimitive;
|
||||
use std::io::stdout;
|
||||
|
||||
@ -83,7 +84,7 @@ There are 4 `key_type` variants:
|
||||
let add_raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
let config = engine_state.get_config();
|
||||
|
||||
terminal::enable_raw_mode()?;
|
||||
terminal::enable_raw_mode().map_err(|err| IoError::new(err.kind(), head, None))?;
|
||||
|
||||
if config.use_kitty_protocol {
|
||||
if let Ok(false) = crossterm::terminal::supports_keyboard_enhancement() {
|
||||
@ -111,7 +112,7 @@ There are 4 `key_type` variants:
|
||||
);
|
||||
}
|
||||
|
||||
let console_state = event_type_filter.enable_events()?;
|
||||
let console_state = event_type_filter.enable_events(head)?;
|
||||
loop {
|
||||
let event = crossterm::event::read().map_err(|_| ShellError::GenericError {
|
||||
error: "Error with user input".into(),
|
||||
@ -122,7 +123,7 @@ There are 4 `key_type` variants:
|
||||
})?;
|
||||
let event = parse_event(head, &event, &event_type_filter, add_raw);
|
||||
if let Some(event) = event {
|
||||
terminal::disable_raw_mode()?;
|
||||
terminal::disable_raw_mode().map_err(|err| IoError::new(err.kind(), head, None))?;
|
||||
if config.use_kitty_protocol {
|
||||
let _ = execute!(
|
||||
std::io::stdout(),
|
||||
@ -226,17 +227,20 @@ impl EventTypeFilter {
|
||||
/// Enable capturing of all events allowed by this filter.
|
||||
/// Call [`DeferredConsoleRestore::restore`] when done capturing events to restore
|
||||
/// console state
|
||||
fn enable_events(&self) -> Result<DeferredConsoleRestore, ShellError> {
|
||||
fn enable_events(&self, span: Span) -> Result<DeferredConsoleRestore, ShellError> {
|
||||
if self.listen_mouse {
|
||||
crossterm::execute!(stdout(), EnableMouseCapture)?;
|
||||
crossterm::execute!(stdout(), EnableMouseCapture)
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
}
|
||||
|
||||
if self.listen_paste {
|
||||
crossterm::execute!(stdout(), EnableBracketedPaste)?;
|
||||
crossterm::execute!(stdout(), EnableBracketedPaste)
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
}
|
||||
|
||||
if self.listen_focus {
|
||||
crossterm::execute!(stdout(), crossterm::event::EnableFocusChange)?;
|
||||
crossterm::execute!(stdout(), crossterm::event::EnableFocusChange)
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
}
|
||||
|
||||
Ok(DeferredConsoleRestore {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use dialoguer::{console::Term, FuzzySelect, MultiSelect, Select};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
@ -141,8 +142,13 @@ impl Command for InputList {
|
||||
.items(&options)
|
||||
.report(false)
|
||||
.interact_on_opt(&Term::stderr())
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: format!("{}: {}", INTERACT_ERROR, err),
|
||||
.map_err(|dialoguer::Error::IO(err)| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
call.head,
|
||||
None,
|
||||
INTERACT_ERROR,
|
||||
)
|
||||
})?,
|
||||
)
|
||||
} else if fuzzy {
|
||||
@ -158,8 +164,13 @@ impl Command for InputList {
|
||||
.default(0)
|
||||
.report(false)
|
||||
.interact_on_opt(&Term::stderr())
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: format!("{}: {}", INTERACT_ERROR, err),
|
||||
.map_err(|dialoguer::Error::IO(err)| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
call.head,
|
||||
None,
|
||||
INTERACT_ERROR,
|
||||
)
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
@ -174,8 +185,13 @@ impl Command for InputList {
|
||||
.default(0)
|
||||
.report(false)
|
||||
.interact_on_opt(&Term::stderr())
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: format!("{}: {}", INTERACT_ERROR, err),
|
||||
.map_err(|dialoguer::Error::IO(err)| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
call.head,
|
||||
None,
|
||||
INTERACT_ERROR,
|
||||
)
|
||||
})?,
|
||||
)
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
|
||||
const CTRL_C: u8 = 3;
|
||||
|
||||
@ -98,15 +99,19 @@ The `prefix` is not included in the output."
|
||||
let prefix = prefix.unwrap_or_default();
|
||||
let terminator: Option<Vec<u8>> = call.get_flag(engine_state, stack, "terminator")?;
|
||||
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
crossterm::terminal::enable_raw_mode()
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
scopeguard::defer! {
|
||||
let _ = crossterm::terminal::disable_raw_mode();
|
||||
}
|
||||
|
||||
// clear terminal events
|
||||
while crossterm::event::poll(Duration::from_secs(0))? {
|
||||
while crossterm::event::poll(Duration::from_secs(0))
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?
|
||||
{
|
||||
// If there's an event, read it to remove it from the queue
|
||||
let _ = crossterm::event::read()?;
|
||||
let _ = crossterm::event::read()
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
}
|
||||
|
||||
let mut b = [0u8; 1];
|
||||
@ -115,13 +120,19 @@ The `prefix` is not included in the output."
|
||||
|
||||
{
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout.write_all(&query)?;
|
||||
stdout.flush()?;
|
||||
stdout
|
||||
.write_all(&query)
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
stdout
|
||||
.flush()
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
}
|
||||
|
||||
// Validate and skip prefix
|
||||
for bc in prefix {
|
||||
stdin.read_exact(&mut b)?;
|
||||
stdin
|
||||
.read_exact(&mut b)
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
if b[0] != bc {
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Input did not begin with expected sequence".into(),
|
||||
@ -138,7 +149,9 @@ The `prefix` is not included in the output."
|
||||
|
||||
if let Some(terminator) = terminator {
|
||||
loop {
|
||||
stdin.read_exact(&mut b)?;
|
||||
stdin
|
||||
.read_exact(&mut b)
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
|
||||
if b[0] == CTRL_C {
|
||||
return Err(ShellError::InterruptedByUser {
|
||||
@ -158,7 +171,9 @@ The `prefix` is not included in the output."
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
stdin.read_exact(&mut b)?;
|
||||
stdin
|
||||
.read_exact(&mut b)
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
|
||||
if b[0] == CTRL_C {
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Signals;
|
||||
use nu_protocol::{shell_error::io::IoError, Signals};
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
@ -94,13 +94,15 @@ fn run(
|
||||
Signals::empty(),
|
||||
ByteStreamType::String,
|
||||
move |buffer| {
|
||||
let from_io_error = IoError::factory(span, None);
|
||||
|
||||
// Write each input to the buffer
|
||||
if let Some(value) = iter.next() {
|
||||
// Write the separator if this is not the first
|
||||
if first {
|
||||
first = false;
|
||||
} else if let Some(separator) = &separator {
|
||||
write!(buffer, "{}", separator)?;
|
||||
write!(buffer, "{}", separator).map_err(&from_io_error)?;
|
||||
}
|
||||
|
||||
match value {
|
||||
@ -109,8 +111,9 @@ fn run(
|
||||
}
|
||||
// Hmm, not sure what we actually want.
|
||||
// `to_expanded_string` formats dates as human readable which feels funny.
|
||||
Value::Date { val, .. } => write!(buffer, "{val:?}")?,
|
||||
value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?,
|
||||
Value::Date { val, .. } => write!(buffer, "{val:?}").map_err(&from_io_error)?,
|
||||
value => write!(buffer, "{}", value.to_expanded_string("\n", &config))
|
||||
.map_err(&from_io_error)?,
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
|
@ -1,7 +1,10 @@
|
||||
use nu_engine::{command_prelude::*, find_in_dirs_env, get_dirs_var_from_call};
|
||||
use nu_parser::{parse, parse_module_block, parse_module_file_or_dir, unescape_unquote_string};
|
||||
use nu_protocol::engine::{FileStack, StateWorkingSet};
|
||||
use std::path::Path;
|
||||
use nu_protocol::{
|
||||
engine::{FileStack, StateWorkingSet},
|
||||
shell_error::io::IoError,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NuCheck;
|
||||
@ -89,17 +92,15 @@ impl Command for NuCheck {
|
||||
stack,
|
||||
get_dirs_var_from_call(stack, call),
|
||||
) {
|
||||
Ok(path) => {
|
||||
if let Some(path) = path {
|
||||
path
|
||||
} else {
|
||||
return Err(ShellError::FileNotFound {
|
||||
file: path_str.item,
|
||||
span: path_span,
|
||||
});
|
||||
}
|
||||
Ok(Some(path)) => path,
|
||||
Ok(None) => {
|
||||
return Err(ShellError::Io(IoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
path_span,
|
||||
PathBuf::from(path_str.item),
|
||||
)))
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let result = if as_module || path.is_dir() {
|
||||
@ -258,13 +259,13 @@ fn parse_file_script(
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let filename = check_path(working_set, path_span, call_head)?;
|
||||
|
||||
if let Ok(contents) = std::fs::read(path) {
|
||||
parse_script(working_set, Some(&filename), &contents, is_debug, call_head)
|
||||
} else {
|
||||
Err(ShellError::IOErrorSpanned {
|
||||
msg: "Could not read path".to_string(),
|
||||
span: path_span,
|
||||
})
|
||||
match std::fs::read(path) {
|
||||
Ok(contents) => parse_script(working_set, Some(&filename), &contents, is_debug, call_head),
|
||||
Err(err) => Err(ShellError::Io(IoError::new(
|
||||
err.kind(),
|
||||
path_span,
|
||||
PathBuf::from(path),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use nu_protocol::shell_error::io::IoError;
|
||||
use windows::{core::PCWSTR, Win32::System::Environment::ExpandEnvironmentStringsW};
|
||||
use winreg::{enums::*, types::FromRegValue, RegKey};
|
||||
|
||||
@ -90,7 +91,9 @@ fn registry_query(
|
||||
let registry_value: Option<Spanned<String>> = call.opt(engine_state, stack, 1)?;
|
||||
|
||||
let reg_hive = get_reg_hive(engine_state, stack, call)?;
|
||||
let reg_key = reg_hive.open_subkey(registry_key.item)?;
|
||||
let reg_key = reg_hive
|
||||
.open_subkey(registry_key.item)
|
||||
.map_err(|err| IoError::new(err.kind(), *registry_key_span, None))?;
|
||||
|
||||
if registry_value.is_none() {
|
||||
let mut reg_values = vec![];
|
||||
|
@ -2,7 +2,8 @@ use nu_cmd_base::hook::eval_hook;
|
||||
use nu_engine::{command_prelude::*, env_to_strings};
|
||||
use nu_path::{dots::expand_ndots_safe, expand_tilde, AbsolutePath};
|
||||
use nu_protocol::{
|
||||
did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals, UseAnsiColoring,
|
||||
did_you_mean, process::ChildProcess, shell_error::io::IoError, ByteStream, NuGlob, OutDest,
|
||||
Signals, UseAnsiColoring,
|
||||
};
|
||||
use nu_system::ForegroundChild;
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
@ -169,7 +170,9 @@ impl Command for External {
|
||||
|
||||
// canonicalize the path to the script so that tests pass
|
||||
let canon_path = if let Ok(cwd) = engine_state.cwd_as_string(None) {
|
||||
canonicalize_with(&expanded_name, cwd)?
|
||||
canonicalize_with(&expanded_name, cwd).map_err(|err| {
|
||||
IoError::new(err.kind(), call.head, PathBuf::from(&expanded_name))
|
||||
})?
|
||||
} else {
|
||||
// If we can't get the current working directory, just provide the expanded name
|
||||
expanded_name
|
||||
@ -191,13 +194,22 @@ impl Command for External {
|
||||
let stdout = stack.stdout();
|
||||
let stderr = stack.stderr();
|
||||
let merged_stream = if matches!(stdout, OutDest::Pipe) && matches!(stderr, OutDest::Pipe) {
|
||||
let (reader, writer) = os_pipe::pipe()?;
|
||||
command.stdout(writer.try_clone()?);
|
||||
let (reader, writer) =
|
||||
os_pipe::pipe().map_err(|err| IoError::new(err.kind(), call.head, None))?;
|
||||
command.stdout(
|
||||
writer
|
||||
.try_clone()
|
||||
.map_err(|err| IoError::new(err.kind(), call.head, None))?,
|
||||
);
|
||||
command.stderr(writer);
|
||||
Some(reader)
|
||||
} else {
|
||||
command.stdout(Stdio::try_from(stdout)?);
|
||||
command.stderr(Stdio::try_from(stderr)?);
|
||||
command.stdout(
|
||||
Stdio::try_from(stdout).map_err(|err| IoError::new(err.kind(), call.head, None))?,
|
||||
);
|
||||
command.stderr(
|
||||
Stdio::try_from(stderr).map_err(|err| IoError::new(err.kind(), call.head, None))?,
|
||||
);
|
||||
None
|
||||
};
|
||||
|
||||
@ -231,13 +243,22 @@ impl Command for External {
|
||||
// Spawn the child process. On Unix, also put the child process to
|
||||
// foreground if we're in an interactive session.
|
||||
#[cfg(windows)]
|
||||
let mut child = ForegroundChild::spawn(command)?;
|
||||
let child = ForegroundChild::spawn(command);
|
||||
#[cfg(unix)]
|
||||
let mut child = ForegroundChild::spawn(
|
||||
let child = ForegroundChild::spawn(
|
||||
command,
|
||||
engine_state.is_interactive,
|
||||
&engine_state.pipeline_externals_state,
|
||||
)?;
|
||||
);
|
||||
|
||||
let mut child = child.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
Span::unknown(),
|
||||
None,
|
||||
"Could not spawn foreground child",
|
||||
)
|
||||
})?;
|
||||
|
||||
// If we need to copy data into the child process, do it now.
|
||||
if let Some(data) = data_to_copy_into_stdin {
|
||||
@ -249,7 +270,14 @@ impl Command for External {
|
||||
.spawn(move || {
|
||||
let _ = write_pipeline_data(engine_state, stack, data, stdin);
|
||||
})
|
||||
.err_span(call.head)?;
|
||||
.map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
call.head,
|
||||
None,
|
||||
"Could not spawn external stdin worker",
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
@ -414,7 +442,14 @@ fn write_pipeline_data(
|
||||
if let PipelineData::ByteStream(stream, ..) = data {
|
||||
stream.write_to(writer)?;
|
||||
} else if let PipelineData::Value(Value::Binary { val, .. }, ..) = data {
|
||||
writer.write_all(&val)?;
|
||||
writer.write_all(&val).map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
Span::unknown(),
|
||||
None,
|
||||
"Could not write pipeline data",
|
||||
)
|
||||
})?;
|
||||
} else {
|
||||
stack.start_collect_value();
|
||||
|
||||
@ -428,7 +463,14 @@ fn write_pipeline_data(
|
||||
// Write the output.
|
||||
for value in output {
|
||||
let bytes = value.coerce_into_binary()?;
|
||||
writer.write_all(&bytes)?;
|
||||
writer.write_all(&bytes).map_err(|err| {
|
||||
IoError::new_with_additional_context(
|
||||
err.kind(),
|
||||
Span::unknown(),
|
||||
None,
|
||||
"Could not write pipeline data",
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -13,7 +13,8 @@ use nu_engine::{command_prelude::*, env_to_string};
|
||||
use nu_path::form::Absolute;
|
||||
use nu_pretty_hex::HexConfig;
|
||||
use nu_protocol::{
|
||||
ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator,
|
||||
shell_error::io::IoError, ByteStream, Config, DataSource, ListStream, PipelineMetadata,
|
||||
Signals, TableMode, ValueIterator,
|
||||
};
|
||||
use nu_table::{
|
||||
common::configure_table, CollapsedTable, ExpandedTable, JustTable, NuRecordsValue, NuTable,
|
||||
@ -518,7 +519,7 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream {
|
||||
(&mut reader)
|
||||
.take(cfg.width as u64)
|
||||
.read_to_end(&mut read_buf)
|
||||
.err_span(span)?;
|
||||
.map_err(|err| IoError::new(err.kind(), span, None))?;
|
||||
|
||||
if !read_buf.is_empty() {
|
||||
nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true))
|
||||
|
Reference in New Issue
Block a user