Use g_list_free_full instead of manual iterations
[pidgin-git.git] / libpurple / cmds.c
blob7d3533733c9240ef7a05447d3dbb276909ba94f7
1 /* Copyright (C) 2003-2004 Timothy Ringenbach <omarvo@hotmail.com
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
19 #include "internal.h"
21 #include "account.h"
22 #include "util.h"
23 #include "cmds.h"
25 static PurpleCommandsUiOps *cmds_ui_ops = NULL;
26 static GList *cmds = NULL;
27 static guint next_id = 1;
29 typedef struct {
30 PurpleCmdId id;
31 gchar *cmd;
32 gchar *args;
33 PurpleCmdPriority priority;
34 PurpleCmdFlag flags;
35 gchar *protocol_id;
36 PurpleCmdFunc func;
37 gchar *help;
38 void *data;
39 } PurpleCmd;
42 static gint cmds_compare_func(const PurpleCmd *a, const PurpleCmd *b)
44 if (a->priority > b->priority)
45 return -1;
46 else if (a->priority < b->priority)
47 return 1;
48 else return 0;
51 PurpleCmdId purple_cmd_register(const gchar *cmd, const gchar *args,
52 PurpleCmdPriority p, PurpleCmdFlag f,
53 const gchar *protocol_id, PurpleCmdFunc func,
54 const gchar *helpstr, void *data)
56 PurpleCmdId id;
57 PurpleCmd *c;
58 PurpleCommandsUiOps *ops;
60 g_return_val_if_fail(cmd != NULL && *cmd != '\0', 0);
61 g_return_val_if_fail(args != NULL, 0);
62 g_return_val_if_fail(func != NULL, 0);
64 id = next_id++;
66 c = g_new0(PurpleCmd, 1);
67 c->id = id;
68 c->cmd = g_strdup(cmd);
69 c->args = g_strdup(args);
70 c->priority = p;
71 c->flags = f;
72 c->protocol_id = g_strdup(protocol_id);
73 c->func = func;
74 c->help = g_strdup(helpstr);
75 c->data = data;
77 cmds = g_list_insert_sorted(cmds, c, (GCompareFunc)cmds_compare_func);
79 ops = purple_cmds_get_ui_ops();
80 if (ops && ops->register_command)
81 ops->register_command(cmd, p, f, protocol_id, helpstr, c->id);
83 purple_signal_emit(purple_cmds_get_handle(), "cmd-added", cmd, p, f);
85 return id;
88 static void purple_cmd_free(PurpleCmd *c)
90 g_free(c->cmd);
91 g_free(c->args);
92 g_free(c->protocol_id);
93 g_free(c->help);
94 g_free(c);
97 void purple_cmd_unregister(PurpleCmdId id)
99 PurpleCmd *c;
100 GList *l;
102 for (l = cmds; l; l = l->next) {
103 c = l->data;
105 if (c->id == id) {
106 PurpleCommandsUiOps *ops = purple_cmds_get_ui_ops();
107 if (ops && ops->unregister_command)
108 ops->unregister_command(c->cmd, c->protocol_id);
110 cmds = g_list_remove(cmds, c);
111 purple_signal_emit(purple_cmds_get_handle(), "cmd-removed", c->cmd);
112 purple_cmd_free(c);
113 return;
119 * This sets args to a NULL-terminated array of strings. It should
120 * be freed using g_strfreev().
122 static gboolean purple_cmd_parse_args(PurpleCmd *cmd, const gchar *s, const gchar *m, gchar ***args)
124 int i;
125 const char *end, *cur;
127 *args = g_new0(char *, strlen(cmd->args) + 1);
129 cur = s;
131 for (i = 0; cmd->args[i]; i++) {
132 if (!*cur)
133 return (cmd->flags & PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS);
135 switch (cmd->args[i]) {
136 case 'w':
137 if (!(end = strchr(cur, ' '))) {
138 end = cur + strlen(cur);
139 (*args)[i] = g_strndup(cur, end - cur);
140 cur = end;
141 } else {
142 (*args)[i] = g_strndup(cur, end - cur);
143 cur = end + 1;
145 break;
146 case 'W':
147 if (!(end = strchr(cur, ' '))) {
148 end = cur + strlen(cur);
149 (*args)[i] = purple_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_pointer_to_offset(s, end));
150 cur = end;
151 } else {
152 (*args)[i] = purple_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_pointer_to_offset(s, end));
153 cur = end +1;
155 break;
156 case 's':
157 (*args)[i] = g_strdup(cur);
158 cur = cur + strlen(cur);
159 break;
160 case 'S':
161 (*args)[i] = purple_markup_slice(m, g_utf8_pointer_to_offset(s, cur), g_utf8_strlen(cur, -1) + 1);
162 cur = cur + strlen(cur);
163 break;
167 if (*cur)
168 return (cmd->flags & PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS);
170 return TRUE;
173 static void purple_cmd_strip_current_char(gunichar c, char *s, guint len)
175 int bytes;
177 bytes = g_unichar_to_utf8(c, NULL);
178 memmove(s, s + bytes, len + 1 - bytes);
181 static void purple_cmd_strip_cmd_from_markup(char *markup)
183 guint len = strlen(markup);
184 char *s = markup;
186 while (*s) {
187 gunichar c = g_utf8_get_char(s);
189 if (c == '<') {
190 s = strchr(s, '>');
191 if (!s)
192 return;
193 } else if (g_unichar_isspace(c)) {
194 purple_cmd_strip_current_char(c, s, len - (s - markup));
195 return;
196 } else {
197 purple_cmd_strip_current_char(c, s, len - (s - markup));
198 continue;
200 s = g_utf8_next_char(s);
204 PurpleCmdStatus purple_cmd_do_command(PurpleConversation *conv, const gchar *cmdline,
205 const gchar *markup, gchar **error)
207 PurpleCmd *c;
208 GList *l;
209 gchar *err = NULL;
210 gboolean is_im = TRUE;
211 gboolean found = FALSE, tried_cmd = FALSE, right_type = FALSE, right_protocol = FALSE;
212 const gchar *protocol_id;
213 gchar **args = NULL;
214 gchar *cmd, *rest, *mrest;
215 PurpleCmdRet ret = PURPLE_CMD_RET_CONTINUE;
217 *error = NULL;
218 protocol_id = purple_account_get_protocol_id(purple_conversation_get_account(conv));
220 if (PURPLE_IS_CHAT_CONVERSATION(conv))
221 is_im = FALSE;
223 rest = strchr(cmdline, ' ');
224 if (rest) {
225 cmd = g_strndup(cmdline, rest - cmdline);
226 rest++;
227 } else {
228 cmd = g_strdup(cmdline);
229 rest = "";
232 mrest = g_strdup(markup);
233 purple_cmd_strip_cmd_from_markup(mrest);
235 for (l = cmds; l; l = l->next) {
236 c = l->data;
238 if (!purple_strequal(c->cmd, cmd))
239 continue;
241 found = TRUE;
243 if (is_im)
244 if (!(c->flags & PURPLE_CMD_FLAG_IM))
245 continue;
246 if (!is_im)
247 if (!(c->flags & PURPLE_CMD_FLAG_CHAT))
248 continue;
250 right_type = TRUE;
252 if ((c->flags & PURPLE_CMD_FLAG_PROTOCOL_ONLY) &&
253 !purple_strequal(c->protocol_id, protocol_id))
254 continue;
256 right_protocol = TRUE;
258 /* this checks the allow bad args flag for us */
259 if (!purple_cmd_parse_args(c, rest, mrest, &args)) {
260 g_strfreev(args);
261 args = NULL;
262 continue;
265 tried_cmd = TRUE;
266 ret = c->func(conv, cmd, args, &err, c->data);
267 if (ret == PURPLE_CMD_RET_CONTINUE) {
268 g_free(err);
269 err = NULL;
270 g_strfreev(args);
271 args = NULL;
272 continue;
273 } else {
274 break;
279 g_strfreev(args);
280 g_free(cmd);
281 g_free(mrest);
283 if (!found)
284 return PURPLE_CMD_STATUS_NOT_FOUND;
286 if (!right_type)
287 return PURPLE_CMD_STATUS_WRONG_TYPE;
288 if (!right_protocol)
289 return PURPLE_CMD_STATUS_WRONG_PROTOCOL;
290 if (!tried_cmd)
291 return PURPLE_CMD_STATUS_WRONG_ARGS;
293 if (ret == PURPLE_CMD_RET_OK) {
294 return PURPLE_CMD_STATUS_OK;
295 } else {
296 *error = err;
297 if (ret == PURPLE_CMD_RET_CONTINUE)
298 return PURPLE_CMD_STATUS_NOT_FOUND;
299 else
300 return PURPLE_CMD_STATUS_FAILED;
305 gboolean purple_cmd_execute(PurpleCmdId id, PurpleConversation *conv,
306 const gchar *cmdline)
308 PurpleCmd *cmd = NULL;
309 PurpleCmdRet ret = PURPLE_CMD_RET_CONTINUE;
310 GList *l = NULL;
311 gchar *err = NULL;
312 gchar **args = NULL;
314 for(l = cmds; l; l = l->next) {
315 cmd = (PurpleCmd*)l->data;
317 if(cmd->id == id) {
318 break;
320 cmd = NULL;
322 if(cmd == NULL) {
323 return FALSE;
326 if (PURPLE_IS_IM_CONVERSATION(conv)) {
327 if (!(cmd->flags & PURPLE_CMD_FLAG_IM))
328 return FALSE;
330 else if (PURPLE_IS_CHAT_CONVERSATION(conv)) {
331 if (!(cmd->flags & PURPLE_CMD_FLAG_CHAT))
332 return FALSE;
334 else
335 return FALSE;
337 /* XXX: Don't worry much about the markup version of the command
338 line, there's not a single use case... */
339 /* this checks the allow bad args flag for us */
340 if (!purple_cmd_parse_args(cmd, cmdline, cmdline, &args)) {
341 g_strfreev(args);
342 return FALSE;
345 ret = cmd->func(conv, cmd->cmd, args, &err, cmd->data);
347 g_free(err);
348 g_strfreev(args);
350 return ret == PURPLE_CMD_RET_OK;
353 GList *purple_cmd_list(PurpleConversation *conv)
355 GList *ret = NULL;
356 PurpleCmd *c;
357 GList *l;
359 for (l = cmds; l; l = l->next) {
360 c = l->data;
362 if (conv && PURPLE_IS_IM_CONVERSATION(conv))
363 if (!(c->flags & PURPLE_CMD_FLAG_IM))
364 continue;
365 if (conv && PURPLE_IS_CHAT_CONVERSATION(conv))
366 if (!(c->flags & PURPLE_CMD_FLAG_CHAT))
367 continue;
369 if (conv && (c->flags & PURPLE_CMD_FLAG_PROTOCOL_ONLY) &&
370 !purple_strequal(c->protocol_id, purple_account_get_protocol_id(purple_conversation_get_account(conv))))
371 continue;
373 ret = g_list_append(ret, c->cmd);
376 ret = g_list_sort(ret, (GCompareFunc)strcmp);
378 return ret;
382 GList *purple_cmd_help(PurpleConversation *conv, const gchar *cmd)
384 GList *ret = NULL;
385 PurpleCmd *c;
386 GList *l;
388 for (l = cmds; l; l = l->next) {
389 c = l->data;
391 if (cmd && !purple_strequal(cmd, c->cmd))
392 continue;
394 if (conv && PURPLE_IS_IM_CONVERSATION(conv))
395 if (!(c->flags & PURPLE_CMD_FLAG_IM))
396 continue;
397 if (conv && PURPLE_IS_CHAT_CONVERSATION(conv))
398 if (!(c->flags & PURPLE_CMD_FLAG_CHAT))
399 continue;
401 if (conv && (c->flags & PURPLE_CMD_FLAG_PROTOCOL_ONLY) &&
402 !purple_strequal(c->protocol_id, purple_account_get_protocol_id(purple_conversation_get_account(conv))))
403 continue;
405 ret = g_list_append(ret, c->help);
408 ret = g_list_sort(ret, (GCompareFunc)strcmp);
410 return ret;
413 gpointer purple_cmds_get_handle(void)
415 static int handle;
416 return &handle;
419 void
420 purple_cmds_set_ui_ops(PurpleCommandsUiOps *ops)
422 cmds_ui_ops = ops;
425 PurpleCommandsUiOps *
426 purple_cmds_get_ui_ops(void)
428 /* It is perfectly acceptable for cmds_ui_ops to be NULL; this just
429 * means that the default libpurple implementation will be used.
431 return cmds_ui_ops;
434 void purple_cmds_init(void)
436 gpointer handle = purple_cmds_get_handle();
438 purple_signal_register(handle, "cmd-added",
439 purple_marshal_VOID__POINTER_INT_INT, G_TYPE_NONE, 3,
440 G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
441 purple_signal_register(handle, "cmd-removed",
442 purple_marshal_VOID__POINTER, G_TYPE_NONE, 1,
443 G_TYPE_STRING);
446 void purple_cmds_uninit(void)
448 purple_signals_unregister_by_instance(purple_cmds_get_handle());
450 g_list_free_full(cmds, (GDestroyNotify)purple_cmd_free);