1#[cfg(any(feature = "serde", test))]
4use serde::{Deserialize, Serialize};
5
6use crate::rug::integer::IntegerExt64;
7use crate::rug::{Complete, Integer};
8
9use crate::Consts;
10use crate::calculation_results::{Calculation, FormatOptions};
11use crate::calculation_tasks::{CalculationBase, CalculationJob};
12use crate::parse::parse;
13
14use std::fmt::Write;
15use std::ops::*;
16#[macro_export]
17macro_rules! impl_bitwise {
18 ($s_name:ident {$($s_fields:ident),*}, $t_name:ident, $fn_name:ident) => {
19 impl $t_name for $s_name {
20 type Output = Self;
21 fn $fn_name(self, rhs: Self) -> Self {
22 Self {
23 $($s_fields: self.$s_fields.$fn_name(rhs.$s_fields),)*
24 }
25 }
26 }
27 };
28}
29#[macro_export]
30macro_rules! impl_all_bitwise {
31 ($s_name:ident {$($s_fields:ident,)*}) => {impl_all_bitwise!($s_name {$($s_fields),*});};
32 ($s_name:ident {$($s_fields:ident),*}) => {
33 impl_bitwise!($s_name {$($s_fields),*}, BitOr, bitor);
34 impl_bitwise!($s_name {$($s_fields),*}, BitXor, bitxor);
35 impl_bitwise!($s_name {$($s_fields),*}, BitAnd, bitand);
36 impl Not for $s_name {
37 type Output = Self;
38 fn not(self) -> Self {
39 Self {
40 $($s_fields: self.$s_fields.not(),)*
41 }
42 }
43 }
44 };
45}
46
47#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
54#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
55pub struct Comment<Meta, S> {
56 pub meta: Meta,
58 pub calculation_list: S,
60 pub notify: Option<String>,
62 pub status: Status,
63 pub commands: Commands,
64 pub max_length: usize,
66 pub locale: String,
67}
68pub type CommentConstructed<Meta> = Comment<Meta, String>;
70pub type CommentExtracted<Meta> = Comment<Meta, Vec<CalculationJob>>;
72pub type CommentCalculated<Meta> = Comment<Meta, Vec<Calculation>>;
74
75#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
76#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
77#[non_exhaustive]
78pub struct Status {
79 pub already_replied_or_rejected: bool,
80 pub not_replied: bool,
81 pub number_too_big_to_calculate: bool,
82 pub no_factorial: bool,
83 pub reply_would_be_too_long: bool,
84 pub factorials_found: bool,
85 pub limit_hit: bool,
86}
87
88impl_all_bitwise!(Status {
89 already_replied_or_rejected,
90 not_replied,
91 number_too_big_to_calculate,
92 no_factorial,
93 reply_would_be_too_long,
94 factorials_found,
95 limit_hit,
96});
97#[allow(dead_code)]
98impl Status {
99 pub const NONE: Self = Self {
100 already_replied_or_rejected: false,
101 not_replied: false,
102 number_too_big_to_calculate: false,
103 no_factorial: false,
104 reply_would_be_too_long: false,
105 factorials_found: false,
106 limit_hit: false,
107 };
108 pub const ALREADY_REPLIED_OR_REJECTED: Self = Self {
109 already_replied_or_rejected: true,
110 ..Self::NONE
111 };
112 pub const NOT_REPLIED: Self = Self {
113 not_replied: true,
114 ..Self::NONE
115 };
116 pub const NUMBER_TOO_BIG_TO_CALCULATE: Self = Self {
117 number_too_big_to_calculate: true,
118 ..Self::NONE
119 };
120 pub const NO_FACTORIAL: Self = Self {
121 no_factorial: true,
122 ..Self::NONE
123 };
124 pub const REPLY_WOULD_BE_TOO_LONG: Self = Self {
125 reply_would_be_too_long: true,
126 ..Self::NONE
127 };
128 pub const FACTORIALS_FOUND: Self = Self {
129 factorials_found: true,
130 ..Self::NONE
131 };
132 pub const LIMIT_HIT: Self = Self {
133 limit_hit: true,
134 ..Self::NONE
135 };
136}
137
138#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
139#[cfg_attr(any(feature = "serde", test), derive(Serialize, Deserialize))]
140#[non_exhaustive]
141pub struct Commands {
142 #[cfg_attr(any(feature = "serde", test), serde(default))]
144 pub shorten: bool,
145 #[cfg_attr(any(feature = "serde", test), serde(default))]
147 pub steps: bool,
148 #[cfg_attr(any(feature = "serde", test), serde(default))]
150 pub nested: bool,
151 #[cfg_attr(any(feature = "serde", test), serde(default))]
153 pub termial: bool,
154 #[cfg_attr(any(feature = "serde", test), serde(default))]
156 pub no_note: bool,
157 #[cfg_attr(any(feature = "serde", test), serde(default))]
159 pub write_out: bool,
160}
161impl_all_bitwise!(Commands {
162 shorten,
163 steps,
164 nested,
165 termial,
166 no_note,
167 write_out,
168});
169#[allow(dead_code)]
170impl Commands {
171 pub const NONE: Self = Self {
172 shorten: false,
173 steps: false,
174 nested: false,
175 termial: false,
176 no_note: false,
177 write_out: false,
178 };
179 pub const SHORTEN: Self = Self {
180 shorten: true,
181 ..Self::NONE
182 };
183 pub const STEPS: Self = Self {
184 steps: true,
185 ..Self::NONE
186 };
187 pub const NESTED: Self = Self {
188 nested: true,
189 ..Self::NONE
190 };
191 pub const TERMIAL: Self = Self {
192 termial: true,
193 ..Self::NONE
194 };
195 pub const NO_NOTE: Self = Self {
196 no_note: true,
197 ..Self::NONE
198 };
199 pub const WRITE_OUT: Self = Self {
200 write_out: true,
201 ..Self::NONE
202 };
203}
204
205impl Commands {
206 fn contains_command_format(text: &str, command: &str) -> bool {
207 let pattern1 = format!("\\[{command}\\]");
208 let pattern2 = format!("[{command}]");
209 let pattern3 = format!("!{command}");
210 text.contains(&pattern1) || text.contains(&pattern2) || text.contains(&pattern3)
211 }
212
213 pub fn from_comment_text(text: &str) -> Self {
214 Self {
215 shorten: Self::contains_command_format(text, "short")
216 || Self::contains_command_format(text, "shorten"),
217 steps: Self::contains_command_format(text, "steps")
218 || Self::contains_command_format(text, "all"),
219 nested: Self::contains_command_format(text, "nest")
220 || Self::contains_command_format(text, "nested"),
221 termial: Self::contains_command_format(text, "termial")
222 || Self::contains_command_format(text, "triangle"),
223 no_note: Self::contains_command_format(text, "no note")
224 || Self::contains_command_format(text, "no\\_note")
225 || Self::contains_command_format(text, "no_note"),
226 write_out: Self::contains_command_format(text, "write_out")
227 || Self::contains_command_format(text, "write\\_out")
228 || Self::contains_command_format(text, "write_num")
229 || Self::contains_command_format(text, "write\\_num"),
230 }
231 }
232 pub fn overrides_from_comment_text(text: &str) -> Self {
233 Self {
234 shorten: !Self::contains_command_format(text, "long"),
235 steps: !(Self::contains_command_format(text, "no steps")
236 || Self::contains_command_format(text, "no_steps")
237 || Self::contains_command_format(text, "no\\_steps")),
238 nested: !(Self::contains_command_format(text, "no_nest")
239 || Self::contains_command_format(text, "no\\_nest")
240 || Self::contains_command_format(text, "multi")),
241 termial: !(Self::contains_command_format(text, "no termial")
242 || Self::contains_command_format(text, "no_termial")
243 || Self::contains_command_format(text, "no\\_termial")),
244 no_note: !Self::contains_command_format(text, "note"),
245 write_out: !(Self::contains_command_format(text, "dont_write_out")
246 || Self::contains_command_format(text, "dont\\_write\\_out")
247 || Self::contains_command_format(text, "normal num")
248 || Self::contains_command_format(text, "normal\\_num")),
249 }
250 }
251}
252
253macro_rules! contains_comb {
254 ($var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
256 $var.contains(concat!($start, $end)) || contains_comb!($var, [$($start_rest),*], [$end,$($end_rest),*]) || contains_comb!(@inner $var, [$start,$($start_rest),*], [$($end_rest),*])
257 };
258 (@inner $var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
260 $var.contains(concat!($start,$end)) || contains_comb!(@inner $var, [$start,$($start_rest),*], [$($end_rest),*])
261 };
262 ($var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt $(,)?]) => {
264 $var.contains(concat!($start, $end)) || contains_comb!($var, [$($start_rest),*], [$end])
265 };
266 ($var:ident, [$start:tt $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
268 $var.contains(concat!($start, $end)) || contains_comb!(@inner $var, [$start], [$($end_rest),*])
269 };
270 (@inner $var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt $(,)?]) => {
272 $var.contains(concat!($start,$end))
273 };
274 (@inner $var:ident, [$start:tt $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
276 $var.contains(concat!($start,$end)) || contains_comb!(@inner $var, [$start], [$($end_rest),*])
277 };
278 ($var:ident, [$start:tt $(,)?], [$end:tt $(,)?]) => {
280 $var.contains(concat!($start, $end))
281 };
282 (@inner $var:ident, [$start:tt $(,)?], [$end:tt $(,)?]) => {
284 $var.contains(concat!($start,$end))
285 };
286}
287
288impl<Meta> CommentConstructed<Meta> {
289 pub fn new(
291 comment_text: &str,
292 meta: Meta,
293 pre_commands: Commands,
294 max_length: usize,
295 locale: &str,
296 ) -> Self {
297 let command_overrides = Commands::overrides_from_comment_text(comment_text);
298 let commands: Commands =
299 (Commands::from_comment_text(comment_text) | pre_commands) & command_overrides;
300
301 let mut status: Status = Default::default();
302
303 let text = if Self::might_have_factorial(comment_text) {
304 comment_text.to_owned()
305 } else {
306 status.no_factorial = true;
307 String::new()
308 };
309
310 Comment {
311 meta,
312 notify: None,
313 calculation_list: text,
314 status,
315 commands,
316 max_length,
317 locale: locale.to_owned(),
318 }
319 }
320
321 fn might_have_factorial(text: &str) -> bool {
322 contains_comb!(
323 text,
324 [
325 "0",
326 "1",
327 "2",
328 "3",
329 "4",
330 "5",
331 "6",
332 "7",
333 "8",
334 "9",
335 ")",
336 "e",
337 "pi",
338 "phi",
339 "tau",
340 "π",
341 "ɸ",
342 "τ",
343 "infinity",
344 "inf",
345 "∞\u{303}",
346 "∞"
347 ],
348 ["!", "?"]
349 ) || contains_comb!(
350 text,
351 ["!"],
352 [
353 "0",
354 "1",
355 "2",
356 "3",
357 "4",
358 "5",
359 "6",
360 "7",
361 "8",
362 "9",
363 "(",
364 "e",
365 "pi",
366 "phi",
367 "tau",
368 "π",
369 "ɸ",
370 "τ",
371 "infinity",
372 "inf",
373 "∞\u{303}",
374 "∞"
375 ]
376 )
377 }
378
379 pub fn extract(self, consts: &Consts) -> CommentExtracted<Meta> {
381 let Comment {
382 meta,
383 calculation_list: comment_text,
384 notify,
385 mut status,
386 commands,
387 max_length,
388 locale,
389 } = self;
390 let mut pending_list: Vec<CalculationJob> = parse(
391 &comment_text,
392 commands.termial,
393 consts,
394 &consts
395 .locales
396 .get(&locale)
397 .unwrap_or(consts.locales.get(&consts.default_locale).unwrap())
398 .format
399 .number_format,
400 );
401
402 if commands.nested {
403 for calc in &mut pending_list {
404 Self::multi_to_nested(calc);
405 }
406 }
407
408 if pending_list.is_empty() {
409 status.no_factorial = true;
410 }
411
412 Comment {
413 meta,
414 calculation_list: pending_list,
415 notify,
416 status,
417 commands,
418 max_length,
419 locale,
420 }
421 }
422
423 fn multi_to_nested(mut calc: &mut CalculationJob) {
424 loop {
425 let level = calc.level.clamp(-1, 1);
426 let depth = calc.level.abs();
427 calc.level = level;
428 for _ in 1..depth {
429 let base = std::mem::replace(
430 &mut calc.base,
431 CalculationBase::Num(
432 crate::calculation_results::CalculationResult::ComplexInfinity,
433 ),
434 );
435 let new_base = CalculationBase::Calc(Box::new(CalculationJob {
436 base,
437 level,
438 negative: 0,
439 }));
440 let _ = std::mem::replace(&mut calc.base, new_base);
441 }
442 let CalculationBase::Calc(next) = &mut calc.base else {
443 return;
444 };
445 calc = next;
446 }
447 }
448
449 pub fn new_already_replied(meta: Meta, max_length: usize, locale: &str) -> Self {
451 let text = String::new();
452 let status: Status = Status {
453 already_replied_or_rejected: true,
454 ..Default::default()
455 };
456 let commands: Commands = Default::default();
457
458 Comment {
459 meta,
460 notify: None,
461 calculation_list: text,
462 status,
463 commands,
464 max_length,
465 locale: locale.to_owned(),
466 }
467 }
468}
469impl<Meta, S> Comment<Meta, S> {
470 pub fn add_status(&mut self, status: Status) {
471 self.status = self.status | status;
472 }
473}
474impl<Meta> CommentExtracted<Meta> {
475 pub fn calc(self, consts: &Consts) -> CommentCalculated<Meta> {
477 let Comment {
478 meta,
479 calculation_list: pending_list,
480 notify,
481 mut status,
482 commands,
483 max_length,
484 locale,
485 } = self;
486 let mut calculation_list: Vec<Calculation> = pending_list
487 .into_iter()
488 .flat_map(|calc| calc.execute(commands.steps, consts))
489 .filter_map(|x| {
490 if x.is_none() {
491 status.number_too_big_to_calculate = true;
492 };
493 x
494 })
495 .collect();
496
497 calculation_list.sort();
498 calculation_list.dedup();
499 calculation_list.sort_by_key(|x| x.steps.len());
500
501 if calculation_list.is_empty() {
502 status.no_factorial = true;
503 } else {
504 status.factorials_found = true;
505 }
506 Comment {
507 meta,
508 calculation_list,
509 notify,
510 status,
511 commands,
512 max_length,
513 locale,
514 }
515 }
516}
517impl<Meta> CommentCalculated<Meta> {
518 pub fn get_reply(&self, consts: &Consts) -> String {
520 let mut fell_back = false;
521 let locale = consts.locales.get(&self.locale).unwrap_or_else(|| {
522 fell_back = true;
523 consts.locales.get(&consts.default_locale).unwrap()
524 });
525 let mut note = self
526 .notify
527 .as_ref()
528 .map(|user| locale.notes.mention.replace("{mention}", user) + "\n\n")
529 .unwrap_or_default();
530
531 if fell_back {
532 let _ = note.write_str("Sorry, I currently don't speak ");
533 let _ = note.write_str(&self.locale);
534 let _ = note.write_str(". Maybe you could [teach me](https://github.com/tolik518/factorion-bot/blob/master/CONTRIBUTING.md#translation)? \n\n");
535 }
536
537 let too_big_number = Integer::u64_pow_u64(10, self.max_length as u64).complete();
538 let too_big_number = &too_big_number;
539
540 let multiple = self.add_note(consts, locale, &mut note, too_big_number);
541
542 let mut reply = self.add_factorials(
544 consts,
545 locale,
546 ¬e,
547 too_big_number,
548 FormatOptions {
549 force_shorten: self.commands.shorten,
550 write_out: self.commands.write_out,
551 ..FormatOptions::NONE
552 },
553 );
554
555 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length
557 && !self.commands.shorten
558 && !self
559 .calculation_list
560 .iter()
561 .all(|fact| fact.is_too_long(too_big_number))
562 {
563 if note.is_empty() && !self.commands.no_note {
564 if multiple {
565 let _ = note.write_str(&locale.notes.too_big_mult);
566 } else {
567 let _ = note.write_str(&locale.notes.too_big);
568 }
569 let _ = note.write_str("\n\n");
570 };
571 reply = self.add_factorials(
572 consts,
573 locale,
574 ¬e,
575 too_big_number,
576 FormatOptions {
577 write_out: self.commands.write_out,
578 ..FormatOptions::FORCE_SHORTEN
579 },
580 );
581 }
582
583 let note = if !self.commands.no_note {
584 locale.notes.tetration.clone().into_owned() + "\n\n"
585 } else {
586 String::new()
587 };
588 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length && !self.commands.steps
590 {
591 reply = self.add_factorials(
592 consts,
593 locale,
594 ¬e,
595 too_big_number,
596 FormatOptions {
597 write_out: self.commands.write_out,
598 ..{ FormatOptions::FORCE_SHORTEN | FormatOptions::AGRESSIVE_SHORTEN }
599 },
600 );
601 }
602
603 let note = if !self.commands.no_note {
604 locale.notes.remove.clone().into_owned() + "\n\n"
605 } else {
606 String::new()
607 };
608 self.add_factorials_to_fit(consts, locale, too_big_number, &mut reply, note);
610 if !locale.bot_disclaimer.is_empty() {
611 reply.push_str("\n*^(");
612 reply.push_str(&locale.bot_disclaimer);
613 reply.push_str(")*");
614 }
615 reply
616 }
617
618 fn add_factorials_to_fit(
619 &self,
620 consts: &Consts<'_>,
621 locale: &crate::locale::Locale<'_>,
622 too_big_number: &Integer,
623 reply: &mut String,
624 note: String,
625 ) {
626 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length {
627 let mut factorial_list: Vec<String> = self
628 .calculation_list
629 .iter()
630 .map(|fact| {
631 let mut res = String::new();
632 let _ = fact.format(
633 &mut res,
634 FormatOptions {
635 agressive_shorten: !self.commands.steps,
636 write_out: self.commands.write_out,
637 ..FormatOptions::FORCE_SHORTEN
638 },
639 too_big_number,
640 consts,
641 &locale.format,
642 );
643 res
644 })
645 .collect();
646 'drop_last: {
647 while note.len()
648 + factorial_list.iter().map(|s| s.len()).sum::<usize>()
649 + locale.bot_disclaimer.len()
650 + 16
651 > self.max_length
652 {
653 factorial_list.pop();
655 if factorial_list.is_empty() {
656 *reply = locale.notes.no_post.to_string();
657 break 'drop_last;
658 }
659 }
660 *reply = factorial_list.iter().fold(note, |mut acc, factorial| {
661 let _ = acc.write_str(factorial);
662 acc
663 });
664 }
665 }
666 }
667
668 fn add_factorials(
669 &self,
670 consts: &Consts<'_>,
671 locale: &crate::locale::Locale<'_>,
672 note: &str,
673 too_big_number: &Integer,
674 format_options: FormatOptions,
675 ) -> String {
676 self.calculation_list
677 .iter()
678 .fold(note.to_owned(), |mut acc, factorial| {
679 let _ = factorial.format(
680 &mut acc,
681 format_options.clone(),
682 too_big_number,
683 consts,
684 &locale.format,
685 );
686 acc
687 })
688 }
689
690 fn add_note(
691 &self,
692 consts: &Consts<'_>,
693 locale: &crate::locale::Locale<'_>,
694 note: &mut String,
695 too_big_number: &Integer,
696 ) -> bool {
697 let multiple = self.calculation_list.len() > 1;
698 if !self.commands.no_note {
699 if self.status.limit_hit {
700 let _ = note.write_str(
701 locale
702 .notes
703 .limit_hit
704 .as_ref()
705 .map(AsRef::as_ref)
706 .unwrap_or(
707 "I have repeated myself enough, I won't do that calculation again.",
708 ),
709 );
710 let _ = note.write_str("\n\n");
711 } else if self
712 .calculation_list
713 .iter()
714 .any(Calculation::is_digit_tower)
715 {
716 if multiple {
717 let _ = note.write_str(&locale.notes.tower_mult);
718 let _ = note.write_str("\n\n");
719 } else {
720 let _ = note.write_str(&locale.notes.tower);
721 let _ = note.write_str("\n\n");
722 }
723 } else if self
724 .calculation_list
725 .iter()
726 .any(Calculation::is_aproximate_digits)
727 {
728 if multiple {
729 let _ = note.write_str(&locale.notes.digits_mult);
730 let _ = note.write_str("\n\n");
731 } else {
732 let _ = note.write_str(&locale.notes.digits);
733 let _ = note.write_str("\n\n");
734 }
735 } else if self
736 .calculation_list
737 .iter()
738 .any(Calculation::is_approximate)
739 {
740 if multiple {
741 let _ = note.write_str(&locale.notes.approx_mult);
742 let _ = note.write_str("\n\n");
743 } else {
744 let _ = note.write_str(&locale.notes.approx);
745 let _ = note.write_str("\n\n");
746 }
747 } else if self.calculation_list.iter().any(Calculation::is_rounded) {
748 if multiple {
749 let _ = note.write_str(&locale.notes.round_mult);
750 let _ = note.write_str("\n\n");
751 } else {
752 let _ = note.write_str(&locale.notes.round);
753 let _ = note.write_str("\n\n");
754 }
755 } else if self
756 .calculation_list
757 .iter()
758 .any(|c| c.is_too_long(too_big_number))
759 && !(self.commands.write_out
760 && self
761 .calculation_list
762 .iter()
763 .all(|c| c.can_write_out(consts.float_precision)))
764 {
765 if multiple {
766 let _ = note.write_str(&locale.notes.too_big_mult);
767 let _ = note.write_str("\n\n");
768 } else {
769 let _ = note.write_str(&locale.notes.too_big);
770 let _ = note.write_str("\n\n");
771 }
772 } else if self.commands.write_out && self.locale != "en" {
773 let _ =
774 note.write_str("I can only write out numbers in english, so I will do that.");
775 let _ = note.write_str("\n\n");
776 }
777 }
778 multiple
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use crate::{
785 calculation_results::Number,
786 calculation_tasks::{CalculationBase, CalculationJob},
787 locale::NumFormat,
788 };
789
790 const MAX_LENGTH: usize = 10_000;
791
792 use super::*;
793
794 type Comment<S> = super::Comment<(), S>;
795
796 #[test]
797 fn test_extraction_dedup() {
798 let consts = Consts::default();
799 let jobs = parse(
800 "24! -24! 2!? (2!?)!",
801 true,
802 &consts,
803 &NumFormat { decimal: '.' },
804 );
805 assert_eq!(
806 jobs,
807 [
808 CalculationJob {
809 base: CalculationBase::Num(Number::Exact(24.into())),
810 level: 1,
811 negative: 0
812 },
813 CalculationJob {
814 base: CalculationBase::Num(Number::Exact(24.into())),
815 level: 1,
816 negative: 1
817 },
818 CalculationJob {
819 base: CalculationBase::Calc(Box::new(CalculationJob {
820 base: CalculationBase::Num(Number::Exact(2.into())),
821 level: 1,
822 negative: 0
823 })),
824 level: -1,
825 negative: 0
826 },
827 CalculationJob {
828 base: CalculationBase::Calc(Box::new(CalculationJob {
829 base: CalculationBase::Calc(Box::new(CalculationJob {
830 base: CalculationBase::Num(Number::Exact(2.into())),
831 level: 1,
832 negative: 0
833 })),
834 level: -1,
835 negative: 0
836 })),
837 level: 1,
838 negative: 0
839 }
840 ]
841 );
842 }
843
844 #[test]
845 fn test_commands_from_comment_text() {
846 let cmd1 = Commands::from_comment_text("!shorten!all !triangle !no_note !nested");
847 assert!(cmd1.shorten);
848 assert!(cmd1.steps);
849 assert!(cmd1.termial);
850 assert!(cmd1.no_note);
851 assert!(cmd1.nested);
852 let cmd2 = Commands::from_comment_text("[shorten][all] [triangle] [no_note] [nest]");
853 assert!(cmd2.shorten);
854 assert!(cmd2.steps);
855 assert!(cmd2.termial);
856 assert!(cmd2.no_note);
857 assert!(cmd2.nested);
858 let comment = r"\[shorten\]\[all\] \[triangle\] \[no_note\] \[nest\]";
859 let cmd3 = Commands::from_comment_text(comment);
860 assert!(cmd3.shorten);
861 assert!(cmd3.steps);
862 assert!(cmd3.termial);
863 assert!(cmd3.no_note);
864 assert!(cmd3.nested);
865 let cmd4 = Commands::from_comment_text("shorten all triangle no_note nest");
866 assert!(!cmd4.shorten);
867 assert!(!cmd4.steps);
868 assert!(!cmd4.termial);
869 assert!(!cmd4.no_note);
870 assert!(!cmd4.nested);
871 }
872
873 #[test]
874 fn test_commands_overrides_from_comment_text() {
875 let cmd1 = Commands::overrides_from_comment_text("long no_steps no_termial note multi");
876 assert!(cmd1.shorten);
877 assert!(cmd1.steps);
878 assert!(cmd1.termial);
879 assert!(cmd1.no_note);
880 assert!(cmd1.nested);
881 }
882
883 #[test]
884 fn test_might_have_factorial() {
885 assert!(Comment::might_have_factorial("5!"));
886 assert!(Comment::might_have_factorial("3?"));
887 assert!(!Comment::might_have_factorial("!?"));
888 }
889
890 #[test]
891 fn test_new_already_replied() {
892 let comment = Comment::new_already_replied((), MAX_LENGTH, "en");
893 assert_eq!(comment.calculation_list, "");
894 assert!(comment.status.already_replied_or_rejected);
895 }
896
897 #[test]
898 fn test_locale_fallback_note() {
899 let consts = Consts::default();
900 let comment = Comment::new_already_replied((), MAX_LENGTH, "n/a")
901 .extract(&consts)
902 .calc(&consts);
903 let reply = comment.get_reply(&consts);
904 assert_eq!(
905 reply,
906 "Sorry, I currently don't speak n/a. Maybe you could [teach me](https://github.com/tolik518/factorion-bot/blob/master/CONTRIBUTING.md#translation)? \n\n\n*^(This action was performed by a bot | [Source code](http://f.r0.fyi))*"
907 );
908 }
909
910 #[test]
911 fn test_limit_hit_note() {
912 let consts = Consts::default();
913 let mut comment = Comment::new_already_replied((), MAX_LENGTH, "en")
914 .extract(&consts)
915 .calc(&consts);
916 comment.add_status(Status::LIMIT_HIT);
917 let reply = comment.get_reply(&consts);
918 assert_eq!(
919 reply,
920 "I have repeated myself enough, I won't do that calculation again.\n\n\n*^(This action was performed by a bot | [Source code](http://f.r0.fyi))*"
921 );
922 }
923
924 #[test]
925 fn test_write_out_unsupported_note() {
926 let consts = Consts::default();
927 let comment = Comment::new("1!", (), Commands::WRITE_OUT, MAX_LENGTH, "de")
928 .extract(&consts)
929 .calc(&consts);
930 let reply = comment.get_reply(&consts);
931 assert_eq!(
932 reply,
933 "I can only write out numbers in english, so I will do that.\n\nFakultät von one ist one \n\n\n*^(Dieser Kommentar wurde automatisch geschrieben | [Quelltext](http://f.r0.fyi))*"
934 );
935 }
936}