mirror of
https://github.com/nushell/nushell.git
synced 2025-05-05 23:42:56 +00:00
fixes #8095
# Description
This approach is a bit straightforward, call access() check with the
flag `X_OK`.
Zsh[^1], Fish perform this check by the same approach.
[^1]:
435cb1b748/Src/exec.c (L6406)
It could also avoid manual xattrs check on other *nix platforms.
BTW, the execution bit for directories in *nix world means permission to
access it's content,
while the read bit means to list it's content. [^0]
[^0]: https://superuser.com/a/169418
# User-Facing Changes
Users could face less permission check bugs in their `cd` usage.
# 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
> ```
-->
# 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.
-->
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
171 lines
6.4 KiB
Rust
171 lines
6.4 KiB
Rust
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)]
|
|
pub struct Cd;
|
|
|
|
impl Command for Cd {
|
|
fn name(&self) -> &str {
|
|
"cd"
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Change directory."
|
|
}
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
vec!["change", "directory", "dir", "folder", "switch"]
|
|
}
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
Signature::build("cd")
|
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
|
.switch("physical", "use the physical directory structure; resolve symbolic links before processing instances of ..", Some('P'))
|
|
.optional("path", SyntaxShape::Directory, "The path to change to.")
|
|
.allow_variants_without_examples(true)
|
|
.category(Category::FileSystem)
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
_input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let physical = call.has_flag(engine_state, stack, "physical")?;
|
|
let path_val: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
|
|
|
// If getting PWD failed, default to the home directory. The user can
|
|
// use `cd` to reset PWD to a good state.
|
|
let cwd = engine_state
|
|
.cwd(Some(stack))
|
|
.ok()
|
|
.or_else(nu_path::home_dir)
|
|
.map(|path| path.into_std_path_buf())
|
|
.unwrap_or_default();
|
|
|
|
let path_val = {
|
|
if let Some(path) = path_val {
|
|
Some(Spanned {
|
|
item: nu_utils::strip_ansi_string_unlikely(path.item),
|
|
span: path.span,
|
|
})
|
|
} else {
|
|
path_val
|
|
}
|
|
};
|
|
|
|
let path = match path_val {
|
|
Some(v) => {
|
|
if v.item == "-" {
|
|
if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
|
|
oldpwd.to_path()?
|
|
} else {
|
|
cwd
|
|
}
|
|
} else {
|
|
// Trim whitespace from the end of path.
|
|
let path_no_whitespace =
|
|
&v.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
|
|
|
// If `--physical` is specified, canonicalize the path; otherwise expand the path.
|
|
if physical {
|
|
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
|
if !path.is_dir() {
|
|
return Err(shell_error::io::IoError::new(
|
|
shell_error::io::ErrorKind::Std(
|
|
std::io::ErrorKind::NotADirectory,
|
|
),
|
|
v.span,
|
|
None,
|
|
)
|
|
.into());
|
|
};
|
|
path
|
|
} else {
|
|
return Err(shell_error::io::IoError::new(
|
|
ErrorKind::DirectoryNotFound,
|
|
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(shell_error::io::IoError::new(
|
|
ErrorKind::DirectoryNotFound,
|
|
v.span,
|
|
PathBuf::from(path_no_whitespace),
|
|
)
|
|
.into());
|
|
};
|
|
if !path.is_dir() {
|
|
return Err(shell_error::io::IoError::new(
|
|
shell_error::io::ErrorKind::Std(std::io::ErrorKind::NotADirectory),
|
|
v.span,
|
|
path,
|
|
)
|
|
.into());
|
|
};
|
|
path
|
|
}
|
|
}
|
|
}
|
|
None => nu_path::expand_tilde("~"),
|
|
};
|
|
|
|
// Set OLDPWD.
|
|
// We're using `Stack::get_env_var()` instead of `EngineState::cwd()` to avoid a conversion roundtrip.
|
|
if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
|
|
stack.add_env_var("OLDPWD".into(), oldpwd.clone())
|
|
}
|
|
|
|
match have_permission(&path) {
|
|
//FIXME: this only changes the current scope, but instead this environment variable
|
|
//should probably be a block that loads the information from the state in the overlay
|
|
PermissionResult::PermissionOk => {
|
|
stack.set_cwd(path)?;
|
|
Ok(PipelineData::empty())
|
|
}
|
|
PermissionResult::PermissionDenied => {
|
|
Err(IoError::new(std::io::ErrorKind::PermissionDenied, call.head, path).into())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![
|
|
Example {
|
|
description: "Change to your home directory",
|
|
example: r#"cd ~"#,
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: r#"Change to the previous working directory (same as "cd $env.OLDPWD")"#,
|
|
example: r#"cd -"#,
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Changing directory with a custom command requires 'def --env'",
|
|
example: r#"def --env gohome [] { cd ~ }"#,
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Move two directories up in the tree (the parent directory's parent). Additional dots can be added for additional levels.",
|
|
example: r#"cd ..."#,
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "The cd command itself is often optional. Simply entering a path to a directory will cd to it.",
|
|
example: r#"/home"#,
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
}
|