1use crate::Handle;
37use crate::expansion::expand_text;
38use std::fmt::Write;
39use yash_env::Env;
40use yash_env::option::OptionSet;
41use yash_env::option::State;
42use yash_env::semantics::Field;
43use yash_env::variable::PS4;
44use yash_quote::quoted;
45use yash_syntax::syntax::Text;
46
47async fn expand_ps4(env: &mut Env) -> String {
48 let value = env.variables.get_scalar(PS4).unwrap_or_default().to_owned();
49
50 let text = match value.parse::<Text>() {
51 Ok(text) => text,
52 Err(error) => {
53 _ = error.handle(env).await;
54 return value;
55 }
56 };
57
58 match expand_text(env, &text).await {
59 Ok((expansion, _exit_status)) => expansion,
60 Err(error) => {
61 _ = error.handle(env).await;
62 value
63 }
64 }
65}
66
67#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
82pub struct XTrace {
83 words: String,
84 assigns: String,
85 redirs: String,
86 here_doc_contents: String,
87}
88
89impl XTrace {
90 #[inline]
92 #[must_use]
93 pub fn new() -> Self {
94 Self::default()
95 }
96
97 #[must_use]
101 pub fn from_options(options: &OptionSet) -> Option<Self> {
102 match options.get(yash_env::option::Option::XTrace) {
103 State::On => Some(Self::new()),
104 State::Off => None,
105 }
106 }
107
108 #[inline]
113 #[must_use]
114 pub fn words(&mut self) -> &mut (impl Write + use<>) {
115 &mut self.words
116 }
117
118 #[inline]
123 #[must_use]
124 pub fn assigns(&mut self) -> &mut (impl Write + use<>) {
125 &mut self.assigns
126 }
127
128 #[inline]
136 #[must_use]
137 pub fn redirs(&mut self) -> &mut (impl Write + use<>) {
138 &mut self.redirs
139 }
140
141 #[inline]
146 #[must_use]
147 pub fn here_doc_contents(&mut self) -> &mut (impl Write + use<>) {
148 &mut self.here_doc_contents
149 }
150
151 pub fn clear(&mut self) {
153 self.words.clear();
154 self.assigns.clear();
155 self.redirs.clear();
156 self.here_doc_contents.clear();
157 }
158
159 #[must_use]
161 pub fn is_empty(&self) -> bool {
162 self.words.is_empty()
163 && self.assigns.is_empty()
164 && self.redirs.is_empty()
165 && self.here_doc_contents.is_empty()
166 }
167
168 pub async fn finish(&self, env: &mut Env) -> String {
181 let len = self.assigns.len()
182 + self.words.len()
183 + self.redirs.len()
184 + self.here_doc_contents.len();
185 if len == 0 {
186 return String::new();
187 }
188
189 let mut result = expand_ps4(env).await;
191 let ps4_len = result.len();
192 result.reserve_exact(len);
193 result += &self.assigns;
194 result += &self.words;
195 result += &self.redirs;
196 result.truncate(ps4_len + result[ps4_len..].trim_end_matches(' ').len());
197 result.push('\n');
198 result += &self.here_doc_contents;
199 result
200 }
201}
202
203pub fn trace_fields(xtrace: Option<&mut XTrace>, fields: &[Field]) {
207 if let Some(xtrace) = xtrace {
208 for field in fields {
209 write!(xtrace.words(), "{} ", quoted(&field.value)).unwrap();
210 }
211 }
212}
213
214pub async fn finish(env: &mut Env, xtrace: Option<XTrace>) -> String {
216 if let Some(xtrace) = xtrace {
217 xtrace.finish(env).await
218 } else {
219 String::new()
220 }
221}
222
223pub async fn print<X: Into<Option<XTrace>>>(env: &mut Env, xtrace: X) {
226 async fn inner(env: &mut Env, xtrace: Option<XTrace>) {
227 let s = finish(env, xtrace).await;
228 env.system.print_error(&s).await;
229 }
230 inner(env, xtrace.into()).await
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use futures_util::FutureExt;
237 use yash_env::variable::Scope::Global;
238
239 #[test]
240 fn tracing_some_fields() {
241 let mut xtrace = XTrace::new();
242 let fields = Field::dummies(["one", "two", "'three'"]);
243 trace_fields(Some(&mut xtrace), &fields);
244 assert_eq!(xtrace.words, r#"one two "'three'" "#);
245 assert_eq!(xtrace.assigns, "");
246 assert_eq!(xtrace.redirs, "");
247 assert_eq!(xtrace.here_doc_contents, "");
248 }
249
250 fn fixture() -> Env {
251 let mut env = Env::new_virtual();
252 env.variables
253 .get_or_new(PS4, Global)
254 .assign("+${X=x}+ ", None)
255 .unwrap();
256 env
257 }
258
259 #[test]
260 fn empty_finish() {
261 let mut env = fixture();
262 let result = XTrace::new().finish(&mut env).now_or_never().unwrap();
263 assert_eq!(result, "");
264
265 assert_eq!(env.variables.get("X"), None);
267 }
268
269 #[test]
270 fn finish_with_assigns() {
271 let mut env = fixture();
272
273 let mut xtrace = XTrace::new();
274 xtrace.assigns.push_str("VAR=VALUE FOO=BAR ");
275 let result = xtrace.finish(&mut env).now_or_never().unwrap();
276 assert_eq!(result, "+x+ VAR=VALUE FOO=BAR\n");
277
278 assert_ne!(env.variables.get("X"), None);
280 }
281
282 #[test]
283 fn finish_with_words() {
284 let mut env = fixture();
285
286 let mut xtrace = XTrace::new();
287 xtrace.words.push_str("abc '~' foo ");
288 let result = xtrace.finish(&mut env).now_or_never().unwrap();
289 assert_eq!(result, "+x+ abc '~' foo\n");
290
291 assert_ne!(env.variables.get("X"), None);
293 }
294
295 #[test]
296 fn finish_with_redirs() {
297 let mut env = fixture();
298
299 let mut xtrace = XTrace::new();
300 xtrace.redirs.push_str("0< /dev/null 1> foo/bar ");
301 let result = xtrace.finish(&mut env).now_or_never().unwrap();
302 assert_eq!(result, "+x+ 0< /dev/null 1> foo/bar\n");
303 }
304
305 #[test]
306 fn finish_with_assigns_and_words() {
307 let mut env = fixture();
308
309 let mut xtrace = XTrace::new();
310 xtrace.assigns.push_str("VAR=VALUE ");
311 xtrace.words.push_str("echo argument ");
312 let result = xtrace.finish(&mut env).now_or_never().unwrap();
313 assert_eq!(result, "+x+ VAR=VALUE echo argument\n");
314
315 let mut xtrace = XTrace::new();
316 xtrace.assigns.push(' ');
317 xtrace.words.push(' ');
318 let result = xtrace.finish(&mut env).now_or_never().unwrap();
319 assert_eq!(result, "+x+ \n");
320 }
321
322 #[test]
323 fn finish_with_words_and_redirs() {
324 let mut env = fixture();
325
326 let mut xtrace = XTrace::new();
327 xtrace.words.push_str("echo argument ");
328 xtrace.redirs.push_str("2> errors ");
329 let result = xtrace.finish(&mut env).now_or_never().unwrap();
330 assert_eq!(result, "+x+ echo argument 2> errors\n");
331
332 let mut xtrace = XTrace::new();
333 xtrace.words.push(' ');
334 xtrace.redirs.push(' ');
335 let result = xtrace.finish(&mut env).now_or_never().unwrap();
336 assert_eq!(result, "+x+ \n");
337 }
338
339 #[test]
340 fn finish_with_here_doc_contents() {
341 let mut env = fixture();
342
343 let mut xtrace = XTrace::new();
344 xtrace.here_doc_contents.push_str("EOF\n");
345 let result = xtrace.finish(&mut env).now_or_never().unwrap();
346 assert_eq!(result, "+x+ \nEOF\n");
347 }
348
349 #[test]
350 fn finish_with_redirs_and_here_doc_contents() {
351 let mut env = fixture();
352
353 let mut xtrace = XTrace::new();
354 xtrace.redirs.push_str("0<< END ");
355 xtrace.here_doc_contents.push_str(" X \nEND\n");
356 let result = xtrace.finish(&mut env).now_or_never().unwrap();
357 assert_eq!(result, "+x+ 0<< END\n X \nEND\n");
358 }
359}