asimov_module_kit/module/
cargo_toml.rs1use alloc::string::String;
6use thiserror::Error;
7use toml_edit::{Array, DocumentMut, Item, Table, value};
8
9#[derive(Debug, Error)]
10pub enum CargoTomlError {
11 #[error("failed to parse Cargo.toml: {0}")]
12 Parse(#[from] toml_edit::TomlError),
13
14 #[error("`[bin]` in Cargo.toml is not an array of tables")]
15 BinNotArrayOfTables,
16}
17
18pub fn insert_bin(
20 doc: &mut DocumentMut,
21 name: &str,
22 path: &str,
23 required_features: &[String],
24) -> Result<(), CargoTomlError> {
25 let mut bin = Table::new();
26 bin.insert("name", value(name));
27 bin.insert("path", value(path));
28 if !required_features.is_empty() {
29 let features: Array = required_features.iter().map(String::as_str).collect();
30 bin.insert("required-features", value(features));
31 }
32
33 doc.entry("bin")
34 .or_insert(Item::ArrayOfTables(Default::default()))
35 .as_array_of_tables_mut()
36 .ok_or(CargoTomlError::BinNotArrayOfTables)?
37 .push(bin);
38
39 Ok(())
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use alloc::string::ToString;
46
47 #[test]
48 fn inserts_into_existing_bin_array() {
49 let toml = r#"# comment kept as-is
50[package]
51name = "asimov-widget-module"
52
53[[bin]]
54name = "asimov-widget-emitter"
55path = "src/emitter/main.rs"
56required-features = ["cli"]
57"#;
58 let mut doc: DocumentMut = toml.parse().unwrap();
59 insert_bin(
60 &mut doc,
61 "asimov-widget-fetcher",
62 "src/fetcher/main.rs",
63 &[String::from("cli")],
64 )
65 .unwrap();
66
67 let rendered = doc.to_string();
68 assert!(rendered.contains("# comment kept as-is"));
69 assert!(rendered.contains("name = \"asimov-widget-emitter\""));
70 assert!(rendered.contains("name = \"asimov-widget-fetcher\""));
71 assert!(rendered.contains("path = \"src/fetcher/main.rs\""));
72
73 let bins = doc["bin"].as_array_of_tables().unwrap();
74 assert_eq!(bins.len(), 2);
75 }
76
77 #[test]
78 fn creates_bin_array_when_absent() {
79 let toml = "[package]\nname = \"asimov-widget-module\"\n";
80 let mut doc: DocumentMut = toml.parse().unwrap();
81 insert_bin(
82 &mut doc,
83 "asimov-widget-emitter",
84 "src/emitter/main.rs",
85 &[],
86 )
87 .unwrap();
88
89 let bins = doc["bin"].as_array_of_tables().unwrap();
90 assert_eq!(bins.len(), 1);
91 assert_eq!(
92 bins.get(0).unwrap()["name"].as_str(),
93 Some("asimov-widget-emitter")
94 );
95 assert!(bins.get(0).unwrap().get("required-features").is_none());
96 }
97
98 #[test]
99 fn omits_required_features_when_empty() {
100 let mut doc: DocumentMut = "[package]\nname = \"x\"\n".parse().unwrap();
101 insert_bin(&mut doc, "x-emitter", "src/emitter/main.rs", &[]).unwrap();
102 let bins = doc["bin"].as_array_of_tables().unwrap();
103 assert!(bins.get(0).unwrap().get("required-features").is_none());
104 }
105
106 #[test]
107 fn includes_required_features_when_given() {
108 let mut doc: DocumentMut = "[package]\nname = \"x\"\n".parse().unwrap();
109 insert_bin(
110 &mut doc,
111 "x-emitter",
112 "src/emitter/main.rs",
113 &[String::from("cli"), String::from("std")],
114 )
115 .unwrap();
116 let bins = doc["bin"].as_array_of_tables().unwrap();
117 let features = bins.get(0).unwrap()["required-features"]
118 .as_array()
119 .unwrap();
120 assert_eq!(features.len(), 2);
121 }
122}