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 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(
537 locale
538 .notes
539 .limit_hit
540 .as_ref()
541 .map(AsRef::as_ref)
542 .unwrap_or(
543 "I have repeated myself enough, I won't do that calculation again.",
544 ),
545 );
546 let _ = note.write_str("\n\n");
547 } else if self
548 .calculation_list
549 .iter()
550 .any(Calculation::is_digit_tower)
551 {
552 if multiple {
553 let _ = note.write_str(&locale.notes.tower_mult);
554 let _ = note.write_str("\n\n");
555 } else {
556 let _ = note.write_str(&locale.notes.tower);
557 let _ = note.write_str("\n\n");
558 }
559 } else if self
560 .calculation_list
561 .iter()
562 .any(Calculation::is_aproximate_digits)
563 {
564 if multiple {
565 let _ = note.write_str(&locale.notes.digits_mult);
566 let _ = note.write_str("\n\n");
567 } else {
568 let _ = note.write_str(&locale.notes.digits);
569 let _ = note.write_str("\n\n");
570 }
571 } else if self
572 .calculation_list
573 .iter()
574 .any(Calculation::is_approximate)
575 {
576 if multiple {
577 let _ = note.write_str(&locale.notes.approx_mult);
578 let _ = note.write_str("\n\n");
579 } else {
580 let _ = note.write_str(&locale.notes.approx);
581 let _ = note.write_str("\n\n");
582 }
583 } else if self.calculation_list.iter().any(Calculation::is_rounded) {
584 if multiple {
585 let _ = note.write_str(&locale.notes.round_mult);
586 let _ = note.write_str("\n\n");
587 } else {
588 let _ = note.write_str(&locale.notes.round);
589 let _ = note.write_str("\n\n");
590 }
591 } else if self
592 .calculation_list
593 .iter()
594 .any(|c| c.is_too_long(too_big_number))
595 && !(self.commands.write_out
596 && self
597 .calculation_list
598 .iter()
599 .all(|c| c.can_write_out(consts.float_precision)))
600 {
601 if multiple {
602 let _ = note.write_str(&locale.notes.too_big_mult);
603 let _ = note.write_str("\n\n");
604 } else {
605 let _ = note.write_str(&locale.notes.too_big);
606 let _ = note.write_str("\n\n");
607 }
608 } else if self.commands.write_out && self.locale != "en" {
609 let _ =
610 note.write_str("I can only write out numbers in english, so I will do that.");
611 let _ = note.write_str("\n\n");
612 }
613 }
614
615 let mut reply = self
617 .calculation_list
618 .iter()
619 .fold(note.clone(), |mut acc, factorial| {
620 let _ = factorial.format(
621 &mut acc,
622 FormatOptions {
623 force_shorten: self.commands.shorten,
624 write_out: self.commands.write_out,
625 ..FormatOptions::NONE
626 },
627 too_big_number,
628 consts,
629 &locale.format,
630 );
631 acc
632 });
633
634 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length
636 && !self.commands.shorten
637 && !self
638 .calculation_list
639 .iter()
640 .all(|fact| fact.is_too_long(too_big_number))
641 {
642 if note.is_empty() && !self.commands.no_note {
643 if multiple {
644 let _ = note.write_str(&locale.notes.too_big_mult);
645 } else {
646 let _ = note.write_str(&locale.notes.too_big);
647 }
648 let _ = note.write_str("\n\n");
649 };
650 reply = self
651 .calculation_list
652 .iter()
653 .fold(note, |mut acc, factorial| {
654 let _ = factorial.format(
655 &mut acc,
656 FormatOptions {
657 write_out: self.commands.write_out,
658 ..FormatOptions::FORCE_SHORTEN
659 },
660 too_big_number,
661 consts,
662 &locale.format,
663 );
664 acc
665 });
666 }
667
668 let note = if !self.commands.no_note {
669 locale.notes.tetration.clone().into_owned() + "\n\n"
670 } else {
671 String::new()
672 };
673 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length && !self.commands.steps
675 {
676 reply = self
677 .calculation_list
678 .iter()
679 .fold(note, |mut acc, factorial| {
680 let _ = factorial.format(
681 &mut acc,
682 FormatOptions {
683 write_out: self.commands.write_out,
684 ..{ FormatOptions::FORCE_SHORTEN | FormatOptions::AGRESSIVE_SHORTEN }
685 },
686 too_big_number,
687 consts,
688 &locale.format,
689 );
690 acc
691 });
692 }
693
694 let note = if !self.commands.no_note {
695 locale.notes.remove.clone().into_owned() + "\n\n"
696 } else {
697 String::new()
698 };
699 if reply.len() + locale.bot_disclaimer.len() + 16 > self.max_length {
701 let mut factorial_list: Vec<String> = self
702 .calculation_list
703 .iter()
704 .map(|fact| {
705 let mut res = String::new();
706 let _ = fact.format(
707 &mut res,
708 FormatOptions {
709 agressive_shorten: !self.commands.steps,
710 write_out: self.commands.write_out,
711 ..FormatOptions::FORCE_SHORTEN
712 },
713 too_big_number,
714 consts,
715 &locale.format,
716 );
717 res
718 })
719 .collect();
720 'drop_last: {
721 while note.len()
722 + factorial_list.iter().map(|s| s.len()).sum::<usize>()
723 + locale.bot_disclaimer.len()
724 + 16
725 > self.max_length
726 {
727 factorial_list.pop();
729 if factorial_list.is_empty() {
730 reply = locale.notes.no_post.to_string();
731 break 'drop_last;
732 }
733 }
734 reply = factorial_list.iter().fold(note, |mut acc, factorial| {
735 let _ = acc.write_str(factorial);
736 acc
737 });
738 }
739 }
740 if !locale.bot_disclaimer.is_empty() {
741 reply.push_str("\n*^(");
742 reply.push_str(&locale.bot_disclaimer);
743 reply.push_str(")*");
744 }
745 reply
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use crate::{
752 calculation_results::Number,
753 calculation_tasks::{CalculationBase, CalculationJob},
754 locale::NumFormat,
755 };
756
757 const MAX_LENGTH: usize = 10_000;
758
759 use super::*;
760
761 type Comment<S> = super::Comment<(), S>;
762
763 #[test]
764 fn test_extraction_dedup() {
765 let consts = Consts::default();
766 let jobs = parse(
767 "24! -24! 2!? (2!?)!",
768 true,
769 &consts,
770 &NumFormat { decimal: '.' },
771 );
772 assert_eq!(
773 jobs,
774 [
775 CalculationJob {
776 base: CalculationBase::Num(Number::Exact(24.into())),
777 level: 1,
778 negative: 0
779 },
780 CalculationJob {
781 base: CalculationBase::Num(Number::Exact(24.into())),
782 level: 1,
783 negative: 1
784 },
785 CalculationJob {
786 base: CalculationBase::Calc(Box::new(CalculationJob {
787 base: CalculationBase::Num(Number::Exact(2.into())),
788 level: 1,
789 negative: 0
790 })),
791 level: -1,
792 negative: 0
793 },
794 CalculationJob {
795 base: CalculationBase::Calc(Box::new(CalculationJob {
796 base: CalculationBase::Calc(Box::new(CalculationJob {
797 base: CalculationBase::Num(Number::Exact(2.into())),
798 level: 1,
799 negative: 0
800 })),
801 level: -1,
802 negative: 0
803 })),
804 level: 1,
805 negative: 0
806 }
807 ]
808 );
809 }
810
811 #[test]
812 fn test_commands_from_comment_text() {
813 let cmd1 = Commands::from_comment_text("!shorten!all !triangle !no_note !nested");
814 assert!(cmd1.shorten);
815 assert!(cmd1.steps);
816 assert!(cmd1.termial);
817 assert!(cmd1.no_note);
818 assert!(cmd1.nested);
819 let cmd2 = Commands::from_comment_text("[shorten][all] [triangle] [no_note] [nest]");
820 assert!(cmd2.shorten);
821 assert!(cmd2.steps);
822 assert!(cmd2.termial);
823 assert!(cmd2.no_note);
824 assert!(cmd2.nested);
825 let comment = r"\[shorten\]\[all\] \[triangle\] \[no_note\] \[nest\]";
826 let cmd3 = Commands::from_comment_text(comment);
827 assert!(cmd3.shorten);
828 assert!(cmd3.steps);
829 assert!(cmd3.termial);
830 assert!(cmd3.no_note);
831 assert!(cmd3.nested);
832 let cmd4 = Commands::from_comment_text("shorten all triangle no_note nest");
833 assert!(!cmd4.shorten);
834 assert!(!cmd4.steps);
835 assert!(!cmd4.termial);
836 assert!(!cmd4.no_note);
837 assert!(!cmd4.nested);
838 }
839
840 #[test]
841 fn test_commands_overrides_from_comment_text() {
842 let cmd1 = Commands::overrides_from_comment_text("long no_steps no_termial note multi");
843 assert!(cmd1.shorten);
844 assert!(cmd1.steps);
845 assert!(cmd1.termial);
846 assert!(cmd1.no_note);
847 assert!(cmd1.nested);
848 }
849
850 #[test]
851 fn test_might_have_factorial() {
852 assert!(Comment::might_have_factorial("5!"));
853 assert!(Comment::might_have_factorial("3?"));
854 assert!(!Comment::might_have_factorial("!?"));
855 }
856
857 #[test]
858 fn test_new_already_replied() {
859 let comment = Comment::new_already_replied((), MAX_LENGTH, "en");
860 assert_eq!(comment.calculation_list, "");
861 assert!(comment.status.already_replied_or_rejected);
862 }
863
864 #[test]
865 fn test_locale_fallback_note() {
866 let consts = Consts::default();
867 let comment = Comment::new_already_replied((), MAX_LENGTH, "n/a")
868 .extract(&consts)
869 .calc(&consts);
870 let reply = comment.get_reply(&consts);
871 assert_eq!(
872 reply,
873 "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))*"
874 );
875 }
876
877 #[test]
878 fn test_limit_hit_note() {
879 let consts = Consts::default();
880 let mut comment = Comment::new_already_replied((), MAX_LENGTH, "en")
881 .extract(&consts)
882 .calc(&consts);
883 comment.add_status(Status::LIMIT_HIT);
884 let reply = comment.get_reply(&consts);
885 assert_eq!(
886 reply,
887 "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))*"
888 );
889 }
890
891 #[test]
892 fn test_write_out_unsupported_note() {
893 let consts = Consts::default();
894 let comment = Comment::new("1!", (), Commands::WRITE_OUT, MAX_LENGTH, "de")
895 .extract(&consts)
896 .calc(&consts);
897 let reply = comment.get_reply(&consts);
898 assert_eq!(
899 reply,
900 "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))*"
901 );
902 }
903}