use super::Mode;
use crate::common::report::report;
use yash_env::Env;
use yash_env::path::Path;
use yash_env::path::PathBuf;
use yash_env::source::pretty::{Report, ReportType, Snippet, Span, SpanRole};
use yash_env::system::{Errno, Fcntl, GetCwd, Isatty, Write};
use yash_env::variable::AssignError;
use yash_env::variable::OLDPWD;
use yash_env::variable::PWD;
use yash_env::variable::Scope::Global;
use yash_env::variable::Value::Scalar;
pub async fn set_oldpwd<S>(env: &mut Env<S>, value: String) -> crate::Result
where
S: Fcntl + Isatty + Write,
{
set_variable(env, OLDPWD, value).await
}
pub async fn set_pwd<S>(env: &mut Env<S>, path: PathBuf) -> crate::Result
where
S: Fcntl + Isatty + Write,
{
let value = path.into_unix_string().into_string().unwrap_or_default();
set_variable(env, PWD, value).await
}
async fn set_variable<S>(env: &mut Env<S>, name: &str, value: String) -> crate::Result
where
S: Fcntl + Isatty + Write,
{
let current_builtin = env.stack.current_builtin();
let current_location = current_builtin.map(|builtin| builtin.name.origin.clone());
let var = &mut env.get_or_create_variable(name, Global);
match var.assign(Scalar(value), current_location) {
Ok(_) => {}
Err(error) => return handle_assign_error(env, name, error).await,
}
var.export(true);
crate::Result::default()
}
async fn handle_assign_error<S>(env: &mut Env<S>, name: &str, error: AssignError) -> crate::Result
where
S: Fcntl + Isatty + Write,
{
let mut report = Report::new();
report.r#type = ReportType::Error;
report.title = format!("cannot update read-only variable `{name}`").into();
report.snippets.push(Snippet::with_code_and_spans(
&error.read_only_location.code,
vec![Span {
range: error.read_only_location.byte_range(),
role: SpanRole::Supplementary {
label: "the variable was made read-only here".into(),
},
}],
));
self::report(env, report, super::EXIT_STATUS_ASSIGN_ERROR).await
}
pub fn new_pwd<T: GetCwd>(env: &Env<T>, mode: Mode, path: &Path) -> Result<PathBuf, Errno> {
match mode {
Mode::Logical => Ok(path.to_owned()),
Mode::Physical => env.system.getcwd(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::FutureExt;
use std::rc::Rc;
use yash_env::VirtualSystem;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::source::Location;
use yash_env::stack::Builtin;
use yash_env::stack::Frame;
use yash_env::test_helper::{assert_stderr, assert_stdout};
#[test]
fn set_oldpwd_new() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let cd = Field::dummy("cd");
let location = cd.origin.clone();
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: cd,
is_special: false,
}));
let result = set_oldpwd(&mut env, "/some/path".to_string())
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::default());
let variable = env.variables.get(OLDPWD).unwrap();
assert_eq!(variable.value, Some(Scalar("/some/path".to_string())));
assert_eq!(variable.quirk, None);
assert_eq!(variable.last_assigned_location, Some(location));
assert!(variable.is_exported);
assert_eq!(variable.read_only_location, None);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
#[test]
fn set_oldpwd_overwrites_existing_variable() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let cd = Field::dummy("cd");
let location = cd.origin.clone();
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: cd,
is_special: false,
}));
env.get_or_create_variable(OLDPWD, Global)
.assign("/old/pwd", None)
.unwrap();
let result = set_oldpwd(&mut env, "/some/dir".to_string())
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::default());
let variable = env.variables.get(OLDPWD).unwrap();
assert_eq!(variable.value, Some(Scalar("/some/dir".to_string())));
assert_eq!(variable.quirk, None);
assert_eq!(variable.last_assigned_location, Some(location));
assert!(variable.is_exported);
assert_eq!(variable.read_only_location, None);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
#[test]
fn set_oldpwd_read_only_error() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("cd"),
is_special: false,
}));
let read_only_location = Location::dummy("read-only");
let mut oldpwd = env.get_or_create_variable(OLDPWD, Global);
oldpwd.assign("/old/pwd", None).unwrap();
oldpwd.make_read_only(read_only_location.clone());
let result = set_oldpwd(&mut env, "/foo".to_string())
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::from(ExitStatus(1)));
let variable = env.variables.get(OLDPWD).unwrap();
assert_eq!(variable.value, Some(Scalar("/old/pwd".to_string())));
assert_eq!(variable.last_assigned_location, None);
assert_eq!(variable.read_only_location, Some(read_only_location));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| assert_ne!(stderr, ""));
}
#[test]
fn set_pwd_new() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let cd = Field::dummy("cd");
let location = cd.origin.clone();
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: cd,
is_special: false,
}));
let result = set_pwd(&mut env, PathBuf::from("/some/path"))
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::default());
let variable = env.variables.get(PWD).unwrap();
assert_eq!(variable.value, Some(Scalar("/some/path".to_string())));
assert_eq!(variable.quirk, None);
assert_eq!(variable.last_assigned_location, Some(location));
assert!(variable.is_exported);
assert_eq!(variable.read_only_location, None);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
#[test]
fn set_pwd_overwrites_existing_variable() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let cd = Field::dummy("cd");
let location = cd.origin.clone();
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: cd,
is_special: false,
}));
env.get_or_create_variable(PWD, Global)
.assign("/old/path", None)
.unwrap();
let result = set_pwd(&mut env, PathBuf::from("/some/path"))
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::default());
let variable = env.variables.get(PWD).unwrap();
assert_eq!(variable.value, Some(Scalar("/some/path".to_string())));
assert_eq!(variable.quirk, None);
assert_eq!(variable.last_assigned_location, Some(location));
assert!(variable.is_exported);
assert_eq!(variable.read_only_location, None);
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
#[test]
fn set_pwd_read_only_error() {
let system = VirtualSystem::new();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let cd = Field::dummy("cd");
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: cd,
is_special: false,
}));
let read_only_location = Location::dummy("read-only");
let mut pwd = env.get_or_create_variable(PWD, Global);
pwd.assign("/old/path", None).unwrap();
pwd.make_read_only(read_only_location.clone());
let result = set_pwd(&mut env, PathBuf::from("/some/path"))
.now_or_never()
.unwrap();
assert_eq!(result, crate::Result::from(ExitStatus(1)));
let variable = env.variables.get(PWD).unwrap();
assert_eq!(variable.value, Some(Scalar("/old/path".to_string())));
assert_eq!(variable.last_assigned_location, None);
assert_eq!(variable.read_only_location, Some(read_only_location));
assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
assert_stderr(&state, |stderr| assert_ne!(stderr, ""));
}
#[test]
fn new_pwd_physical() {
let system = VirtualSystem::new();
system
.current_process_mut()
.chdir(PathBuf::from("/some/path"));
let env = Env::with_system(system);
let result = new_pwd(&env, Mode::Physical, Path::new("..")).unwrap();
assert_eq!(result, Path::new("/some/path"));
}
#[test]
fn new_pwd_logical() {
let system = VirtualSystem::new();
system
.current_process_mut()
.chdir(PathBuf::from("/some/path"));
let env = Env::with_system(system);
let result = new_pwd(&env, Mode::Logical, Path::new("/foo/bar")).unwrap();
assert_eq!(result, Path::new("/foo/bar"));
}
}