1 /* 2 * Copyright (C) 2012 Vivien Malerba <malerba@gnome-db.org> 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 */ 18 19 #include "tool-command.h" 20 #include "tool-help.h" 21 #include <glib/gi18n-lib.h> 22 23 struct _ToolCommandGroup { 24 GSList *name_ordered; 25 GSList *group_ordered; 26 }; 27 28 /* module error */ 29 GQuark 30 tool_command_error_quark (void) 31 { 32 static GQuark quark; 33 if (!quark) 34 quark = g_quark_from_static_string ("tool_command_error"); 35 return quark; 36 } 37 38 39 /** 40 * tool_command_result_new: 41 * @type: a #ToolCommandResultType 42 * 43 * Returns: (transfer full): a new #ToolCommandResult 44 */ 45 ToolCommandResult * 46 tool_command_result_new (GdaConnection *cnc, ToolCommandResultType type) 47 { 48 ToolCommandResult *res; 49 res = g_new0 (ToolCommandResult, 1); 50 res->type = type; 51 if (cnc) { 52 res->was_in_transaction_before_exec = 53 gda_connection_get_transaction_status (cnc) ? TRUE : FALSE; 54 res->cnc = g_object_ref (cnc); 55 } 56 return res; 57 } 58 59 /** 60 * tool_command_result_free: 61 * @res: (allow-none): a #ToolCommandResult, or %NULL 62 * 63 * Frees any resource used by @res 64 */ 65 void 66 tool_command_result_free (ToolCommandResult *res) 67 { 68 if (!res) 69 return; 70 switch (res->type) { 71 case TOOL_COMMAND_RESULT_DATA_MODEL: 72 if (res->u.model) 73 g_object_unref (res->u.model); 74 if (! res->was_in_transaction_before_exec && 75 res->cnc && 76 gda_connection_get_transaction_status (res->cnc)) 77 gda_connection_rollback_transaction (res->cnc, NULL, NULL); 78 break; 79 case TOOL_COMMAND_RESULT_SET: 80 if (res->u.set) 81 g_object_unref (res->u.set); 82 break; 83 case TOOL_COMMAND_RESULT_TREE: 84 if (res->u.tree) 85 g_object_unref (res->u.tree); 86 break; 87 case TOOL_COMMAND_RESULT_TXT: 88 case TOOL_COMMAND_RESULT_TXT_STDOUT: 89 if (res->u.txt) 90 g_string_free (res->u.txt, TRUE); 91 break; 92 case TOOL_COMMAND_RESULT_MULTIPLE: { 93 GSList *list; 94 95 for (list = res->u.multiple_results; list; list = list->next) 96 tool_command_result_free ((ToolCommandResult *) list->data); 97 g_slist_free (res->u.multiple_results); 98 break; 99 } 100 default: 101 break; 102 } 103 if (res->cnc) 104 g_object_unref (res->cnc); 105 g_free (res); 106 } 107 108 /** 109 * tool_command_group_new: 110 * 111 * Returns: (transfer full): a new #ToolCommandGroup structure 112 */ 113 ToolCommandGroup * 114 tool_command_group_new (void) 115 { 116 ToolCommandGroup *group; 117 group = g_new0 (ToolCommandGroup, 1); 118 return group; 119 } 120 121 /** 122 * tool_command_group_free: 123 * @group: (allow-none) (transfer full): a #ToolCommandGroup pointer 124 * 125 * Frees any resource used by @group 126 */ 127 void 128 tool_command_group_free (ToolCommandGroup *group) 129 { 130 if (group) { 131 g_slist_free (group->name_ordered); 132 g_slist_free (group->group_ordered); 133 g_free (group); 134 } 135 } 136 137 static gint 138 commands_compare_name (ToolCommand *a, ToolCommand *b) 139 { 140 gint cmp, alength, blength; 141 if (!a->name || !b->name) { 142 g_warning (_("Invalid unnamed command")); 143 if (!a->name) { 144 if (b->name) 145 return 1; 146 else 147 return 0; 148 } 149 else 150 return -1; 151 } 152 return strcmp (a->name, b->name); 153 } 154 155 static gint 156 commands_compare_group (ToolCommand *a, ToolCommand *b) 157 { 158 gint cmp, alength, blength; 159 if (!a->group || !b->group) { 160 g_warning (_("Invalid unnamed command")); 161 if (!a->group) { 162 if (b->group) 163 return 1; 164 else 165 return 0; 166 } 167 else 168 return -1; 169 } 170 return strcmp (a->group, b->group); 171 } 172 173 /** 174 * tool_command_group_add: 175 * @group: a #ToolCommandGroup pointer 176 * @cmd: (transfer none): the command to add 177 * 178 * Add @cmd to @group. If a previous command with the same name existed, 179 * it is replaced by the new one. 180 * 181 * @cmd is used as it is (i.e. not copied). 182 */ 183 void 184 tool_command_group_add (ToolCommandGroup *group, ToolCommand *cmd) 185 { 186 g_return_if_fail (group); 187 g_return_if_fail (cmd); 188 g_return_if_fail (cmd->name && *cmd->name && g_ascii_isalpha (*cmd->name)); 189 g_return_if_fail (cmd->group && *cmd->group); 190 if (cmd->name && !cmd->name_args) { 191 cmd->name_args = cmd->name; 192 gchar *tmp; 193 for (tmp = cmd->name_args; *tmp && !g_ascii_isspace(*tmp); tmp++); 194 cmd->name = g_strndup (cmd->name_args, tmp - cmd->name_args); 195 } 196 197 tool_command_group_remove (group, cmd->name); 198 199 group->name_ordered = g_slist_insert_sorted (group->name_ordered, cmd, 200 (GCompareFunc) commands_compare_name); 201 group->group_ordered = g_slist_insert_sorted (group->group_ordered, cmd, 202 (GCompareFunc) commands_compare_group); 203 } 204 205 /** 206 * tool_command_group_remove: 207 * @group: a #ToolCommandGroup pointer 208 * @name: the name of the command to remove 209 * 210 * Remove @cmd from @group. If @cmd is not in @group, then nothing is done. 211 */ 212 void 213 tool_command_group_remove (ToolCommandGroup *group, const gchar *name) 214 { 215 g_return_if_fail (group); 216 217 GSList *list; 218 for (list = group->name_ordered; list; list = list->next) { 219 ToolCommand *ec; 220 gint cmp; 221 ec = (ToolCommand*) list->data; 222 cmp = strcmp (name, ec->name); 223 if (!cmp) { 224 group->name_ordered = g_slist_remove (group->name_ordered, ec); 225 group->group_ordered = g_slist_remove (group->group_ordered, ec); 226 break; 227 } 228 else if (cmp > 0) 229 break; 230 } 231 } 232 233 /** 234 * tool_command_group_find: 235 */ 236 ToolCommand * 237 tool_command_group_find (ToolCommandGroup *group, const gchar *name, GError **error) 238 { 239 ToolCommand *cmd = NULL; 240 GSList *list; 241 gsize length; 242 243 if (!name) 244 return NULL; 245 246 for (list = group->name_ordered; list; list = list->next) { 247 ToolCommand *command; 248 command = (ToolCommand*) list->data; 249 gint cmp; 250 cmp = strcmp (command->name, name); 251 if (!cmp) { 252 cmd = command; 253 break; 254 } 255 else if (cmp > 0) 256 break; 257 } 258 259 if (! cmd) { 260 /* see if a shortcut version leads to a command */ 261 length = strlen (name); 262 guint nmatch = 0; 263 for (list = group->name_ordered; list; list = list->next) { 264 ToolCommand *command; 265 command = (ToolCommand*) list->data; 266 if (!strncmp (command->name, name, MIN (length, strlen (command->name)))) { 267 nmatch ++; 268 cmd = command; 269 } 270 } 271 if (nmatch != 1) 272 cmd = NULL; 273 } 274 275 if (!cmd && 276 ((*name == 'h') || (*name == 'H'))) 277 cmd = tool_command_group_find (group, "?", NULL); 278 279 if (!cmd) { 280 g_set_error (error, TOOL_COMMAND_ERROR, 281 TOOL_COMMAND_COMMAND_NOT_FOUND_ERROR, 282 _("Command '%s' not found"), name); 283 } 284 285 return cmd; 286 } 287 288 static gchar ** 289 split_command_string (const gchar *cmde, guint *out_n_args, GError **error) 290 { 291 GArray *args; 292 gchar *ptr, *str; 293 g_assert (cmde && *cmde); 294 g_assert (out_n_args); 295 296 *out_n_args = 0; 297 str = g_strdup (cmde); 298 args = g_array_new (TRUE, FALSE, sizeof (gchar*)); 299 for (ptr = str; *ptr; ptr++) { 300 gchar *tmp; 301 gboolean inquotes = FALSE; 302 303 for (tmp = ptr; *tmp && g_ascii_isspace (*tmp); tmp++); /* ignore spaces */ 304 305 for (; *tmp; tmp++) { 306 if (*tmp == '"') 307 inquotes = !inquotes; 308 else if (*tmp == '\\') { 309 tmp++; 310 if (! *tmp) { 311 g_set_error (error, TOOL_COMMAND_ERROR, 312 TOOL_COMMAND_SYNTAX_ERROR, 313 _("Syntax error after '\\'")); 314 goto onerror; 315 } 316 } 317 else if (!inquotes && g_ascii_isspace (*tmp)) { 318 gchar hold; 319 gchar *dup; 320 hold = *tmp; 321 *tmp = 0; 322 if (*ptr == '"') { 323 gint len; 324 dup = g_strdup (ptr + 1); 325 len = strlen (dup); 326 g_assert (dup [len-1] == '"'); 327 dup [len-1] = 0; 328 } 329 else 330 dup = g_strdup (ptr); 331 g_array_append_val (args, dup); 332 *out_n_args += 1; 333 ptr = tmp; 334 *tmp = hold; 335 break; 336 } 337 } 338 339 if (!*tmp) { 340 if (inquotes) { 341 g_set_error (error, TOOL_COMMAND_ERROR, 342 TOOL_COMMAND_SYNTAX_ERROR, 343 _("Unbalanced usage of quotes")); 344 goto onerror; 345 } 346 else { 347 /* last command */ 348 gchar *dup; 349 if (*ptr == '"') { 350 gint len; 351 dup = g_strdup (ptr + 1); 352 len = strlen (dup); 353 g_assert (dup [len-1] == '"'); 354 dup [len-1] = 0; 355 } 356 else 357 dup = g_strdup (ptr); 358 g_array_append_val (args, dup); 359 *out_n_args += 1; 360 break; 361 } 362 } 363 } 364 365 g_free (str); 366 if (0) { 367 /* debug */ 368 guint i; 369 g_print ("split [%s] => %d parts\n", cmde, *out_n_args); 370 for (i = 0; ; i++) { 371 gchar *tmp; 372 tmp = g_array_index (args, gchar*, i); 373 if (!tmp) 374 break; 375 g_print ("\t[%s]\n", tmp); 376 } 377 } 378 return (gchar **) g_array_free (args, FALSE); 379 380 onerror: 381 g_free (str); 382 g_array_free (args, TRUE); 383 *out_n_args = 0; 384 return NULL; 385 } 386 387 /** 388 * tool_command_group_execute: 389 * @group: a #ToolCommandGroup pointer 390 * @cmde: the command as a string (name + arguments) 391 * @user_data: (allow-none): a pointer 392 * @error: (allow-none): a place to store errors, or %NULL 393 * 394 * Executes @cmde 395 * 396 * Returns: (transfer full): a new #ToolCommandResult result 397 */ 398 ToolCommandResult * 399 tool_command_group_execute (ToolCommandGroup *group, const gchar *cmde, 400 ToolOutputFormat format, gpointer user_data, GError **error) 401 { 402 g_return_val_if_fail (group, NULL); 403 404 if (!cmde || !*cmde) { 405 ToolCommandResult *res; 406 res = g_new0 (ToolCommandResult, 1); 407 res->type = TOOL_COMMAND_RESULT_EMPTY; 408 return res; 409 } 410 411 ToolCommand *cmd; 412 gchar **args; 413 guint nargs; 414 args = split_command_string (cmde, &nargs, error); 415 if (!args) 416 return NULL; 417 418 ToolCommandResult *res = NULL; 419 GError *lerror = NULL; 420 cmd = tool_command_group_find (group, args[0], &lerror); 421 if (!cmd) { 422 if (args[0] && ((*args[0] == 'h') || (*args[0] == '?'))) { 423 /* help requested */ 424 res = tool_help_get_command_help (group, args [1], format); 425 g_clear_error (&lerror); 426 } 427 else { 428 g_strfreev (args); 429 g_propagate_error (error, lerror); 430 return NULL; 431 } 432 } 433 434 if (!res) { 435 if (cmd->command_func) 436 res = cmd->command_func (cmd, nargs - 1, (const gchar**) args + 1, user_data, error); 437 else 438 g_warning ("Tool command has no associated function to execute"); 439 } 440 g_strfreev (args); 441 return res; 442 } 443 444 /** 445 * tool_command_get_all_commands: 446 * @group: a #ToolCommandGroup group of commands 447 * 448 * Get a list of all the commands (sorted by group) in @group 449 * 450 * Returns: (transfer none): a list of all the #ToolCommand commands 451 */ 452 GSList * 453 tool_command_get_all_commands (ToolCommandGroup *group) 454 { 455 g_return_val_if_fail (group, NULL); 456 return group->group_ordered; 457 } 458 459 /** 460 * tool_command_get_commands: 461 * @group: a #ToolCommandGroup group of commands 462 * @prefix: (allow-none): a prefix 463 * 464 * Get a list of all the commands (sorted by group) in @group, starting by @prefix 465 * 466 * Returns: (transfer container): a list of all the #ToolCommand commands, free using g_slist_free() 467 */ 468 GSList * 469 tool_command_get_commands (ToolCommandGroup *group, const gchar *prefix) 470 { 471 g_return_val_if_fail (group, NULL); 472 if (!prefix || !*prefix) 473 return g_slist_copy (group->group_ordered); 474 475 GSList *list, *ret = NULL; 476 gsize len; 477 len = strlen (prefix); 478 for (list = group->group_ordered; list; list = list->next) { 479 ToolCommand *tc; 480 tc = (ToolCommand*) list->data; 481 if (!strncmp (tc->name, prefix, len)) 482 ret = g_slist_prepend (ret, tc); 483 } 484 485 return g_slist_reverse (ret); 486 } 487