Skip to content

Commit

Permalink
feat: implement physical and logical cwd
Browse files Browse the repository at this point in the history
  • Loading branch information
39555 committed Oct 20, 2024
1 parent 1227650 commit 9262eb8
Show file tree
Hide file tree
Showing 16 changed files with 981 additions and 148 deletions.
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

0 comments on commit 9262eb8

Please sign in to comment.