cargo/util/toml/embedded.rs
1use cargo_util_schemas::manifest::PackageName;
2
3use crate::CargoResult;
4use crate::util::frontmatter::ScriptSource;
5use crate::util::restricted_names;
6
7pub(super) fn expand_manifest(content: &str) -> CargoResult<String> {
8 let source = ScriptSource::parse(content)?;
9 if let Some(span) = source.frontmatter_span() {
10 match source.info() {
11 Some("cargo") | None => {}
12 Some(other) => {
13 if let Some(remainder) = other.strip_prefix("cargo,") {
14 anyhow::bail!(
15 "cargo does not support frontmatter infostring attributes like `{remainder}` at this time"
16 )
17 } else {
18 anyhow::bail!(
19 "frontmatter infostring `{other}` is unsupported by cargo; specify `cargo` for embedding a manifest"
20 )
21 }
22 }
23 }
24
25 // Include from file start to frontmatter end when we parse the TOML to get line numbers
26 // correct and so if a TOML error says "entire file", it shows the existing content, rather
27 // than blank lines.
28 //
29 // HACK: Since frontmatter open isn't valid TOML, we insert a comment
30 let mut frontmatter = content[0..span.end].to_owned();
31 let open_span = source.open_span().unwrap();
32 frontmatter.insert(open_span.start, '#');
33 Ok(frontmatter)
34 } else {
35 // Consider the shebang to be part of the frontmatter
36 // so if a TOML error says "entire file", it shows the existing content, rather
37 // than blank lines.
38 let span = source.shebang_span().unwrap_or(0..0);
39 Ok(content[span].to_owned())
40 }
41}
42
43/// Ensure the package name matches the validation from `ops::cargo_new::check_name`
44pub fn sanitize_name(name: &str) -> String {
45 let placeholder = if name.contains('_') {
46 '_'
47 } else {
48 // Since embedded manifests only support `[[bin]]`s, prefer arrow-case as that is the
49 // more common convention for CLIs
50 '-'
51 };
52
53 let mut name = PackageName::sanitize(name, placeholder).into_inner();
54
55 loop {
56 if restricted_names::is_keyword(&name) {
57 name.push(placeholder);
58 } else if restricted_names::is_conflicting_artifact_name(&name) {
59 // Being an embedded manifest, we always assume it is a `[[bin]]`
60 name.push(placeholder);
61 } else if name == "test" {
62 name.push(placeholder);
63 } else if restricted_names::is_windows_reserved(&name) {
64 // Go ahead and be consistent across platforms
65 name.push(placeholder);
66 } else {
67 break;
68 }
69 }
70
71 name
72}
73
74#[cfg(test)]
75mod test {
76 use snapbox::assert_data_eq;
77 use snapbox::str;
78
79 use super::*;
80
81 #[track_caller]
82 fn expand(source: &str) -> String {
83 expand_manifest(source).unwrap_or_else(|err| panic!("{}", err))
84 }
85
86 #[test]
87 fn expand_default() {
88 assert_data_eq!(expand(r#"fn main() {}"#), str![""]);
89 }
90
91 #[test]
92 fn expand_dependencies() {
93 assert_data_eq!(
94 expand(
95 r#"---cargo
96[dependencies]
97time="0.1.25"
98---
99fn main() {}
100"#
101 ),
102 str![[r##"
103#---cargo
104[dependencies]
105time="0.1.25"
106
107"##]]
108 );
109 }
110}