Skip to main content

weechat_macro/
lib.rs

1#![recursion_limit = "256"]
2
3extern crate proc_macro;
4use proc_macro2::{Ident, Literal};
5use std::collections::HashMap;
6
7use syn::{
8    parse::{Parse, ParseStream, Result},
9    parse_macro_input,
10    punctuated::Punctuated,
11    Error, LitStr,
12};
13
14use quote::quote;
15
16struct WeechatPluginInfo {
17    plugin: syn::Ident,
18    name: (usize, Literal),
19    author: (usize, Literal),
20    description: (usize, Literal),
21    version: (usize, Literal),
22    license: (usize, Literal),
23}
24
25enum WeechatVariable {
26    Name(syn::LitStr),
27    Author(syn::LitStr),
28    Description(syn::LitStr),
29    Version(syn::LitStr),
30    License(syn::LitStr),
31}
32
33impl WeechatVariable {
34    #[allow(clippy::wrong_self_convention)]
35    fn to_pair(string: &LitStr) -> (usize, Literal) {
36        let mut bytes = string.value().into_bytes();
37        // Push a null byte since this goes to the C side.
38        bytes.push(0);
39
40        (bytes.len(), Literal::byte_string(&bytes))
41    }
42
43    fn as_pair(&self) -> (usize, Literal) {
44        match self {
45            WeechatVariable::Name(string) => WeechatVariable::to_pair(string),
46            WeechatVariable::Author(string) => WeechatVariable::to_pair(string),
47            WeechatVariable::Description(string) => WeechatVariable::to_pair(string),
48            WeechatVariable::Version(string) => WeechatVariable::to_pair(string),
49            WeechatVariable::License(string) => WeechatVariable::to_pair(string),
50        }
51    }
52
53    fn default_literal() -> (usize, Literal) {
54        let bytes = vec![0];
55        (bytes.len(), Literal::byte_string(&bytes))
56    }
57}
58
59impl Parse for WeechatVariable {
60    fn parse(input: ParseStream) -> Result<Self> {
61        let key: Ident = input.parse()?;
62        input.parse::<syn::Token![:]>()?;
63        let value = input.parse()?;
64
65        match key.to_string().to_lowercase().as_ref() {
66            "name" => Ok(WeechatVariable::Name(value)),
67            "author" => Ok(WeechatVariable::Author(value)),
68            "description" => Ok(WeechatVariable::Description(value)),
69            "version" => Ok(WeechatVariable::Version(value)),
70            "license" => Ok(WeechatVariable::License(value)),
71            _ => Err(Error::new(
72                key.span(),
73                "expected one of name, author, description, version or license",
74            )),
75        }
76    }
77}
78
79impl Parse for WeechatPluginInfo {
80    fn parse(input: ParseStream) -> Result<Self> {
81        let plugin: syn::Ident = input.parse().map_err(|_e| {
82            Error::new(
83                input.span(),
84                "a struct that implements the Plugin trait needs to be given",
85            )
86        })?;
87        input.parse::<syn::Token![,]>()?;
88
89        let args: Punctuated<WeechatVariable, syn::Token![,]> =
90            input.parse_terminated(WeechatVariable::parse)?;
91        let mut variables = HashMap::new();
92
93        for arg in args.pairs() {
94            let variable = arg.value();
95            match variable {
96                WeechatVariable::Name(_) => variables.insert("name", *variable),
97                WeechatVariable::Author(_) => variables.insert("author", *variable),
98                WeechatVariable::Description(_) => variables.insert("description", *variable),
99                WeechatVariable::Version(_) => variables.insert("version", *variable),
100                WeechatVariable::License(_) => variables.insert("license", *variable),
101            };
102        }
103
104        Ok(WeechatPluginInfo {
105            plugin,
106            name: variables.remove("name").map_or_else(
107                || {
108                    Err(Error::new(
109                        input.span(),
110                        "the name of the plugin needs to be defined",
111                    ))
112                },
113                |v| Ok(v.as_pair()),
114            )?,
115            author: variables
116                .remove("author")
117                .map_or_else(WeechatVariable::default_literal, |v| v.as_pair()),
118            description: variables
119                .remove("description")
120                .map_or_else(WeechatVariable::default_literal, |v| v.as_pair()),
121            version: variables
122                .remove("version")
123                .map_or_else(WeechatVariable::default_literal, |v| v.as_pair()),
124            license: variables
125                .remove("license")
126                .map_or_else(WeechatVariable::default_literal, |v| v.as_pair()),
127        })
128    }
129}
130
131/// Register a struct that implements the `Plugin` trait as a Weechat plugin.
132///
133/// This configures the Weechat init and end method as well as additonal plugin
134/// metadata.
135///
136/// # Example
137/// ```
138/// # use weechat::{plugin, Args, Weechat, Plugin};
139/// # struct SamplePlugin;
140/// # impl Plugin for SamplePlugin {
141/// #    fn init(weechat: &Weechat, _args: Args) -> Result<Self, ()> {
142/// #        Ok(SamplePlugin)
143/// #    }
144/// # }
145/// plugin!(
146///     SamplePlugin,
147///     name: "rust_sample",
148///     author: "poljar",
149///     description: "",
150///     version: "0.1.0",
151///     license: "MIT"
152/// );
153/// ```
154#[proc_macro]
155pub fn plugin(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
156    let WeechatPluginInfo {
157        plugin,
158        name,
159        author,
160        description,
161        version,
162        license,
163    } = parse_macro_input!(input as WeechatPluginInfo);
164
165    let (name_len, name) = name;
166    let (author_len, author) = author;
167    let (description_len, description) = description;
168    let (license_len, license) = license;
169    let (version_len, version) = version;
170
171    let result = quote! {
172        #[doc(hidden)]
173        #[no_mangle]
174        pub static weechat_plugin_api_version: [u8; weechat::weechat_sys::WEECHAT_PLUGIN_API_VERSION_LENGTH] =
175            *weechat::weechat_sys::WEECHAT_PLUGIN_API_VERSION;
176
177        #[doc(hidden)]
178        #[no_mangle]
179        pub static weechat_plugin_name: [u8; #name_len] = *#name;
180
181        #[doc(hidden)]
182        #[no_mangle]
183        pub static weechat_plugin_author: [u8; #author_len] = *#author;
184
185        #[doc(hidden)]
186        #[no_mangle]
187        pub static weechat_plugin_description: [u8; #description_len] = *#description;
188
189        #[doc(hidden)]
190        #[no_mangle]
191        pub static weechat_plugin_version: [u8; #version_len] = *#version;
192
193        #[doc(hidden)]
194        #[no_mangle]
195        pub static weechat_plugin_license: [u8; #license_len] = *#license;
196
197        #[doc(hidden)]
198        static mut __PLUGIN: Option<#plugin> = None;
199
200        /// This function is called when plugin is loaded by WeeChat.
201        ///
202        /// # Safety
203        /// This function needs to be an extern C function and it can't be
204        /// mangled, otherwise Weechat will not find the symbol.
205        #[doc(hidden)]
206        #[no_mangle]
207        pub unsafe extern "C" fn weechat_plugin_init(
208            plugin: *mut weechat::weechat_sys::t_weechat_plugin,
209            argc: weechat::libc::c_int,
210            argv: *mut *mut weechat::libc::c_char,
211        ) -> weechat::libc::c_int {
212            let weechat = unsafe {
213                Weechat::init_from_ptr(plugin)
214            };
215            let args = Args::new(argc, argv);
216            match <#plugin as ::weechat::Plugin>::init(&weechat, args) {
217                Ok(p) => {
218                    unsafe {
219                        __PLUGIN = Some(p);
220                    }
221                    return weechat::weechat_sys::WEECHAT_RC_OK;
222                }
223                Err(_e) => {
224                    return weechat::weechat_sys::WEECHAT_RC_ERROR;
225                }
226            }
227        }
228
229        /// This function is called when plugin is unloaded by WeeChat.
230        ///
231        /// # Safety
232        /// This function needs to be an extern C function and it can't be
233        /// mangled, otherwise Weechat will not find the symbol.
234        #[doc(hidden)]
235        #[no_mangle]
236        pub unsafe extern "C" fn weechat_plugin_end(
237            _plugin: *mut weechat::weechat_sys::t_weechat_plugin
238        ) -> weechat::libc::c_int {
239            unsafe {
240                __PLUGIN = None;
241                Weechat::free();
242            }
243            weechat::weechat_sys::WEECHAT_RC_OK
244        }
245
246        impl #plugin {
247            /// Get a reference to our created plugin.
248            ///
249            /// # Panic
250            ///
251            /// Panics if this is called before the plugin `init()` method is
252            /// done.
253            pub fn get() -> &'static mut #plugin {
254                unsafe {
255                    match &mut __PLUGIN {
256                        Some(p) => p,
257                        None => panic!("Weechat plugin isn't initialized"),
258                    }
259                }
260            }
261        }
262    };
263
264    result.into()
265}