1use super::trigger::TriggerPriority;
2use super::BotCmdAttr;
3use super::BotCmdAuthLvl;
4use super::BotCmdHandler;
5use super::BotCommand;
6use super::Error;
7use super::ErrorKind;
8use super::GetDebugInfo;
9use super::Result;
10use super::State;
11use super::Trigger;
12use super::TriggerAttr;
13use super::TriggerHandler;
14use itertools;
15use regex::Regex;
16use std;
17use std::borrow::Cow;
18use std::sync::Arc;
19use std::sync::RwLock;
20use util;
21use uuid::Uuid;
22use yaml_rust::Yaml;
23
24pub struct Module {
25 pub name: Cow<'static, str>,
26 uuid: Uuid,
27 features: Vec<ModuleFeature>,
28}
29
30impl PartialEq for Module {
31 fn eq(&self, other: &Self) -> bool {
32 if self.uuid == other.uuid {
33 debug_assert_eq!(self.name, other.name);
34 true
35 } else {
36 false
37 }
38 }
39}
40
41impl Eq for Module {}
42
43impl GetDebugInfo for Module {
44 type Output = ModuleInfo;
45
46 fn dbg_info(&self) -> ModuleInfo {
47 ModuleInfo {
48 name: self.name.to_string(),
49 }
50 }
51}
52
53pub struct ModuleBuilder {
54 name: Cow<'static, str>,
55 features: Vec<ModuleFeature>,
56}
57
58pub fn mk_module<'modl, S>(name: S) -> ModuleBuilder
59where
60 S: Into<Cow<'static, str>>,
61{
62 ModuleBuilder {
63 name: name.into(),
64 features: Default::default(),
65 }
66}
67
68impl ModuleBuilder {
69 pub fn command<'attr, Attrs, S1, S2, S3>(
70 mut self,
71 name: S1,
72 syntax: S2,
73 help_msg: S3,
74 auth_lvl: BotCmdAuthLvl,
75 handler: Box<BotCmdHandler>,
76 attrs: Attrs,
77 ) -> Self
78 where
79 S1: Into<Cow<'static, str>>,
80 S2: Into<Cow<'static, str>>,
81 S3: Into<Cow<'static, str>>,
82 Attrs: IntoIterator<Item = &'attr BotCmdAttr>,
83 {
84 let name = name.into();
85
86 assert!(
87 !name.as_ref().contains(char::is_whitespace),
88 "The name of the bot command {:?} contains whitespace, which is not allowed.",
89 name.as_ref()
90 );
91
92 let syntax = syntax.into();
93 let usage_yaml = util::yaml::parse_node(&syntax)
94 .unwrap()
95 .unwrap_or(Yaml::Hash(Default::default()));
96
97 let cmd = ModuleFeature::Command {
98 name: name,
99 usage_str: syntax,
100 usage_yaml,
101 help_msg: help_msg.into(),
102 auth_lvl: auth_lvl,
103 handler: handler.into(),
104 };
105
106 for attr in attrs {
107 match *attr {
108 }
110 }
111
112 self.features.push(cmd);
113
114 self
115 }
116
117 pub fn trigger<'attr, Attrs, Rx1, S1, S2>(
118 mut self,
119 name: S1,
120 regex_str: Rx1,
121 help_msg: S2,
122 priority: TriggerPriority,
123 handler: Box<TriggerHandler>,
124 attrs: Attrs,
125 ) -> Self
126 where
127 Rx1: util::regex::IntoRegexCI,
128 S1: Into<Cow<'static, str>>,
129 S2: Into<Cow<'static, str>>,
130 Attrs: IntoIterator<Item = &'attr TriggerAttr>,
131 {
132 for attr in attrs {
133 match attr {
134 &TriggerAttr::AlwaysWatching => unimplemented!(),
135 }
136 }
137
138 let trigger = ModuleFeature::Trigger {
139 name: name.into(),
140 regex: Arc::new(RwLock::new(regex_str.into_regex_ci().expect(
141 "Your regex was erroneous, it \
142 seems.",
143 ))),
144 help_msg: help_msg.into(),
145 handler: handler.into(),
146 priority,
147 uuid: Uuid::new_v4(),
148 };
149
150 self.features.push(trigger);
151
152 self
153 }
154
155 pub fn end(self) -> Module {
156 let ModuleBuilder { name, mut features } = self;
157
158 features.shrink_to_fit();
159
160 Module {
161 name: name,
162 uuid: Uuid::new_v4(),
163 features: features,
164 }
165 }
166}
167
168#[derive(Clone, Debug, Eq, PartialEq)]
170pub struct ModuleInfo {
171 name: String,
172}
173
174enum ModuleFeature {
175 Command {
176 name: Cow<'static, str>,
177 usage_str: Cow<'static, str>,
178 usage_yaml: Yaml,
179 help_msg: Cow<'static, str>,
180 auth_lvl: BotCmdAuthLvl,
181 handler: Arc<BotCmdHandler>,
182 },
183 Trigger {
184 name: Cow<'static, str>,
185 help_msg: Cow<'static, str>,
186 regex: Arc<RwLock<Regex>>,
187 handler: Arc<TriggerHandler>,
188 priority: TriggerPriority,
189 uuid: Uuid,
190 },
191}
192
193impl GetDebugInfo for ModuleFeature {
194 type Output = ModuleFeatureInfo;
195
196 fn dbg_info(&self) -> ModuleFeatureInfo {
197 ModuleFeatureInfo {
198 name: self.name().to_string(),
199 kind: match self {
200 &ModuleFeature::Command { .. } => ModuleFeatureKind::Command,
201 &ModuleFeature::Trigger { .. } => ModuleFeatureKind::Trigger,
202 },
203 }
204 }
205}
206
207#[derive(Clone, Debug, Eq, PartialEq)]
210pub struct ModuleFeatureInfo {
211 name: String,
212 kind: ModuleFeatureKind,
213}
214
215#[derive(Clone, Debug, Eq, PartialEq)]
216pub enum ModuleFeatureKind {
217 Command,
218 Trigger,
219}
220
221impl ModuleFeature {
222 pub fn name(&self) -> &str {
223 match self {
224 &ModuleFeature::Command { ref name, .. } => name.as_ref(),
225 &ModuleFeature::Trigger { ref name, .. } => name.as_ref(),
226 }
227 }
228
229 }
236
237impl GetDebugInfo for BotCommand {
238 type Output = ModuleFeatureInfo;
239
240 fn dbg_info(&self) -> ModuleFeatureInfo {
241 ModuleFeatureInfo {
242 name: self.name.to_string(),
243 kind: ModuleFeatureKind::Command,
244 }
245 }
246}
247
248impl GetDebugInfo for Trigger {
249 type Output = ModuleFeatureInfo;
250
251 fn dbg_info(&self) -> ModuleFeatureInfo {
252 ModuleFeatureInfo {
253 name: self.name.to_string(),
254 kind: ModuleFeatureKind::Trigger,
255 }
256 }
257}
258
259#[derive(Clone, Copy, Debug, Eq, PartialEq)]
260pub enum ModuleLoadMode {
261 Add,
264 Replace,
267 Force,
269}
270
271impl State {
272 pub fn load_modules<Modls>(
273 &mut self,
274 modules: Modls,
275 mode: ModuleLoadMode,
276 ) -> std::result::Result<(), Vec<Error>>
277 where
278 Modls: IntoIterator<Item = Module>,
279 {
280 let errs = itertools::flatten(modules.into_iter().filter_map(|module| {
281 match self.load_module(module, mode) {
282 Ok(()) => None,
283 Err(e) => Some(e),
284 }
285 })).collect::<Vec<Error>>();
286
287 if errs.is_empty() {
288 Ok(())
289 } else {
290 Err(errs)
291 }
292 }
293
294 pub fn load_module(
295 &mut self,
296 module: Module,
297 mode: ModuleLoadMode,
298 ) -> std::result::Result<(), Vec<Error>> {
299 debug!(
300 "Loading module {:?}, mode {:?}, providing {:?}",
301 module.name,
302 mode,
303 module
304 .features
305 .iter()
306 .map(GetDebugInfo::dbg_info)
307 .collect::<Vec<_>>()
308 );
309
310 if let Some(existing_module) = match (mode, self.modules.get(module.name.as_ref())) {
311 (_, None) | (ModuleLoadMode::Replace, _) | (ModuleLoadMode::Force, _) => None,
312 (ModuleLoadMode::Add, Some(old)) => Some(old),
313 } {
314 return Err(vec![
315 ErrorKind::ModuleRegistryClash(existing_module.dbg_info(), module.dbg_info())
316 .into(),
317 ]);
318 }
319
320 let module = Arc::new(module);
321
322 self.modules.insert(module.name.clone(), module.clone());
323
324 let errs = module
325 .features
326 .iter()
327 .filter_map(
328 |feature| match self.load_module_feature(module.clone(), feature, mode) {
329 Ok(()) => None,
330 Err(e) => Some(e),
331 },
332 )
333 .collect::<Vec<Error>>();
334
335 if errs.is_empty() {
336 Ok(())
337 } else {
338 Err(errs)
339 }
340 }
341
342 fn load_module_feature<'modl>(
343 &mut self,
344 provider: Arc<Module>,
345 feature: &'modl ModuleFeature,
346 mode: ModuleLoadMode,
347 ) -> Result<()> {
348 debug!("Loading module feature (f1): {:?}", feature.dbg_info());
349
350 if let Some(existing_feature) = match feature {
351 &ModuleFeature::Command { .. } => match (mode, self.commands.get(feature.name())) {
352 (_, None) | (ModuleLoadMode::Force, _) => None,
353 (ModuleLoadMode::Replace, Some(old)) if old.provider.name == provider.name => None,
354 (ModuleLoadMode::Replace, Some(old)) => Some(old.dbg_info()),
355 (ModuleLoadMode::Add, Some(old)) => Some(old.dbg_info()),
356 },
357 &ModuleFeature::Trigger { .. } => None,
358 } {
359 bail!(ErrorKind::ModuleFeatureRegistryClash(
360 existing_feature,
361 feature.dbg_info(),
362 ))
363 }
364
365 self.force_load_module_feature(provider, feature);
366
367 Ok(())
368 }
369
370 fn force_load_module_feature<'modl>(
371 &mut self,
372 provider: Arc<Module>,
373 feature: &'modl ModuleFeature,
374 ) {
375 debug!("Loading module feature (f2): {:?}", feature.dbg_info());
376
377 match feature {
378 &ModuleFeature::Command {
379 ref name,
380 ref handler,
381 ref auth_lvl,
382 ref usage_str,
383 ref usage_yaml,
384 ref help_msg,
385 } => {
386 self.commands.insert(
387 name.clone(),
388 BotCommand {
389 provider: provider,
390 name: name.clone(),
391 auth_lvl: auth_lvl.clone(),
392 handler: handler.clone(),
393 usage_str: usage_str.clone(),
394 usage_yaml: usage_yaml.clone(),
395 help_msg: help_msg.clone(),
396 },
397 );
398 }
399 &ModuleFeature::Trigger {
400 ref name,
401 ref regex,
402 ref handler,
403 ref help_msg,
404 priority,
405 uuid,
406 } => {
407 self.triggers
408 .entry(priority)
409 .or_insert_with(Default::default)
410 .push(Trigger {
411 provider,
412 name: name.clone(),
413 regex: regex.clone(),
414 handler: handler.clone(),
415 priority,
416 help_msg: help_msg.clone(),
417 uuid,
418 });
419 }
420 };
421 }
422}