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