cargo/util/toml/
embedded.rs

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