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
tool_command_error_quark(void)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 *
tool_command_result_new(GdaConnection * cnc,ToolCommandResultType type)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
tool_command_result_free(ToolCommandResult * res)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 *
tool_command_group_new(void)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
tool_command_group_free(ToolCommandGroup * group)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
commands_compare_name(ToolCommand * a,ToolCommand * b)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
commands_compare_group(ToolCommand * a,ToolCommand * b)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
tool_command_group_add(ToolCommandGroup * group,ToolCommand * cmd)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
tool_command_group_remove(ToolCommandGroup * group,const gchar * name)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 *
tool_command_group_find(ToolCommandGroup * group,const gchar * name,GError ** error)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 **
split_command_string(const gchar * cmde,guint * out_n_args,GError ** error)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 *
tool_command_group_execute(ToolCommandGroup * group,const gchar * cmde,ToolOutputFormat format,gpointer user_data,GError ** error)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 *
tool_command_get_all_commands(ToolCommandGroup * group)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 *
tool_command_get_commands(ToolCommandGroup * group,const gchar * prefix)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