1use super::*;
18use std::fmt::Write;
19use yash_env::variable::{Value, VariableSet};
20
21impl PrintVariables {
22 pub fn execute(
24 self,
25 variables: &VariableSet,
26 context: &PrintContext,
27 ) -> Result<String, Vec<ExecuteError>> {
28 let mut output = String::new();
29 let mut errors = Vec::new();
30
31 if self.variables.is_empty() {
32 let mut variables = variables.iter(self.scope.into()).collect::<Vec<_>>();
33 variables.sort_unstable_by_key(|&(name, _)| name);
35 for (name, var) in variables {
36 print_one(name, var, &self.attrs, context, &mut output);
37 }
38 } else {
39 for name in self.variables {
40 match variables.get_scoped(&name.value, self.scope.into()) {
41 Some(var) => print_one(&name.value, var, &self.attrs, context, &mut output),
42 None => errors.push(ExecuteError::PrintUnsetVariable(name)),
43 }
44 }
45 }
46
47 if errors.is_empty() {
48 Ok(output)
49 } else {
50 Err(errors)
51 }
52 }
53}
54
55fn print_one(
57 name: &str,
58 var: &Variable,
59 filter_attrs: &[(VariableAttr, State)],
60 context: &PrintContext,
61 output: &mut String,
62) {
63 if name.contains('=') {
67 return;
68 }
69
70 if filter_attrs
72 .iter()
73 .any(|&(attr, state)| attr.test(var) != state)
74 {
75 return;
76 }
77
78 let options = AttributeOption {
80 var,
81 options_allowed: context.options_allowed,
82 };
83 let separator = if name.starts_with('-') { "-- " } else { "" };
84 let quoted_name = yash_quote::quoted(name);
85 match &var.value {
86 Some(value @ Value::Scalar(_)) => writeln!(
87 output,
88 "{} {}{}{}={}",
89 context.builtin_name,
90 options,
91 separator,
92 quoted_name,
93 value.quote()
94 )
95 .unwrap(),
96
97 Some(value @ Value::Array(_)) => {
98 writeln!(output, "{}={}", quoted_name, value.quote()).unwrap();
99
100 let options = options.to_string();
101 if !options.is_empty() || context.builtin_is_significant {
102 writeln!(
103 output,
104 "{} {}{}{}",
105 context.builtin_name, options, separator, quoted_name
106 )
107 .unwrap();
108 }
109 }
110
111 None => writeln!(
112 output,
113 "{} {}{}{}",
114 context.builtin_name, options, separator, quoted_name
115 )
116 .unwrap(),
117 }
118}
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123struct AttributeOption<'a> {
124 var: &'a Variable,
126 options_allowed: &'a [OptionSpec<'a>],
128}
129
130impl std::fmt::Display for AttributeOption<'_> {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 for option in self.options_allowed {
133 if let Some(attr) = option.attr {
134 if let Ok(attr) = VariableAttr::try_from(attr) {
135 if attr.test(self.var).into() {
136 write!(f, "-{} ", option.short)?;
137 }
138 }
139 }
140 }
141 Ok(())
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use yash_env::option::{Off, On};
149 use yash_env::variable::Context;
150
151 #[test]
152 fn printing_one_variable() {
153 let mut vars = VariableSet::new();
154 vars.get_or_new("foo", Scope::Global.into())
155 .assign("value", None)
156 .unwrap();
157 let pv = PrintVariables {
158 variables: Field::dummies(["foo"]),
159 attrs: vec![],
160 scope: Scope::Global,
161 };
162
163 let output = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
164 assert_eq!(output, "typeset foo=value\n")
165 }
166
167 #[test]
168 fn printing_multiple_variables() {
169 let mut vars = VariableSet::new();
170 vars.get_or_new("first", Scope::Global.into())
171 .assign("1", None)
172 .unwrap();
173 vars.get_or_new("second", Scope::Global.into())
174 .assign("2", None)
175 .unwrap();
176 vars.get_or_new("third", Scope::Global.into())
177 .assign("3", None)
178 .unwrap();
179 let pv = PrintVariables {
180 variables: Field::dummies(["first", "second", "third"]),
181 attrs: vec![],
182 scope: Scope::Global,
183 };
184
185 assert_eq!(
186 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
187 "typeset first=1\n\
188 typeset second=2\n\
189 typeset third=3\n",
190 );
191 }
192
193 #[test]
194 fn printing_array_variable() {
195 let mut vars = VariableSet::new();
196 vars.get_or_new("a", Scope::Global.into())
197 .assign(Value::array(["1", "2 2", "3"]), None)
198 .unwrap();
199 let pv = PrintVariables {
200 variables: Field::dummies(["a"]),
201 attrs: vec![],
202 scope: Scope::Global,
203 };
204
205 let result = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
206 assert_eq!(result, "a=(1 '2 2' 3)\n");
207 }
208
209 #[test]
210 fn printing_valueless_variable() {
211 let mut vars = VariableSet::new();
212 vars.get_or_new("x", Scope::Global.into());
213 let pv = PrintVariables {
214 variables: Field::dummies(["x"]),
215 attrs: vec![],
216 scope: Scope::Global,
217 };
218
219 let result = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
220 assert_eq!(result, "typeset x\n");
221 }
222
223 #[test]
224 fn quoting_variable_names_and_values() {
225 let mut vars = VariableSet::new();
226 vars.get_or_new("valueless$", Scope::Global.into());
227 vars.get_or_new("scalar$", Scope::Global.into())
228 .assign("=;", None)
229 .unwrap();
230 vars.get_or_new("array$", Scope::Global.into())
231 .assign(Value::array(["~", "'", "*?"]), None)
232 .unwrap();
233 let pv = PrintVariables {
234 variables: Field::dummies(["valueless$", "scalar$", "array$"]),
235 attrs: vec![],
236 scope: Scope::Global,
237 };
238
239 assert_eq!(
240 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
241 "typeset 'valueless$'\n\
242 typeset 'scalar$'='=;'\n\
243 'array$'=('~' \"'\" '*?')\n",
244 );
245 }
246
247 #[test]
248 fn printing_global_and_local_variables_at_once() {
249 let mut outer = VariableSet::new();
250 outer
251 .get_or_new("global", Scope::Global.into())
252 .assign("global value", None)
253 .unwrap();
254 let mut inner = outer.push_context(Context::default());
255 inner
256 .get_or_new("local", Scope::Local.into())
257 .assign("local value", None)
258 .unwrap();
259 let pv = PrintVariables {
260 variables: Field::dummies(["global", "local"]),
261 attrs: vec![],
262 scope: Scope::Global,
263 };
264
265 assert_eq!(
266 pv.execute(&inner, &PRINT_CONTEXT).unwrap(),
267 "typeset global='global value'\n\
268 typeset local='local value'\n",
269 );
270 }
271
272 #[test]
273 fn printing_local_variables_only() {
274 let mut outer = VariableSet::new();
275 outer
276 .get_or_new("global", Scope::Global.into())
277 .assign("global value", None)
278 .unwrap();
279 let mut inner = outer.push_context(Context::default());
280 inner
281 .get_or_new("local", Scope::Local.into())
282 .assign("local value", None)
283 .unwrap();
284
285 let pv = PrintVariables {
286 variables: Field::dummies(["local"]),
287 attrs: vec![],
288 scope: Scope::Local,
289 };
290 let output = pv.execute(&inner, &PRINT_CONTEXT).unwrap();
291 assert_eq!(output, "typeset local='local value'\n");
292
293 let pv = PrintVariables {
294 variables: Field::dummies(["global"]),
295 attrs: vec![],
296 scope: Scope::Local,
297 };
298 assert_eq!(
299 pv.execute(&inner, &PRINT_CONTEXT).unwrap_err(),
300 [ExecuteError::PrintUnsetVariable(Field::dummy("global"))]
301 );
302 }
303
304 #[test]
305 fn printing_all_global_and_local_variables() {
306 let mut outer = VariableSet::new();
307 outer
308 .get_or_new("one", Scope::Global.into())
309 .assign("1", None)
310 .unwrap();
311 let mut inner = outer.push_context(Context::default());
312 inner
313 .get_or_new("two", Scope::Local.into())
314 .assign("2", None)
315 .unwrap();
316 inner
317 .get_or_new("three", Scope::Local.into())
318 .assign("3", None)
319 .unwrap();
320 let pv = PrintVariables {
321 variables: vec![],
322 attrs: vec![],
323 scope: Scope::Global,
324 };
325
326 assert_eq!(
327 pv.execute(&inner, &PRINT_CONTEXT).unwrap(),
328 "typeset one=1\n\
330 typeset three=3\n\
331 typeset two=2\n",
332 );
333 }
334
335 #[test]
336 fn printing_all_local_variables() {
337 let mut outer = VariableSet::new();
338 outer
339 .get_or_new("one", Scope::Global.into())
340 .assign("1", None)
341 .unwrap();
342 let mut inner = outer.push_context(Context::default());
343 inner
344 .get_or_new("two", Scope::Local.into())
345 .assign("2", None)
346 .unwrap();
347 inner
348 .get_or_new("three", Scope::Local.into())
349 .assign("3", None)
350 .unwrap();
351 let pv = PrintVariables {
352 variables: vec![],
353 attrs: vec![],
354 scope: Scope::Local,
355 };
356
357 assert_eq!(
358 pv.execute(&inner, &PRINT_CONTEXT).unwrap(),
359 "typeset three=3\n\
361 typeset two=2\n",
362 );
363 }
364
365 #[test]
366 fn printing_attributes_of_valueless_variables() {
367 let mut vars = VariableSet::new();
368 let mut x = vars.get_or_new("x", Scope::Global.into());
369 x.export(true);
370 let mut y = vars.get_or_new("y", Scope::Global.into());
371 y.make_read_only(Location::dummy("y location"));
372 let mut z = vars.get_or_new("z", Scope::Global.into());
373 z.export(true);
374 z.make_read_only(Location::dummy("z location"));
375 let pv = PrintVariables {
376 variables: Field::dummies(["x", "y", "z"]),
377 attrs: vec![],
378 scope: Scope::Global,
379 };
380
381 assert_eq!(
382 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
383 "typeset -x x\n\
384 typeset -r y\n\
385 typeset -r -x z\n",
386 );
387 }
388
389 #[test]
390 fn printing_attributes_of_scalar_variables() {
391 let mut vars = VariableSet::new();
392 let mut x = vars.get_or_new("x", Scope::Global.into());
393 x.assign("X", None).unwrap();
394 x.export(true);
395 let mut y = vars.get_or_new("y", Scope::Global.into());
396 y.assign("Y", None).unwrap();
397 y.make_read_only(Location::dummy("y location"));
398 let mut z = vars.get_or_new("z", Scope::Global.into());
399 z.assign("Z", None).unwrap();
400 z.export(true);
401 z.make_read_only(Location::dummy("z location"));
402 let pv = PrintVariables {
403 variables: Field::dummies(["x", "y", "z"]),
404 attrs: vec![],
405 scope: Scope::Global,
406 };
407
408 assert_eq!(
409 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
410 "typeset -x x=X\n\
411 typeset -r y=Y\n\
412 typeset -r -x z=Z\n",
413 );
414 }
415
416 #[test]
417 fn printing_attributes_of_array_variables() {
418 let mut vars = VariableSet::new();
419 let mut x = vars.get_or_new("x", Scope::Global.into());
420 x.assign(Value::array(["X"]), None).unwrap();
421 x.export(true);
422 let mut y = vars.get_or_new("y", Scope::Global.into());
423 y.assign(Value::array(["Y"]), None).unwrap();
424 y.make_read_only(Location::dummy("y location"));
425 let mut z = vars.get_or_new("z", Scope::Global.into());
426 z.assign(Value::array(["Z"]), None).unwrap();
427 z.export(true);
428 z.make_read_only(Location::dummy("z location"));
429 let pv = PrintVariables {
430 variables: Field::dummies(["x", "y", "z"]),
431 attrs: vec![],
432 scope: Scope::Global,
433 };
434
435 assert_eq!(
436 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
437 "x=(X)\n\
438 typeset -x x\n\
439 y=(Y)\n\
440 typeset -r y\n\
441 z=(Z)\n\
442 typeset -r -x z\n",
443 );
444 }
445
446 #[test]
447 fn printing_valueless_variable_name_starting_with_hyphen() {
448 let mut vars = VariableSet::new();
449 let mut var = vars.get_or_new("-v", Scope::Global.into());
450 var.export(true);
451 let pv = PrintVariables {
452 variables: Field::dummies(["-v"]),
453 attrs: vec![],
454 scope: Scope::Global,
455 };
456
457 let output = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
458 assert_eq!(output, "typeset -x -- -v\n");
459 }
460
461 #[test]
462 fn printing_scalar_variable_name_starting_with_hyphen() {
463 let mut vars = VariableSet::new();
464 let mut var = vars.get_or_new("-v", Scope::Global.into());
465 var.assign("value", None).unwrap();
466 var.export(true);
467 let pv = PrintVariables {
468 variables: Field::dummies(["-v"]),
469 attrs: vec![],
470 scope: Scope::Global,
471 };
472
473 let output = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
474 assert_eq!(output, "typeset -x -- -v=value\n");
475 }
476
477 #[test]
478 fn printing_array_variable_name_starting_with_hyphen() {
479 let mut vars = VariableSet::new();
480 let mut var = vars.get_or_new("-v", Scope::Global.into());
481 var.assign(Value::array(["1", "2"]), None).unwrap();
482 var.export(true);
483 let pv = PrintVariables {
484 variables: Field::dummies(["-v"]),
485 attrs: vec![],
486 scope: Scope::Global,
487 };
488
489 let output = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
490 assert_eq!(output, "-v=(1 2)\ntypeset -x -- -v\n");
491 }
492
493 fn variables_with_different_attributes() -> VariableSet {
494 let mut vars = VariableSet::new();
495 let mut a = vars.get_or_new("a", Scope::Global.into());
496 a.export(true);
497 let mut b = vars.get_or_new("b", Scope::Global.into());
498 b.make_read_only(Location::dummy("b location"));
499 let mut c = vars.get_or_new("c", Scope::Global.into());
500 c.export(true);
501 c.make_read_only(Location::dummy("c location"));
502 vars.get_or_new("d", Scope::Global.into());
503 vars
504 }
505
506 #[test]
507 fn selecting_readonly_variables() {
508 let vars = variables_with_different_attributes();
509 let pv = PrintVariables {
510 variables: vec![],
511 attrs: vec![(VariableAttr::ReadOnly, On)],
512 scope: Scope::Global,
513 };
514
515 assert_eq!(
516 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
517 "typeset -r b\n\
518 typeset -r -x c\n",
519 );
520 }
521
522 #[test]
523 fn selecting_non_readonly_variables() {
524 let vars = variables_with_different_attributes();
525 let pv = PrintVariables {
526 variables: vec![],
527 attrs: vec![(VariableAttr::ReadOnly, Off)],
528 scope: Scope::Global,
529 };
530
531 assert_eq!(
532 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
533 "typeset -x a\n\
534 typeset d\n",
535 );
536 }
537
538 #[test]
539 fn selecting_exported_variables() {
540 let vars = variables_with_different_attributes();
541 let pv = PrintVariables {
542 variables: vec![],
543 attrs: vec![(VariableAttr::Export, On)],
544 scope: Scope::Global,
545 };
546
547 assert_eq!(
548 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
549 "typeset -x a\n\
550 typeset -r -x c\n",
551 );
552 }
553
554 #[test]
555 fn selecting_non_exported_variables() {
556 let vars = variables_with_different_attributes();
557 let pv = PrintVariables {
558 variables: vec![],
559 attrs: vec![(VariableAttr::Export, Off)],
560 scope: Scope::Global,
561 };
562
563 assert_eq!(
564 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
565 "typeset -r b\n\
566 typeset d\n",
567 );
568 }
569
570 #[test]
571 fn selecting_with_multiple_filtering_attributes() {
572 let vars = variables_with_different_attributes();
573 let pv = PrintVariables {
574 variables: vec![],
575 attrs: vec![(VariableAttr::ReadOnly, On), (VariableAttr::Export, Off)],
576 scope: Scope::Global,
577 };
578
579 let result = pv.execute(&vars, &PRINT_CONTEXT).unwrap();
580 assert_eq!(result, "typeset -r b\n");
581 }
582
583 #[test]
584 fn variable_not_found() {
585 let foo = Field::dummy("foo");
586 let bar = Field::dummy("bar");
587 let pv = PrintVariables {
588 variables: vec![foo.clone(), bar.clone()],
589 attrs: vec![],
590 scope: Scope::Global,
591 };
592
593 assert_eq!(
594 pv.execute(&VariableSet::new(), &PRINT_CONTEXT).unwrap_err(),
595 [
596 ExecuteError::PrintUnsetVariable(foo),
597 ExecuteError::PrintUnsetVariable(bar)
598 ]
599 );
600 }
601
602 #[test]
603 fn variable_name_containing_equal_is_ignored() {
604 let mut vars = VariableSet::new();
605 vars.get_or_new("first", Scope::Global.into())
606 .assign("1", None)
607 .unwrap();
608 vars.get_or_new("second=!", Scope::Global.into())
609 .assign("2", None)
610 .unwrap();
611 vars.get_or_new("third", Scope::Global.into())
612 .assign("3", None)
613 .unwrap();
614 let pv = PrintVariables {
615 variables: Field::dummies(["first", "second=!", "third"]),
616 attrs: vec![],
617 scope: Scope::Global,
618 };
619
620 assert_eq!(
621 pv.execute(&vars, &PRINT_CONTEXT).unwrap(),
622 "typeset first=1\n\
623 typeset third=3\n",
624 );
625 }
626
627 mod non_default_context {
628 use super::*;
629 use crate::typeset::syntax::{EXPORT_OPTION, READONLY_OPTION};
630
631 #[test]
632 fn builtin_name() {
633 let mut vars = VariableSet::new();
634 vars.get_or_new("foo", Scope::Global.into())
635 .assign("value", None)
636 .unwrap();
637 let bar = &mut vars.get_or_new("bar", Scope::Global.into());
638 bar.assign(Value::array(["1", "2"]), None).unwrap();
639 bar.make_read_only(Location::dummy("bar location"));
640 vars.get_or_new("baz", Scope::Global.into());
641 let pv = PrintVariables {
642 variables: Field::dummies(["foo", "bar", "baz"]),
643 attrs: vec![],
644 scope: Scope::Global,
645 };
646 let context = PrintContext {
647 builtin_name: "export",
648 ..PRINT_CONTEXT
649 };
650
651 assert_eq!(
652 pv.execute(&vars, &context).unwrap(),
653 "export foo=value\n\
654 bar=(1 2)\n\
655 export -r bar\n\
656 export baz\n"
657 );
658 }
659
660 #[test]
661 fn builtin_is_significant() {
662 let mut vars = VariableSet::new();
663 vars.get_or_new("a", Scope::Global.into())
664 .assign(Value::array(["foo", "bar"]), None)
665 .unwrap();
666 let pv = PrintVariables {
667 variables: Field::dummies(["a"]),
668 attrs: vec![],
669 scope: Scope::Global,
670 };
671
672 let context = PrintContext {
673 builtin_is_significant: false,
674 ..PRINT_CONTEXT
675 };
676 assert_eq!(
677 pv.clone().execute(&vars, &context).unwrap(),
678 "a=(foo bar)\n"
679 );
680
681 let context = PrintContext {
682 builtin_is_significant: true,
683 ..PRINT_CONTEXT
684 };
685 assert_eq!(
686 pv.clone().execute(&vars, &context).unwrap(),
687 "a=(foo bar)\ntypeset a\n"
688 );
689 }
690
691 #[test]
692 fn insignificant_builtin_with_attributed_array() {
693 let mut vars = VariableSet::new();
694 let a = &mut vars.get_or_new("a", Scope::Global.into());
695 a.assign(Value::array(["foo", "bar"]), None).unwrap();
696 a.make_read_only(Location::dummy("a location"));
697 let pv = PrintVariables {
698 variables: Field::dummies(["a"]),
699 attrs: vec![],
700 scope: Scope::Global,
701 };
702
703 let context = PrintContext {
704 builtin_is_significant: false,
705 ..PRINT_CONTEXT
706 };
707 assert_eq!(
708 pv.clone().execute(&vars, &context).unwrap(),
709 "a=(foo bar)\ntypeset -r a\n"
710 );
711 }
712
713 #[test]
714 fn options_allowed() {
715 let mut vars = VariableSet::new();
716 let mut a = vars.get_or_new("a", Scope::Global.into());
717 a.assign("A", None).unwrap();
718 let mut b = vars.get_or_new("b", Scope::Global.into());
719 b.assign("B", None).unwrap();
720 b.export(true);
721 let mut c = vars.get_or_new("c", Scope::Global.into());
722 c.assign("C", None).unwrap();
723 c.make_read_only(Location::dummy("c location"));
724 let mut d = vars.get_or_new("d", Scope::Global.into());
725 d.assign("D", None).unwrap();
726 d.export(true);
727 d.make_read_only(Location::dummy("d location"));
728 let pv = PrintVariables {
729 variables: vec![],
730 attrs: vec![],
731 scope: Scope::Global,
732 };
733
734 let context = PrintContext {
735 options_allowed: &[READONLY_OPTION],
736 ..PRINT_CONTEXT
737 };
738 assert_eq!(
739 pv.clone().execute(&vars, &context).unwrap(),
740 "typeset a=A\n\
741 typeset b=B\n\
742 typeset -r c=C\n\
743 typeset -r d=D\n",
744 );
745
746 let context = PrintContext {
747 options_allowed: &[EXPORT_OPTION],
748 ..PRINT_CONTEXT
749 };
750 assert_eq!(
751 pv.clone().execute(&vars, &context).unwrap(),
752 "typeset a=A\n\
753 typeset -x b=B\n\
754 typeset c=C\n\
755 typeset -x d=D\n",
756 );
757
758 let context = PrintContext {
759 options_allowed: &[],
760 ..PRINT_CONTEXT
761 };
762 assert_eq!(
763 pv.execute(&vars, &context).unwrap(),
764 "typeset a=A\n\
765 typeset b=B\n\
766 typeset c=C\n\
767 typeset d=D\n",
768 );
769 }
770 }
771}