1pub use crate::basic_types::{Compression, PasswordHashAlgo, Pointer};
2
3use std::fmt::Write;
4
5pub struct Command<T: CommandType> {
7 pub id: Option<String>,
8 pub command: T,
9}
10
11pub struct DynCommand {
13 pub id: Option<String>,
14 pub command: Box<dyn CommandType>,
15}
16
17impl<T: CommandType> Command<T> {
18 pub fn new(id: Option<String>, command: T) -> Self {
19 Command { id, command }
20 }
21}
22
23impl<T: CommandType> std::fmt::Display for Command<T> {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 let mut fields = Vec::with_capacity(2 + self.command.arguments().len());
26 if let Some(id) = &self.id {
27 fields.push(format!("({})", id));
28 }
29 fields.push(self.command.command().to_string());
30 fields.extend(self.command.arguments());
31 writeln!(f, "{}", fields.join(" "))
32 }
33}
34
35impl std::fmt::Display for DynCommand {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 if let Some(ref id) = self.id {
38 writeln!(f, "({}) {}", id, self.command)
39 } else {
40 self.command.fmt(f)
41 }
42 }
43}
44
45macro_rules! escaped {
46 ($self:ident) => {{
47 let mut fields = Vec::with_capacity(2 + $self.command.arguments().len());
48 if let Some(id) = &$self.id {
49 fields.push(format!("({})", id));
50 }
51 fields.push($self.command.command().to_string());
52
53 fields.extend(
54 $self
55 .command
56 .arguments()
57 .iter()
58 .map(|s| s.replace('\\', "\\\\").replace('\n', "\\n")),
59 );
60
61 let mut ret = fields.join(" ");
62 ret.push('\n');
63 ret
64 }};
65}
66
67impl<T: CommandType> Command<T> {
68 pub fn escaped(&self) -> String {
69 escaped!(self)
70 }
71}
72
73impl DynCommand {
74 pub fn escaped(&self) -> String {
75 escaped!(self)
76 }
77}
78
79pub trait CommandType {
80 fn command(&self) -> &'static str;
81 fn arguments(&self) -> Vec<String>;
82}
83
84impl std::fmt::Display for dyn CommandType {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 let mut fields = Vec::with_capacity(1 + self.arguments().len());
87 fields.push(self.command().to_string());
88 fields.extend(self.arguments());
89 writeln!(f, "{}", fields.join(" "))
90 }
91}
92
93#[derive(Debug)]
101pub struct HandshakeCommand {
102 pub password_hash_algo: Vec<PasswordHashAlgo>,
104 pub compression: Vec<Compression>,
106 pub escape_commands: bool,
108}
109
110impl HandshakeCommand {
112 fn command(&self) -> &'static str {
113 "handshake"
114 }
115
116 fn arguments(&self) -> Vec<String> {
117 let mut ret = vec![];
118 if !self.password_hash_algo.is_empty() {
119 ret.push(format!(
120 "password_hash_algo={}",
121 self.password_hash_algo
122 .iter()
123 .map(PasswordHashAlgo::to_str)
124 .collect::<Vec<&str>>()
125 .join(":")
126 ));
127 }
128 if !self.compression.is_empty() {
129 ret.push(format!(
130 "compression={}",
131 self.compression
132 .iter()
133 .map(Compression::to_str)
134 .collect::<Vec<&str>>()
135 .join(":")
136 ));
137 }
138 if self.escape_commands {
139 ret.push("escape_commands=on".to_string());
140 }
141 if ret.is_empty() {
142 vec![]
143 } else {
144 vec![ret.join(",")]
145 }
146 }
147}
148
149impl std::fmt::Display for HandshakeCommand {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 let mut fields = Vec::with_capacity(1 + self.arguments().len());
152 fields.push(self.command().to_string());
153 fields.extend(self.arguments());
154 writeln!(f, "{}", fields.join(" "))
155 }
156}
157
158pub struct InitCommand {
164 pub password: Option<String>,
166 pub password_hash: Option<PasswordHash>,
168 pub totp: Option<String>,
170}
171
172impl CommandType for InitCommand {
173 fn command(&self) -> &'static str {
174 "init"
175 }
176
177 fn arguments(&self) -> Vec<String> {
178 fn escape(arg: &str) -> String {
179 arg.replace(',', "\\,")
180 }
181 let mut ret = vec![];
182 let pw_hash: String;
183 if let Some(password) = &self.password {
184 ret.push(format!("password={}", escape(password)))
185 }
186 if let Some(password_hash) = &self.password_hash {
187 pw_hash = password_hash.to_string();
188 ret.push(format!("password_hash={}", pw_hash));
189 }
190 if let Some(totp) = &self.totp {
191 ret.push(format!("totp={}", escape(totp)));
192 }
193 vec![ret.join(",")]
194 }
195}
196
197pub struct HdataCommand {
203 pub name: String,
205 pub pointer: Countable<PointerOrName>,
207 pub vars: Vec<Countable<String>>,
210 pub keys: Vec<String>,
212}
213
214impl HdataCommand {
215 fn path(&self) -> String {
216 if self.vars.is_empty() {
217 format!("{}:{}", self.name, self.pointer)
218 } else {
219 format!(
220 "{}:{}/{}",
221 self.name,
222 self.pointer,
223 self.vars
224 .iter()
225 .map(Countable::to_string)
226 .collect::<Vec<String>>()
227 .join("/")
228 )
229 }
230 }
231}
232
233impl CommandType for HdataCommand {
234 fn command(&self) -> &'static str {
235 "hdata"
236 }
237
238 fn arguments(&self) -> Vec<String> {
239 let mut args = vec![self.path()];
240 if !self.keys.is_empty() {
241 args.push(
242 self.keys
243 .iter()
244 .map(|s| s.as_str())
245 .collect::<Vec<&str>>()
246 .join(","),
247 );
248 }
249 args
250 }
251}
252
253pub struct InfoCommand {
259 pub name: String,
261 pub arguments: Vec<String>,
263}
264
265impl CommandType for InfoCommand {
266 fn command(&self) -> &'static str {
267 "info"
268 }
269
270 fn arguments(&self) -> Vec<String> {
271 let mut ret = vec![self.name.clone()];
272 ret.extend(self.arguments.iter().cloned());
273 ret
274 }
275}
276
277pub struct InfolistCommand {
283 name: String,
284 arguments: Option<(Pointer, Vec<String>)>,
289}
290
291impl InfolistCommand {
292 pub fn new(name: String, pointer: Option<Pointer>, arguments: Vec<String>) -> Self {
298 let arguments = match (pointer, !arguments.is_empty()) {
299 (None, false) => None,
300 (Some(p), _) => Some((p, arguments)),
301 (None, true) => Some((Pointer::null(), arguments)),
302 };
303 Self { name, arguments }
304 }
305}
306
307impl CommandType for InfolistCommand {
308 fn command(&self) -> &'static str {
309 "infolist"
310 }
311
312 fn arguments(&self) -> Vec<String> {
313 let mut ret = vec![self.name.clone()];
314 if let Some(arguments) = &self.arguments {
315 ret.push(arguments.0.to_string());
316 ret.extend(arguments.1.iter().cloned());
317 }
318 ret
319 }
320}
321
322pub struct NicklistCommand {
328 pub buffer: Option<PointerOrName>,
329}
330
331impl CommandType for NicklistCommand {
332 fn command(&self) -> &'static str {
333 "nicklist"
334 }
335
336 fn arguments(&self) -> Vec<String> {
337 if let Some(buffer) = &self.buffer {
338 vec![buffer.to_string()]
339 } else {
340 vec![]
341 }
342 }
343}
344
345pub struct InputCommand {
351 pub buffer: PointerOrName,
353 pub data: String,
355}
356
357impl CommandType for InputCommand {
358 fn command(&self) -> &'static str {
359 "input"
360 }
361
362 fn arguments(&self) -> Vec<String> {
363 vec![self.buffer.to_string(), self.data.clone()]
364 }
365}
366
367pub struct CompletionCommand {
373 pub buffer: PointerOrName,
375 pub position: Option<u16>,
380 pub data: Option<String>,
382}
383
384impl CommandType for CompletionCommand {
385 fn command(&self) -> &'static str {
386 "completion"
387 }
388
389 fn arguments(&self) -> Vec<String> {
390 let position = match self.position {
391 Some(position) => position.to_string(),
392 None => "-1".to_string(),
393 };
394 let mut ret = vec![self.buffer.to_string(), position];
395 if let Some(data) = &self.data {
396 ret.push(data.clone());
397 }
398 ret
399 }
400}
401
402macro_rules! sync_args {
405 ( $self:ident ) => {
406 match $self {
407 Self::AllBuffers(options) => {
408 if let Some(options) = options.to_string() {
409 vec![options]
410 } else {
411 vec![]
412 }
413 }
414 Self::SomeBuffers(buffers, options) => {
415 let mut args = vec![buffers
416 .iter()
417 .map(PointerOrName::to_string)
418 .collect::<Vec<String>>()
419 .join(",")];
420 match options {
421 SyncSomeBuffers::Buffer => args.push("buffer".to_string()),
422 SyncSomeBuffers::Nicklist => args.push("nicklist".to_string()),
423 SyncSomeBuffers::All => (),
424 }
425 args
426 }
427 }
428 };
429}
430
431pub enum SyncCommand {
438 AllBuffers(SyncAllBuffers),
439 SomeBuffers(Vec<PointerOrName>, SyncSomeBuffers),
440}
441
442impl CommandType for SyncCommand {
443 fn command(&self) -> &'static str {
444 "sync"
445 }
446
447 fn arguments(&self) -> Vec<String> {
448 sync_args!(self)
449 }
450}
451
452pub enum DesyncCommand {
458 AllBuffers(SyncAllBuffers),
459 SomeBuffers(Vec<PointerOrName>, SyncSomeBuffers),
460}
461
462impl CommandType for DesyncCommand {
463 fn command(&self) -> &'static str {
464 "desync"
465 }
466
467 fn arguments(&self) -> Vec<String> {
468 sync_args!(self)
469 }
470}
471
472#[derive(Default)]
485pub struct TestCommand {}
486
487impl CommandType for TestCommand {
488 fn command(&self) -> &'static str {
489 "test"
490 }
491 fn arguments(&self) -> Vec<String> {
492 vec![]
493 }
494}
495
496pub struct PingCommand {
503 pub argument: String,
504}
505
506impl CommandType for PingCommand {
507 fn command(&self) -> &'static str {
508 "ping"
509 }
510 fn arguments(&self) -> Vec<String> {
511 vec![self.argument.clone()]
512 }
513}
514
515#[derive(Default)]
521pub struct QuitCommand {}
522
523impl CommandType for QuitCommand {
524 fn command(&self) -> &'static str {
525 "quit"
526 }
527 fn arguments(&self) -> Vec<String> {
528 vec![]
529 }
530}
531
532pub struct SyncAllBuffers {
534 pub buffers: bool,
537 pub upgrade: bool,
539 pub buffer: bool,
542 pub nicklist: bool,
544}
545
546impl SyncAllBuffers {
547 fn to_string(&self) -> Option<String> {
548 if (self.buffers && self.upgrade && self.buffer && self.nicklist)
551 || (!self.buffers && !self.upgrade && !self.buffer && !self.nicklist)
552 {
553 return None;
554 }
555 let mut ret = vec![];
556 if self.buffers {
557 ret.push("buffers");
558 }
559 if self.upgrade {
560 ret.push("upgrade");
561 }
562 if self.buffer {
563 ret.push("buffer");
564 }
565 if self.nicklist {
566 ret.push("nicklist");
567 }
568 Some(format!("* {}", ret.join(",")))
569 }
570}
571
572pub enum SyncSomeBuffers {
574 Buffer,
577 Nicklist,
579 All,
581}
582
583pub enum PasswordHash {
585 Sha256 {
586 salt: Vec<u8>,
587 hash: [u8; 32],
588 },
589 Sha512 {
590 salt: Vec<u8>,
591 hash: [u8; 64],
592 },
593 Pbkdf2Sha256 {
594 salt: Vec<u8>,
595 iterations: u32,
596 hash: [u8; 32],
597 },
598 Pbkdf2Sha512 {
599 salt: Vec<u8>,
600 iterations: u32,
601 hash: [u8; 64],
602 },
603}
604
605fn hex_encode(bytes: &[u8]) -> String {
606 bytes.iter().fold(String::new(), |mut output, b| {
607 let _ = write!(output, "{b:02x}");
608 output
609 })
610}
611
612impl std::fmt::Display for PasswordHash {
613 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614 match self {
615 PasswordHash::Sha256 { salt, hash } => {
616 write!(f, "sha256:{}:{}", hex_encode(salt), hex_encode(hash))
617 }
618 PasswordHash::Sha512 { salt, hash } => {
619 write!(f, "sha512:{}:{}", hex_encode(salt), hex_encode(hash))
620 }
621 PasswordHash::Pbkdf2Sha256 {
622 salt,
623 iterations,
624 hash,
625 } => write!(
626 f,
627 "pbkdf2+sha256:{}:{}:{}",
628 hex_encode(salt),
629 iterations,
630 hex_encode(hash)
631 ),
632 PasswordHash::Pbkdf2Sha512 {
633 salt,
634 iterations,
635 hash,
636 } => write!(
637 f,
638 "pbkdf2+sha512:{}:{}:{}",
639 hex_encode(salt),
640 iterations,
641 hex_encode(hash)
642 ),
643 }
644 }
645}
646
647pub enum Count {
652 Count(i32),
653 Glob,
654}
655
656impl std::fmt::Display for Count {
657 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658 match self {
659 Count::Count(c) => c.fmt(f),
660 Count::Glob => "*".fmt(f),
661 }
662 }
663}
664
665pub struct Countable<T: std::fmt::Display> {
667 pub count: Option<Count>,
668 pub object: T,
669}
670
671impl<T: std::fmt::Display> Countable<T> {
672 pub fn new(count: Option<Count>, object: T) -> Self {
673 Self { count, object }
674 }
675}
676
677impl<T: std::fmt::Display> std::fmt::Display for Countable<T> {
678 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679 match &self.count {
680 Some(count) => write!(f, "{}({})", self.object, count),
681 None => self.object.fmt(f),
682 }
683 }
684}
685
686pub enum PointerOrName {
688 Pointer(Pointer),
689 Name(String),
690}
691
692impl std::fmt::Display for PointerOrName {
693 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
694 match self {
695 PointerOrName::Pointer(pointer) => pointer.fmt(f),
696 PointerOrName::Name(string) => string.fmt(f),
697 }
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use super::*;
704
705 #[test]
708 fn test_handshake() {
709 use crate::basic_types::Compression;
710
711 let default_handshake = HandshakeCommand {
712 password_hash_algo: vec![],
713 compression: vec![],
714 escape_commands: false,
715 };
716 let compression_handshake = HandshakeCommand {
717 password_hash_algo: vec![],
718 compression: vec![Compression::Zstd],
719 escape_commands: false,
720 };
721 let all_hash_algos = vec![
722 PasswordHashAlgo::Plain,
723 PasswordHashAlgo::Sha256,
724 PasswordHashAlgo::Sha512,
725 PasswordHashAlgo::Pbkdf2Sha256,
726 PasswordHashAlgo::Pbkdf2Sha512,
727 ];
728 let full_handshake = HandshakeCommand {
729 password_hash_algo: all_hash_algos,
730 compression: vec![Compression::Zstd, Compression::Zlib, Compression::Off],
731 escape_commands: false,
732 };
733
734 assert_eq!(default_handshake.to_string(), "handshake\n");
735 assert_eq!(
736 compression_handshake.to_string(),
737 "handshake compression=zstd\n"
738 );
739 assert_eq!(
742 full_handshake.to_string(),
743 "handshake password_hash_algo=plain:sha256:sha512:pbkdf2+sha256:pbkdf2+sha512,compression=zstd:zlib:off\n"
744 );
745 }
746
747 #[test]
748 fn test_init() {
749 let normal_password = InitCommand {
750 password: Some("mypass".to_string()),
751 password_hash: None,
752 totp: None,
753 };
754 let password_with_commas = InitCommand {
755 password: Some("mypass,with,commas".to_string()),
756 password_hash: None,
757 totp: None,
758 };
759 let password_with_totp = InitCommand {
760 password: Some("mypass".to_string()),
761 password_hash: None,
762 totp: Some("123456".to_string()),
763 };
764
765 let salt = vec![
766 0x85, 0xb1, 0xee, 0x00, 0x69, 0x5a, 0x5b, 0x25, 0x4e, 0x14, 0xf4, 0x88, 0x55, 0x38,
767 0xdf, 0x0d, 0xa4, 0xb7, 0x32, 0x07, 0xf5, 0xaa, 0xe4,
768 ];
769 let salt_string = hex_encode(&salt);
770
771 let sha256_password = InitCommand {
772 password: None,
773 password_hash: Some(PasswordHash::Sha256 {
774 salt: salt.clone(),
775 hash: [
776 0x2c, 0x6e, 0xd1, 0x2e, 0xb0, 0x10, 0x9f, 0xca, 0x3a, 0xed, 0xc0, 0x3b, 0xf0,
777 0x3d, 0x9b, 0x6e, 0x80, 0x4c, 0xd6, 0x0a, 0x23, 0xe1, 0x73, 0x1f, 0xd1, 0x77,
778 0x94, 0xda, 0x42, 0x3e, 0x21, 0xdb,
779 ],
780 }),
781 totp: None,
782 };
783
784 let sha512_password = InitCommand {
785 password: None,
786 password_hash: Some(PasswordHash::Sha512 {
787 salt: salt.clone(),
788 hash: [
789 0x0a, 0x1f, 0x01, 0x72, 0xa5, 0x42, 0x91, 0x6b, 0xd8, 0x6e, 0x0c, 0xbc, 0xee,
790 0xbc, 0x1c, 0x38, 0xed, 0x79, 0x1f, 0x6b, 0xe2, 0x46, 0x12, 0x04, 0x52, 0x82,
791 0x5f, 0x0d, 0x74, 0xef, 0x10, 0x78, 0xc7, 0x9e, 0x98, 0x12, 0xde, 0x8b, 0x0a,
792 0xb3, 0xdf, 0xaf, 0x59, 0x8b, 0x6c, 0xa1, 0x45, 0x22, 0x37, 0x4e, 0xc6, 0xa8,
793 0x65, 0x3a, 0x46, 0xdf, 0x3f, 0x96, 0xa6, 0xb5, 0x4a, 0xc1, 0xf0, 0xf8,
794 ],
795 }),
796 totp: None,
797 };
798
799 let pbkdf2_password = InitCommand {
800 password: None,
801 password_hash: Some(PasswordHash::Pbkdf2Sha256 {
802 salt,
803 iterations: 100000,
804 hash: [
805 0xba, 0x7f, 0xac, 0xc3, 0xed, 0xb8, 0x9c, 0xd0, 0x6a, 0xe8, 0x10, 0xe2, 0x9c,
806 0xed, 0x85, 0x98, 0x0f, 0xf3, 0x6d, 0xe2, 0xbb, 0x59, 0x6f, 0xcf, 0x51, 0x3a,
807 0xaa, 0xb6, 0x26, 0x87, 0x64, 0x40,
808 ],
809 }),
810 totp: None,
811 };
812
813 let command = Command::new(None, normal_password);
814 assert_eq!(command.to_string(), "init password=mypass\n");
815
816 let command = Command::new(None, password_with_commas);
817 assert_eq!(
818 command.to_string(),
819 "init password=mypass\\,with\\,commas\n"
820 );
821
822 let command = Command::new(None, password_with_totp);
823 assert_eq!(command.to_string(), "init password=mypass,totp=123456\n");
824
825 let command = Command::new(None, sha256_password);
826 let hash = "2c6ed12eb0109fca3aedc03bf03d9b6e804cd60a23e1731fd17794da423e21db";
827 assert_eq!(
828 command.to_string(),
829 format!("init password_hash=sha256:{salt_string}:{hash}\n")
830 );
831
832 let command = Command::new(None, sha512_password);
833 let hash = "0a1f0172a542916bd86e0cbceebc1c38ed791f6be246120452825f0d74ef1078c79e9812de8b0ab3dfaf598b6ca14522374ec6a8653a46df3f96a6b54ac1f0f8";
834 assert_eq!(
835 command.to_string(),
836 format!("init password_hash=sha512:{salt_string}:{hash}\n")
837 );
838
839 let command = Command::new(None, pbkdf2_password);
840 let hash = "ba7facc3edb89cd06ae810e29ced85980ff36de2bb596fcf513aaab626876440";
841 assert_eq!(
842 command.to_string(),
843 format!("init password_hash=pbkdf2+sha256:{salt_string}:100000:{hash}\n")
844 );
845 }
846
847 #[test]
848 fn test_hdata() {
849 let hdata_buffers = HdataCommand {
850 name: "buffer".to_string(),
851 pointer: Countable::new(
852 Some(Count::Glob),
853 PointerOrName::Name("gui_buffers".to_string()),
854 ),
855 vars: vec![],
856 keys: vec!["number".to_string(), "full_name".to_string()],
857 };
858
859 let hdata_lines = HdataCommand {
860 name: "buffer".to_string(),
861 pointer: Countable::new(None, PointerOrName::Name("gui_buffers".to_string())),
862 vars: vec![
863 Countable::new(None, "own_lines".to_string()),
864 Countable::new(Some(Count::Glob), "first_line".to_string()),
865 Countable::new(None, "data".to_string()),
866 ],
867 keys: vec![],
868 };
869
870 let hdata_hotlist = HdataCommand {
871 name: "hotlist".to_string(),
872 pointer: Countable::new(
873 Some(Count::Glob),
874 PointerOrName::Name("gui_hotlist".to_string()),
875 ),
876 vars: vec![],
877 keys: vec![],
878 };
879
880 let command = Command::new(Some("hdata_buffers".to_string()), hdata_buffers);
881 assert_eq!(
882 command.to_string(),
883 "(hdata_buffers) hdata buffer:gui_buffers(*) number,full_name\n"
884 );
885
886 let command = Command::new(Some("hdata_lines".to_string()), hdata_lines);
887 assert_eq!(
888 command.to_string(),
889 "(hdata_lines) hdata buffer:gui_buffers/own_lines/first_line(*)/data\n"
890 );
891
892 let command = Command::new(Some("hdata_hotlist".to_string()), hdata_hotlist);
893 assert_eq!(
894 command.to_string(),
895 "(hdata_hotlist) hdata hotlist:gui_hotlist(*)\n"
896 );
897 }
898
899 #[test]
900 fn test_info() {
901 let info = InfoCommand {
902 name: "version".to_string(),
903 arguments: vec![],
904 };
905 let command = Command::new(Some("info_version".to_string()), info);
906 assert_eq!(command.to_string(), "(info_version) info version\n");
907
908 let info = InfoCommand {
909 name: "nick_color".to_string(),
910 arguments: vec!["foo".to_string()],
911 };
912 let command = Command::new(Some("foo_color".to_string()), info);
913 assert_eq!(command.to_string(), "(foo_color) info nick_color foo\n");
914 }
915
916 #[test]
917 fn test_infolist() {
918 let id = "infolist_buffer".to_string();
919 let name = "buffer".to_string();
920 let pointer = Pointer::new("1234abcd".as_bytes().to_vec()).expect("invalid pointer");
921 let arguments = vec!["core.weechat".to_string()];
922
923 let infolist_buffer = InfolistCommand::new(name.clone(), None, vec![]);
924 let command = Command::new(Some(id.clone()), infolist_buffer);
925 assert_eq!(command.to_string(), "(infolist_buffer) infolist buffer\n");
926
927 let infolist_buffer = InfolistCommand::new(name.clone(), Some(pointer.clone()), vec![]);
928 let command = Command::new(Some(id.clone()), infolist_buffer);
929 assert_eq!(
930 command.to_string(),
931 "(infolist_buffer) infolist buffer 0x1234abcd\n"
932 );
933
934 let infolist_buffer = InfolistCommand::new(name.clone(), None, arguments.clone());
935 let command = Command::new(Some(id.clone()), infolist_buffer);
936 assert_eq!(
937 command.to_string(),
938 "(infolist_buffer) infolist buffer 0x0 core.weechat\n"
939 );
940
941 let infolist_buffer = InfolistCommand::new(name, Some(pointer), arguments);
942 let command = Command::new(Some(id), infolist_buffer);
943 assert_eq!(
944 command.to_string(),
945 "(infolist_buffer) infolist buffer 0x1234abcd core.weechat\n"
946 );
947 }
948
949 #[test]
950 fn test_nicklist() {
951 let all_buffers = NicklistCommand { buffer: None };
952 let one_buffer = NicklistCommand {
953 buffer: Some(PointerOrName::Name("irc.libera.#weechat".to_string())),
954 };
955
956 let command = Command::new(Some("nicklist_all".to_string()), all_buffers);
957 assert_eq!(command.to_string(), "(nicklist_all) nicklist\n");
958
959 let command = Command::new(Some("nicklist_weechat".to_string()), one_buffer);
960 assert_eq!(
961 command.to_string(),
962 "(nicklist_weechat) nicklist irc.libera.#weechat\n"
963 );
964 }
965
966 #[test]
967 fn test_input() {
968 let help = InputCommand {
969 buffer: PointerOrName::Name("core.weechat".to_string()),
970 data: "/help filter".to_string(),
971 };
972
973 let hello = InputCommand {
974 buffer: PointerOrName::Name("irc.libera.#weechat".to_string()),
975 data: "hello!".to_string(),
976 };
977
978 let command = Command::new(None, help);
979 assert_eq!(command.to_string(), "input core.weechat /help filter\n");
980
981 let command = Command::new(None, hello);
982 assert_eq!(command.to_string(), "input irc.libera.#weechat hello!\n");
983 }
984
985 #[test]
986 fn test_completion() {
987 let completion_help = CompletionCommand {
988 buffer: PointerOrName::Name("core.weechat".to_string()),
989 position: None,
990 data: Some("/help fi".to_string()),
991 };
992
993 let completion_query = CompletionCommand {
994 buffer: PointerOrName::Name("core.weechat".to_string()),
995 position: Some(5),
996 data: Some("/quernick".to_string()),
997 };
998
999 let command = Command::new(Some("completion_help".to_string()), completion_help);
1000 assert_eq!(
1001 command.to_string(),
1002 "(completion_help) completion core.weechat -1 /help fi\n"
1003 );
1004
1005 let command = Command::new(Some("completion_query".to_string()), completion_query);
1006 assert_eq!(
1007 command.to_string(),
1008 "(completion_query) completion core.weechat 5 /quernick\n"
1009 );
1010 }
1011
1012 #[test]
1013 fn test_sync() {
1014 let all_buffers = SyncCommand::AllBuffers(SyncAllBuffers {
1015 buffers: true,
1016 upgrade: true,
1017 buffer: true,
1018 nicklist: true,
1019 });
1020
1021 let core_buffer = SyncCommand::SomeBuffers(
1022 vec![PointerOrName::Name("core.buffer".to_string())],
1023 SyncSomeBuffers::All,
1024 );
1025
1026 let without_nicklist = SyncCommand::SomeBuffers(
1027 vec![PointerOrName::Name("irc.libera.#weechat".to_string())],
1028 SyncSomeBuffers::Buffer,
1029 );
1030
1031 let general_signals = SyncCommand::AllBuffers(SyncAllBuffers {
1032 buffers: true,
1033 upgrade: true,
1034 buffer: false,
1035 nicklist: false,
1036 });
1037
1038 let command = Command {
1039 id: None,
1040 command: all_buffers,
1041 };
1042 assert_eq!(command.to_string(), "sync\n");
1043
1044 let command = Command::new(None, core_buffer);
1045 assert_eq!(command.to_string(), "sync core.buffer\n");
1046
1047 let command = Command::new(None, without_nicklist);
1048 assert_eq!(command.to_string(), "sync irc.libera.#weechat buffer\n");
1049
1050 let command = Command::new(None, general_signals);
1051 assert_eq!(command.to_string(), "sync * buffers,upgrade\n");
1052 }
1053
1054 #[test]
1055 fn test_desync() {
1056 let all_buffers = DesyncCommand::AllBuffers(SyncAllBuffers {
1057 buffers: true,
1058 upgrade: true,
1059 buffer: true,
1060 nicklist: true,
1061 });
1062
1063 let nicklist = DesyncCommand::SomeBuffers(
1064 vec![PointerOrName::Name("irc.libera.#weechat".to_string())],
1065 SyncSomeBuffers::Nicklist,
1066 );
1067
1068 let all_signals = DesyncCommand::SomeBuffers(
1069 vec![PointerOrName::Name("irc.libera.#weechat".to_string())],
1070 SyncSomeBuffers::All,
1071 );
1072
1073 let command = Command::new(None, all_buffers);
1074 assert_eq!(command.to_string(), "desync\n");
1075
1076 let command = Command::new(None, nicklist);
1077 assert_eq!(command.to_string(), "desync irc.libera.#weechat nicklist\n");
1078
1079 let command = Command::new(None, all_signals);
1080 assert_eq!(command.to_string(), "desync irc.libera.#weechat\n");
1081 }
1082
1083 #[test]
1084 fn test_test() {
1085 let test = TestCommand {};
1086 let command = Command::new(None, test);
1087 assert_eq!(command.to_string(), "test\n");
1088 }
1089
1090 #[test]
1091 fn test_ping() {
1092 let ping = PingCommand {
1093 argument: "foo".to_string(),
1094 };
1095 let command = Command::new(None, ping);
1096 assert_eq!(command.to_string(), "ping foo\n");
1097 }
1098
1099 #[test]
1100 fn test_quit() {
1101 let quit = QuitCommand {};
1102 let command = Command::new(None, quit);
1103 assert_eq!(command.to_string(), "quit\n");
1104 }
1105}