weechat/
config_macros.rs

1#[doc(hidden)]
2#[macro_export]
3macro_rules! option_settings {
4    ($option_type:ident, $option_name:ident, $description:literal, $default:literal $(,)?) => {
5        $crate::paste::expr! {
6            weechat::config::[<$option_type OptionSettings>]::new(stringify!($option_name))
7                .description($description)
8                .default_value($default)
9        }
10    };
11    (Integer, $option_name:ident, $description:literal, $default:literal, $min:literal..$max:literal $(,)?) => {
12        weechat::config::IntegerOptionSettings::new(stringify!($option_name))
13            .description($description)
14            .default_value($default)
15            .min($min)
16            .max($max)
17    };
18    (Enum, $option_name:ident, $description:literal, $out_type:ty $(,)?) => {
19        weechat::config::IntegerOptionSettings::new(stringify!($option_name))
20            .description($description)
21            .default_value(<$out_type>::default() as i32)
22            .string_values(
23                <$out_type>::VARIANTS
24                    .iter()
25                    .map(|v| v.to_string())
26                    .collect::<Vec<String>>(),
27            );
28    };
29}
30
31#[doc(hidden)]
32#[macro_export]
33macro_rules! option_create {
34    ($option_type:ident, $option_weechat_type:ident, $option_name:ident, $($args:tt)*) => {
35        $crate::paste::item! {
36            fn [<create_option_ $option_name>](section: &mut weechat::config::SectionHandleMut) {
37                let option_settings = $crate::option_settings!($option_type, $option_name, $($args)*);
38                section.[<new_ $option_weechat_type:lower _option>](option_settings)
39                    .expect(&format!("Can't create option {}", stringify!($option_name)));
40            }
41        }
42    };
43}
44
45#[doc(hidden)]
46#[macro_export]
47macro_rules! option_getter {
48    ($option_type:ident, $name:ident, $string_name:expr, $description:literal, $output_type:ty) => {
49        $crate::paste::item! {
50            pub fn [<$name>](&self) -> $output_type {
51                if let weechat::config::ConfigOption::[<$option_type>](o) = self.0.search_option($string_name)
52                    .expect(&format!("Couldn't find option {} in section {}",
53                                     $string_name, self.0.name()))
54                {
55                    $output_type::from(o.value())
56                } else {
57                    panic!("Incorect option type for option {} in section {}",
58                           $string_name, self.0.name());
59                }
60            }
61        }
62    };
63
64    (EvaluatedString, $name:ident, $string_name:expr, $description:literal) => {
65        $crate::paste::item! {
66            pub fn [<$name>](&self) -> String {
67                let option = self.0.search_option($string_name)
68                    .expect(&format!("Couldn't find option {} in section {}",
69                                     $string_name, self.0.name()));
70
71                if let weechat::config::ConfigOption::String(o) = option {
72                    weechat::Weechat::eval_string_expression(&o.value())
73                        .expect(&format!(
74                            "Can't evaluate string expression for option {} in section {}",
75                            $string_name,
76                            self.0.name())
77                        )
78                } else {
79                    panic!("Incorect option type for option {} in section {}",
80                           $string_name, self.0.name());
81                }
82            }
83        }
84    };
85}
86
87#[doc(hidden)]
88#[macro_export]
89macro_rules! option {
90    (String, $name:ident, $description:literal, $($args:tt)*) => {
91        $crate::option_create!(String, String, $name, $description, $($args)*);
92        $crate::option_getter!(String, $name, stringify!($name), $description, String);
93    };
94
95    (Color, $name:ident, $description:literal, $($args:tt)*) => {
96        $crate::option_create!(Color, Color, $name, $description, $($args)*);
97        $crate::option_getter!(Color, $name, stringify!($name), $description, String);
98    };
99
100    (bool, $name:ident, $description:literal, $($args:tt)*) => {
101        $crate::option_create!(Boolean, Boolean, $name, $description, $($args)*);
102        $crate::option_getter!(Boolean, $name, stringify!($name), $description, bool);
103    };
104
105    (Integer, $name:ident, $description:literal, $($args:tt)*) => {
106        $crate::option_create!(Integer, Integer, $name, $description, $($args)*);
107        $crate::option_getter!(Integer, $name, stringify!($name), $description, i64);
108    };
109
110    (Enum, $name:ident, $description:literal, $out_type:ty $(,)?) => {
111        $crate::option_create!(Enum, Integer, $name, $description, $out_type);
112        $crate::option_getter!(Integer, $name, stringify!($name), $description, $out_type);
113    };
114
115    (EvaluatedString, $name:ident, $description:literal, $($args:tt)*) => {
116        $crate::option_create!(String, String, $name, $description, $($args)*);
117        $crate::option_getter!(EvaluatedString, $name, stringify!($name), $description);
118    };
119}
120
121#[doc(hidden)]
122#[macro_export]
123macro_rules! section {
124    ($section:ident { $($option_name:ident: $option_type:ident {$($option:tt)*}), * $(,)? }) => {
125        $crate::paste::item! {
126            pub struct [<$section:camel Section>]<'a>(weechat::config::SectionHandle<'a>);
127
128            impl<'a> [<$section:camel Section>]<'a> {
129                fn create(config: &mut Config) {
130                    let section_settings = weechat::config::ConfigSectionSettings::new(stringify!($section));
131
132                    let mut $section = config.new_section(section_settings)
133                        .expect(&format!("Can't create config section {}", stringify!($section)));
134
135                    [<$section:camel Section>]::create_options(&mut $section);
136                }
137
138                fn create_options(section: &mut weechat::config::SectionHandleMut) {
139                    $(
140                        [<$section:camel Section>]::[<create_option_ $option_name>](section);
141                    )*
142                }
143
144                $(
145                    $crate::option!($option_type, $option_name, $($option)*);
146                )*
147            }
148        }
149    }
150}
151
152#[doc(hidden)]
153#[macro_export]
154macro_rules! section_getter {
155    ($section:ident, $section_name:expr) => {
156        $crate::paste::item! {
157            pub fn $section(&self) -> [<$section:camel Section>] {
158                let section = self.0.search_section($section_name)
159                    .expect(&format!("Couldn't find section {}", $section_name));
160
161                $crate::paste::item! { [<$section:camel Section>](section) }
162            }
163        }
164    };
165}
166
167/// Declare a Weechat configuration file.
168///
169/// This will generate a struct called `Config` which wraps the Weechat struct
170/// of the same name. The generated struct will have accessors for every
171/// section and option that is declared.
172///
173/// The generated struct dereferences into the Weechat `Config` struct so
174/// additional sections and options can be created the usual way as well.
175///
176/// The config still needs to be created in the `init()` method of the plugin
177/// using `Config::new()`.
178///
179/// # Example
180/// ```
181/// # use weechat::{Weechat, config};
182/// use strum_macros::EnumVariantNames;
183///
184/// #[derive(EnumVariantNames)]
185/// #[strum(serialize_all = "kebab_case")]
186/// pub enum ServerBufferMerge {
187///     MergeWithCore,
188///     MergeWithoutCore,
189///     Independent,
190/// }
191///
192/// impl Default for ServerBufferMerge {
193///     fn default() -> Self {
194///         ServerBufferMerge::MergeWithCore
195///     }
196/// }
197///
198/// impl From<i32> for ServerBufferMerge {
199///     fn from(value: i32) -> Self {
200///         match value {
201///             0 => ServerBufferMerge::MergeWithCore,
202///             1 => ServerBufferMerge::MergeWithoutCore,
203///             2 => ServerBufferMerge::Independent,
204///             _ => unreachable!(),
205///         }
206///     }
207/// }
208///
209/// config!(
210///     // The name of the config
211///     "my-plugin",
212///     Section look {
213///         encrypted_room_sign: String {
214///             // Description.
215///             "A sign that is used to show that the current room is encrypted",
216///
217///             // Default value.
218///             "🔒",
219///         },
220///
221///         server_buffer: Enum {
222///             // Description.
223///             "Merge server buffers",
224///
225///             // This is an enum that needs to have the following traits
226///             // implemented:
227///             //    * Default - To define the default value of the option.
228///             //    * From<i32> - To convert the internal Weechat integer option
229///             //      to the enum.
230///             //    * VariantNames - To get the string representation of the
231///             //      enum variants. This is a trait defined in the strum library,
232///             //      a simple macro that derives an implementation is provided by
233///             //      strum.
234///             ServerBufferMerge,
235///         },
236///
237///         quote_fg: Color {
238///             // Description.
239///             "Foreground color for Matrix style blockquotes",
240///
241///             // Default value.
242///             "lightgreen",
243///         },
244///     },
245///
246///     Section network {
247///         username: EvaluatedString {
248///             // Description.
249///             "The username that will be used to log in to the server \
250///              (note: content is evaluated, see /help eval)",
251///
252///             // Default value.
253///             "",
254///         },
255///
256///         timeout: Integer {
257///             // Description.
258///             "A timeout (in seconds) that determines how long we should wait \
259///             for a request to finish before aborting.",
260///
261///             // Default value.
262///             30,
263///
264///             // The range that the value is allowed to have, note that both of
265///             // those are inclusive.
266///             0..100,
267///         },
268///
269///         autoconnect: bool {
270///             // Description.
271///             "Automatically connect to the server when Weechat is starting",
272///
273///             // Default value.
274///             false,
275///         },
276///    }
277/// );
278/// ```
279#[cfg(feature = "config_macro")]
280#[cfg_attr(feature = "docs", doc(cfg(config_macro)))]
281#[macro_export]
282macro_rules! config {
283    ($config_name:literal, $(Section $section:ident { $($option:tt)* }), * $(,)?) => {
284        #[allow(unused_imports)]
285        use weechat::strum::VariantNames;
286        #[allow(unused_imports)]
287        pub struct Config(weechat::config::Config);
288
289        impl std::ops::Deref for Config {
290            type Target = weechat::config::Config;
291
292            fn deref(&self) -> &Self::Target {
293                &self.0
294            }
295        }
296
297        impl std::ops::DerefMut for Config {
298            fn deref_mut(&mut self) -> &mut Self::Target {
299                &mut self.0
300            }
301        }
302
303        impl Config {
304            /// Create a new Weechat configuration file, returns a `Config` object.
305            /// The configuration file is freed when the `Config` object is dropped.
306            pub fn new() -> Result<Self, ()> {
307                let config = weechat::config::Config::new($config_name)?;
308                let mut config = Config(config);
309
310                config.create_sections();
311
312                Ok(config)
313            }
314
315            /// Create a new Weechat configuration file with the given reload
316            /// callback.
317            pub fn new_with_callback(
318                reload_callback: impl weechat::config::ConfigReloadCallback,
319            ) -> Result<Self, ()> {
320                let config = weechat::config::Config::new_with_callback(
321                    $config_name,
322                    reload_callback
323                )?;
324                let mut config = Config(config);
325
326                config.create_sections();
327
328                Ok(config)
329            }
330
331
332            $crate::paste::item! {
333                fn create_sections(&mut self) {
334                    $(
335                        $crate::paste::expr! { [<$section:camel Section>]::create(self) };
336                    )*
337                }
338            }
339
340            $(
341                $crate::section_getter!($section, stringify!($section));
342            )*
343        }
344
345        $(
346            $crate::section!($section { $($option)* });
347        )*
348    }
349}