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, Number};
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 write_out: Self::contains_command_format(text, "write_out")
226 || Self::contains_command_format(text, "write_num"),
227 }
228 }
229 pub fn overrides_from_comment_text(text: &str) -> Self {
230 Self {
231 shorten: !Self::contains_command_format(text, "long"),
232 steps: !(Self::contains_command_format(text, "no steps")
233 || Self::contains_command_format(text, "no_steps")),
234 nested: !(Self::contains_command_format(text, "no_nest")
235 || Self::contains_command_format(text, "multi")),
236 termial: !(Self::contains_command_format(text, "no termial")
237 || Self::contains_command_format(text, "no_termial")),
238 no_note: !Self::contains_command_format(text, "note"),
239 write_out: !(Self::contains_command_format(text, "dont_write_out")
240 || Self::contains_command_format(text, "normal_num")),
241 }
242 }
243}
244
245macro_rules! contains_comb {
246 ($var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
248 $var.contains(concat!($start, $end)) || contains_comb!($var, [$($start_rest),*], [$end,$($end_rest),*]) || contains_comb!(@inner $var, [$start,$($start_rest),*], [$($end_rest),*])
249 };
250 (@inner $var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
252 $var.contains(concat!($start,$end)) || contains_comb!(@inner $var, [$start,$($start_rest),*], [$($end_rest),*])
253 };
254 ($var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt $(,)?]) => {
256 $var.contains(concat!($start, $end)) || contains_comb!($var, [$($start_rest),*], [$end])
257 };
258 ($var:ident, [$start:tt $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
260 $var.contains(concat!($start, $end)) || contains_comb!(@inner $var, [$start], [$($end_rest),*])
261 };
262 (@inner $var:ident, [$start:tt,$($start_rest:tt),* $(,)?], [$end:tt $(,)?]) => {
264 $var.contains(concat!($start,$end))
265 };
266 (@inner $var:ident, [$start:tt $(,)?], [$end:tt,$($end_rest:tt),* $(,)?]) => {
268 $var.contains(concat!($start,$end)) || contains_comb!(@inner $var, [$start], [$($end_rest),*])
269 };
270 ($var:ident, [$start:tt $(,)?], [$end:tt $(,)?]) => {
272 $var.contains(concat!($start, $end))
273 };
274 (@inner $var:ident, [$start:tt $(,)?], [$end:tt $(,)?]) => {
276 $var.contains(concat!($start,$end))
277 };
278}
279
280impl<Meta> CommentConstructed<Meta> {
281 pub fn new(
283 comment_text: &str,
284 meta: Meta,
285 pre_commands: Commands,
286 max_length: usize,
287 locale: &str,
288 ) -> Self {
289 let command_overrides = Commands::overrides_from_comment_text(comment_text);
290 let commands: Commands =
291 (Commands::from_comment_text(comment_text) | pre_commands) & command_overrides;
292
293 let mut status: Status = Default::default();
294
295 let text = if Self::might_have_factorial(comment_text) {
296 comment_text.to_owned()
297 } else {
298 status.no_factorial = true;
299 String::new()
300 };
301
302 Comment {
303 meta,
304 notify: None,
305 calculation_list: text,
306 status,
307 commands,
308 max_length,
309 locale: locale.to_owned(),
310 }
311 }
312
313 fn might_have_factorial(text: &str) -> bool {
314 contains_comb!(
315 text,
316 [
317 "0",
318 "1",
319 "2",
320 "3",
321 "4",
322 "5",
323 "6",
324 "7",
325 "8",
326 "9",
327 ")",
328 "e",
329 "pi",
330 "phi",
331 "tau",
332 "π",
333 "ɸ",
334 "τ",
335 "infinity",
336 "inf",
337 "∞\u{303}",
338 "∞"
339 ],
340 ["!", "?"]
341 ) || contains_comb!(
342 text,
343 ["!"],
344 [
345 "0",
346 "1",
347 "2",
348 "3",
349 "4",
350 "5",
351 "6",
352 "7",
353 "8",
354 "9",
355 "(",
356 "e",
357 "pi",
358 "phi",
359 "tau",
360 "π",
361 "ɸ",
362 "τ",
363 "infinity",
364 "inf",
365 "∞\u{303}",
366 "∞"
367 ]
368 )
369 }
370
371 pub fn extract(self, consts: &Consts) -> CommentExtracted<Meta> {
373 let Comment {
374 meta,
375 calculation_list: comment_text,
376 notify,
377 mut status,
378 commands,
379 max_length,
380 locale,
381 } = self;
382 let mut pending_list: Vec<CalculationJob> = parse(
383 &comment_text,
384 commands.termial,
385 consts,
386 &consts
387 .locales
388 .get(&locale)
389 .unwrap_or(consts.locales.get(&consts.default_locale).unwrap())
390 .format()
391 .number_format(),
392 );
393
394 if commands.nested {
395 for calc in &mut pending_list {
396 Self::multi_to_nested(calc);
397 }
398 }
399
400 if pending_list.is_empty() {
401 status.no_factorial = true;
402 }
403
404 Comment {
405 meta,
406 calculation_list: pending_list,
407 notify,
408 status,
409 commands,
410 max_length,
411 locale,
412 }
413 }
414
415 fn multi_to_nested(mut calc: &mut CalculationJob) {
416 loop {
417 let level = calc.level.clamp(-1, 1);
418 let depth = calc.level.abs();
419 calc.level = level;
420 for _ in 1..depth {
421 let base = std::mem::replace(
422 &mut calc.base,
423 CalculationBase::Num(
424 crate::calculation_results::CalculationResult::ComplexInfinity,
425 ),
426 );
427 let new_base = CalculationBase::Calc(Box::new(CalculationJob {
428 base,
429 level,
430 negative: 0,
431 }));
432 let _ = std::mem::replace(&mut calc.base, new_base);
433 }
434 let CalculationBase::Calc(next) = &mut calc.base else {
435 return;
436 };
437 calc = next;
438 }
439 }
440
441 pub fn new_already_replied(meta: Meta, max_length: usize, locale: &str) -> Self {
443 let text = String::new();
444 let status: Status = Status {
445 already_replied_or_rejected: true,
446 ..Default::default()
447 };
448 let commands: Commands = Default::default();
449
450 Comment {
451 meta,
452 notify: None,
453 calculation_list: text,
454 status,
455 commands,
456 max_length,
457 locale: locale.to_owned(),
458 }
459 }
460}
461impl<Meta, S> Comment<Meta, S> {
462 pub fn add_status(&mut self, status: Status) {
463 self.status = self.status | status;
464 }
465}
466impl<Meta> CommentExtracted<Meta> {
467 pub fn calc(self, consts: &Consts) -> CommentCalculated<Meta> {
469 let Comment {
470 meta,
471 calculation_list: pending_list,
472 notify,
473 mut status,
474 commands,
475 max_length,
476 locale,
477 } = self;
478 let mut calculation_list: Vec<Calculation> = pending_list
479 .into_iter()
480 .flat_map(|calc| calc.execute(commands.steps, consts))
481 .filter_map(|x| {
482 if x.is_none() {
483 status.number_too_big_to_calculate = true;
484 };
485 x
486 })
487 .collect();
488
489 calculation_list.sort();
490 calculation_list.dedup();
491 calculation_list.sort_by_key(|x| x.steps.len());
492
493 if calculation_list.is_empty() {
494 status.no_factorial = true;
495 } else {
496 status.factorials_found = true;
497 }
498 Comment {
499 meta,
500 calculation_list,
501 notify,
502 status,
503 commands,
504 max_length,
505 locale,
506 }
507 }
508}
509impl<Meta> CommentCalculated<Meta> {
510 pub fn get_reply(&self, consts: &Consts) -> String {
512 let mut fell_back = false;
513 let locale = consts.locales.get(&self.locale).unwrap_or_else(|| {
514 fell_back = true;
515 consts.locales.get(&consts.default_locale).unwrap()
516 });
517 let mut note = self
518 .notify
519 .as_ref()
520 .map(|user| locale.notes().mention().replace("{mention}", user) + "\n\n")
521 .unwrap_or_default();
522
523 if fell_back {
524 let _ = note.write_str("Sorry, I currently don't speak ");
525 let _ = note.write_str(&self.locale);
526 let _ = note.write_str(". Maybe you could [teach me](https://github.com/tolik518/factorion-bot/blob/master/CONTRIBUTING.md#translation)? \n\n");
527 }
528
529 let too_big_number = Integer::u64_pow_u64(10, self.max_length as u64).complete();
530 let too_big_number = &too_big_number;
531
532 let multiple = self.calculation_list.len() > 1;
534 if !self.commands.no_note {
535 if self.status.limit_hit {
536 let _ = note.write_str(locale.notes().limit_hit().map(AsRef::as_ref).unwrap_or(
537 "I have repeated myself enough, I won't do that calculation again.",
538 ));
539 let _ = note.write_str("\n\n");
540 } else if self
541 .calculation_list
542 .iter()
543 .any(Calculation::is_digit_tower)
544 {
545 if multiple {
546 let _ = note.write_str(locale.notes().tower_mult());
547 let _ = note.write_str("\n\n");
548 } else {
549 let _ = note.write_str(locale.notes().tower());
550 let _ = note.write_str("\n\n");
551 }
552 } else if self
553 .calculation_list
554 .iter()
555 .any(Calculation::is_aproximate_digits)
556 {
557 if multiple {
558 let _ = note.write_str(locale.notes().digits_mult());
559 let _ = note.write_str("\n\n");
560 } else {
561 let _ = note.write_str(locale.notes().digits());
562 let _ = note.write_str("\n\n");
563 }
564 } else if self
565 .calculation_list
566 .iter()
567 .any(Calculation::is_approximate)
568 {
569 if multiple {
570 let _ = note.write_str(locale.notes().approx_mult());
571 let _ = note.write_str("\n\n");
572 } else {
573 let _ = note.write_str(locale.notes().approx());
574 let _ = note.write_str("\n\n");
575 }
576 } else if self.calculation_list.iter().any(Calculation::is_rounded) {
577 if multiple {
578 let _ = note.write_str(locale.notes().round_mult());
579 let _ = note.write_str("\n\n");
580 } else {
581 let _ = note.write_str(locale.notes().round());
582 let _ = note.write_str("\n\n");
583 }
584 } else if self
585 .calculation_list
586 .iter()
587 .any(|c| c.is_too_long(too_big_number))
588 && !(self.commands.write_out
589 && self
590 .calculation_list
591 .iter()
592 .all(|c| c.can_write_out(consts.float_precision)))
593 {
594 if multiple {
595 let _ = note.write_str(locale.notes().too_big_mult());
596 let _ = note.write_str("\n\n");
597 } else {
598 let _ = note.write_str(locale.notes().too_big());
599 let _ = note.write_str("\n\n");
600 }
601 } else if self.commands.write_out && self.locale != "en" {
602 let _ =
603 note.write_str("I can only write out numbers in english, so I will do that.");
604 let _ = note.write_str("\n\n");
605 }
606 }
607
608 let mut reply = self
610 .calculation_list
611 .iter()
612 .fold(note.clone(), |mut acc, factorial| {
613 let _ = factorial.format(
614 &mut acc,
615 FormatOptions {
616 force_shorten: self.commands.shorten,
617 write_out: self.commands.write_out,
618 ..FormatOptions::NONE
619 },
620 too_big_number,
621 consts,
622 &locale.format(),
623 );
624 acc
625 });
626
627 if reply.len() + locale.bot_disclaimer().len() + 16 > self.max_length
629 && !self.commands.shorten
630 && !self
631 .calculation_list
632 .iter()
633 .all(|fact| fact.is_too_long(too_big_number))
634 {
635 if note.is_empty() && !self.commands.no_note {
636 if multiple {
637 let _ = note.write_str(locale.notes().too_big_mult());
638 } else {
639 let _ = note.write_str(locale.notes().too_big());
640 }
641 let _ = note.write_str("\n\n");
642 };
643 reply = self
644 .calculation_list
645 .iter()
646 .fold(note, |mut acc, factorial| {
647 let _ = factorial.format(
648 &mut acc,
649 FormatOptions {
650 write_out: self.commands.write_out,
651 ..FormatOptions::FORCE_SHORTEN
652 },
653 too_big_number,
654 consts,
655 &locale.format(),
656 );
657 acc
658 });
659 }
660
661 let note = if !self.commands.no_note {
662 locale.notes().tetration().clone().into_owned() + "\n\n"
663 } else {
664 String::new()
665 };
666 if reply.len() + locale.bot_disclaimer().len() + 16 > self.max_length
668 && !self.commands.steps
669 {
670 reply = self
671 .calculation_list
672 .iter()
673 .fold(note, |mut acc, factorial| {
674 let _ = factorial.format(
675 &mut acc,
676 FormatOptions {
677 write_out: self.commands.write_out,
678 ..{ FormatOptions::FORCE_SHORTEN | FormatOptions::AGRESSIVE_SHORTEN }
679 },
680 too_big_number,
681 consts,
682 &locale.format(),
683 );
684 acc
685 });
686 }
687
688 let note = if !self.commands.no_note {
689 locale.notes().remove().clone().into_owned() + "\n\n"
690 } else {
691 String::new()
692 };
693 if reply.len() + locale.bot_disclaimer().len() + 16 > self.max_length {
695 let mut factorial_list: Vec<String> = self
696 .calculation_list
697 .iter()
698 .map(|fact| {
699 let mut res = String::new();
700 let _ = fact.format(
701 &mut res,
702 FormatOptions {
703 agressive_shorten: !self.commands.steps,
704 write_out: self.commands.write_out,
705 ..FormatOptions::FORCE_SHORTEN
706 },
707 too_big_number,
708 consts,
709 &locale.format(),
710 );
711 res
712 })
713 .collect();
714 'drop_last: {
715 while note.len()
716 + factorial_list.iter().map(|s| s.len()).sum::<usize>()
717 + locale.bot_disclaimer().len()
718 + 16
719 > self.max_length
720 {
721 factorial_list.pop();
723 if factorial_list.is_empty() {
724 reply = locale.notes().no_post().to_string();
725 break 'drop_last;
726 }
727 }
728 reply = factorial_list.iter().fold(note, |mut acc, factorial| {
729 let _ = acc.write_str(&factorial);
730 acc
731 });
732 }
733 }
734 if !locale.bot_disclaimer().is_empty() {
735 reply.push_str("\n*^(");
736 reply.push_str(locale.bot_disclaimer());
737 reply.push_str(")*");
738 }
739 reply
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use crate::{
746 calculation_results::Number,
747 calculation_tasks::{CalculationBase, CalculationJob},
748 locale::NumFormat,
749 };
750
751 const MAX_LENGTH: usize = 10_000;
752
753 use super::*;
754
755 type Comment<S> = super::Comment<(), S>;
756
757 #[test]
758 fn test_extraction_dedup() {
759 let consts = Consts::default();
760 let jobs = parse(
761 "24! -24! 2!? (2!?)!",
762 true,
763 &consts,
764 &NumFormat::V1(&crate::locale::v1::NumFormat { decimal: '.' }),
765 );
766 assert_eq!(
767 jobs,
768 [
769 CalculationJob {
770 base: CalculationBase::Num(Number::Exact(24.into())),
771 level: 1,
772 negative: 0
773 },
774 CalculationJob {
775 base: CalculationBase::Num(Number::Exact(24.into())),
776 level: 1,
777 negative: 1
778 },
779 CalculationJob {
780 base: CalculationBase::Calc(Box::new(CalculationJob {
781 base: CalculationBase::Num(Number::Exact(2.into())),
782 level: 1,
783 negative: 0
784 })),
785 level: -1,
786 negative: 0
787 },
788 CalculationJob {
789 base: CalculationBase::Calc(Box::new(CalculationJob {
790 base: CalculationBase::Calc(Box::new(CalculationJob {
791 base: CalculationBase::Num(Number::Exact(2.into())),
792 level: 1,
793 negative: 0
794 })),
795 level: -1,
796 negative: 0
797 })),
798 level: 1,
799 negative: 0
800 }
801 ]
802 );
803 }
804
805 #[test]
806 fn test_commands_from_comment_text() {
807 let cmd1 = Commands::from_comment_text("!shorten!all !triangle !no_note !nested");
808 assert!(cmd1.shorten);
809 assert!(cmd1.steps);
810 assert!(cmd1.termial);
811 assert!(cmd1.no_note);
812 assert!(cmd1.nested);
813 let cmd2 = Commands::from_comment_text("[shorten][all] [triangle] [no_note] [nest]");
814 assert!(cmd2.shorten);
815 assert!(cmd2.steps);
816 assert!(cmd2.termial);
817 assert!(cmd2.no_note);
818 assert!(cmd2.nested);
819 let comment = r"\[shorten\]\[all\] \[triangle\] \[no_note\] \[nest\]";
820 let cmd3 = Commands::from_comment_text(comment);
821 assert!(cmd3.shorten);
822 assert!(cmd3.steps);
823 assert!(cmd3.termial);
824 assert!(cmd3.no_note);
825 assert!(cmd3.nested);
826 let cmd4 = Commands::from_comment_text("shorten all triangle no_note nest");
827 assert!(!cmd4.shorten);
828 assert!(!cmd4.steps);
829 assert!(!cmd4.termial);
830 assert!(!cmd4.no_note);
831 assert!(!cmd4.nested);
832 }
833
834 #[test]
835 fn test_commands_overrides_from_comment_text() {
836 let cmd1 = Commands::overrides_from_comment_text("long no_steps no_termial note multi");
837 assert!(cmd1.shorten);
838 assert!(cmd1.steps);
839 assert!(cmd1.termial);
840 assert!(cmd1.no_note);
841 assert!(cmd1.nested);
842 }
843
844 #[test]
845 fn test_might_have_factorial() {
846 assert!(Comment::might_have_factorial("5!"));
847 assert!(Comment::might_have_factorial("3?"));
848 assert!(!Comment::might_have_factorial("!?"));
849 }
850
851 #[test]
852 fn test_new_already_replied() {
853 let comment = Comment::new_already_replied((), MAX_LENGTH, "en");
854 assert_eq!(comment.calculation_list, "");
855 assert!(comment.status.already_replied_or_rejected);
856 }
857
858 #[test]
859 fn test_locale_fallback_note() {
860 let consts = Consts::default();
861 let comment = Comment::new_already_replied((), MAX_LENGTH, "n/a")
862 .extract(&consts)
863 .calc(&consts);
864 let reply = comment.get_reply(&consts);
865 assert_eq!(
866 reply,
867 "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))*"
868 );
869 }
870
871 #[test]
872 fn test_limit_hit_note() {
873 let consts = Consts::default();
874 let mut comment = Comment::new_already_replied((), MAX_LENGTH, "en")
875 .extract(&consts)
876 .calc(&consts);
877 comment.add_status(Status::LIMIT_HIT);
878 let reply = comment.get_reply(&consts);
879 assert_eq!(
880 reply,
881 "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))*"
882 );
883 }
884
885 #[test]
886 fn test_write_out_unsupported_note() {
887 let consts = Consts::default();
888 let comment = Comment::new("1!", (), Commands::WRITE_OUT, MAX_LENGTH, "de")
889 .extract(&consts)
890 .calc(&consts);
891 let reply = comment.get_reply(&consts);
892 assert_eq!(
893 reply,
894 "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))*"
895 );
896 }
897}