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