sh_builtin_bash_proc/
lib.rs

1use 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/// Struct for a function exported as a dynamically loaded bash builtin.
13#[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/// Exports the current function as a Bash shell builtin function.
43/// This requires the crate to be built as a cdylib.
44///
45/// # Example
46/// ```compile
47/// # use sh_builtin_bash_proc::bash_builtin;
48/// #[bash_builtin(function = "hello_world")]
49/// fn hello(args: &Vec<String>) -> std::result::Result<(), Box<dyn std::error::Error>> {
50///     println!("Hello, world!");
51///     Ok(())
52/// }
53/// ```
54#[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    // Get the desired shell function name and validate it.
61    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    // Get the identifiers for the function wrapper and the function itself.
73    let func_wrapper = format_ident!("__bashexport__{}__wrapper", input.fn_ident);
74    let func = &input.inner.sig.ident;
75
76    // Get the identifier and linker symbol for the builtin's descriptor struct.
77    let descriptor = format_ident!("__bashexport__{}__descriptor", input.fn_ident);
78    let descriptor_symbol = format!("{}_struct", export_symbol_name);
79
80    // Generate the contents of the descriptor fields.
81    let descriptor_field_usage = CString(input.usage());
82    let descriptor_field_name = CString(export_symbol_name.clone());
83
84    // Generate the boilerplate.
85    TokenStream::from(quote! {
86
87        #[allow(non_upper_case_globals, non_snake_case)]
88        #[export_name = #descriptor_symbol]
89        #[link_section = ".data,shell_builtin"] // FIXME: Does this actually work on Linux?
90        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}