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