use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro_error::{abort, proc_macro_error};
use quote::{quote, ToTokens};
#[proc_macro_attribute]
pub fn export_cmd(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let cmd_name = attr.to_string();
let export_cmd_fn = syn::Ident::new(&format!("export_cmd_{}", cmd_name), Span::call_site());
let orig_function: syn::ItemFn = syn::parse2(item.into()).unwrap();
let fn_ident = &orig_function.sig.ident;
let mut new_functions = orig_function.to_token_stream();
new_functions.extend(quote! (
fn #export_cmd_fn() {
export_cmd(#cmd_name, #fn_ident);
}
));
new_functions.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn use_custom_cmd(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let item: proc_macro2::TokenStream = item.into();
let mut cmd_fns = vec![];
for t in item {
if let TokenTree::Punct(ref ch) = t {
if ch.as_char() != ',' {
abort!(t, "only comma is allowed");
}
} else if let TokenTree::Ident(cmd) = t {
let cmd_fn = syn::Ident::new(&format!("export_cmd_{}", cmd), Span::call_site());
cmd_fns.push(cmd_fn);
} else {
abort!(t, "expect a list of comma separated commands");
}
}
quote! (
#(#cmd_fns();)*
)
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn use_builtin_cmd(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let item: proc_macro2::TokenStream = item.into();
let mut ret = TokenStream::new();
for t in item {
if let TokenTree::Punct(ref ch) = t {
if ch.as_char() != ',' {
abort!(t, "only comma is allowed");
}
} else if let TokenTree::Ident(cmd) = t {
let cmd_name = cmd.to_string();
let cmd_fn = syn::Ident::new(&format!("builtin_{}", cmd_name), Span::call_site());
ret.extend(quote!(::cmd_lib_cf::export_cmd(#cmd_name, ::cmd_lib_cf::#cmd_fn);));
} else {
abort!(t, "expect a list of comma separated commands");
}
}
ret.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn run_cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let cmds = lexer::Lexer::new(input.into()).scan().parse(false);
quote! ({
use ::cmd_lib_cf::AsOsStr;
#cmds.run_cmd()
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn run_fun(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let cmds = lexer::Lexer::new(input.into()).scan().parse(false);
quote! ({
use ::cmd_lib_cf::AsOsStr;
#cmds.run_fun()
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn spawn(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let cmds = lexer::Lexer::new(input.into()).scan().parse(true);
quote! ({
use ::cmd_lib_cf::AsOsStr;
#cmds.spawn(false)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn spawn_with_output(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let cmds = lexer::Lexer::new(input.into()).scan().parse(true);
quote! ({
use ::cmd_lib_cf::AsOsStr;
#cmds.spawn_with_output()
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
::cmd_lib_cf::log::error!("{}", #msg)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_warn(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
::cmd_lib_cf::log::warn!("{}", #msg)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_echo(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
println!("{}", #msg)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_info(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
::cmd_lib_cf::log::info!("{}", #msg)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
::cmd_lib_cf::log::debug!("{}", #msg)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_trace(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
::cmd_lib_cf::log::trace!("{}", #msg)
})
.into()
}
#[proc_macro]
#[proc_macro_error]
pub fn cmd_die(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let msg = parse_msg(input.into());
quote!({
use ::cmd_lib_cf::AsOsStr;
::cmd_lib_cf::log::error!("FATAL: {}", #msg);
std::process::exit(1)
})
.into()
}
fn parse_msg(input: TokenStream) -> TokenStream {
let mut iter = input.into_iter();
let mut output = TokenStream::new();
let mut valid = false;
if let Some(ref tt) = iter.next() {
if let TokenTree::Literal(lit) = tt {
let s = lit.to_string();
if s.starts_with('\"') || s.starts_with('r') {
let str_lit = lexer::scan_str_lit(lit);
output.extend(quote!(#str_lit));
valid = true;
}
}
if !valid {
abort!(tt, "invalid format: expect string literal");
}
if let Some(tt) = iter.next() {
abort!(
tt,
"expect string literal only, found extra {}",
tt.to_string()
);
}
}
output
}
mod lexer;
mod parser;