#![feature(proc_macro_diagnostic)]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro::{ Diagnostic, Level };
use proc_macro2::TokenStream as TokenStream2;
use syn::Result;
use syn::parse::{ Parse, ParseStream };
use quote::quote;
use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::rc::Rc;
struct Args {
key: syn::LitStr,
}
impl Parse for Args {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Args {
key: input.parse()?,
})
}
}
#[proc_macro]
pub fn metadata(tokens: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(tokens as Args);
let manifest = load_manifest();
let metadata = {
use toml::value::Table;
if let Some(package) = manifest.get("package") {
if let Some(metadata) = package.get("metadata") {
if let Some(tbl) = metadata.as_table() {
tbl.clone()
} else {
let msg = "TOML property has an incorrect type";
Diagnostic::new(Level::Error, msg)
.note(format!("key `package.metadata`"))
.note(format!("expected type `Table`"))
.note(format!("actual type `{}`", toml_typename(metadata)))
.emit();
panic!(msg)
}
} else {
Table::new()
}
} else {
Table::new()
}
};
let key = &args.key.value();
if let Some(value) = toml_get(&metadata, key) {
toml_codegen(&value).into()
} else {
let msg = "key not present in the Cargo manifest";
Diagnostic::new(Level::Error, msg)
.note(format!("key `package.metadata.{}`", key))
.emit();
panic!(msg)
}
}
fn load_manifest() -> toml::value::Value {
let path = format!("{}/Cargo.toml", env::var("CARGO_MANIFEST_DIR").unwrap());
let path = Rc::new(path);
let mut file = {
let path = Rc::clone(&path);
File::open(&*path)
.unwrap_or_else(move |err| {
let msg = "error occurred opening Cargo manifest";
Diagnostic::new(Level::Error, msg)
.note(format!("{}", err))
.note(format!("at `{}`", *path))
.emit();
panic!(msg)
})
};
let mut contents = String::new();
{
let path = Rc::clone(&path);
file.read_to_string(&mut contents)
.unwrap_or_else(|err| {
let msg = "error occurred reading Cargo manifest";
Diagnostic::new(Level::Error, msg)
.note(format!("{}", err))
.note(format!("at `{}`", *path))
.emit();
panic!(msg)
});
}
toml::from_str(&contents)
.unwrap_or_else(|err| {
let msg = "failed to parse Cargo manifest";
Diagnostic::new(Level::Error, msg)
.note(format!("{}", err))
.note(format!("at `{}`", *path))
.emit();
panic!(msg)
})
}
fn toml_get(mut tbl: &toml::value::Table, key: &str) -> Option<toml::value::Value> {
use toml::value::Value;
let key_parts: Vec<_> = key.split(".").collect();
let mut ret = Value::Table(tbl.clone());
for key in key_parts.iter() {
match tbl.get(key.clone())? {
Value::Table(tbl2) => {
tbl = tbl2;
},
value => {
ret = value.clone();
},
}
}
Some(ret)
}
fn toml_codegen(value: &toml::value::Value) -> TokenStream2 {
use toml::value::Value;
use Value::*;
match value {
String(s) => quote! {{
cargo_meta::id::<&str>(#s)
}},
Integer(i) => quote! {{
cargo_meta::id::<i64>(#i)
}},
Float(f) => quote! {{
cargo_meta::id::<f64>(#f)
}},
Boolean(b) => quote! {{
cargo_meta::id::<bool>(#b)
}},
Array(a) => toml_array_codegen(a),
Table(_t) => {
let msg = "TOML tables are not supported";
Diagnostic::new(Level::Error, msg)
.note(format!("this would require statically keyed hash tables"))
.emit();
panic!(msg)
},
Datetime(d) => {
let msg = "TOML dates are emitted as `&'static str`";
Diagnostic::new(Level::Warning, msg)
.note(format!("there are no const constructors for `Datetime`"))
.emit();
let date_str = toml::ser::to_string(d).unwrap();
quote! {{
#date_str
}}
},
}
}
fn toml_typename(value: &toml::value::Value) -> &'static str {
use toml::value::Value::*;
match value {
String(_) => "String",
Integer(_) => "Integer",
Float(_) => "Float",
Boolean(_) => "Boolean",
Datetime(_) => "Datetime",
Array(_) => "Array",
Table(_) => "Table",
}
}
fn toml_array_codegen(arr: &toml::value::Array) -> TokenStream2 {
let statements = emit_array_items(arr);
let emit = quote! {{
[
#statements
]
}};
emit.into()
}
fn emit_array_items(arr: &toml::value::Array) -> TokenStream2 {
arr.iter().flat_map(|val| {
let val = toml_codegen(val);
quote! {
#val,
}
}).collect()
}