seedle-macros 1.8.2

cbor codegeneration
Documentation
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;

    // Make sure we have the required "file" attribute to parse the cddl
    let file_attr = attrs
        .file
        .ok_or_else(|| SynError::new(ident.span(), "File attribute required for modules"))?;

    // Read CDDL file and parse CDDL data
    let cddl = crate::cddl::parse(read_cddl(&file_attr)?)
        .map_err(|e| into_syn_error(file_attr.span(), e))?;

    // If caller wants meta then we print meta data about the parsed CDDL
    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];
        }}"#
    });

    // Get the prelude for the module
    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();

    // Generate bindings to export constants literals
    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())
}