use crate::Result;
use crate::common::report::report;
use yash_env::Env;
use yash_env::path::Path;
use yash_env::path::PathBuf;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::source::pretty::{Footnote, FootnoteType, Report, ReportType};
use yash_env::system::Errno;
use yash_env::variable::PWD;
pub const EXIT_STATUS_SUCCESS: ExitStatus = ExitStatus(0);
pub const EXIT_STATUS_STALE_PWD: ExitStatus = ExitStatus(1);
pub const EXIT_STATUS_ASSIGN_ERROR: ExitStatus = ExitStatus(1);
pub const EXIT_STATUS_CHDIR_ERROR: ExitStatus = ExitStatus(2);
pub const EXIT_STATUS_CANNOT_CANONICALIZE: ExitStatus = ExitStatus(3);
pub const EXIT_STATUS_UNSET_VARIABLE: ExitStatus = ExitStatus(4);
pub const EXIT_STATUS_SYNTAX_ERROR: ExitStatus = ExitStatus(5);
#[derive(Debug, Clone, Copy, Default, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Mode {
#[default]
Logical,
Physical,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct Command {
pub mode: Mode,
pub ensure_pwd: bool,
pub operand: Option<Field>,
}
pub mod assign;
pub mod canonicalize;
pub mod cdpath;
pub mod chdir;
pub mod print;
pub mod shorten;
pub mod syntax;
pub mod target;
fn get_pwd(env: &Env) -> String {
env.variables.get_scalar(PWD).unwrap_or_default().to_owned()
}
async fn report_pwd_error(env: &mut Env, errno: Errno, ensure_pwd: bool) -> Result {
let (r#type, exit_status) = if ensure_pwd {
(ReportType::Error, EXIT_STATUS_STALE_PWD)
} else {
(ReportType::Warning, EXIT_STATUS_SUCCESS)
};
let mut report = Report::new();
report.r#type = r#type;
report.title = "cannot compute new $PWD".into();
report.footnotes.push(Footnote {
r#type: FootnoteType::Note,
label: format!("error from underlying system call: {errno}").into(),
});
self::report(env, report, exit_status).await
}
pub async fn main(env: &mut Env, args: Vec<Field>) -> Result {
let command = match syntax::parse(env, args) {
Ok(command) => command,
Err(e) => return report(env, &e, EXIT_STATUS_SYNTAX_ERROR).await,
};
let pwd = get_pwd(env);
let (path, origin) = match target::target(env, &command, &pwd) {
Ok(target) => target,
Err(e) => return report(env, &e, e.exit_status()).await,
};
let short_path = shorten::shorten(&path, Path::new(&pwd), command.mode);
match chdir::chdir(env, short_path) {
Ok(()) => {}
Err(e) => return chdir::report_failure(env, command.operand.as_ref(), &path, &e).await,
}
let (new_pwd, result1) = match assign::new_pwd(env, command.mode, &path) {
Ok(new_pwd) => (new_pwd, Result::from(EXIT_STATUS_SUCCESS)),
Err(errno) => (
PathBuf::default(),
report_pwd_error(env, errno, command.ensure_pwd).await,
),
};
print::print_path(env, &new_pwd, &origin).await;
let result2 = assign::set_oldpwd(env, pwd).await;
let result3 = assign::set_pwd(env, new_pwd).await;
result1.max(result2).max(result3)
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::FutureExt as _;
use std::rc::Rc;
use yash_env::VirtualSystem;
use yash_env_test_helper::assert_stderr;
#[test]
fn report_pwd_error_with_ensure_pwd() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let result = report_pwd_error(&mut env, Errno::ENAMETOOLONG, true)
.now_or_never()
.unwrap();
assert_stderr(&state, |stderr| assert!(!stderr.is_empty()));
assert_eq!(result, Result::from(ExitStatus(1)));
}
#[test]
fn report_pwd_error_without_ensure_pwd() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let result = report_pwd_error(&mut env, Errno::ENAMETOOLONG, false)
.now_or_never()
.unwrap();
assert_stderr(&state, |stderr| assert!(!stderr.is_empty()));
assert_eq!(result, Result::from(ExitStatus(0)));
}
}