1use crate::Result;
24use crate::common::report::report;
25use yash_env::Env;
26use yash_env::path::Path;
27use yash_env::path::PathBuf;
28use yash_env::semantics::ExitStatus;
29use yash_env::semantics::Field;
30use yash_env::source::pretty::{Footnote, FootnoteType, Report, ReportType};
31use yash_env::system::{Chdir, Errno, Fcntl, Fstat, GetCwd, Isatty, Write};
32use yash_env::variable::PWD;
33
34pub const EXIT_STATUS_SUCCESS: ExitStatus = ExitStatus(0);
36
37pub const EXIT_STATUS_STALE_PWD: ExitStatus = ExitStatus(1);
39
40pub const EXIT_STATUS_ASSIGN_ERROR: ExitStatus = ExitStatus(1);
42
43pub const EXIT_STATUS_CHDIR_ERROR: ExitStatus = ExitStatus(2);
45
46pub const EXIT_STATUS_CANNOT_CANONICALIZE: ExitStatus = ExitStatus(3);
49
50pub const EXIT_STATUS_UNSET_VARIABLE: ExitStatus = ExitStatus(4);
52
53pub const EXIT_STATUS_SYNTAX_ERROR: ExitStatus = ExitStatus(5);
55
56#[derive(Debug, Clone, Copy, Default, Eq, Hash, PartialEq)]
58#[non_exhaustive]
59pub enum Mode {
60 #[default]
62 Logical,
63
64 Physical,
66}
67
68#[derive(Debug, Clone, Default, Eq, PartialEq)]
70#[non_exhaustive]
71pub struct Command {
72 pub mode: Mode,
76
77 pub ensure_pwd: bool,
82
83 pub operand: Option<Field>,
85}
86
87pub mod assign;
88pub mod canonicalize;
89pub mod cdpath;
90pub mod chdir;
91pub mod print;
92pub mod shorten;
93pub mod syntax;
94pub mod target;
95
96fn get_pwd<S>(env: &Env<S>) -> String {
97 env.variables.get_scalar(PWD).unwrap_or_default().to_owned()
98}
99
100async fn report_pwd_error<S>(env: &mut Env<S>, errno: Errno, ensure_pwd: bool) -> Result
103where
104 S: Fcntl + Isatty + Write,
105{
106 let (r#type, exit_status) = if ensure_pwd {
107 (ReportType::Error, EXIT_STATUS_STALE_PWD)
108 } else {
109 (ReportType::Warning, EXIT_STATUS_SUCCESS)
110 };
111
112 let mut report = Report::new();
113 report.r#type = r#type;
114 report.title = "cannot compute new $PWD".into();
115 report.footnotes.push(Footnote {
116 r#type: FootnoteType::Note,
117 label: format!("error from underlying system call: {errno}").into(),
118 });
119 self::report(env, report, exit_status).await
120}
121
122pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> Result
126where
127 S: Chdir + Fcntl + Fstat + GetCwd + Isatty + Write,
128{
129 let command = match syntax::parse(env, args) {
130 Ok(command) => command,
131 Err(e) => return report(env, &e, EXIT_STATUS_SYNTAX_ERROR).await,
132 };
133
134 let pwd = get_pwd(env);
135
136 let (path, origin) = match target::target(env, &command, &pwd) {
137 Ok(target) => target,
138 Err(e) => return report(env, &e, e.exit_status()).await,
139 };
140
141 let short_path = shorten::shorten(&path, Path::new(&pwd), command.mode);
142
143 match chdir::chdir(env, short_path) {
144 Ok(()) => {}
145 Err(e) => return chdir::report_failure(env, command.operand.as_ref(), &path, &e).await,
146 }
147
148 let (new_pwd, result1) = match assign::new_pwd(env, command.mode, &path) {
149 Ok(new_pwd) => (new_pwd, Result::from(EXIT_STATUS_SUCCESS)),
150 Err(errno) => (
151 PathBuf::default(),
152 report_pwd_error(env, errno, command.ensure_pwd).await,
153 ),
154 };
155
156 print::print_path(env, &new_pwd, &origin).await;
157
158 let result2 = assign::set_oldpwd(env, pwd).await;
159 let result3 = assign::set_pwd(env, new_pwd).await;
160
161 result1.max(result2).max(result3)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use futures_util::FutureExt as _;
168 use std::rc::Rc;
169 use yash_env::VirtualSystem;
170 use yash_env::test_helper::assert_stderr;
171
172 #[test]
173 fn report_pwd_error_with_ensure_pwd() {
174 let system = VirtualSystem::new();
175 let state = Rc::clone(&system.state);
176 let mut env = Env::with_system(system);
177
178 let result = report_pwd_error(&mut env, Errno::ENAMETOOLONG, true)
179 .now_or_never()
180 .unwrap();
181
182 assert_stderr(&state, |stderr| assert!(!stderr.is_empty()));
184
185 assert_eq!(result, Result::from(ExitStatus(1)));
186 }
187
188 #[test]
189 fn report_pwd_error_without_ensure_pwd() {
190 let system = VirtualSystem::new();
191 let state = Rc::clone(&system.state);
192 let mut env = Env::with_system(system);
193
194 let result = report_pwd_error(&mut env, Errno::ENAMETOOLONG, false)
195 .now_or_never()
196 .unwrap();
197
198 assert_stderr(&state, |stderr| assert!(!stderr.is_empty()));
200
201 assert_eq!(result, Result::from(ExitStatus(0)));
202 }
203}