toml_const_macros 1.3.0

proc-macros for toml_const
Documentation
mod check;
mod instantiate;
mod normalize;
mod parse;

use std::path::PathBuf;

use instantiate::Instantiate;
use proc_macro as pm;
use proc_macro2::{self as pm2, Span};

use parse::{MacroInput, MultipleMacroInput};
use quote::{quote, ToTokens};
use syn::parse_macro_input;

use crate::{instantiate::ConstIdentDef, normalize::TomlValue};

/// Private map field for tables that can be represented as hashmaps
const MAP_FIELD: &str = "__map__";

/// Instantiate a const definition of the contents from a TOML file.
///
/// This macro resolves paths relative to the first parent directory containing a `Cargo.toml` file.
#[proc_macro]
pub fn toml_const(input: pm::TokenStream) -> pm::TokenStream {
    let input: MultipleMacroInput = parse_macro_input!(input);

    let manifest_path =
        std::env::var("CARGO_MANIFEST_DIR").expect("manifest dir variable must exist");
    let manifest_path = PathBuf::from(manifest_path);
    assert!(manifest_path.is_dir());
    let abs_manifest_path = manifest_path
        .canonicalize()
        .expect("path must canonicalize");

    let const_defs = input
        .0
        .iter()
        .map(|i| i.to_const_defs(&abs_manifest_path))
        .collect::<pm2::TokenStream>();

    let inner_calls = input
        .0
        .iter()
        .map(|i| {
            let absolute = i.to_abs_path(&abs_manifest_path);
            quote! {
                toml_const::toml_const_inner! {
                    #absolute
                }
            }
        })
        .collect::<pm2::TokenStream>();

    quote! {
        #const_defs

        #inner_calls
    }
    .into()
}

/// Instantiate a const definition of the contents from a TOML file.
///
/// If this macro is used in a workspace, it will resolve paths relative to the workspace's `Cargo.toml`.
///
/// If this macro is used in a crate, it will resolve paths relative to the crate's `Cargo.toml`.
#[proc_macro]
pub fn toml_const_ws(input: pm::TokenStream) -> pm::TokenStream {
    let input: MultipleMacroInput = parse_macro_input!(input);

    let ws_dir = std::env::current_dir()
        .expect("current directory must exist")
        .to_string_lossy()
        .to_string();

    let ws_path = PathBuf::from(ws_dir);
    assert!(ws_path.is_dir());
    let abs_ws_path = ws_path.canonicalize().expect("path must canonicalize");

    let const_defs = input
        .0
        .iter()
        .map(|i| i.to_const_defs(&abs_ws_path))
        .collect::<pm2::TokenStream>();

    // let collected = input.to_const_defs(&abs_ws_path);
    let inner_calls = input
        .0
        .iter()
        .map(|i| {
            let absolute = i.to_abs_path(&abs_ws_path);
            quote! {
                toml_const::toml_const_inner! {
                    #absolute
                }
            }
        })
        .collect::<pm2::TokenStream>();

    quote! {
        #const_defs

        #inner_calls
    }
    .into()
}

/// TODO: working title
#[proc_macro_attribute]
pub fn unwrap_datetime(_attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream {
    let syn::ItemMacro {
        attrs,
        ident,
        mac,
        semi_token,
    } = parse_macro_input!(item);

    syn::ItemMacro {
        attrs,
        ident,
        mac,
        semi_token,
    }
    .to_token_stream()
    .into()
}

/// Inner method call generated by public macros
#[doc(hidden)]
#[proc_macro]
pub fn toml_const_inner(input: pm::TokenStream) -> pm::TokenStream {
    let input: MacroInput = parse_macro_input!(input);

    let toml_table = match input.generate_toml_table() {
        Ok(tt) => tt,
        Err(e) => return e.into(),
    };

    match check::check_unauthorized_keys(&toml_table) {
        Ok(_) => (),
        Err(e) => return e.into(),
    }

    // perform normalization
    let toml_val_table = TomlValue::from(toml_table.clone());
    let toml_val_table = match toml_val_table.normalize() {
        Ok(n) => n,
        Err(e) => {
            return syn::Error::new(Span::call_site(), e.to_string())
                .to_compile_error()
                .into()
        }
    };
    let toml_val_table = toml_val_table.reduce();

    let mut toml_table_val = toml::Value::Table(toml_table);
    toml_val_table.normalize_toml(&mut toml_table_val);
    let toml_table = toml_table_val
        .as_table()
        .expect("conversion back to table must not fail");

    let definition_attrs = match input.definition_attrs() {
        Ok(def) => def,
        Err(e) => return e.to_compile_error().into(),
    };

    let table_definitions =
        toml_val_table.definition(&input.item_ident.to_string(), &definition_attrs);

    let instantiation =
        toml_table.instantiate(&input.item_ident.to_string(), &toml_val_table, vec![]);

    let pub_token = if input.is_pub {
        quote! {pub}
    } else {
        quote! {}
    };

    let static_const_token = match input.static_const {
        true => quote! {const},
        false => quote! {static},
    };

    let item_ident = &input.item_ident;
    let item_ty = input.item_ident.to_string().to_type_ident();

    let instance_attrs = match input.instantiation_attrs() {
        Ok(instance) => instance
            .into_iter()
            .map(|a| a.to_token_stream())
            .collect::<pm2::TokenStream>(),
        Err(e) => return e.to_compile_error().into(),
    };

    quote! {
        #table_definitions

        #instance_attrs
        #pub_token #static_const_token #item_ident: #item_ty = #instantiation;
    }
    .into()
}