degesch: some more progress

Whoa, this thing is huge.

Started implementing the basis for IRC and user command handlers.
This commit is contained in:
Přemysl Eric Janouch 2015-04-16 00:56:05 +02:00
parent 08c0027397
commit c421532e6e
1 changed files with 167 additions and 41 deletions

208
degesch.c
View File

@ -78,6 +78,8 @@ static struct config_item g_config_table[] =
{ "socks_username", NULL, "SOCKS auth. username" },
{ "socks_password", NULL, "SOCKS auth. password" },
{ "isolate_buffers", "off", "Isolate global/server buffers" },
{ ATTR_PROMPT, NULL, "Terminal attributes for the prompt" },
{ ATTR_RESET, NULL, "String to reset terminal attributes" },
{ ATTR_WARNING, NULL, "Terminal attributes for warnings" },
@ -244,6 +246,7 @@ struct app_context
enum color_mode color_mode; ///< Colour output mode
bool reconnect; ///< Whether to reconnect on conn. fail.
unsigned long reconnect_delay; ///< Reconnect delay in seconds
bool isolate_buffers; ///< Isolate global/server buffers
// Server connection:
@ -810,7 +813,8 @@ buffer_add (struct app_context *ctx, struct buffer *buffer)
str_map_set (&ctx->buffers_by_name, buffer->name, buffer);
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
// TODO: refresh the prompt? Or caller?
// In theory this can't cause changes in the prompt
refresh_prompt (ctx);
}
static void
@ -848,7 +852,7 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
if (buffer == ctx->server_buffer)
ctx->server_buffer = NULL;
// TODO: refresh the prompt? Or caller?
refresh_prompt (ctx);
}
static void
@ -931,7 +935,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
rl_redisplay ();
}
// TODO: refresh the prompt? Or caller?
refresh_prompt (ctx);
}
static void
@ -1395,6 +1399,38 @@ init_readline (void)
// --- Input handling ----------------------------------------------------------
// TODO: we will need a proper mode parser; to be shared with kike
// TODO: we alse definitely need to parse server capability messages
static void
irc_handle_ping (struct app_context *ctx, const struct irc_message *msg)
{
if (msg->params.len)
irc_send (ctx, "PONG :%s", msg->params.vector[0]);
else
irc_send (ctx, "PONG");
}
static struct irc_handler
{
char *name;
void (*handler) (struct app_context *ctx, const struct irc_message *msg);
}
g_irc_handlers[] =
{
// This list needs to stay sorted
// TODO: handle as much as we can
{ "PING", irc_handle_ping },
};
static int
irc_handler_cmp_by_name (const void *a, const void *b)
{
const struct irc_handler *first = a;
const struct irc_handler *second = b;
return strcasecmp_ascii (first->name, second->name);
}
static void
irc_process_message (const struct irc_message *msg,
const char *raw, void *user_data)
@ -1414,16 +1450,7 @@ irc_process_message (const struct irc_message *msg,
app_readline_restore (&state, ctx->readline_prompt);
}
bool show_to_user = true;
if (!strcasecmp (msg->command, "PING"))
{
show_to_user = false;
if (msg->params.len)
irc_send (ctx, "PONG :%s", msg->params.vector[0]);
else
irc_send (ctx, "PONG");
}
else if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE")
if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE")
|| !strcasecmp (msg->command, "376") // RPL_ENDOFMOTD
|| !strcasecmp (msg->command, "422"))) // ERR_NOMOTD
{
@ -1435,29 +1462,56 @@ irc_process_message (const struct irc_message *msg,
if (autojoin)
irc_send (ctx, "JOIN :%s", autojoin);
}
else
{
// TODO: whatever processing we need
}
// This is going to be a lot more complicated
if (show_to_user)
struct irc_handler key = { .name = msg->command };
struct irc_handler *handler = bsearch (&key, g_irc_handlers,
N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name);
if (handler)
handler->handler (ctx, msg);
// Numerics typically have human-readable information
unsigned long dummy;
if (xstrtoul (&dummy, msg->command, 10))
// TODO: ensure proper encoding
// FIXME: print to the server buffer
print_status ("%s", raw);
}
// TODO: load and preprocess this table so that shortcuts are accepted
struct command_handler
// --- User input handling -----------------------------------------------------
static void handle_command_help (struct app_context *, const char *);
static void
handle_command_buffer (struct app_context *ctx, const char *arguments)
{
// TODO: parse the arguments
}
static void
handle_command_quit (struct app_context *ctx, const char *arguments)
{
if (ctx->irc_fd != -1)
{
if (*arguments)
irc_send (ctx, "QUIT :%s", arguments);
else
irc_send (ctx, "QUIT :%s", PROGRAM_NAME " " PROGRAM_VERSION);
}
initiate_quit (ctx);
}
static struct command_handler
{
char *name;
void (*handler) (struct app_context *ctx, const char *arguments);
// TODO: probably also a usage string
}
g_handlers[] =
g_command_handlers[] =
{
{ "buffer", NULL },
{ "help", NULL },
{ "help", handle_command_help },
{ "quit", handle_command_quit },
{ "buffer", handle_command_buffer },
#if 0
{ "msg", NULL },
{ "query", NULL },
{ "notice", NULL },
@ -1483,17 +1537,93 @@ g_handlers[] =
{ "motd", NULL },
{ "away", NULL },
{ "quote", NULL },
{ "quit", NULL },
#endif
};
static void
process_internal_command (struct app_context *ctx, const char *command)
handle_command_help (struct app_context *ctx, const char *arguments)
{
// TODO: resolve commands from a map
// TODO: show a list of all user commands
}
static int
command_handler_cmp_by_length (const void *a, const void *b)
{
const struct command_handler *first = a;
const struct command_handler *second = b;
return strlen (first->name) - strlen (second->name);
}
static void
init_partial_matching_user_command_map (struct str_map *partial)
{
str_map_init (partial);
partial->key_xfrm = tolower_ascii_strxfrm;
// We process them from the longest to the shortest one,
// so that common prefixes favor shorter entries
struct command_handler *by_length[N_ELEMENTS (g_command_handlers)];
for (size_t i = 0; i < N_ELEMENTS (by_length); i++)
by_length[i] = &g_command_handlers[i];
qsort (by_length, N_ELEMENTS (by_length), sizeof *by_length,
command_handler_cmp_by_length);
for (size_t i = N_ELEMENTS (by_length); i--; )
{
char *copy = xstrdup (by_length[i]->name);
for (size_t part = strlen (copy); part; part--)
{
copy[part] = '\0';
str_map_set (partial, copy, by_length[i]);
}
free (copy);
}
}
static void
process_user_command (struct app_context *ctx, char *command)
{
// Trivially create a partial matching map
static bool initialized = false;
struct str_map partial;
if (!initialized)
{
init_partial_matching_user_command_map (&partial);
initialized = true;
}
// TODO: cut a single word (strtok_r()?)
// TODO: if it's a number, switch to the given buffer
if (!strcmp (command, "quit"))
initiate_quit (ctx);
struct command_handler *handler = str_map_find (&partial, command);
if (handler)
// FIXME: pass arguments correctly
handler->handler (ctx, "");
}
static void
send_message_to_current_buffer (struct app_context *ctx, char *message)
{
struct buffer *buffer = ctx->current_buffer;
if (!buffer)
{
// TODO: print an error message to the global buffer
return;
}
switch (buffer->type)
{
case BUFFER_GLOBAL:
case BUFFER_SERVER:
// TODO: print a message to the buffer that it's not a channel
break;
case BUFFER_CHANNEL:
// TODO: send an IRC message to the channel
break;
case BUFFER_PM:
// TODO: send an IRC message to the user
break;
}
}
static void
@ -1503,19 +1633,12 @@ process_input (struct app_context *ctx, char *user_input)
size_t len;
if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len)))
{
print_error ("character conversion failed for `%s'", "user input");
goto fail;
}
if (*input == '/')
process_internal_command (ctx, input + 1);
else if (input[0] == '/' && input[1] != '/')
process_user_command (ctx, input + 1);
else
{
// TODO: send a message to the current buffer
}
send_message_to_current_buffer (ctx, input);
fail:
free (input);
}
@ -2048,7 +2171,10 @@ load_config (struct app_context *ctx, struct error **e)
if (!success)
return false;
if (!irc_get_boolean_from_config (ctx, "reconnect", &ctx->reconnect, e))
if (!irc_get_boolean_from_config (ctx,
"reconnect", &ctx->reconnect, e)
|| !irc_get_boolean_from_config (ctx,
"isolate_buffers", &ctx->isolate_buffers, e))
return false;
const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");