use super::literals::{GroupedLiterals, LitToks};
use super::vtable::VTable;
use crate::cddl::Fields;
use crate::parse::Attributes;
use crate::parse::Language;
use crate::parse::Mod;
use crate::print::ffi;
use crate::print::structs::Struct;
use indoc::formatdoc;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::path::PathBuf;
use quote::quote;
use std::fs;
use syn::Error as SynError;
pub fn build(s: Mod, attrs: Attributes) -> syn::Result<TokenStream> {
let ident = s.ident;
let outer_attrs = s.attrs;
let language = attrs.language;
let prefix = attrs.prefix;
let file_attr = attrs
.file
.ok_or_else(|| SynError::new(ident.span(), "File attribute required for modules"))?;
let cddl = crate::cddl::parse(read_cddl(&file_attr)?)
.map_err(|e| into_syn_error(file_attr.span(), e))?;
if let Some(meta_attr) = attrs.meta {
resolve_from_manifest_dir(meta_attr.value())
.ok_or_else(|| SynError::new(meta_attr.span(), "Could not track root manifest dir"))
.and_then(|file| {
let structs = cddl
.iter()
.filter_map(crate::cddl::structs_borrowed)
.map(|(key, _)| format!("\"{}\"", key))
.collect::<Vec<String>>()
.join(",");
let literals = cddl
.iter()
.filter_map(crate::cddl::literals_borrowed)
.map(|(key, _)| format!("\"{}\"", key))
.collect::<Vec<String>>()
.join(",");
let json = formatdoc! {r#"{{
"meta": {{
"structs": [ {structs} ],
"literals": [ {literals} ]
}}
}}"#};
fs::write(file, json).map_err(|e| into_syn_error(meta_attr.span(), e))
})?;
}
let recursive_partial = proc_macro2::Literal::string(&formatdoc! {r#"
type RecursivePartial<T> = {{
[P in keyof T]?:
T[P] extends (infer U)[] ? RecursivePartial<U>[]:
T[P] extends object | undefined ? RecursivePartial<T[P]>:
T[P];
}}"#
});
let prelude = match language {
Language::C => quote! {},
Language::Typescript => {
let grouped_literals: Vec<GroupedLiterals> = cddl
.iter()
.filter_map(crate::cddl::literals_borrowed)
.fold(BTreeMap::new(), crate::cddl::fold_group_borrowed("-"))
.into_iter()
.map(|(group, literals)| GroupedLiterals {
language,
prefix: prefix.as_ref(),
group,
literals,
})
.collect();
quote! {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(typescript_custom_section)]
const WASM_PRELUDE: &'static str = #recursive_partial;
#(#grouped_literals)*
}
}
_ => quote! {},
};
let struct_nodes: Vec<(_, Cow<'_, Fields>)> = cddl
.iter()
.filter_map(crate::cddl::structs_borrowed)
.collect();
let vtable = match language {
Language::Typescript | Language::C => Some(VTable {
ident: &ident,
structs: struct_nodes.clone(),
language,
prefix: prefix.as_ref(),
}),
_ => None,
};
let structs: Vec<Struct> = struct_nodes
.into_iter()
.map(|(name, fields)| Struct {
name,
prefix: prefix.as_ref(),
fields,
language,
})
.collect();
let literals: Vec<TokenStream> = cddl
.iter()
.filter_map(crate::cddl::literals_borrowed)
.map(|(name, lit)| {
LitToks {
prefix: prefix.as_ref(),
name,
lit: lit.as_ref(),
language,
}
.into_token_stream()
})
.collect();
let ffi = ffi::build(language, prefix.as_ref());
Ok(quote! {
#(#outer_attrs)*
pub mod #ident {
#prelude
#vtable
#(#literals)*
#(#structs)*
#ffi
}
})
}
fn resolve_from_manifest_dir<P: Into<PathBuf>>(into_path: P) -> Option<PathBuf> {
let path = into_path.into();
match path.is_absolute() {
true => Some(path),
_ => std::env::var_os("CARGO_MANIFEST_DIR_OVERRIDE")
.or(std::env::var_os("CARGO_MANIFEST_DIR"))
.map(|manifest| {
let mut manifest = PathBuf::from(manifest);
manifest.push(path);
manifest
}),
}
}
fn read_cddl(lit: &syn::LitStr) -> syn::Result<String> {
lit.value()
.split(";")
.map(|v| {
let path = resolve_from_manifest_dir(v)
.ok_or_else(|| SynError::new(lit.span(), "Could not track root manifest dir"))?;
fs::read(path)
.map_err(|e| into_syn_error(lit.span(), e))
.and_then(|bytes| {
String::from_utf8(bytes).map_err(|e| into_syn_error(lit.span(), e))
})
})
.collect()
}
fn into_syn_error<E: std::error::Error>(span: Span, e: E) -> SynError {
SynError::new(span, e.to_string())
}