atlas_package_metadata_macro/
lib.rs1#![cfg_attr(docsrs, feature(doc_cfg))]
3
4extern crate proc_macro;
5
6use {
7 proc_macro::TokenStream,
8 quote::quote,
9 std::{env, fs},
10 syn::parse_macro_input,
11 toml::value::{Array, Value},
12};
13
14#[proc_macro]
64pub fn package_metadata(input: TokenStream) -> TokenStream {
65 let key = parse_macro_input!(input as syn::LitStr);
66 let full_key = &key.value();
67 let path = format!("{}/Cargo.toml", env::var("CARGO_MANIFEST_DIR").unwrap());
68 let manifest = load_manifest(&path);
69 let value = package_metadata_value(&manifest, full_key);
70 toml_value_codegen(value).into()
71}
72
73fn package_metadata_value<'a>(manifest: &'a Value, full_key: &str) -> &'a Value {
74 let error_message =
75 format!("Key `package.metadata.{full_key}` must be present in the Cargo manifest");
76 manifest
77 .get("package")
78 .and_then(|package| package.get("metadata"))
79 .and_then(|metadata| {
80 let mut table = metadata
81 .as_table()
82 .expect("TOML property `package.metadata` must be a table");
83 let mut value = None;
84 for key in full_key.split('.') {
85 match table.get(key).expect(&error_message) {
86 Value::Table(t) => {
87 table = t;
88 }
89 v => {
90 value = Some(v);
91 }
92 }
93 }
94 value
95 })
96 .expect(&error_message)
97}
98
99fn toml_value_codegen(value: &Value) -> proc_macro2::TokenStream {
100 match value {
101 Value::String(s) => quote! {{ #s }},
102 Value::Integer(i) => quote! {{ #i }},
103 Value::Float(f) => quote! {{ #f }},
104 Value::Boolean(b) => quote! {{ #b }},
105 Value::Array(a) => toml_array_codegen(a),
106 Value::Datetime(d) => {
107 let date_str = toml::ser::to_string(d).unwrap();
108 quote! {{
109 #date_str
110 }}
111 }
112 Value::Table(_) => {
113 panic!("Tables are not supported");
114 }
115 }
116}
117
118fn toml_array_codegen(array: &Array) -> proc_macro2::TokenStream {
119 let statements = array
120 .iter()
121 .flat_map(|val| {
122 let val = toml_value_codegen(val);
123 quote! {
124 #val,
125 }
126 })
127 .collect::<proc_macro2::TokenStream>();
128 quote! {{
129 [
130 #statements
131 ]
132 }}
133}
134
135fn load_manifest(path: &str) -> Value {
136 let contents = fs::read_to_string(path)
137 .unwrap_or_else(|err| panic!("error occurred reading Cargo manifest {path}: {err}"));
138 toml::from_str(&contents)
139 .unwrap_or_else(|err| panic!("error occurred parsing Cargo manifest {path}: {err}"))
140}
141
142#[cfg(test)]
143mod tests {
144 use {super::*, std::str::FromStr};
145
146 #[test]
147 fn package_metadata_string() {
148 let copyright = "Copyright (c) 2024 ACME Inc.";
149 let manifest = toml::from_str(&format!(
150 r#"
151 [package.metadata]
152 copyright = "{copyright}"
153 "#
154 ))
155 .unwrap();
156 assert_eq!(
157 package_metadata_value(&manifest, "copyright")
158 .as_str()
159 .unwrap(),
160 copyright
161 );
162 }
163
164 #[test]
165 fn package_metadata_nested() {
166 let program_id = "11111111111111111111111111111111";
167 let manifest = toml::from_str(&format!(
168 r#"
169 [package.metadata.atlas]
170 program-id = "{program_id}"
171 "#
172 ))
173 .unwrap();
174 assert_eq!(
175 package_metadata_value(&manifest, "atlas.program-id")
176 .as_str()
177 .unwrap(),
178 program_id
179 );
180 }
181
182 #[test]
183 fn package_metadata_bool() {
184 let manifest = toml::from_str(
185 r#"
186 [package.metadata]
187 is-ok = true
188 "#,
189 )
190 .unwrap();
191 assert!(package_metadata_value(&manifest, "is-ok")
192 .as_bool()
193 .unwrap());
194 }
195
196 #[test]
197 fn package_metadata_int() {
198 let number = 123;
199 let manifest = toml::from_str(&format!(
200 r#"
201 [package.metadata]
202 number = {number}
203 "#
204 ))
205 .unwrap();
206 assert_eq!(
207 package_metadata_value(&manifest, "number")
208 .as_integer()
209 .unwrap(),
210 number
211 );
212 }
213
214 #[test]
215 fn package_metadata_float() {
216 let float = 123.456;
217 let manifest = toml::from_str(&format!(
218 r#"
219 [package.metadata]
220 float = {float}
221 "#
222 ))
223 .unwrap();
224 assert_eq!(
225 package_metadata_value(&manifest, "float")
226 .as_float()
227 .unwrap(),
228 float
229 );
230 }
231
232 #[test]
233 fn package_metadata_array() {
234 let array = ["1", "2", "3"];
235 let manifest = toml::from_str(&format!(
236 r#"
237 [package.metadata]
238 array = {array:?}
239 "#
240 ))
241 .unwrap();
242 assert_eq!(
243 package_metadata_value(&manifest, "array")
244 .as_array()
245 .unwrap()
246 .iter()
247 .map(|x| x.as_str().unwrap())
248 .collect::<Vec<_>>(),
249 array
250 );
251 }
252
253 #[test]
254 fn package_metadata_datetime() {
255 let datetime = "1979-05-27T07:32:00Z";
256 let manifest = toml::from_str(&format!(
257 r#"
258 [package.metadata]
259 datetime = {datetime}
260 "#
261 ))
262 .unwrap();
263 let toml_datetime = toml::value::Datetime::from_str(datetime).unwrap();
264 assert_eq!(
265 package_metadata_value(&manifest, "datetime")
266 .as_datetime()
267 .unwrap(),
268 &toml_datetime
269 );
270 }
271}