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}