use std::{
path::{Path, PathBuf},
sync::atomic::{AtomicUsize, Ordering},
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use subplot::{ScenarioFilterElement, SCENARIO_FILTER_EVERYTHING, SCENARIO_FILTER_NOTHING};
use syn::{parse::Parse, Error, Ident, LitStr, Token};
pub(crate) struct CodegenArgs {
subplot_lit: LitStr,
subplot_path: PathBuf,
include: Option<LitStr>,
exclude: Option<LitStr>,
}
impl CodegenArgs {
fn new(subplot_lit: LitStr) -> syn::Result<Self> {
let manifest = std::env::var_os("CARGO_MANIFEST_DIR").ok_or_else(|| {
syn::Error::new_spanned(&subplot_lit, "Cannot find CARGO_MANIFEST_DIR")
})?;
let subplot_path = Path::new(&manifest).join(subplot_lit.value());
if let Err(e) = std::fs::metadata(&subplot_path) {
return Err(syn::Error::new_spanned(
&subplot_lit,
format!("{}: {}", subplot_path.display(), e),
));
}
let subplot_path = subplot_path.canonicalize().map_err(|e| {
syn::Error::new_spanned(&subplot_lit, format!("Failure canonicalising: {e}"))
})?;
Ok(Self {
subplot_lit,
subplot_path,
include: None,
exclude: None,
})
}
fn basedir(&self) -> syn::Result<&Path> {
self.subplot_path.parent().ok_or_else(|| {
syn::Error::new_spanned(
&self.subplot_lit,
"Cannot determine directory for subplot file",
)
})
}
}
impl Parse for CodegenArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let inputfile: LitStr = input.parse()?;
let mut ret = Self::new(inputfile)?;
while !input.is_empty() {
let _comma: Token![,] = input.parse()?;
let key: Ident = input.parse()?;
let _eq: Token![=] = input.parse()?;
match key.to_string().as_str() {
"include" => {
ret.include = Some(input.parse()?);
}
"exclude" => {
ret.exclude = Some(input.parse()?);
}
_ => {
return Err(syn::Error::new_spanned(key, "Unknown parameter"));
}
}
}
Ok(ret)
}
}
struct ItemList {
items: Vec<syn::Item>,
}
impl Parse for ItemList {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut items = Vec::new();
while !input.is_empty() {
items.push(input.parse()?);
}
Ok(Self { items })
}
}
impl ToTokens for ItemList {
fn to_tokens(&self, tokens: &mut TokenStream) {
for item in &self.items {
item.to_tokens(tokens);
}
}
}
static UNIQ_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn do_codegen(input: CodegenArgs) -> Result<TokenStream, Error> {
let mut filter = if input.include.is_some() {
SCENARIO_FILTER_NOTHING.clone()
} else {
SCENARIO_FILTER_EVERYTHING.clone()
};
if let Some(include) = input.include.as_ref() {
filter.push(ScenarioFilterElement::new(true, include.value().as_str()));
}
if let Some(exclude) = input.exclude.as_ref() {
filter.push(ScenarioFilterElement::new(false, exclude.value().as_str()));
}
let code = subplot::codegen_to_memory(&input.subplot_path, Some("rust"), &filter)
.map_err(|e| syn::Error::new_spanned(&input.subplot_lit, e))?;
let items: ItemList = syn::parse_str(&code.code)?;
let basedir = input.basedir()?;
let docimpl = code.doc.meta().document_impl("rust").ok_or_else(|| {
syn::Error::new_spanned(
&input.subplot_lit,
"Could not find rust impl in subplot document",
)
})?;
let files = code
.doc
.meta()
.markdown_filenames()
.iter()
.map(|md| basedir.join(md))
.chain(
code.doc
.meta()
.bindings_filenames()
.iter()
.map(|b| basedir.join(b)),
)
.chain(docimpl.functions_filenames().map(|f| basedir.join(f)))
.chain(Some(input.subplot_path.clone()))
.enumerate()
.map(|(i, p)| {
let ctr = UNIQ_COUNTER.fetch_add(1, Ordering::SeqCst);
let uniq_name =
syn::Ident::new(&format!("_SUBPLOT_INPUT_{i}_{ctr}"), Span::call_site());
let pp = format!("{}", p.display());
if std::fs::metadata(&p).is_ok() {
quote! {
const #uniq_name: (&str, &[u8]) = (#pp, include_bytes!(#pp));
}
} else {
quote! {
const #uniq_name: (&str, &[u8]) = (#pp, b"(builtin)");
}
}
})
.collect::<Vec<_>>();
Ok(quote! {
#(#files)*
#items
})
}