cargo/util/toml/
embedded.rs

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