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_inner_hyphens_1() {
383 assert_source(
384 r#"---
385x ---🚧️
386---
387
388// Regression test for #141483
389//@check-pass
390
391#![feature(frontmatter)]
392
393fn main() {}
394"#,
395 str![[r#"
396shebang: None
397info: None
398frontmatter: "x ---🚧\u{fe0f}\n"
399content: "\n// Regression test for #141483\n//@check-pass\n\n#![feature(frontmatter)]\n\nfn main() {}\n"
400
401"#]],
402 );
403 }
404
405 #[test]
406 fn rustc_frontmatter_inner_hyphens_2() {
407 assert_source(
408 r#"---
409x ---y
410---
411
412// Test that hypens are allowed inside frontmatters if there is some
413// non-whitespace character preceding them.
414//@check-pass
415
416#![feature(frontmatter)]
417
418fn main() {}
419"#,
420 str![[r#"
421shebang: None
422info: None
423frontmatter: "x ---y\n"
424content: "\n// Test that hypens are allowed inside frontmatters if there is some\n// non-whitespace character preceding them.\n//@check-pass\n\n#![feature(frontmatter)]\n\nfn main() {}\n"
425
426"#]],
427 );
428 }
429
430 #[test]
431 fn rustc_frontmatter_non_lexible_tokens() {
432 assert_source(
433 r#"---uwu
434🏳️⚧️
435---
436
437//@ check-pass
438
439// check that frontmatter blocks can have tokens that are otherwise not accepted by
440// the lexer as Rust code.
441
442fn main() {}
443"#,
444 str![[r#"
445shebang: None
446info: "uwu"
447frontmatter: "🏳\u{fe0f}\u{200d}⚧\u{fe0f}\n"
448content: "\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"
449
450"#]],
451 );
452 }
453
454 #[test]
455 fn rustc_frontmatter_whitespace_1() {
456 assert_source(
458 r#" ---
459//~^ ERROR: invalid preceding whitespace for frontmatter opening
460 ---
461//~^ ERROR: invalid preceding whitespace for frontmatter close
462
463// check that whitespaces should not precede the frontmatter opening or close.
464
465fn main() {}
466"#,
467 str![[r#"
468shebang: None
469info: None
470frontmatter: None
471content: " ---\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"
472
473"#]],
474 );
475 }
476
477 #[test]
478 fn rustc_frontmatter_whitespace_2() {
479 assert_err(
480 ScriptSource::parse(
481 r#"---cargo
482
483//@ compile-flags: --crate-type lib
484
485fn foo(x: i32) -> i32 {
486 ---x
487 //~^ ERROR: invalid preceding whitespace for frontmatter close
488 //~| ERROR: extra characters after frontmatter close are not allowed
489}
490//~^ ERROR: unexpected closing delimiter: `}`
491
492// this test is for the weird case that valid Rust code can have three dashes
493// within them and get treated as a frontmatter close.
494"#,
495 ),
496 str!["no closing `---` found for frontmatter"],
497 );
498 }
499
500 #[test]
501 fn rustc_frontmatter_whitespace_3() {
502 assert_source(
503 r#"
504
505
506---cargo
507---
508
509// please note the whitespace characters after the first four lines.
510// This ensures that we accept whitespaces before the frontmatter, after
511// the frontmatter opening and the frontmatter close.
512
513//@ check-pass
514// ignore-tidy-end-whitespace
515// ignore-tidy-leading-newlines
516
517fn main() {}
518"#,
519 str![[r#"
520shebang: None
521info: "cargo"
522frontmatter: ""
523content: "\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"
524
525"#]],
526 );
527 }
528
529 #[test]
530 fn rustc_frontmatter_whitespace_4() {
531 assert_source(
532 r#"--- cargo
533---
534
535//@ check-pass
536// A frontmatter infostring can have leading whitespace.
537
538fn main() {}
539"#,
540 str![[r#"
541shebang: None
542info: "cargo"
543frontmatter: ""
544content: "\n//@ check-pass\n// A frontmatter infostring can have leading whitespace.\n\nfn main() {}\n"
545
546"#]],
547 );
548 }
549
550 #[test]
551 fn rustc_hyphen_in_infostring_leading() {
552 assert_source(
554 r#"--- -toml
555//~^ ERROR: invalid infostring for frontmatter
556---
557
558// infostrings cannot have leading hyphens
559
560#![feature(frontmatter)]
561
562fn main() {}
563"#,
564 str![[r#"
565shebang: None
566info: "-toml"
567frontmatter: "//~^ ERROR: invalid infostring for frontmatter\n"
568content: "\n// infostrings cannot have leading hyphens\n\n#![feature(frontmatter)]\n\nfn main() {}\n"
569
570"#]],
571 );
572 }
573
574 #[test]
575 fn rustc_hyphen_in_infostring_non_leading() {
576 assert_source(
577 r#"--- Cargo-toml
578---
579
580// infostrings can contain hyphens as long as a hyphen isn't the first character.
581//@ check-pass
582
583#![feature(frontmatter)]
584
585fn main() {}
586"#,
587 str![[r#"
588shebang: None
589info: "Cargo-toml"
590frontmatter: ""
591content: "\n// infostrings can contain hyphens as long as a hyphen isn't the first character.\n//@ check-pass\n\n#![feature(frontmatter)]\n\nfn main() {}\n"
592
593"#]],
594 );
595 }
596
597 #[test]
598 fn rustc_included_frontmatter() {
599 assert_source(
601 r#"#![feature(frontmatter)]
602
603//@ check-pass
604
605include!("auxiliary/lib.rs");
606
607// auxiliary/lib.rs contains a frontmatter. Ensure that we can use them in an
608// `include!` macro.
609
610fn main() {
611 foo(1);
612}
613"#,
614 str![[r##"
615shebang: None
616info: None
617frontmatter: None
618content: "#![feature(frontmatter)]\n\n//@ check-pass\n\ninclude!(\"auxiliary/lib.rs\");\n\n// auxiliary/lib.rs contains a frontmatter. Ensure that we can use them in an\n// `include!` macro.\n\nfn main() {\n foo(1);\n}\n"
619
620"##]],
621 );
622 }
623
624 #[test]
625 fn rustc_infostring_fail() {
626 assert_source(
628 r#"
629---cargo,clippy
630//~^ ERROR: invalid infostring for frontmatter
631---
632
633// infostrings can only be a single identifier.
634
635fn main() {}
636"#,
637 str![[r#"
638shebang: None
639info: "cargo,clippy"
640frontmatter: "//~^ ERROR: invalid infostring for frontmatter\n"
641content: "\n// infostrings can only be a single identifier.\n\nfn main() {}\n"
642
643"#]],
644 );
645 }
646
647 #[test]
648 fn rustc_mismatch_1() {
649 assert_err(
650 ScriptSource::parse(
651 r#"---cargo
652//~^ ERROR: frontmatter close does not match the opening
653----
654
655// there must be the same number of dashes for both the opening and the close
656// of the frontmatter.
657
658fn main() {}
659"#,
660 ),
661 str!["trailing characters found after frontmatter close"],
662 );
663 }
664
665 #[test]
666 fn rustc_mismatch_2() {
667 assert_err(
668 ScriptSource::parse(
669 r#"----cargo
670//~^ ERROR: frontmatter close does not match the opening
671---cargo
672//~^ ERROR: extra characters after frontmatter close are not allowed
673
674fn main() {}
675"#,
676 ),
677 str!["no closing `----` found for frontmatter"],
678 );
679 }
680
681 #[test]
682 fn rustc_multifrontmatter_2() {
683 assert_source(
685 r#"---
686 ---
687//~^ ERROR: invalid preceding whitespace for frontmatter close
688
689 ---
690//~^ ERROR: expected item, found `-`
691// FIXME(frontmatter): make this diagnostic better
692---
693
694fn main() {}
695"#,
696 str![[r#"
697shebang: None
698info: None
699frontmatter: " ---\n//~^ ERROR: invalid preceding whitespace for frontmatter close\n\n ---\n//~^ ERROR: expected item, found `-`\n// FIXME(frontmatter): make this diagnostic better\n"
700content: "\nfn main() {}\n"
701
702"#]],
703 );
704 }
705
706 #[test]
707 fn rustc_multifrontmatter() {
708 assert_err(
709 ScriptSource::parse(
710 r#"---
711---
712
713---
714//~^ ERROR: expected item, found `-`
715// FIXME(frontmatter): make this diagnostic better
716---
717
718// test that we do not parse another frontmatter block after the first one.
719
720fn main() {}
721"#,
722 ),
723 str!["only one frontmatter is supported"],
724 );
725 }
726
727 #[test]
728 fn rustc_shebang() {
729 assert_source(
730 r#"#!/usr/bin/env -S cargo -Zscript
731---
732[dependencies]
733clap = "4"
734---
735
736//@ check-pass
737
738// Shebangs on a file can precede a frontmatter.
739
740fn main () {}
741"#,
742 str![[r##"
743shebang: "#!/usr/bin/env -S cargo -Zscript\n"
744info: None
745frontmatter: "[dependencies]\nclap = \"4\"\n"
746content: "\n//@ check-pass\n\n// Shebangs on a file can precede a frontmatter.\n\nfn main () {}\n"
747
748"##]],
749 );
750 }
751
752 #[test]
753 fn rustc_unclosed_1() {
754 assert_err(
755 ScriptSource::parse(
756 r#"----cargo
757//~^ ERROR: unclosed frontmatter
758
759// This test checks that the #! characters can help us recover a frontmatter
760// close. There should not be a "missing `main` function" error as the rest
761// are properly parsed.
762
763fn main() {}
764"#,
765 ),
766 str!["no closing `----` found for frontmatter"],
767 );
768 }
769
770 #[test]
771 fn rustc_unclosed_2() {
772 assert_err(
773 ScriptSource::parse(
774 r#"----cargo
775//~^ ERROR: unclosed frontmatter
776//~| ERROR: frontmatters are experimental
777
778//@ compile-flags: --crate-type lib
779
780// Leading whitespace on the feature line prevents recovery. However
781// the dashes quoted will not be used for recovery and the entire file
782// should be treated as within the frontmatter block.
783
784fn foo() -> &str {
785 "----"
786}
787"#,
788 ),
789 str!["no closing `----` found for frontmatter"],
790 );
791 }
792
793 #[test]
794 fn rustc_unclosed_3() {
795 assert_err(
796 ScriptSource::parse(
797 r#"----cargo
798//~^ ERROR: frontmatter close does not match the opening
799
800//@ compile-flags: --crate-type lib
801
802// Unfortunate recovery situation. Not really preventable with improving the
803// recovery strategy, but this type of code is rare enough already.
804
805fn foo(x: i32) -> i32 {
806 ---x
807 //~^ ERROR: invalid preceding whitespace for frontmatter close
808 //~| ERROR: extra characters after frontmatter close are not allowed
809}
810//~^ ERROR: unexpected closing delimiter: `}`
811"#,
812 ),
813 str!["no closing `----` found for frontmatter"],
814 );
815 }
816
817 #[test]
818 fn rustc_unclosed_4() {
819 assert_err(
820 ScriptSource::parse(
821 r#"
822----cargo
823//~^ ERROR: unclosed frontmatter
824
825//! Similarly, a module-level content should allow for recovery as well (as
826//! per unclosed-1.rs)
827
828fn main() {}
829"#,
830 ),
831 str!["no closing `----` found for frontmatter"],
832 );
833 }
834
835 #[test]
836 fn rustc_unclosed_5() {
837 assert_err(
838 ScriptSource::parse(
839 r#"----cargo
840//~^ ERROR: unclosed frontmatter
841//~| ERROR: frontmatters are experimental
842
843// Similarly, a use statement should allow for recovery as well (as
844// per unclosed-1.rs)
845
846use std::env;
847
848fn main() {}
849"#,
850 ),
851 str!["no closing `----` found for frontmatter"],
852 );
853 }
854
855 #[test]
856 fn split_default() {
857 assert_source(
858 r#"fn main() {}
859"#,
860 str![[r#"
861shebang: None
862info: None
863frontmatter: None
864content: "fn main() {}\n"
865
866"#]],
867 );
868 }
869
870 #[test]
871 fn split_dependencies() {
872 assert_source(
873 r#"---
874[dependencies]
875time="0.1.25"
876---
877fn main() {}
878"#,
879 str![[r#"
880shebang: None
881info: None
882frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
883content: "fn main() {}\n"
884
885"#]],
886 );
887 }
888
889 #[test]
890 fn split_infostring() {
891 assert_source(
892 r#"---cargo
893[dependencies]
894time="0.1.25"
895---
896fn main() {}
897"#,
898 str![[r#"
899shebang: None
900info: "cargo"
901frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
902content: "fn main() {}\n"
903
904"#]],
905 );
906 }
907
908 #[test]
909 fn split_infostring_whitespace() {
910 assert_source(
911 r#"--- cargo
912[dependencies]
913time="0.1.25"
914---
915fn main() {}
916"#,
917 str![[r#"
918shebang: None
919info: "cargo"
920frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
921content: "fn main() {}\n"
922
923"#]],
924 );
925 }
926
927 #[test]
928 fn split_shebang() {
929 assert_source(
930 r#"#!/usr/bin/env cargo
931---
932[dependencies]
933time="0.1.25"
934---
935fn main() {}
936"#,
937 str![[r##"
938shebang: "#!/usr/bin/env cargo\n"
939info: None
940frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
941content: "fn main() {}\n"
942
943"##]],
944 );
945 }
946
947 #[test]
948 fn split_crlf() {
949 assert_source(
950 "#!/usr/bin/env cargo\r\n---\r\n[dependencies]\r\ntime=\"0.1.25\"\r\n---\r\nfn main() {}",
951 str![[r##"
952shebang: "#!/usr/bin/env cargo\r\n"
953info: None
954frontmatter: "[dependencies]\r\ntime=\"0.1.25\"\r\n"
955content: "fn main() {}"
956
957"##]],
958 );
959 }
960
961 #[test]
962 fn split_leading_newlines() {
963 assert_source(
964 r#"#!/usr/bin/env cargo
965
966
967
968---
969[dependencies]
970time="0.1.25"
971---
972
973
974fn main() {}
975"#,
976 str![[r##"
977shebang: "#!/usr/bin/env cargo\n"
978info: None
979frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
980content: "\n\nfn main() {}\n"
981
982"##]],
983 );
984 }
985
986 #[test]
987 fn split_attribute() {
988 assert_source(
989 r#"#[allow(dead_code)]
990---
991[dependencies]
992time="0.1.25"
993---
994fn main() {}
995"#,
996 str![[r##"
997shebang: None
998info: None
999frontmatter: None
1000content: "#[allow(dead_code)]\n---\n[dependencies]\ntime=\"0.1.25\"\n---\nfn main() {}\n"
1001
1002"##]],
1003 );
1004 }
1005
1006 #[test]
1007 fn split_extra_dash() {
1008 assert_source(
1009 r#"#!/usr/bin/env cargo
1010----------
1011[dependencies]
1012time="0.1.25"
1013----------
1014
1015fn main() {}"#,
1016 str![[r##"
1017shebang: "#!/usr/bin/env cargo\n"
1018info: None
1019frontmatter: "[dependencies]\ntime=\"0.1.25\"\n"
1020content: "\nfn main() {}"
1021
1022"##]],
1023 );
1024 }
1025
1026 #[test]
1027 fn split_too_few_dashes() {
1028 assert_err(
1029 ScriptSource::parse(
1030 r#"#!/usr/bin/env cargo
1031--
1032[dependencies]
1033time="0.1.25"
1034--
1035fn main() {}
1036"#,
1037 ),
1038 str!["found 2 `-` in rust frontmatter, expected at least 3"],
1039 );
1040 }
1041
1042 #[test]
1043 fn split_indent() {
1044 assert_source(
1045 r#"#!/usr/bin/env cargo
1046 ---
1047 [dependencies]
1048 time="0.1.25"
1049 ----
1050
1051fn main() {}
1052"#,
1053 str![[r##"
1054shebang: "#!/usr/bin/env cargo\n"
1055info: None
1056frontmatter: None
1057content: " ---\n [dependencies]\n time=\"0.1.25\"\n ----\n\nfn main() {}\n"
1058
1059"##]],
1060 );
1061 }
1062
1063 #[test]
1064 fn split_escaped() {
1065 assert_source(
1066 r#"#!/usr/bin/env cargo
1067-----
1068---
1069---
1070-----
1071
1072fn main() {}
1073"#,
1074 str![[r##"
1075shebang: "#!/usr/bin/env cargo\n"
1076info: None
1077frontmatter: "---\n---\n"
1078content: "\nfn main() {}\n"
1079
1080"##]],
1081 );
1082 }
1083
1084 #[test]
1085 fn split_invalid_escaped() {
1086 assert_err(
1087 ScriptSource::parse(
1088 r#"#!/usr/bin/env cargo
1089---
1090-----
1091-----
1092---
1093
1094fn main() {}
1095"#,
1096 ),
1097 str!["trailing characters found after frontmatter close"],
1098 );
1099 }
1100
1101 #[test]
1102 fn split_dashes_in_body() {
1103 assert_source(
1104 r#"#!/usr/bin/env cargo
1105---
1106Hello---
1107World
1108---
1109
1110fn main() {}
1111"#,
1112 str![[r##"
1113shebang: "#!/usr/bin/env cargo\n"
1114info: None
1115frontmatter: "Hello---\nWorld\n"
1116content: "\nfn main() {}\n"
1117
1118"##]],
1119 );
1120 }
1121
1122 #[test]
1123 fn split_mismatched_dashes() {
1124 assert_err(
1125 ScriptSource::parse(
1126 r#"#!/usr/bin/env cargo
1127---
1128[dependencies]
1129time="0.1.25"
1130----
1131fn main() {}
1132"#,
1133 ),
1134 str!["trailing characters found after frontmatter close"],
1135 );
1136 }
1137
1138 #[test]
1139 fn split_missing_close() {
1140 assert_err(
1141 ScriptSource::parse(
1142 r#"#!/usr/bin/env cargo
1143---
1144[dependencies]
1145time="0.1.25"
1146fn main() {}
1147"#,
1148 ),
1149 str!["no closing `---` found for frontmatter"],
1150 );
1151 }
1152
1153 #[track_caller]
1154 fn expand(source: &str) -> String {
1155 expand_manifest(source).unwrap_or_else(|err| panic!("{}", err))
1156 }
1157
1158 #[test]
1159 fn expand_default() {
1160 assert_data_eq!(expand(r#"fn main() {}"#), str![""]);
1161 }
1162
1163 #[test]
1164 fn expand_dependencies() {
1165 assert_data_eq!(
1166 expand(
1167 r#"---cargo
1168[dependencies]
1169time="0.1.25"
1170---
1171fn main() {}
1172"#
1173 ),
1174 str![[r#"
1175[dependencies]
1176time="0.1.25"
1177
1178"#]]
1179 );
1180 }
1181}