extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;
extern crate syn;
extern crate toml;
use crate::{
proc_macro::TokenStream,
proc_macro2::{Literal, Span as Span2, TokenStream as TokenStream2},
quote::{quote, ToTokens},
syn::{
parse::{Parse, ParseBuffer},
parse_macro_input,
token::Dot,
Error as SynError, Lit, LitBool,
},
toml::Value,
};
use std::env::var;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
enum Index {
Int(usize),
Str(String),
}
struct TomlIndex(Vec<Index>);
impl Parse for TomlIndex {
fn parse(input: &ParseBuffer) -> Result<Self, SynError> {
let mut another_one = true;
let mut index = Vec::new();
while another_one {
index.push(match input.parse::<Lit>() {
Ok(lit) => match lit {
Lit::Str(lit_str) => Index::Str(lit_str.value()),
Lit::Int(lit_int) => Index::Int(
lit_int
.base10_digits()
.parse()
.expect("Cannot parse literal integer"),
),
_ => return Err(SynError::new(input.span(), "Unsupported literal")),
},
Err(e) => {
return Err(SynError::new(
input.span(),
format!("Cannot parse index item: {}", e),
))
}
});
if let Err(_) = input.parse::<Dot>() {
another_one = false;
}
}
Ok(Self(index))
}
}
fn translate(input: Value) -> TokenStream2 {
match input {
Value::String(s) => Lit::new(Literal::string(&s)).to_token_stream().into(),
Value::Integer(i) => Lit::new(Literal::i64_suffixed(i)).to_token_stream().into(),
Value::Float(f) => Lit::new(Literal::f64_suffixed(f)).to_token_stream().into(),
Value::Datetime(d) => Lit::new(Literal::string(&d.to_string()))
.to_token_stream()
.into(),
Value::Boolean(b) => Lit::Bool(LitBool::new(b, Span2::call_site()))
.to_token_stream()
.into(),
Value::Array(a) => {
let mut ts = TokenStream2::new();
for value in a {
let v = translate(value);
ts.extend(quote! (#v,));
}
quote! ((#ts))
}
Value::Table(t) => {
let mut ts = TokenStream2::new();
for (key, value) in t {
let v = translate(value);
ts.extend(quote! ((#key, #v)));
}
quote! ((#ts))
}
}
}
#[proc_macro]
pub fn include_toml(input: TokenStream) -> TokenStream {
let dir = var("CARGO_MANIFEST_DIR").expect("Environment variable CARGO_MANIFEST_DIR not set!");
let path = Path::new(&dir).join("Cargo.toml");
let cargo_toml = parse(&path);
let index: TomlIndex = parse_macro_input!(input);
let result = lookup(index, cargo_toml);
translate(result).into()
}
fn parse(path: &PathBuf) -> Value {
let content = read_to_string(path).expect("Cannot read Cargo.toml");
content.parse::<Value>().expect("Cannot parse Cargo.toml to json")
}
fn lookup(index: TomlIndex, mut toml: Value) -> Value {
for item in index.0 {
match item {
Index::Int(index) => {
toml = toml[index].clone();
}
Index::Str(index) => {
toml = toml[index].clone();
}
}
}
toml
}
#[cfg(test)]
mod tests {
use crate::{lookup, parse};
use std::env::var;
use std::path::Path;
use toml::Value;
#[test]
fn should_parse_when_cargo_toml_is_valid() {
let dir = var("CARGO_MANIFEST_DIR").expect("Environment variable CARGO_MANIFEST_DIR must be set!");
let path = Path::new(&dir).join("Cargo.toml");
println!("{}", dir);
let toml = parse(&path);
assert_eq!("include-cargo-toml2", toml["package"]["name"].as_str().unwrap());
}
#[test]
fn should_fetch_attribute_when_cargo_toml_is_given() {
let cargo_toml = r#"
[package]
edition="2021"
version="0.1.0"
"#;
let toml: Value = cargo_toml.parse::<Value>().expect("Cannot parse Cargo.toml");
let index = syn::parse_str(r#""package"."version""#).unwrap();
let result = lookup(index, toml);
assert_eq!("0.1.0", result.as_str().unwrap());
}
#[test]
fn should_fetch_custom_attribute_when_cargo_toml_is_given() {
let cargo_toml = r#"
[package]
edition="2021"
[package.metadata.deb]
revision=4
"#;
let toml: Value = cargo_toml.parse::<Value>().expect("Cannot parse Cargo.toml");
let index = syn::parse_str(r#""package"."metadata"."deb"."revision""#).unwrap();
let result = lookup(index, toml);
assert_eq!(4, result.as_integer().unwrap());
}
}