1use cargo_util_schemas::manifest::PackageName;
2
3use crate::CargoResult;
4use crate::util::restricted_names;
5
6pub(super) fn expand_manifest(content: &str) -> CargoResult<String> {
7 let source = ScriptSource::parse(content)?;
8 if let Some(frontmatter) = source.frontmatter() {
9 match source.info() {
10 Some("cargo") | None => {}
11 Some(other) => {
12 if let Some(remainder) = other.strip_prefix("cargo,") {
13 anyhow::bail!(
14 "cargo does not support frontmatter infostring attributes like `{remainder}` at this time"
15 )
16 } else {
17 anyhow::bail!(
18 "frontmatter infostring `{other}` is unsupported by cargo; specify `cargo` for embedding a manifest"
19 )
20 }
21 }
22 }
23
24 Ok(frontmatter.to_owned())
25 } else {
26 let frontmatter = "";
27 Ok(frontmatter.to_owned())
28 }
29}
30
31pub fn sanitize_name(name: &str) -> String {
33 let placeholder = if name.contains('_') {
34 '_'
35 } else {
36 '-'
39 };
40
41 let mut name = PackageName::sanitize(name, placeholder).into_inner();
42
43 loop {
44 if restricted_names::is_keyword(&name) {
45 name.push(placeholder);
46 } else if restricted_names::is_conflicting_artifact_name(&name) {
47 name.push(placeholder);
49 } else if name == "test" {
50 name.push(placeholder);
51 } else if restricted_names::is_windows_reserved(&name) {
52 name.push(placeholder);
54 } else {
55 break;
56 }
57 }
58
59 name
60}
61
62#[derive(Debug)]
63pub struct ScriptSource<'s> {
64 shebang: Option<&'s str>,
65 info: Option<&'s str>,
66 frontmatter: Option<&'s str>,
67 content: &'s str,
68}
69
70impl<'s> ScriptSource<'s> {
71 pub fn parse(input: &'s str) -> CargoResult<Self> {
72 let mut source = Self {
73 shebang: None,
74 info: None,
75 frontmatter: None,
76 content: input,
77 };
78
79 if let Some(shebang_end) = strip_shebang(source.content) {
80 let (shebang, content) = source.content.split_at(shebang_end);
81 source.shebang = Some(shebang);
82 source.content = content;
83 }
84
85 let mut rest = source.content;
86
87 let trimmed = rest.trim_start_matches(is_whitespace);
89 if trimmed.len() != rest.len() {
90 let trimmed_len = rest.len() - trimmed.len();
91 let last_trimmed_index = trimmed_len - 1;
92 if rest.as_bytes()[last_trimmed_index] != b'\n' {
93 return Ok(source);
95 }
96 }
97 rest = trimmed;
98
99 const FENCE_CHAR: char = '-';
101 let fence_length = rest
102 .char_indices()
103 .find_map(|(i, c)| (c != FENCE_CHAR).then_some(i))
104 .unwrap_or(rest.len());
105 match fence_length {
106 0 => {
107 return Ok(source);
108 }
109 1 | 2 => {
110 anyhow::bail!(
112 "found {fence_length} `{FENCE_CHAR}` in rust frontmatter, expected at least 3"
113 )
114 }
115 _ => {}
116 }
117 let (fence_pattern, rest) = rest.split_at(fence_length);
118 let Some(info_end_index) = rest.find('\n') else {
119 anyhow::bail!("no closing `{fence_pattern}` found for frontmatter");
120 };
121 let (info, rest) = rest.split_at(info_end_index);
122 let info = info.trim_matches(is_whitespace);
123 if !info.is_empty() {
124 source.info = Some(info);
125 }
126
127 let nl_fence_pattern = format!("\n{fence_pattern}");
129 let Some(frontmatter_nl) = rest.find(&nl_fence_pattern) else {
130 anyhow::bail!("no closing `{fence_pattern}` found for frontmatter");
131 };
132 let frontmatter = &rest[..frontmatter_nl + 1];
133 let frontmatter = frontmatter
134 .strip_prefix('\n')
135 .expect("earlier `found` + `split_at` left us here");
136 source.frontmatter = Some(frontmatter);
137 let rest = &rest[frontmatter_nl + nl_fence_pattern.len()..];
138
139 let (after_closing_fence, rest) = rest.split_once("\n").unwrap_or((rest, ""));
140 let after_closing_fence = after_closing_fence.trim_matches(is_whitespace);
141 if !after_closing_fence.is_empty() {
142 anyhow::bail!("trailing characters found after frontmatter close");
144 }
145
146 let frontmatter_len = input.len() - rest.len();
147 source.content = &input[frontmatter_len..];
148
149 let repeat = Self::parse(source.content)?;
150 if repeat.frontmatter.is_some() {
151 anyhow::bail!("only one frontmatter is supported");
152 }
153
154 Ok(source)
155 }
156
157 pub fn shebang(&self) -> Option<&'s str> {
158 self.shebang
159 }
160
161 pub fn info(&self) -> Option<&'s str> {
162 self.info
163 }
164
165 pub fn frontmatter(&self) -> Option<&'s str> {
166 self.frontmatter
167 }
168
169 pub fn content(&self) -> &'s str {
170 self.content
171 }
172}
173
174fn strip_shebang(input: &str) -> Option<usize> {
175 if let Some(rest) = input.strip_prefix("#!") {
180 if !rest.trim_start().starts_with('[') {
186 let newline_end = input.find('\n').map(|pos| pos + 1).unwrap_or(input.len());
188 return Some(newline_end);
189 }
190 }
191 None
192}
193
194fn is_whitespace(c: char) -> bool {
200 matches!(
206 c,
207 '\u{0009}' | '\u{000A}' | '\u{000B}' | '\u{000C}' | '\u{000D}' | '\u{0020}' | '\u{0085}'
217
218 | '\u{200E}' | '\u{200F}' | '\u{2028}' | '\u{2029}' )
226}
227
228#[cfg(test)]
229mod test_expand {
230 use snapbox::assert_data_eq;
231 use snapbox::prelude::*;
232 use snapbox::str;
233
234 use super::*;
235
236 #[track_caller]
237 fn assert_source(source: &str, expected: impl IntoData) {
238 use std::fmt::Write as _;
239
240 let actual = match ScriptSource::parse(source) {
241 Ok(actual) => actual,
242 Err(err) => panic!("unexpected err: {err}"),
243 };
244
245 let mut rendered = String::new();
246 write_optional_field(&mut rendered, "shebang", actual.shebang());
247 write_optional_field(&mut rendered, "info", actual.info());
248 write_optional_field(&mut rendered, "frontmatter", actual.frontmatter());
249 writeln!(&mut rendered, "content: {:?}", actual.content()).unwrap();
250 assert_data_eq!(rendered, expected.raw());
251 }
252
253 fn write_optional_field(writer: &mut dyn std::fmt::Write, field: &str, value: Option<&str>) {
254 if let Some(value) = value {
255 writeln!(writer, "{field}: {value:?}").unwrap();
256 } else {
257 writeln!(writer, "{field}: None").unwrap();
258 }
259 }
260
261 #[track_caller]
262 fn assert_err(
263 result: Result<impl std::fmt::Debug, impl std::fmt::Display>,
264 err: impl IntoData,
265 ) {
266 match result {
267 Ok(d) => panic!("unexpected Ok({d:#?})"),
268 Err(actual) => snapbox::assert_data_eq!(actual.to_string(), err.raw()),
269 }
270 }
271
272 #[test]
273 fn rustc_dot_in_infostring_leading() {
274 assert_source(
276 r#"---.toml
277//~^ ERROR: invalid infostring for frontmatter
278---
279
280// infostrings cannot have leading dots
281
282fn main() {}
283"#,
284 str![[r#"
285shebang: None
286info: ".toml"
287frontmatter: "//~^ ERROR: invalid infostring for frontmatter\n"
288content: "\n// infostrings cannot have leading dots\n\nfn main() {}\n"
289
290"#]],
291 );
292 }
293
294 #[test]
295 fn rustc_dot_in_infostring_non_leading() {
296 assert_source(
297 r#"---Cargo.toml
298---
299
300// infostrings can contain dots as long as a dot isn't the first character.
301//@ check-pass
302
303fn main() {}
304"#,
305 str![[r#"
306shebang: None
307info: "Cargo.toml"
308frontmatter: ""
309content: "\n// infostrings can contain dots as long as a dot isn't the first character.\n//@ check-pass\n\nfn main() {}\n"
310
311"#]],
312 );
313 }
314
315 #[test]
316 fn rustc_escape() {
317 assert_source(
318 r#"----
319
320---
321
322----
323
324//@ check-pass
325
326// This test checks that longer dashes for opening and closing can be used to
327// escape sequences such as three dashes inside the frontmatter block.
328
329fn main() {}
330"#,
331 str![[r#"
332shebang: None
333info: None
334frontmatter: "\n---\n\n"
335content: "\n//@ check-pass\n\n// This test checks that longer dashes for opening and closing can be used to\n// escape sequences such as three dashes inside the frontmatter block.\n\nfn main() {}\n"
336
337"#]],
338 );
339 }
340
341 #[test]
342 fn rustc_extra_after_end() {
343 assert_err(
344 ScriptSource::parse(
345 r#"---
346---cargo
347//~^ ERROR: extra characters after frontmatter close are not allowed
348
349fn main() {}
350"#,
351 ),
352 str!["trailing characters found after frontmatter close"],
353 );
354 }
355
356 #[test]
357 fn rustc_frontmatter_after_tokens() {
358 assert_source(
360 r#"#![feature(frontmatter)]
361
362---
363//~^ ERROR: expected item, found `-`
364// FIXME(frontmatter): make this diagnostic better
365---
366
367// frontmatters must be at the start of a file. This test ensures that.
368
369fn main() {}
370"#,
371 str![[r##"
372shebang: None
373info: None
374frontmatter: None
375content: "#![feature(frontmatter)]\n\n---\n//~^ ERROR: expected item, found `-`\n// FIXME(frontmatter): make this diagnostic better\n---\n\n// frontmatters must be at the start of a file. This test ensures that.\n\nfn main() {}\n"
376
377"##]],
378 );
379 }
380
381 #[test]
382 fn rustc_frontmatter_non_lexible_tokens() {
383 assert_source(
384 r#"---uwu
385🏳️⚧️
386---
387
388//@ check-pass
389
390// check that frontmatter blocks can have tokens that are otherwise not accepted by
391// the lexer as Rust code.
392
393fn main() {}
394"#,
395 str![[r#"
396shebang: None
397info: "uwu"
398frontmatter: "🏳\u{fe0f}\u{200d}⚧\u{fe0f}\n"
399content: "\n//@ check-pass\n\n// check that frontmatter blocks can have tokens that are otherwise not accepted by\n// the lexer as Rust code.\n\nfn main() {}\n"
400
401"#]],
402 );
403 }
404
405 #[test]
406 fn rustc_frontmatter_whitespace_1() {
407 assert_source(
409 r#" ---
410//~^ ERROR: invalid preceding whitespace for frontmatter opening
411 ---
412//~^ ERROR: invalid preceding whitespace for frontmatter close
413
414// check that whitespaces should not precede the frontmatter opening or close.
415
416fn main() {}
417"#,
418 str![[r#"
419shebang: None
420info: None
421frontmatter: None
422content: " ---\n//~^ ERROR: invalid preceding whitespace for frontmatter opening\n ---\n//~^ ERROR: invalid preceding whitespace for frontmatter close\n\n// check that whitespaces should not precede the frontmatter opening or close.\n\nfn main() {}\n"
423
424"#]],
425 );
426 }
427
428 #[test]
429 fn rustc_frontmatter_whitespace_2() {
430 assert_err(
431 ScriptSource::parse(
432 r#"---cargo
433
434//@ compile-flags: --crate-type lib
435
436fn foo(x: i32) -> i32 {
437 ---x
438 //~^ ERROR: invalid preceding whitespace for frontmatter close
439 //~| ERROR: extra characters after frontmatter close are not allowed
440}
441//~^ ERROR: unexpected closing delimiter: `}`
442
443// this test is for the weird case that valid Rust code can have three dashes
444// within them and get treated as a frontmatter close.
445"#,
446 ),
447 str!["no closing `---` found for frontmatter"],
448 );
449 }
450
451 #[test]
452 fn rustc_frontmatter_whitespace_3() {
453 assert_source(
454 r#"
455
456
457---cargo
458---
459
460// please note the whitespace characters after the first four lines.
461// This ensures that we accept whitespaces before the frontmatter, after
462// the frontmatter opening and the frontmatter close.
463
464//@ check-pass
465// ignore-tidy-end-whitespace
466// ignore-tidy-leading-newlines
467
468fn main() {}
469"#,
470 str![[r#"
471shebang: None
472info: "cargo"
473frontmatter: ""
474content: "\n// please note the whitespace characters after the first four lines.\n// This ensures that we accept whitespaces before the frontmatter, after\n// the frontmatter opening and the frontmatter close.\n\n//@ check-pass\n// ignore-tidy-end-whitespace\n// ignore-tidy-leading-newlines\n\nfn main() {}\n"
475
476"#]],
477 );
478 }
479
480 #[test]
481 fn rustc_frontmatter_whitespace_4() {
482 assert_source(
483 r#"--- cargo
484---
485
486//@ check-pass
487// A frontmatter infostring can have leading whitespace.
488
489fn main() {}
490"#,
491 str![[r#"
492shebang: None
493info: "cargo"
494frontmatter: ""
495content: "\n//@ check-pass\n// A frontmatter infostring can have leading whitespace.\n\nfn main() {}\n"
496
497"#]],
498 );
499 }
500
501 #[test]
502 fn rustc_infostring_fail() {
503 assert_source(
505 r#"
506---cargo,clippy
507//~^ ERROR: invalid infostring for frontmatter
508---
509
510// infostrings can only be a single identifier.
511
512fn main() {}
513"#,
514 str![[r#"
515shebang: None
516info: "cargo,clippy"
517frontmatter: "//~^ ERROR: invalid infostring for frontmatter\n"
518content: "\n// infostrings can only be a single identifier.\n\nfn main() {}\n"
519
520"#]],
521 );
522 }
523
524 #[test]
525 fn rustc_mismatch_1() {
526 assert_err(
527 ScriptSource::parse(
528 r#"---cargo
529//~^ ERROR: frontmatter close does not match the opening
530----
531
532// there must be the same number of dashes for both the opening and the close
533// of the frontmatter.
534
535fn main() {}
536"#,
537 ),
538 str!["trailing characters found after frontmatter close"],
539 );
540 }
541
542 #[test]
543 fn rustc_mismatch_2() {
544 assert_err(
545 ScriptSource::parse(
546 r#"----cargo
547//~^ ERROR: frontmatter close does not match the opening
548---cargo
549//~^ ERROR: extra characters after frontmatter close are not allowed
550
551fn main() {}
552"#,
553 ),
554 str!["no closing `----` found for frontmatter"],
555 );
556 }
557
558 #[test]
559 fn rustc_multifrontmatter_2() {
560 assert_source(
562 r#"---
563 ---
564//~^ ERROR: invalid preceding whitespace for frontmatter close
565
566 ---
567//~^ ERROR: expected item, found `-`
568// FIXME(frontmatter): make this diagnostic better
569---
570
571fn main() {}
572"#,
573 str![[r#"
574shebang: None
575info: None
576frontmatter: " ---\n//~^ ERROR: invalid preceding whitespace for frontmatter close\n\n ---\n//~^ ERROR: expected item, found `-`\n// FIXME(frontmatter): make this diagnostic better\n"
577content: "\nfn main() {}\n"
578
579"#]],
580 );
581 }
582
583 #[test]
584 fn rustc_multifrontmatter() {
585 assert_err(
586 ScriptSource::parse(
587 r#"---
588---
589
590---
591//~^ ERROR: expected item, found `-`
592// FIXME(frontmatter): make this diagnostic better
593---
594
595// test that we do not parse another frontmatter block after the first one.
596
597fn main() {}
598"#,
599 ),
600 str!["only one frontmatter is supported"],
601 );
602 }
603
604 #[test]
605 fn rustc_shebang() {
606 assert_source(
607 r#"#!/usr/bin/env -S cargo -Zscript
608---
609[dependencies]
610clap = "4"
611---
612
613//@ check-pass
614
615// Shebangs on a file can precede a frontmatter.
616
617fn main () {}
618"#,
619 str![[r##"
620shebang: "#!/usr/bin/env -S cargo -Zscript\n"
621info: None
622frontmatter: "[dependencies]\nclap = \"4\"\n"
623content: "\n//@ check-pass\n\n// Shebangs on a file can precede a frontmatter.\n\nfn main () {}\n"
624
625"##]],
626 );
627 }
628
629 #[test]
630 fn rustc_unclosed_1() {
631 assert_err(
632 ScriptSource::parse(
633 r#"----cargo
634//~^ ERROR: unclosed frontmatter
635
636// This test checks that the #! characters can help us recover a frontmatter
637// close. There should not be a "missing `main` function" error as the rest
638// are properly parsed.
639
640fn main() {}
641"#,
642 ),
643 str!["no closing `----` found for frontmatter"],
644 );
645 }
646
647 #[test]
648 fn rustc_unclosed_2() {
649 assert_err(
650 ScriptSource::parse(
651 r#"----cargo
652//~^ ERROR: unclosed frontmatter
653//~| ERROR: frontmatters are experimental
654
655//@ compile-flags: --crate-type lib
656
657// Leading whitespace on the feature line prevents recovery. However
658// the dashes quoted will not be used for recovery and the entire file
659// should be treated as within the frontmatter block.
660
661fn foo() -> &str {
662 "----"
663}
664"#,
665 ),
666 str!["no closing `----` found for frontmatter"],
667 );
668 }
669
670 #[test]
671 fn rustc_unclosed_3() {
672 assert_err(
673 ScriptSource::parse(
674 r#"----cargo
675//~^ ERROR: frontmatter close does not match the opening
676
677//@ compile-flags: --crate-type lib
678
679// Unfortunate recovery situation. Not really preventable with improving the
680// recovery strategy, but this type of code is rare enough already.
681
682fn foo(x: i32) -> i32 {
683 ---x
684 //~^ ERROR: invalid preceding whitespace for frontmatter close
685 //~| ERROR: extra characters after frontmatter close are not allowed
686}
687//~^ ERROR: unexpected closing delimiter: `}`
688"#,
689 ),
690 str!["no closing `----` found for frontmatter"],
691 );
692 }
693
694 #[test]
695 fn rustc_unclosed_4() {
696 assert_err(
697 ScriptSource::parse(
698 r#"
699----cargo
700//~^ ERROR: unclosed frontmatter
701
702//! Similarly, a module-level content should allow for recovery as well (as
703//! per unclosed-1.rs)
704
705fn main() {}
706"#,
707 ),
708 str!["no closing `----` found for frontmatter"],
709 );
710 }
711
712 #[test]
713 fn rustc_unclosed_5() {
714 assert_err(
715 ScriptSource::parse(
716 r#"----cargo
717//~^ ERROR: unclosed frontmatter
718//~| ERROR: frontmatters are experimental
719
720// Similarly, a use statement should allow for recovery as well (as
721// per unclosed-1.rs)
722
723use std::env;
724
725fn main() {}
726"#,
727 ),
728 str!["no closing `----` found for frontmatter"],
729 );
730 }
731
732 #[test]
733 fn split_default() {
734 assert_source(
735 r#"fn main() {}
736"#,
737 str![[r#"
738shebang: None
739info: None
740frontmatter: None
741content: "fn main() {}\n"
742
743"#]],
744 );
745 }
746
747 #[test]
748 fn split_dependencies() {
749 assert_source(
750 r#"---
751[dependencies]
752time="0.1.25"
753---
754fn main() {}
755"#,
756 str![[r#"
757shebang: None
758info: None
759frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
760content: "fn main() {}\n"
761
762"#]],
763 );
764 }
765
766 #[test]
767 fn split_infostring() {
768 assert_source(
769 r#"---cargo
770[dependencies]
771time="0.1.25"
772---
773fn main() {}
774"#,
775 str![[r#"
776shebang: None
777info: "cargo"
778frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
779content: "fn main() {}\n"
780
781"#]],
782 );
783 }
784
785 #[test]
786 fn split_infostring_whitespace() {
787 assert_source(
788 r#"--- cargo
789[dependencies]
790time="0.1.25"
791---
792fn main() {}
793"#,
794 str![[r#"
795shebang: None
796info: "cargo"
797frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
798content: "fn main() {}\n"
799
800"#]],
801 );
802 }
803
804 #[test]
805 fn split_shebang() {
806 assert_source(
807 r#"#!/usr/bin/env cargo
808---
809[dependencies]
810time="0.1.25"
811---
812fn main() {}
813"#,
814 str![[r##"
815shebang: "#!/usr/bin/env cargo\n"
816info: None
817frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
818content: "fn main() {}\n"
819
820"##]],
821 );
822 }
823
824 #[test]
825 fn split_crlf() {
826 assert_source(
827 "#!/usr/bin/env cargo\r\n---\r\n[dependencies]\r\ntime=\"0.1.25\"\r\n---\r\nfn main() {}",
828 str![[r##"
829shebang: "#!/usr/bin/env cargo\r\n"
830info: None
831frontmatter: "[dependencies]\r\ntime=\"0.1.25\"\r\n"
832content: "fn main() {}"
833
834"##]],
835 );
836 }
837
838 #[test]
839 fn split_leading_newlines() {
840 assert_source(
841 r#"#!/usr/bin/env cargo
842
843
844
845---
846[dependencies]
847time="0.1.25"
848---
849
850
851fn main() {}
852"#,
853 str![[r##"
854shebang: "#!/usr/bin/env cargo\n"
855info: None
856frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
857content: "\n\nfn main() {}\n"
858
859"##]],
860 );
861 }
862
863 #[test]
864 fn split_attribute() {
865 assert_source(
866 r#"#[allow(dead_code)]
867---
868[dependencies]
869time="0.1.25"
870---
871fn main() {}
872"#,
873 str![[r##"
874shebang: None
875info: None
876frontmatter: None
877content: "#[allow(dead_code)]\n---\n[dependencies]\ntime=\"0.1.25\"\n---\nfn main() {}\n"
878
879"##]],
880 );
881 }
882
883 #[test]
884 fn split_extra_dash() {
885 assert_source(
886 r#"#!/usr/bin/env cargo
887----------
888[dependencies]
889time="0.1.25"
890----------
891
892fn main() {}"#,
893 str![[r##"
894shebang: "#!/usr/bin/env cargo\n"
895info: None
896frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
897content: "\nfn main() {}"
898
899"##]],
900 );
901 }
902
903 #[test]
904 fn split_too_few_dashes() {
905 assert_err(
906 ScriptSource::parse(
907 r#"#!/usr/bin/env cargo
908--
909[dependencies]
910time="0.1.25"
911--
912fn main() {}
913"#,
914 ),
915 str!["found 2 `-` in rust frontmatter, expected at least 3"],
916 );
917 }
918
919 #[test]
920 fn split_indent() {
921 assert_source(
922 r#"#!/usr/bin/env cargo
923 ---
924 [dependencies]
925 time="0.1.25"
926 ----
927
928fn main() {}
929"#,
930 str![[r##"
931shebang: "#!/usr/bin/env cargo\n"
932info: None
933frontmatter: None
934content: " ---\n [dependencies]\n time=\"0.1.25\"\n ----\n\nfn main() {}\n"
935
936"##]],
937 );
938 }
939
940 #[test]
941 fn split_escaped() {
942 assert_source(
943 r#"#!/usr/bin/env cargo
944-----
945---
946---
947-----
948
949fn main() {}
950"#,
951 str![[r##"
952shebang: "#!/usr/bin/env cargo\n"
953info: None
954frontmatter: "---\n---\n"
955content: "\nfn main() {}\n"
956
957"##]],
958 );
959 }
960
961 #[test]
962 fn split_invalid_escaped() {
963 assert_err(
964 ScriptSource::parse(
965 r#"#!/usr/bin/env cargo
966---
967-----
968-----
969---
970
971fn main() {}
972"#,
973 ),
974 str!["trailing characters found after frontmatter close"],
975 );
976 }
977
978 #[test]
979 fn split_dashes_in_body() {
980 assert_source(
981 r#"#!/usr/bin/env cargo
982---
983Hello---
984World
985---
986
987fn main() {}
988"#,
989 str![[r##"
990shebang: "#!/usr/bin/env cargo\n"
991info: None
992frontmatter: "Hello---\nWorld\n"
993content: "\nfn main() {}\n"
994
995"##]],
996 );
997 }
998
999 #[test]
1000 fn split_mismatched_dashes() {
1001 assert_err(
1002 ScriptSource::parse(
1003 r#"#!/usr/bin/env cargo
1004---
1005[dependencies]
1006time="0.1.25"
1007----
1008fn main() {}
1009"#,
1010 ),
1011 str!["trailing characters found after frontmatter close"],
1012 );
1013 }
1014
1015 #[test]
1016 fn split_missing_close() {
1017 assert_err(
1018 ScriptSource::parse(
1019 r#"#!/usr/bin/env cargo
1020---
1021[dependencies]
1022time="0.1.25"
1023fn main() {}
1024"#,
1025 ),
1026 str!["no closing `---` found for frontmatter"],
1027 );
1028 }
1029
1030 #[track_caller]
1031 fn expand(source: &str) -> String {
1032 expand_manifest(source).unwrap_or_else(|err| panic!("{}", err))
1033 }
1034
1035 #[test]
1036 fn expand_default() {
1037 assert_data_eq!(expand(r#"fn main() {}"#), str![""]);
1038 }
1039
1040 #[test]
1041 fn expand_dependencies() {
1042 assert_data_eq!(
1043 expand(
1044 r#"---cargo
1045[dependencies]
1046time="0.1.25"
1047---
1048fn main() {}
1049"#
1050 ),
1051 str![[r#"
1052[dependencies]
1053time="0.1.25"
1054
1055"#]]
1056 );
1057 }
1058}