Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement physical and logical cwd #219

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions brush-core/src/builtins/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ use crate::{builtins, commands};
#[derive(Parser)]
pub(crate) struct CdCommand {
/// Force following symlinks.
#[arg(short = 'L')]
#[arg(short = 'L', overrides_with = "use_physical_dir")]
force_follow_symlinks: bool,

/// Use physical dir structure without following symlinks.
#[arg(short = 'P')]
#[arg(short = 'P', overrides_with = "force_follow_symlinks")]
use_physical_dir: bool,

/// Exit with non zero exit status if current working directory resolution fails.
Expand All @@ -35,16 +35,12 @@ impl builtins::Command for CdCommand {
&self,
context: commands::ExecutionContext<'_>,
) -> Result<crate::builtins::ExitCode, crate::error::Error> {
// TODO: implement options
if self.force_follow_symlinks
|| self.use_physical_dir
|| self.exit_on_failed_cwd_resolution
|| self.file_with_xattr_as_dir
{
if self.exit_on_failed_cwd_resolution || self.file_with_xattr_as_dir {
return crate::error::unimp("options to cd");
}

let mut should_print = false;

let target_dir = if let Some(target_dir) = &self.target_dir {
// `cd -', equivalent to `cd $OLDPWD'
if target_dir.as_os_str() == "-" {
Expand All @@ -69,16 +65,28 @@ impl builtins::Command for CdCommand {
}
};

if let Err(e) = context.shell.set_working_dir(&target_dir) {
// TODO: CDPATH, LCD_PRINTPATH, LCD_DOSPELL and LCD_DOVARS

let result = if self.use_physical_dir {
context.shell.set_current_working_dir(&target_dir)
// the logical dir by default
} else {
context
.shell
.set_current_working_dir_from_logical(&target_dir)
};

if let Err(e) = result {
writeln!(context.stderr(), "cd: {e}")?;
return Ok(builtins::ExitCode::Custom(1));
}

// Bash compatibility
// https://www.gnu.org/software/bash/manual/bash.html#index-cd
// If a non-empty directory name from CDPATH is used, or if '-' is the first argument, and
// the directory change is successful, the absolute pathname of the new working
// directory is written to the standard output.
// If a non-empty directory name from CDPATH is used, or if '-' is the first
// argument, and the directory change is successful, the absolute
// pathname of the new working directory is written to the standard
// output.
if should_print {
writeln!(context.stdout(), "{}", target_dir.display())?;
}
Expand Down
11 changes: 9 additions & 2 deletions brush-core/src/builtins/dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,16 @@ impl builtins::Command for DirsCommand {
if self.clear {
context.shell.directory_stack.clear();
} else {
let dirs = vec![&context.shell.working_dir]
let dirs = vec![context.shell.get_current_working_dir()]
.into_iter()
.chain(context.shell.directory_stack.iter().rev())
.chain(
context
.shell
.directory_stack
.iter()
.rev()
.map(|p| p.as_path()),
)
.collect::<Vec<_>>();

let one_per_line = self.print_one_per_line || self.print_one_per_line_with_index;
Expand Down
2 changes: 1 addition & 1 deletion brush-core/src/builtins/popd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl builtins::Command for PopdCommand {
) -> Result<crate::builtins::ExitCode, crate::error::Error> {
if let Some(popped) = context.shell.directory_stack.pop() {
if !self.no_directory_change {
context.shell.set_working_dir(&popped)?;
context.shell.set_current_working_dir(&popped)?;
}

// Display dirs.
Expand Down
4 changes: 2 additions & 2 deletions brush-core/src/builtins/pushd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ impl builtins::Command for PushdCommand {
.directory_stack
.push(std::path::PathBuf::from(&self.dir));
} else {
let prev_working_dir = context.shell.working_dir.clone();
let prev_working_dir = context.shell.get_current_working_dir().to_path_buf();

let dir = std::path::Path::new(&self.dir);
context.shell.set_working_dir(dir)?;
context.shell.set_current_working_dir(dir)?;

context.shell.directory_stack.push(prev_working_dir);
}
Expand Down
26 changes: 14 additions & 12 deletions brush-core/src/builtins/pwd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ use std::io::Write;
#[derive(Parser)]
pub(crate) struct PwdCommand {
/// Print the physical directory without any symlinks.
#[arg(short = 'P')]
#[arg(short = 'P', overrides_with = "allow_symlinks")]
physical: bool,

/// Print $PWD if it names the current working directory.
#[arg(short = 'L')]
#[arg(short = 'L', overrides_with = "physical")]
allow_symlinks: bool,
}

Expand All @@ -19,19 +19,21 @@ impl builtins::Command for PwdCommand {
&self,
context: commands::ExecutionContext<'_>,
) -> Result<crate::builtins::ExitCode, crate::error::Error> {
//
// TODO: implement flags
// TODO: look for 'physical' option in execution context
//
// POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html

if self.physical || self.allow_symlinks {
writeln!(context.stderr(), "UNIMPLEMENTED: pwd with -P or -L")?;
return Ok(builtins::ExitCode::Unimplemented);
}
// TODO: look for 'physical' option in execution context options (set -P)

let cwd = context.shell.working_dir.to_string_lossy().into_owned();
// if POSIXLY_CORRECT is set, we want to a logical resolution.
// This produces a different output when doing mkdir -p a/b && ln -s a/b c && cd c && pwd
// We should get c in this case instead of a/b at the end of the path
let cwd = if self.physical && context.shell.env.get_str("POSIXLY_CORRECT").is_none() {
context.shell.get_current_working_dir()
// -L logical by default or when POSIXLY_CORRECT is set
} else {
context.shell.get_current_logical_working_dir()
};

writeln!(context.stdout(), "{cwd}")?;
writeln!(context.stdout(), "{}", cwd.display())?;

Ok(builtins::ExitCode::Success)
}
Expand Down
2 changes: 1 addition & 1 deletion brush-core/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub(crate) fn compose_std_command<S: AsRef<OsStr>>(
}

// Use the shell's current working dir.
cmd.current_dir(shell.working_dir.as_path());
cmd.current_dir(shell.working_dir.physical());

// Start with a clear environment.
cmd.env_clear();
Expand Down
4 changes: 2 additions & 2 deletions brush-core/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ impl Spec {
.set_extended_globbing(shell.options.extended_globbing);

let expansions = pattern.expand(
shell.working_dir.as_path(),
shell.working_dir.physical(),
Some(&patterns::Pattern::accept_all_expand_filter),
)?;

Expand Down Expand Up @@ -922,7 +922,7 @@ fn get_file_completions(shell: &Shell, context: &Context, must_be_dir: bool) ->
patterns::Pattern::from(glob).set_extended_globbing(shell.options.extended_globbing);

pattern
.expand(shell.working_dir.as_path(), Some(&path_filter))
.expand(shell.working_dir.physical(), Some(&path_filter))
.unwrap_or_default()
.into_iter()
.collect()
Expand Down
2 changes: 1 addition & 1 deletion brush-core/src/expansion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ impl<'a> WordExpander<'a> {

let expansions = pattern
.expand(
self.shell.working_dir.as_path(),
self.shell.working_dir.physical(),
Some(&patterns::Pattern::accept_all_expand_filter),
)
.unwrap_or_default();
Expand Down
25 changes: 12 additions & 13 deletions brush-core/src/interp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::io::Write;
use std::os::fd::{AsFd, AsRawFd};
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::sync::Arc;

use crate::arithmetic::ExpandAndEvaluate;
Expand Down Expand Up @@ -1257,15 +1257,15 @@ pub(crate) async fn setup_redirect<'a>(
return Err(error::Error::InvalidRedirection);
}

let expanded_file_path: PathBuf =
shell.get_absolute_path(Path::new(expanded_fields.remove(0).as_str()));
let expanded_file_path = PathBuf::from(expanded_fields.remove(0));
let expanded_file_path = shell.get_absolute_path(&expanded_file_path);

let opened_file = std::fs::File::options()
.create(true)
.write(true)
.truncate(!*append)
.append(*append)
.open(expanded_file_path.as_path())
.open(&expanded_file_path)
.map_err(|err| {
error::Error::RedirectionFailure(
expanded_file_path.to_string_lossy().to_string(),
Expand Down Expand Up @@ -1331,16 +1331,15 @@ pub(crate) async fn setup_redirect<'a>(
return Err(error::Error::InvalidRedirection);
}

let expanded_file_path: PathBuf =
shell.get_absolute_path(Path::new(expanded_fields.remove(0).as_str()));
let expanded_file_path = PathBuf::from(expanded_fields.remove(0));
let expanded_file_path = shell.get_absolute_path(&expanded_file_path);

let opened_file =
options.open(expanded_file_path.as_path()).map_err(|err| {
error::Error::RedirectionFailure(
expanded_file_path.to_string_lossy().to_string(),
err,
)
})?;
let opened_file = options.open(&expanded_file_path).map_err(|err| {
error::Error::RedirectionFailure(
expanded_file_path.to_string_lossy().to_string(),
err,
)
})?;
target_file = OpenFile::File(opened_file);
}
ast::IoFileRedirectTarget::Fd(fd) => {
Expand Down
3 changes: 2 additions & 1 deletion brush-core/src/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ impl Pattern {
}

if self.multiline {
// Set option for multiline matching + set option for allowing '.' pattern to match newline.
// Set option for multiline matching + set option for allowing '.' pattern to match
// newline.
regex_str.push_str("(?ms)");
}

Expand Down
2 changes: 1 addition & 1 deletion brush-core/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub(crate) fn format_prompt_piece(
}

fn format_current_working_directory(shell: &Shell, tilde_replaced: bool, basename: bool) -> String {
let mut working_dir_str = shell.working_dir.to_string_lossy().to_string();
let mut working_dir_str = shell.working_dir.physical().to_string_lossy().to_string();

if tilde_replaced {
working_dir_str = shell.tilde_shorten(working_dir_str);
Expand Down
Loading
Loading