#![cfg_attr(docsrs, doc = include_str!("../README.md"))]
use proc_macro;
use syn::{ self, Token, parse::{ Parse, ParseStream } };
use proc_macro2::{
TokenStream, Span, Group, Delimiter, Literal, Ident, Punct, Spacing
};
use quote::{ ToTokens, TokenStreamExt };
mod err;
mod sql;
mod gen;
mod conv;
#[proc_macro]
pub fn include_sql(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let file_path = syn::parse_macro_input!(input as syn::LitStr);
let path = file_path.value();
match read_and_parse_sql_file(&path) {
Ok(included_sql) => {
let mut tokens = TokenStream::new();
output_include_bytes(&path, &mut tokens);
if !included_sql.stmt_list.is_empty() {
included_sql.to_tokens(&mut tokens);
}
tokens.into()
}
Err(err) => {
syn::Error::new(file_path.span(), err.to_string()).to_compile_error().into()
}
}
}
fn read_and_parse_sql_file(file_path: &str) -> err::Result<sql::IncludedSql> {
use std::path::PathBuf;
use std::fs;
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let mut path = PathBuf::from(&manifest_dir);
path.push(file_path);
let text = fs::read_to_string(&path)?;
let file_name = path.file_stem().unwrap_or_default().to_str().unwrap_or_default().replace('-', "_");
sql::parse(&text, &file_name)
}
fn output_include_bytes(file_path: &str, tokens: &mut TokenStream) {
use std::path::PathBuf;
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let mut path = PathBuf::from(&manifest_dir);
path.push(file_path);
tokens.append(Ident::new("const", Span::call_site()));
tokens.append(Ident::new("_", Span::call_site()));
tokens.append(Punct::new(':', Spacing::Alone));
tokens.append(Punct::new('&', Spacing::Alone));
let mut type_tokens = TokenStream::new();
type_tokens.append(Ident::new("u8", Span::call_site()));
tokens.append(Group::new(Delimiter::Bracket, type_tokens));
tokens.append(Punct::new('=', Spacing::Alone));
tokens.append(Ident::new("include_bytes", Span::call_site()));
tokens.append(Punct::new('!', Spacing::Alone));
let mut macro_tokens = TokenStream::new();
macro_tokens.append(Literal::string(path.to_str().unwrap()));
tokens.append(Group::new(Delimiter::Parenthesis, macro_tokens));
tokens.append(Punct::new(';', Spacing::Alone));
}
#[proc_macro]
pub fn index_of(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let IndexOfArgs { param_name, start_index, stmt_params } = syn::parse_macro_input!(input as IndexOfArgs);
let param_lookup = stmt_params.iter().position(|param| param == ¶m_name);
if let Some( pos ) = param_lookup {
let mut tokens = TokenStream::new();
tokens.append(Literal::usize_unsuffixed(start_index + pos));
tokens.into()
} else {
syn::Error::new(param_name.span(), "no such parameter").to_compile_error().into()
}
}
struct IndexOfArgs {
param_name : syn::Ident,
stmt_params : syn::punctuated::Punctuated<syn::Ident, Token![,]>,
start_index : usize,
}
impl Parse for IndexOfArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let param_name = input.parse()?;
input.parse::<Token![in]>()?;
let param_list;
syn::bracketed!(param_list in input);
input.parse::<Token![+]>()?;
let start_index : syn::LitInt = input.parse()?;
let start_index = start_index.base10_parse()?;
let stmt_params = param_list.parse_terminated(syn::Ident::parse)?;
Ok(Self { param_name, stmt_params, start_index })
}
}
#[proc_macro]
pub fn to_camel_case(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let in_arg = syn::parse_macro_input!(input as syn::Ident);
let in_name = in_arg.to_string();
let out_name = conv::to_camel_case(&in_name);
let mut tokens = TokenStream::new();
tokens.append(Ident::new(&out_name, in_arg.span()));
tokens.into()
}
#[proc_macro]
pub fn to_snake_case(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let in_arg = syn::parse_macro_input!(input as syn::Ident);
let in_name = in_arg.to_string();
let out_name = conv::to_snake_case(&in_name);
let mut tokens = TokenStream::new();
tokens.append(Ident::new(&out_name, in_arg.span()));
tokens.into()
}