sh_builtin_bash_proc/
lib.rs1use proc_macro::TokenStream;
2
3use quote::format_ident;
4use quote::quote;
5use sh_builtin_common_util::attr_doc::parse_doc_comments;
6use sh_builtin_common_util::attr_shell_builtin::parse_named_function;
7use sh_builtin_common_util::common::ShellFunctionInfo;
8use sh_builtin_common_util::tokens::CString;
9use syn::parse::{Parse, ParseStream};
10use syn::{parse_macro_input, AttributeArgs, Ident, ItemFn, Result};
11
12#[derive(Clone, Debug)]
14struct BashFunction {
15 fn_ident: Ident,
16 doc: Vec<String>,
17
18 inner: ItemFn,
19}
20
21impl ShellFunctionInfo for BashFunction {
22 fn doc_comments(&self) -> &Vec<String> {
23 &self.doc
24 }
25
26 fn name(&self) -> String {
27 self.fn_ident.to_string()
28 }
29}
30
31impl Parse for BashFunction {
32 fn parse(input: ParseStream) -> Result<Self> {
33 let inner = ItemFn::parse(input)?;
34 Ok(BashFunction {
35 fn_ident: inner.sig.ident.clone(),
36 doc: parse_doc_comments(inner.attrs.iter()),
37 inner,
38 })
39 }
40}
41
42#[proc_macro_attribute]
55pub fn bash_builtin(args: TokenStream, input: TokenStream) -> TokenStream {
56 let args = parse_macro_input!(args as AttributeArgs);
57 let input = parse_macro_input!(input as BashFunction);
58 let inner = &input.inner;
59
60 let export_symbol_name = parse_named_function(&args).unwrap_or_else(|| input.name());
62 if !export_symbol_name
63 .chars()
64 .all(|c| c == '_' || c == ':' || c == '-' || c.is_ascii_alphanumeric())
65 {
66 let error = format!("'{}' is not a valid bash function name", export_symbol_name);
67 return TokenStream::from(quote! {
68 compile_error!(#error)
69 });
70 }
71
72 let func_wrapper = format_ident!("__bashexport__{}__wrapper", input.fn_ident);
74 let func = &input.inner.sig.ident;
75
76 let descriptor = format_ident!("__bashexport__{}__descriptor", input.fn_ident);
78 let descriptor_symbol = format!("{}_struct", export_symbol_name);
79
80 let descriptor_field_usage = CString(input.usage());
82 let descriptor_field_name = CString(export_symbol_name.clone());
83
84 TokenStream::from(quote! {
86
87 #[allow(non_upper_case_globals, non_snake_case)]
88 #[export_name = #descriptor_symbol]
89 #[link_section = ".data,shell_builtin"] pub static #descriptor: ::sh_builtin_bash::__internal::builtin_descriptor = ::sh_builtin_bash::__internal::builtin_descriptor {
91 name: (#descriptor_field_name) as _,
92 function: Some(#func_wrapper as _),
93 flags: ::sh_builtin_bash::__internal::BUILTIN_ENABLED as i32,
94 long_doc: 0 as _,
95 short_doc: (#descriptor_field_usage) as _,
96 handle: 0 as *mut i8,
97 };
98
99 #inner
100
101 #[allow(non_snake_case)]
102 pub unsafe extern "C" fn #func_wrapper(words: *mut ::sh_builtin_bash::__internal::WORD_LIST) -> ::std::os::raw::c_int {
103 let args: Vec<String> = if words.is_null() {
104 vec![]
105 } else {
106 (&unsafe { *words }).into()
107 };
108
109 match #func(&args) {
110 Ok(()) => ::sh_builtin_bash::__internal::EXECUTION_SUCCESS as _,
111 Err(err) => {
112 eprintln!("{}", err);
113 ::sh_builtin_bash::__internal::EXECUTION_FAILURE as _
114 }
115 }
116 }
117
118 })
119}