1use crate::{
2 HasSpan, Parser, Span,
3 attributes::Attrlist,
4 blocks::{ContentModel, IsBlock},
5 content::{Content, SubstitutionGroup},
6 span::MatchedItem,
7 strings::CowStr,
8};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct Attribute<'src> {
33 name: Span<'src>,
34 value_source: Option<Span<'src>>,
35 value: InterpretedValue,
36 source: Span<'src>,
37}
38
39impl<'src> Attribute<'src> {
40 pub(crate) fn parse(source: Span<'src>, parser: &Parser) -> Option<MatchedItem<'src, Self>> {
41 let attr_line = source.take_line_with_continuation()?;
42 let colon = attr_line.item.take_prefix(":")?;
43
44 let mut unset = false;
45 let line = if colon.after.starts_with('!') {
46 unset = true;
47 colon.after.slice_from(1..)
48 } else {
49 colon.after
50 };
51
52 let name = line.take_user_attr_name()?;
53
54 let line = if name.after.starts_with('!') && !unset {
55 unset = true;
56 name.after.slice_from(1..)
57 } else {
58 name.after
59 };
60
61 let line = line.take_prefix(":")?;
62
63 let (value, value_source) = if unset {
64 (InterpretedValue::Unset, None)
66 } else if line.after.is_empty() {
67 (InterpretedValue::Set, None)
68 } else {
69 let raw_value = line.after.take_whitespace();
70 (
71 InterpretedValue::from_raw_value(&raw_value.after, parser),
72 Some(raw_value.after),
73 )
74 };
75
76 let source = source.trim_remainder(attr_line.after);
77 Some(MatchedItem {
78 item: Self {
79 name: name.item,
80 value_source,
81 value,
82 source: source.trim_trailing_whitespace(),
83 },
84 after: attr_line.after,
85 })
86 }
87
88 pub fn name(&'src self) -> &'src Span<'src> {
90 &self.name
91 }
92
93 pub fn raw_value(&'src self) -> Option<Span<'src>> {
95 self.value_source
96 }
97
98 pub fn value(&'src self) -> &'src InterpretedValue {
100 &self.value
101 }
102}
103
104impl<'src> HasSpan<'src> for Attribute<'src> {
105 fn span(&self) -> Span<'src> {
106 self.source
107 }
108}
109
110impl<'src> IsBlock<'src> for Attribute<'src> {
111 fn content_model(&self) -> ContentModel {
112 ContentModel::Empty
113 }
114
115 fn raw_context(&self) -> CowStr<'src> {
116 "attribute".into()
117 }
118
119 fn title_source(&'src self) -> Option<Span<'src>> {
120 None
121 }
122
123 fn title(&self) -> Option<&str> {
124 None
125 }
126
127 fn anchor(&'src self) -> Option<Span<'src>> {
128 None
129 }
130
131 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
132 None
133 }
134
135 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
136 None
137 }
138}
139
140#[derive(Clone, Eq, PartialEq)]
146pub enum InterpretedValue {
147 Value(String),
149
150 Set,
153
154 Unset,
156}
157
158impl std::fmt::Debug for InterpretedValue {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 match self {
161 InterpretedValue::Value(value) => f
162 .debug_tuple("InterpretedValue::Value")
163 .field(value)
164 .finish(),
165
166 InterpretedValue::Set => write!(f, "InterpretedValue::Set"),
167 InterpretedValue::Unset => write!(f, "InterpretedValue::Unset"),
168 }
169 }
170}
171
172impl InterpretedValue {
173 fn from_raw_value(raw_value: &Span<'_>, parser: &Parser) -> Self {
174 let data = raw_value.data();
175 let mut content = Content::from(*raw_value);
176
177 if data.contains('\n') {
178 let lines: Vec<&str> = data.lines().collect();
179 let last_count = lines.len() - 1;
180
181 let value: Vec<String> = lines
182 .iter()
183 .enumerate()
184 .map(|(count, line)| {
185 let line = if count > 0 {
186 line.trim_start_matches(' ')
187 } else {
188 line
189 };
190
191 let line = line.trim_start_matches('\r').trim_end_matches(' ');
192
193 let line = if count < last_count {
198 line.trim_end_matches('\\').trim_end_matches(' ')
199 } else {
200 line
201 };
202
203 if line.ends_with(" +") {
211 format!("{line}\n")
212 } else if count < last_count {
213 format!("{line} ")
214 } else {
215 line.to_string()
216 }
217 })
218 .collect();
219
220 content.rendered = CowStr::Boxed(value.join("").into_boxed_str());
221 } else if let Some(stripped) = data.strip_suffix(" +") {
222 content.rendered = stripped
234 .trim_end_matches([' ', '\t', '\n', '\r', '\x0C', '\x0B'])
235 .into();
236 }
237
238 SubstitutionGroup::Header.apply(&mut content, parser, None);
239
240 InterpretedValue::Value(content.rendered.into_string())
241 }
242
243 pub(crate) fn as_maybe_str(&self) -> Option<&str> {
244 match self {
245 InterpretedValue::Value(value) => Some(value.as_ref()),
246 _ => None,
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 #![allow(clippy::panic)]
254 #![allow(clippy::unwrap_used)]
255
256 use std::ops::Deref;
257
258 use crate::{blocks::ContentModel, tests::prelude::*};
259
260 #[test]
261 fn impl_clone() {
262 let h1 =
264 crate::document::Attribute::parse(crate::Span::new(":foo: bar"), &Parser::default())
265 .unwrap();
266 let h2 = h1.clone();
267 assert_eq!(h1, h2);
268 }
269
270 #[test]
271 fn simple_value() {
272 let mi = crate::document::Attribute::parse(
273 crate::Span::new(":foo: bar\nblah"),
274 &Parser::default(),
275 )
276 .unwrap();
277
278 assert_eq!(
279 mi.item,
280 Attribute {
281 name: Span {
282 data: "foo",
283 line: 1,
284 col: 2,
285 offset: 1,
286 },
287 value_source: Some(Span {
288 data: "bar",
289 line: 1,
290 col: 7,
291 offset: 6,
292 }),
293 value: InterpretedValue::Value("bar"),
294 source: Span {
295 data: ":foo: bar",
296 line: 1,
297 col: 1,
298 offset: 0,
299 }
300 }
301 );
302
303 assert_eq!(mi.item.value(), InterpretedValue::Value("bar"));
304
305 assert_eq!(
306 mi.after,
307 Span {
308 data: "blah",
309 line: 2,
310 col: 1,
311 offset: 10
312 }
313 );
314 }
315
316 #[test]
317 fn no_value() {
318 let mi =
319 crate::document::Attribute::parse(crate::Span::new(":foo:\nblah"), &Parser::default())
320 .unwrap();
321
322 assert_eq!(
323 mi.item,
324 Attribute {
325 name: Span {
326 data: "foo",
327 line: 1,
328 col: 2,
329 offset: 1,
330 },
331 value_source: None,
332 value: InterpretedValue::Set,
333 source: Span {
334 data: ":foo:",
335 line: 1,
336 col: 1,
337 offset: 0,
338 }
339 }
340 );
341
342 assert_eq!(mi.item.value(), InterpretedValue::Set);
343
344 assert_eq!(
345 mi.after,
346 Span {
347 data: "blah",
348 line: 2,
349 col: 1,
350 offset: 6
351 }
352 );
353 }
354
355 #[test]
356 fn name_with_hyphens() {
357 let mi = crate::document::Attribute::parse(
358 crate::Span::new(":name-with-hyphen:"),
359 &Parser::default(),
360 )
361 .unwrap();
362
363 assert_eq!(
364 mi.item,
365 Attribute {
366 name: Span {
367 data: "name-with-hyphen",
368 line: 1,
369 col: 2,
370 offset: 1,
371 },
372 value_source: None,
373 value: InterpretedValue::Set,
374 source: Span {
375 data: ":name-with-hyphen:",
376 line: 1,
377 col: 1,
378 offset: 0,
379 }
380 }
381 );
382
383 assert_eq!(mi.item.value(), InterpretedValue::Set);
384
385 assert_eq!(
386 mi.after,
387 Span {
388 data: "",
389 line: 1,
390 col: 19,
391 offset: 18
392 }
393 );
394 }
395
396 #[test]
397 fn unset_prefix() {
398 let mi =
399 crate::document::Attribute::parse(crate::Span::new(":!foo:\nblah"), &Parser::default())
400 .unwrap();
401
402 assert_eq!(
403 mi.item,
404 Attribute {
405 name: Span {
406 data: "foo",
407 line: 1,
408 col: 3,
409 offset: 2,
410 },
411 value_source: None,
412 value: InterpretedValue::Unset,
413 source: Span {
414 data: ":!foo:",
415 line: 1,
416 col: 1,
417 offset: 0,
418 }
419 }
420 );
421
422 assert_eq!(mi.item.value(), InterpretedValue::Unset);
423
424 assert_eq!(
425 mi.after,
426 Span {
427 data: "blah",
428 line: 2,
429 col: 1,
430 offset: 7
431 }
432 );
433 }
434
435 #[test]
436 fn unset_postfix() {
437 let mi =
438 crate::document::Attribute::parse(crate::Span::new(":foo!:\nblah"), &Parser::default())
439 .unwrap();
440
441 assert_eq!(
442 mi.item,
443 Attribute {
444 name: Span {
445 data: "foo",
446 line: 1,
447 col: 2,
448 offset: 1,
449 },
450 value_source: None,
451 value: InterpretedValue::Unset,
452 source: Span {
453 data: ":foo!:",
454 line: 1,
455 col: 1,
456 offset: 0,
457 }
458 }
459 );
460
461 assert_eq!(mi.item.value(), InterpretedValue::Unset);
462
463 assert_eq!(
464 mi.after,
465 Span {
466 data: "blah",
467 line: 2,
468 col: 1,
469 offset: 7
470 }
471 );
472 }
473
474 #[test]
475 fn err_unset_prefix_and_postfix() {
476 assert!(
477 crate::document::Attribute::parse(
478 crate::Span::new(":!foo!:\nblah"),
479 &Parser::default()
480 )
481 .is_none()
482 );
483 }
484
485 #[test]
486 fn err_invalid_ident1() {
487 assert!(
488 crate::document::Attribute::parse(
489 crate::Span::new(":@invalid:\nblah"),
490 &Parser::default()
491 )
492 .is_none()
493 );
494 }
495
496 #[test]
497 fn err_invalid_ident2() {
498 assert!(
499 crate::document::Attribute::parse(
500 crate::Span::new(":invalid@:\nblah"),
501 &Parser::default()
502 )
503 .is_none()
504 );
505 }
506
507 #[test]
508 fn err_invalid_ident3() {
509 assert!(
510 crate::document::Attribute::parse(
511 crate::Span::new(":-invalid:\nblah"),
512 &Parser::default()
513 )
514 .is_none()
515 );
516 }
517
518 #[test]
519 fn value_with_soft_wrap() {
520 let mi = crate::document::Attribute::parse(
521 crate::Span::new(":foo: bar \\\n blah"),
522 &Parser::default(),
523 )
524 .unwrap();
525
526 assert_eq!(
527 mi.item,
528 Attribute {
529 name: Span {
530 data: "foo",
531 line: 1,
532 col: 2,
533 offset: 1,
534 },
535 value_source: Some(Span {
536 data: "bar \\\n blah",
537 line: 1,
538 col: 7,
539 offset: 6,
540 }),
541 value: InterpretedValue::Value("bar blah"),
542 source: Span {
543 data: ":foo: bar \\\n blah",
544 line: 1,
545 col: 1,
546 offset: 0,
547 }
548 }
549 );
550
551 assert_eq!(mi.item.value(), InterpretedValue::Value("bar blah"));
552
553 assert_eq!(
554 mi.after,
555 Span {
556 data: "",
557 line: 2,
558 col: 6,
559 offset: 17
560 }
561 );
562 }
563
564 #[test]
565 fn bare_trailing_backslash_is_literal() {
566 let mi = crate::document::Attribute::parse(
570 crate::Span::new(":longpath: very/long/path/to/some/\\\nsubdirectory"),
571 &Parser::default(),
572 )
573 .unwrap();
574
575 assert_eq!(
576 mi.item,
577 Attribute {
578 name: Span {
579 data: "longpath",
580 line: 1,
581 col: 2,
582 offset: 1,
583 },
584 value_source: Some(Span {
585 data: "very/long/path/to/some/\\",
586 line: 1,
587 col: 12,
588 offset: 11,
589 }),
590 value: InterpretedValue::Value("very/long/path/to/some/\\"),
591 source: Span {
592 data: ":longpath: very/long/path/to/some/\\",
593 line: 1,
594 col: 1,
595 offset: 0,
596 }
597 }
598 );
599
600 assert_eq!(
601 mi.item.value(),
602 InterpretedValue::Value("very/long/path/to/some/\\")
603 );
604
605 assert_eq!(
607 mi.after,
608 Span {
609 data: "subdirectory",
610 line: 2,
611 col: 1,
612 offset: 36
613 }
614 );
615 }
616
617 #[test]
618 fn literal_trailing_backslash_on_final_line() {
619 let mi = crate::document::Attribute::parse(
622 crate::Span::new(":foo: bar \\\nbaz\\"),
623 &Parser::default(),
624 )
625 .unwrap();
626
627 assert_eq!(mi.item.value(), InterpretedValue::Value("bar baz\\"));
628 }
629
630 #[test]
631 fn value_with_hard_wrap() {
632 let mi = crate::document::Attribute::parse(
633 crate::Span::new(":foo: bar + \\\n blah"),
634 &Parser::default(),
635 )
636 .unwrap();
637
638 assert_eq!(
639 mi.item,
640 Attribute {
641 name: Span {
642 data: "foo",
643 line: 1,
644 col: 2,
645 offset: 1,
646 },
647 value_source: Some(Span {
648 data: "bar + \\\n blah",
649 line: 1,
650 col: 7,
651 offset: 6,
652 }),
653 value: InterpretedValue::Value("bar +\nblah"),
654 source: Span {
655 data: ":foo: bar + \\\n blah",
656 line: 1,
657 col: 1,
658 offset: 0,
659 }
660 }
661 );
662
663 assert_eq!(mi.item.value(), InterpretedValue::Value("bar +\nblah"));
664
665 assert_eq!(
666 mi.after,
667 Span {
668 data: "",
669 line: 2,
670 col: 6,
671 offset: 19
672 }
673 );
674 }
675
676 #[test]
677 fn single_line_trailing_hard_break_marker_is_stripped() {
678 let mi = crate::document::Attribute::parse(
684 crate::Span::new(":foo: bar +\nblah"),
685 &Parser::default(),
686 )
687 .unwrap();
688
689 assert_eq!(
690 mi.item,
691 Attribute {
692 name: Span {
693 data: "foo",
694 line: 1,
695 col: 2,
696 offset: 1,
697 },
698 value_source: Some(Span {
699 data: "bar +",
700 line: 1,
701 col: 7,
702 offset: 6,
703 }),
704 value: InterpretedValue::Value("bar"),
705 source: Span {
706 data: ":foo: bar +",
707 line: 1,
708 col: 1,
709 offset: 0,
710 }
711 }
712 );
713
714 assert_eq!(mi.item.value(), InterpretedValue::Value("bar"));
715
716 assert_eq!(
719 mi.after,
720 Span {
721 data: "blah",
722 line: 2,
723 col: 1,
724 offset: 12
725 }
726 );
727 }
728
729 #[test]
730 fn single_line_hard_break_marker_edge_cases() {
731 let value = |src| {
735 crate::document::Attribute::parse(crate::Span::new(src), &Parser::default())
736 .unwrap()
737 .item
738 .value()
739 .clone()
740 };
741
742 assert_eq!(value(":foo: bar +\nx"), InterpretedValue::Value("bar"));
744
745 assert_eq!(value(":foo: bar\t +\nx"), InterpretedValue::Value("bar"));
747
748 assert_eq!(value(":foo: bar ++\nx"), InterpretedValue::Value("bar ++"));
750
751 assert_eq!(value(":foo: bar+\nx"), InterpretedValue::Value("bar+"));
753
754 assert_eq!(value(":foo: bar\t+\nx"), InterpretedValue::Value("bar\t+"));
756
757 assert_eq!(value(":foo: +\nx"), InterpretedValue::Value("+"));
759
760 assert_eq!(value(":foo: bar + +\nx"), InterpretedValue::Value("bar +"));
762
763 assert_eq!(
766 value(":foo: bar\u{00a0} +\nx"),
767 InterpretedValue::Value("bar\u{00a0}")
768 );
769 }
770
771 #[test]
772 fn is_block() {
773 let mut parser = Parser::default();
774 let maw = crate::blocks::Block::parse(crate::Span::new(":foo: bar\nblah"), &mut parser);
775
776 let mi = maw.item.unwrap();
777 let block = mi.item;
778
779 assert_eq!(
780 block,
781 Block::DocumentAttribute(Attribute {
782 name: Span {
783 data: "foo",
784 line: 1,
785 col: 2,
786 offset: 1,
787 },
788 value_source: Some(Span {
789 data: "bar",
790 line: 1,
791 col: 7,
792 offset: 6,
793 }),
794 value: InterpretedValue::Value("bar"),
795 source: Span {
796 data: ":foo: bar",
797 line: 1,
798 col: 1,
799 offset: 0,
800 }
801 })
802 );
803
804 assert_eq!(block.content_model(), ContentModel::Empty);
805 assert!(block.rendered_content().is_none());
806 assert_eq!(block.raw_context().deref(), "attribute");
807 assert!(block.nested_blocks().next().is_none());
808 assert!(block.title_source().is_none());
809 assert!(block.title().is_none());
810 assert!(block.anchor().is_none());
811 assert!(block.anchor_reftext().is_none());
812 assert!(block.attrlist().is_none());
813 assert_eq!(block.substitution_group(), SubstitutionGroup::Normal);
814
815 assert_eq!(
816 block.span(),
817 Span {
818 data: ":foo: bar",
819 line: 1,
820 col: 1,
821 offset: 0,
822 }
823 );
824
825 let crate::blocks::Block::DocumentAttribute(attr) = block else {
826 panic!("Wrong type");
827 };
828
829 assert_eq!(attr.value(), InterpretedValue::Value("bar"));
830
831 assert_eq!(
832 mi.after,
833 Span {
834 data: "blah",
835 line: 2,
836 col: 1,
837 offset: 10
838 }
839 );
840 }
841
842 #[test]
843 fn affects_document_state() {
844 let mut parser = Parser::default().with_intrinsic_attribute(
845 "agreed",
846 "yes",
847 ModificationContext::Anywhere,
848 );
849
850 let doc =
851 parser.parse("We are agreed? {agreed}\n\n:agreed: no\n\nAre we still agreed? {agreed}");
852
853 let mut blocks = doc.nested_blocks();
854
855 let block1 = blocks.next().unwrap();
856
857 assert_eq!(
858 block1,
859 &Block::Simple(SimpleBlock {
860 content: Content {
861 original: Span {
862 data: "We are agreed? {agreed}",
863 line: 1,
864 col: 1,
865 offset: 0,
866 },
867 rendered: "We are agreed? yes",
868 },
869 source: Span {
870 data: "We are agreed? {agreed}",
871 line: 1,
872 col: 1,
873 offset: 0,
874 },
875 style: SimpleBlockStyle::Paragraph,
876 title_source: None,
877 title: None,
878 caption: None,
879 number: None,
880 anchor: None,
881 anchor_reftext: None,
882 attrlist: None,
883 })
884 );
885
886 let _ = blocks.next().unwrap();
887
888 let block3 = blocks.next().unwrap();
889
890 assert_eq!(
891 block3,
892 &Block::Simple(SimpleBlock {
893 content: Content {
894 original: Span {
895 data: "Are we still agreed? {agreed}",
896 line: 5,
897 col: 1,
898 offset: 38,
899 },
900 rendered: "Are we still agreed? no",
901 },
902 source: Span {
903 data: "Are we still agreed? {agreed}",
904 line: 5,
905 col: 1,
906 offset: 38,
907 },
908 style: SimpleBlockStyle::Paragraph,
909 title_source: None,
910 title: None,
911 caption: None,
912 number: None,
913 anchor: None,
914 anchor_reftext: None,
915 attrlist: None,
916 })
917 );
918
919 let mut warnings = doc.warnings();
920 assert!(warnings.next().is_none());
921 }
922
923 #[test]
924 fn block_enforces_permission() {
925 let mut parser = Parser::default().with_intrinsic_attribute(
926 "agreed",
927 "yes",
928 ModificationContext::ApiOnly,
929 );
930
931 let doc = parser.parse("Hello\n\n:agreed: no\n\nAre we agreed? {agreed}");
932
933 let mut blocks = doc.nested_blocks();
934 let _ = blocks.next().unwrap();
935 let _ = blocks.next().unwrap();
936 let block3 = blocks.next().unwrap();
937
938 assert_eq!(
939 block3,
940 &Block::Simple(SimpleBlock {
941 content: Content {
942 original: Span {
943 data: "Are we agreed? {agreed}",
944 line: 5,
945 col: 1,
946 offset: 20,
947 },
948 rendered: "Are we agreed? yes",
949 },
950 source: Span {
951 data: "Are we agreed? {agreed}",
952 line: 5,
953 col: 1,
954 offset: 20,
955 },
956 style: SimpleBlockStyle::Paragraph,
957 title_source: None,
958 title: None,
959 caption: None,
960 number: None,
961 anchor: None,
962 anchor_reftext: None,
963 attrlist: None,
964 })
965 );
966
967 let mut warnings = doc.warnings();
968 let warning1 = warnings.next().unwrap();
969
970 assert_eq!(
971 &warning1.source,
972 Span {
973 data: ":agreed: no",
974 line: 3,
975 col: 1,
976 offset: 7,
977 }
978 );
979
980 assert_eq!(
981 warning1.warning,
982 WarningType::AttributeValueIsLocked("agreed".to_owned(),)
983 );
984
985 assert!(warnings.next().is_none());
986 }
987
988 mod interpreted_value {
989 mod impl_debug {
990 use crate::document::InterpretedValue;
991
992 #[test]
993 fn value_empty_string() {
994 let interpreted_value = InterpretedValue::Value("".to_string());
995 let debug_output = format!("{:?}", interpreted_value);
996 assert_eq!(debug_output, "InterpretedValue::Value(\"\")");
997 }
998
999 #[test]
1000 fn value_simple_string() {
1001 let interpreted_value = InterpretedValue::Value("hello".to_string());
1002 let debug_output = format!("{:?}", interpreted_value);
1003 assert_eq!(debug_output, "InterpretedValue::Value(\"hello\")");
1004 }
1005
1006 #[test]
1007 fn value_string_with_spaces() {
1008 let interpreted_value = InterpretedValue::Value("hello world".to_string());
1009 let debug_output = format!("{:?}", interpreted_value);
1010 assert_eq!(debug_output, "InterpretedValue::Value(\"hello world\")");
1011 }
1012
1013 #[test]
1014 fn value_string_with_special_chars() {
1015 let interpreted_value = InterpretedValue::Value("test!@#$%^&*()".to_string());
1016 let debug_output = format!("{:?}", interpreted_value);
1017 assert_eq!(debug_output, "InterpretedValue::Value(\"test!@#$%^&*()\")");
1018 }
1019
1020 #[test]
1021 fn value_string_with_quotes() {
1022 let interpreted_value = InterpretedValue::Value("value\"with'quotes".to_string());
1023 let debug_output = format!("{:?}", interpreted_value);
1024 assert_eq!(
1025 debug_output,
1026 "InterpretedValue::Value(\"value\\\"with'quotes\")"
1027 );
1028 }
1029
1030 #[test]
1031 fn value_string_with_newlines() {
1032 let interpreted_value = InterpretedValue::Value("line1\nline2\nline3".to_string());
1033 let debug_output = format!("{:?}", interpreted_value);
1034 assert_eq!(
1035 debug_output,
1036 "InterpretedValue::Value(\"line1\\nline2\\nline3\")"
1037 );
1038 }
1039
1040 #[test]
1041 fn value_string_with_backslashes() {
1042 let interpreted_value = InterpretedValue::Value("path\\to\\file".to_string());
1043 let debug_output = format!("{:?}", interpreted_value);
1044 assert_eq!(
1045 debug_output,
1046 "InterpretedValue::Value(\"path\\\\to\\\\file\")"
1047 );
1048 }
1049
1050 #[test]
1051 fn value_string_with_unicode() {
1052 let interpreted_value = InterpretedValue::Value("café 🚀 ñoño".to_string());
1053 let debug_output = format!("{:?}", interpreted_value);
1054 assert_eq!(debug_output, "InterpretedValue::Value(\"café 🚀 ñoño\")");
1055 }
1056
1057 #[test]
1058 fn set() {
1059 let interpreted_value = InterpretedValue::Set;
1060 let debug_output = format!("{:?}", interpreted_value);
1061 assert_eq!(debug_output, "InterpretedValue::Set");
1062 }
1063
1064 #[test]
1065 fn unset() {
1066 let interpreted_value = InterpretedValue::Unset;
1067 let debug_output = format!("{:?}", interpreted_value);
1068 assert_eq!(debug_output, "InterpretedValue::Unset");
1069 }
1070 }
1071 }
1072}