]> cgit.babelmonkeys.de Git - jubjub.git/blobdiff - src/gui/cli/JubCLIUI.m
Add a zxcv-like CLI frontend
[jubjub.git] / src / gui / cli / JubCLIUI.m
diff --git a/src/gui/cli/JubCLIUI.m b/src/gui/cli/JubCLIUI.m
new file mode 100644 (file)
index 0000000..e44a3f3
--- /dev/null
@@ -0,0 +1,319 @@
+#import "JubCLIChatUI.h"
+#import "JubChatClient.h"
+#import "JubCLIColor.h"
+#import "JubCLICommand.h"
+#import "JubCLIUI.h"
+
+BEGINCLICOMMAND(JubCLIReplyCommand, @":r", nil,
+    @"Sets the sender of the last incomming message as the default recipient")
+{
+       if (_ui.lastIn == nil) {
+               [of_stdout writeLine: @"No message has been received yet"];
+               return;
+       }
+
+       _ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: _ui.lastIn];
+       [of_stdout writeFormat: @"Set sink to %@\n",
+           [_ui.lastIn.rosterItem.JID bareJID]];
+}
+ENDCLICOMMAND
+
+BEGINCLICOMMAND(JubCLISetSinkCommand, @":s", @"<who>",
+    @"Selects <who> as the default recipient")
+{
+       if ([parameters count] != 1) {
+               [of_stdout writeLine: @"Syntax: ':s <who>'"];
+               return;
+       }
+
+       OFString *param = parameters[0];
+       XMPPContact *contact = _ui.client.contactManager.contacts[param];
+
+       if (contact == nil) {
+               [of_stdout writeFormat: @"Contact '%@' not found in your "
+                   @"roster\n", param];
+               return;
+       }
+
+       _ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: contact];
+
+       [of_stdout writeFormat: @"Set sink to %@\n", param];
+}
+ENDCLICOMMAND
+
+BEGINCLICOMMAND(JubCLIMessageCommand, @":m", @"<who> <message>",
+    @"Sends a single message to <who>")
+{
+       if ([parameters count] < 2) {
+               [of_stdout writeLine: @"Syntax: ':m <who> <message>'"];
+               return;
+       }
+
+       XMPPContact *contact =
+           _ui.client.contactManager.contacts[parameters[0]];
+
+       if (contact == nil) {
+               [of_stdout writeFormat: @"Contact %@ not found in your "
+                   @"roster\n", parameters[0]];
+               return;
+       }
+
+       JubCLIChatUI *chat =
+           (JubCLIChatUI*)[_ui.client chatForContact: contact];
+
+       OFArray *message =
+           [parameters arrayByRemovingObject: [parameters firstObject]];
+
+       [chat send: [message componentsJoinedByString: @" "]];
+}
+ENDCLICOMMAND
+
+BEGINCLICOMMAND(JubCLIPresenceCommand, @":t", @"<status> [<message>]",
+    @"Changes your presence")
+{
+       if ([parameters count] < 1) {
+               [of_stdout writeLine:
+                   @"Syntax: ':t <status> [<message>]'"];
+               return;
+       }
+
+       XMPPPresence *presence;
+       OFString *show = parameters[0];
+
+       if (![@[ @"available", @"away", @"dnd", @"xa", @"chat", @"unavailable" ]
+           containsObject: show]) {
+               [of_stdout writeLine: @"<status> must be one of:"
+                   @"available, away, dnd, xa, chat, unavailable"];
+               return;
+       }
+
+       if ([show isEqual: @"unavailable"])
+               presence = [XMPPPresence presenceWithType: show];
+       else
+               presence = [XMPPPresence presence];
+
+       if (![@[ @"available", @"unavailable" ] containsObject: show])
+               presence.show = show;
+
+       if ([parameters count] == 2) {
+               [_ui.client.connection sendStanza: presence];
+               return;
+       }
+
+       OFArray *message =
+           [parameters arrayByRemovingObject: [parameters firstObject]];
+       presence.status = [message componentsJoinedByString: @" "];
+
+       [_ui.client.connection sendStanza: presence];
+}
+ENDCLICOMMAND
+
+BEGINCLICOMMAND(JubCLIRosterCommand, @":roster", nil, @"Shows your roster")
+{
+       OFDictionary *contacts = _ui.client.contactManager.contacts;
+       for (OFString *key in contacts) {
+               XMPPContact *contact = contacts[key];
+               OFString *name = contact.rosterItem.name;
+               XMPPPresence *presence =
+                   [[[contact.presences allObjects] sortedArray] firstObject];
+
+               if (name != nil)
+                       [of_stdout writeFormat: @"%@ <%@> (", name, key];
+               else
+                       [of_stdout writeFormat: @"%@ (", key];
+
+               if (presence == nil)
+                       [of_stdout writeFormat: COL_OFFLINE(@"offline")];
+               else if ([presence.show isEqual: @"chat"])
+                       [of_stdout writeFormat: COL_CHAT(@"free for chat")];
+               else if ([presence.show isEqual: @"away"])
+                       [of_stdout writeFormat: COL_AWAY(@"away")];
+               else if ([presence.show isEqual: @"xa"])
+                       [of_stdout writeFormat: COL_XA(@"extended away")];
+               else if ([presence.show isEqual: @"dnd"])
+                       [of_stdout writeFormat: COL_DND(@"do not disturb")];
+               else
+                       [of_stdout writeFormat: COL_ONLINE(@"online")];
+
+               [of_stdout writeString: @")\n"];
+       }
+}
+ENDCLICOMMAND
+
+@implementation JubCLIUI
+@synthesize client = _client;
+@synthesize lastIn = _lastIn;
+@synthesize sink = _sink;
+
+- initWithClient: (JubChatClient*)client
+{
+       self = [super init];
+
+       @try {
+               _commands =  [OFMutableDictionary new];
+               _client = [client retain];
+               _contactManager = client.contactManager;
+               [_contactManager addDelegate: self];
+
+               [self addCommand: [[[JubCLIReplyCommand alloc]
+                                      initWithCLIUI: self] autorelease]];
+
+               [self addCommand: [[[JubCLISetSinkCommand alloc]
+                                      initWithCLIUI: self] autorelease]];
+
+               [self addCommand: [[[JubCLIMessageCommand alloc]
+                                      initWithCLIUI: self] autorelease]];
+
+               [self addCommand: [[[JubCLIPresenceCommand alloc]
+                                      initWithCLIUI: self] autorelease]];
+
+               [self addCommand: [[[JubCLIRosterCommand alloc]
+                                      initWithCLIUI: self] autorelease]];
+       } @catch (id e) {
+               [self release];
+               @throw e;
+       }
+
+       return self;
+}
+
+- (void)dealloc
+{
+       [_contactManager removeDelegate: self];
+       [_client release];
+       [_commands release];
+       [super dealloc];
+}
+
+- (void)startUIThread
+{
+       [of_stdin asyncReadLineWithTarget: self
+                                selector: @selector(Jub_userInputWithStream:
+                                                    line:exception:)];
+}
+
+-      (void)client: (JubChatClient*)client
+  didChangePresence: (XMPPPresence*)presence
+{
+}
+
+- (Class)chatUIClass
+{
+       return [JubCLIChatUI class];
+}
+
+- (void)addCommand: (id<JubCLICommand>)command
+{
+       [_commands setObject: command
+                     forKey: command.command];
+}
+
+- (BOOL)Jub_userInputWithStream: (OFStream*)stream
+                          line: (OFString*)line
+                     exception: (OFException*)exception
+{
+       if (line == nil)
+               [OFApplication terminate];
+
+       if ([line length] == 0)
+               return YES;
+
+       if ([line characterAtIndex: 0] != ':') {
+               if (_sink == nil)
+                       [of_stdout writeLine: @"No default sink selected, "
+                           @"type `:h` for help"];
+
+               [_sink send: line];
+
+               return YES;
+       }
+
+       line = [line stringByDeletingTrailingWhitespaces];
+
+       OFArray *input= [line componentsSeparatedByString: @" "];
+
+       if ([input[0] isEqual: @":h"]) {
+               __block size_t longest = 0;
+
+               [_commands enumerateKeysAndObjectsUsingBlock:
+                   ^(OFString *key, id<JubCLICommand> command, BOOL *stop) {
+                       size_t length = [command.command length] +
+                           (command.params == nil ? 0 :
+                               (1 + [command.params length]));
+
+                       if (length > longest)
+                               longest = length;
+               }];
+
+               for (OFString *key in [[_commands allKeys] sortedArray]) {
+                       id<JubCLICommand> command = _commands[key];
+                       size_t length = [command.command length] +
+                           (command.params == nil ? 0 :
+                               (1 + [command.params length]));
+
+                       if (command.params == nil)
+                               [of_stdout writeFormat: @"`%@`",
+                                   command.command];
+                       else
+                               [of_stdout writeFormat: @"`%@ %@`",
+                                   command.command, command.params];
+
+                       // This is NOT distributive due to integer arithmetic
+                       size_t padding = (longest + 2)/8 - (length + 2)/8;
+
+                       for (size_t i = 0; i <= padding; i++)
+                               [of_stdout writeString: @"\t"];
+
+                       [of_stdout writeFormat: @"- %@\n", command.help];
+               };
+
+               return YES;
+       }
+
+       id<JubCLICommand> command = _commands[input[0]];
+
+       if (command) {
+               [command callWithParameters:
+                   [input arrayByRemovingObject: [input firstObject]]];
+
+               return YES;
+       }
+
+       [of_stdout writeLine: @"Invalid command, type `:h` for help"];
+
+       return YES;
+}
+
+-  (void)contact: (XMPPContact*)contact
+  didSendMessage: (XMPPMessage*)message
+{
+       if (message.body == nil || ![message.type isEqual: @"chat"])
+               return;
+
+       _lastIn =  contact;
+}
+
+-   (void)contact: (XMPPContact*)contact
+  didSendPresence: (XMPPPresence*)presence
+{
+       [of_stdout writeFormat: BOLD("%@") @" is now in state ", presence.from];
+
+       if ([presence.type isEqual: @"unavailable"])
+               [of_stdout writeFormat: COL_OFFLINE(@"offline")];
+       else if ([presence.show isEqual: @"chat"])
+               [of_stdout writeFormat: COL_CHAT(@"free for chat")];
+       else if ([presence.show isEqual: @"away"])
+               [of_stdout writeFormat: COL_AWAY(@"away")];
+       else if ([presence.show isEqual: @"xa"])
+               [of_stdout writeFormat: COL_XA(@"extended away")];
+       else if ([presence.show isEqual: @"dnd"])
+               [of_stdout writeFormat: COL_DND(@"do not disturb")];
+       else
+               [of_stdout writeFormat: COL_ONLINE(@"online")];
+
+       if (presence.status != nil)
+               [of_stdout writeFormat: @": %@", presence.status];
+
+       [of_stdout writeString: @"\n"];
+}
+@end