include_cargo_toml2/
lib.rs1extern crate proc_macro;
4extern crate proc_macro2;
5extern crate quote;
6extern crate syn;
7extern crate toml;
8
9use crate::{
10 proc_macro::TokenStream,
11 proc_macro2::{Literal, Span as Span2, TokenStream as TokenStream2},
12 quote::{quote, ToTokens},
13 syn::{
14 parse::{Parse, ParseBuffer},
15 parse_macro_input,
16 token::Dot,
17 Error as SynError, Lit, LitBool,
18 },
19 toml::Value,
20};
21use std::env::var;
22use std::fs::read_to_string;
23use std::path::{Path, PathBuf};
24
25enum Index {
29 Int(usize),
30 Str(String),
31}
32
33struct TomlIndex(Vec<Index>);
37
38impl Parse for TomlIndex {
39 fn parse(input: &ParseBuffer) -> Result<Self, SynError> {
40 let mut another_one = true;
41 let mut index = Vec::new();
42 while another_one {
43 index.push(match input.parse::<Lit>() {
44 Ok(lit) => match lit {
45 Lit::Str(lit_str) => Index::Str(lit_str.value()),
46 Lit::Int(lit_int) => Index::Int(
47 lit_int
48 .base10_digits()
49 .parse()
50 .expect("Cannot parse literal integer"),
51 ),
52 _ => return Err(SynError::new(input.span(), "Unsupported literal")),
53 },
54 Err(e) => {
55 return Err(SynError::new(
56 input.span(),
57 format!("Cannot parse index item: {}", e),
58 ))
59 }
60 });
61 if let Err(_) = input.parse::<Dot>() {
62 another_one = false;
63 }
64 }
65 Ok(Self(index))
66 }
67}
68
69fn translate(input: Value) -> TokenStream2 {
71 match input {
72 Value::String(s) => Lit::new(Literal::string(&s)).to_token_stream().into(),
73 Value::Integer(i) => Lit::new(Literal::i64_suffixed(i)).to_token_stream().into(),
74 Value::Float(f) => Lit::new(Literal::f64_suffixed(f)).to_token_stream().into(),
75 Value::Datetime(d) => Lit::new(Literal::string(&d.to_string()))
76 .to_token_stream()
77 .into(),
78 Value::Boolean(b) => Lit::Bool(LitBool::new(b, Span2::call_site()))
79 .to_token_stream()
80 .into(),
81 Value::Array(a) => {
82 let mut ts = TokenStream2::new();
83 for value in a {
84 let v = translate(value);
85 ts.extend(quote! (#v,));
86 }
87 quote! ((#ts))
88 }
89 Value::Table(t) => {
90 let mut ts = TokenStream2::new();
91 for (key, value) in t {
92 let v = translate(value);
93 ts.extend(quote! ((#key, #v)));
94 }
95 quote! ((#ts))
96 }
97 }
98}
99
100#[proc_macro]
160pub fn include_toml(input: TokenStream) -> TokenStream {
161 let dir = var("CARGO_MANIFEST_DIR").expect("Environment variable CARGO_MANIFEST_DIR not set!");
162 let path = Path::new(&dir).join("Cargo.toml");
163
164 let cargo_toml = parse(&path);
165 let index: TomlIndex = parse_macro_input!(input);
166 let result = lookup(index, cargo_toml);
167
168 translate(result).into()
169}
170
171fn parse(path: &PathBuf) -> Value {
172 let content = read_to_string(path).expect("Cannot read Cargo.toml");
173 content.parse::<Value>().expect("Cannot parse Cargo.toml to json")
174}
175
176fn lookup(index: TomlIndex, mut toml: Value) -> Value {
177 for item in index.0 {
178 match item {
179 Index::Int(index) => {
180 toml = toml[index].clone();
181 }
182 Index::Str(index) => {
183 toml = toml[index].clone();
184 }
185 }
186 }
187 toml
188}
189
190#[cfg(test)]
191mod tests {
192 use crate::{lookup, parse};
193 use std::env::var;
194 use std::path::Path;
195 use toml::Value;
196
197 #[test]
198 fn should_parse_when_cargo_toml_is_valid() {
199 let dir = var("CARGO_MANIFEST_DIR").expect("Environment variable CARGO_MANIFEST_DIR must be set!");
200
201 let path = Path::new(&dir).join("Cargo.toml");
202 println!("{}", dir);
203 let toml = parse(&path);
204
205 assert_eq!("include-cargo-toml2", toml["package"]["name"].as_str().unwrap());
206 }
207
208 #[test]
209 fn should_fetch_attribute_when_cargo_toml_is_given() {
210 let cargo_toml = r#"
211 [package]
212 edition="2021"
213 version="0.1.0"
214 "#;
215
216 let toml: Value = cargo_toml.parse::<Value>().expect("Cannot parse Cargo.toml");
217 let index = syn::parse_str(r#""package"."version""#).unwrap();
218
219 let result = lookup(index, toml);
220
221 assert_eq!("0.1.0", result.as_str().unwrap());
222 }
223
224 #[test]
225 fn should_fetch_custom_attribute_when_cargo_toml_is_given() {
226 let cargo_toml = r#"
227 [package]
228 edition="2021"
229 [package.metadata.deb]
230 revision=4
231 "#;
232
233 let toml: Value = cargo_toml.parse::<Value>().expect("Cannot parse Cargo.toml");
234 let index = syn::parse_str(r#""package"."metadata"."deb"."revision""#).unwrap();
235
236 let result = lookup(index, toml);
237
238 assert_eq!(4, result.as_integer().unwrap());
239 }
240}