[libgda] Tools: better handle commands entered in consoles: correctly identify the parts
- From: Vivien Malerba <vivien src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgda] Tools: better handle commands entered in consoles: correctly identify the parts
- Date: Sun, 5 Oct 2014 16:42:33 +0000 (UTC)
commit a067bac32004ba06ad57fa442a72ec77c0b1188e
Author: Vivien Malerba <malerba gnome-db org>
Date: Sun Oct 5 18:26:26 2014 +0200
Tools: better handle commands entered in consoles: correctly identify the parts
tools/.gitignore | 1 +
tools/Makefile.am | 16 ++++
tools/base/base-tool-command.c | 24 +++++-
tools/base/base-tool-command.h | 1 +
tools/common/t-context.c | 16 +++-
tools/common/t-term-context.c | 3 +-
tools/common/t-utils.c | 187 +++++++++++++++++++++++++++++++++++----
tools/common/t-utils.h | 4 +-
tools/common/web-server.c | 2 +-
tools/test-splitter.c | 112 ++++++++++++++++++++++++
10 files changed, 339 insertions(+), 27 deletions(-)
---
diff --git a/tools/.gitignore b/tools/.gitignore
index 9bdefcb..3371f24 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -7,3 +7,4 @@ information-schema-types
gda-sql-6.*.1
gda-ldap-browser-6*
tools.gresources.c
+test-splitter
diff --git a/tools/Makefile.am b/tools/Makefile.am
index bdaa644..0fa1c96 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -14,6 +14,9 @@ SUBDIRS+=browser
noinst_LTLIBRARIES = libbrowser.la
endif
+TESTS = test-splitter
+check_PROGRAMS = $(TESTS)
+
AM_CPPFLAGS = \
-I$(top_srcdir) \
-I$(top_builddir) \
@@ -205,3 +208,16 @@ uninstall-local:
if DEFAULT_BINARY
rm -f $(DESTDIR)$(bindir)/gda-sql$(EXEEXT)
endif
+
+test_splitter_SOURCES = test-splitter.c
+test_splitter_LDADD = \
+ common/libcommon.la \
+ base/libbasetool.la \
+ $(top_builddir)/libgda/libgda-6.0.la \
+ $(COREDEPS_LIBS) \
+ $(READLINE_LIB) \
+ $(HISTORY_LIB)
+
+if LIBSOUP
+test_splitter_LDADD += $(LIBSOUP_LIBS)
+endif
diff --git a/tools/base/base-tool-command.c b/tools/base/base-tool-command.c
index 19ee96e..07c6388 100644
--- a/tools/base/base-tool-command.c
+++ b/tools/base/base-tool-command.c
@@ -238,6 +238,7 @@ base_tool_command_group_find (ToolCommandGroup *group, const gchar *name, GError
GSList *list;
gsize length;
+ g_return_val_if_fail (group, NULL);
if (!name)
return NULL;
@@ -383,15 +384,32 @@ split_command_string (const gchar *cmde, guint *out_n_args, GError **error)
}
/**
+ * base_tool_command_is_internal:
+ * @group: a #ToolCommandGroup pointer
+ * @cmde: any command (internal or SQL)
+ *
+ * Returns: %TRUE if @cmde is considered as an internal command (starts with '.' or '\\')
+ */
+gboolean
+base_tool_command_is_internal (const gchar *cmde)
+{
+ g_return_val_if_fail (cmde, FALSE);
+ if ((*cmde == '.') || (*cmde == '\\'))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+/**
* base_tool_command_group_execute:
* @group: a #ToolCommandGroup pointer
- * @cmde: the command as a string (name + arguments)
+ * @cmde: an internal command as a string (name + arguments), without the '.' or '\\' beginning
* @limit_width: pass to %TRUE if, in case the command is a HELP command, the width must be limited by a
terminal's width
* @color_term: pass to %TRUE if, in case the command is a HELP command, the output will be for a terminal
color
- * @user_data: (allow-none): a pointer
+ * @user_data: (allow-none): a pointer, see the #ToolCommandFunc type
* @error: (allow-none): a place to store errors, or %NULL
*
- * Executes @cmde
+ * Executes @cmde, which must be an internal command.
*
* Returns: (transfer full): a new #ToolCommandResult result
*/
diff --git a/tools/base/base-tool-command.h b/tools/base/base-tool-command.h
index caa29fc..536d19a 100644
--- a/tools/base/base-tool-command.h
+++ b/tools/base/base-tool-command.h
@@ -109,6 +109,7 @@ ToolCommand *base_tool_command_group_find (ToolCommandGroup *group, con
GSList *base_tool_command_get_all_commands (ToolCommandGroup *group);
GSList *base_tool_command_get_commands (ToolCommandGroup *group, const gchar *prefix);
+gboolean base_tool_command_is_internal (const gchar *cmde);
ToolCommandResult *base_tool_command_group_execute (ToolCommandGroup *group, const gchar *cmde,
gpointer user_data, GError **error);
diff --git a/tools/common/t-context.c b/tools/common/t-context.c
index 34873e8..422ad19 100644
--- a/tools/common/t-context.c
+++ b/tools/common/t-context.c
@@ -378,6 +378,17 @@ t_context_execute_sql_command (TContext *console, const gchar *command,
return res;
}
+/**
+ * t_context_command_execute:
+ * @console: a #TContext
+ * @command: a string containing one or more internal or SQL commands
+ * @usage: a #GdaStatementModelUsage for the returned #GdaDataModel objects, if any
+ * @error: a place to store errors, or %NULL
+ *
+ * Executes @command
+ *
+ * Returns: (transfer full): a new #ToolCommandResult
+ */
ToolCommandResult *
t_context_command_execute (TContext *console, const gchar *command,
GdaStatementModelUsage usage, GError **error)
@@ -387,10 +398,13 @@ t_context_command_execute (TContext *console, const gchar *command,
g_return_val_if_fail (T_IS_CONTEXT (console), NULL);
tcnc = console->priv->current;
+ gchar **parts;
+ parts = t_utils_split_text_into_single_commands (console, command, error);
+
if (!command || !(*command))
return NULL;
- if ((*command == '\\') || (*command == '.'))
+ if (base_tool_command_is_internal (command))
return base_tool_command_group_execute (console->priv->command_group, command + 1,
console, error);
diff --git a/tools/common/t-term-context.c b/tools/common/t-term-context.c
index 7b03747..1e0c643 100644
--- a/tools/common/t-term-context.c
+++ b/tools/common/t-term-context.c
@@ -299,7 +299,8 @@ term_treat_line (TTermContext *term_console, const gchar *cmde, TermLineData *ld
g_string_append_c (term_console->priv->partial_command, '\n');
g_string_append (term_console->priv->partial_command, loc_cmde);
}
- if (ldata->single_line || t_utils_command_is_complete
(term_console->priv->partial_command->str)) {
+ if (ldata->single_line ||
+ t_utils_command_is_complete (T_CONTEXT (term_console),
term_console->priv->partial_command->str)) {
/* execute command */
ToolCommandResult *res;
FILE *to_stream;
diff --git a/tools/common/t-utils.c b/tools/common/t-utils.c
index 198686f..876289d 100644
--- a/tools/common/t-utils.c
+++ b/tools/common/t-utils.c
@@ -129,33 +129,180 @@ t_utils_compute_prompt (TContext *console, gboolean in_command, gboolean for_rea
}
/**
+ * t_utils_split_text_into_single_commands:
+ * @console: a #TContext
+ * @commands: a string containing one or more internal or SQL commands
+ * @error: (allow-none): a place to store errors, or %NULL
+ *
+ * Splits @commands into separate commands, either internal or SQL.
+ * Also this function:
+ * - ignores comment lines (starting with '#')
+ * - ignores "would be commands" composed of only isspace() characters
+ *
+ * Returns: (transfer full): an array of strings, one for each actual command, or %NULL if @commands is
%NULL or empty (""). Free using g_strfreev().
+ */
+gchar **
+t_utils_split_text_into_single_commands (TContext *console, const gchar *commands, GError **error)
+{
+ g_return_val_if_fail (! console || T_IS_CONTEXT (console), NULL);
+
+ if (!commands || !(*commands))
+ return NULL;
+
+ GArray *parts;
+ parts = g_array_new (TRUE, FALSE, sizeof (gchar*));
+ const gchar *start;
+ for (start = commands; *start; ) {
+ ignore_parts:
+ /* ignore newlines */
+ for (; *start == '\n'; start++);
+ if (! *start)
+ break;
+
+ /* ignore comments */
+ if (*start == '#') {
+ for (; *start != '\n'; start++);
+ if (! *start)
+ break;
+ goto ignore_parts;
+ }
+
+ /* ignore parts which are composed of spaces only */
+ const gchar *hold = start;
+ for (; *start && isspace (*start); start++);
+ if (!*start) {
+ if (parts->len == 0) {
+ /* the whole @commands argument is empty => return NULL */
+ g_array_free (parts, TRUE);
+ parts = NULL;
+ }
+ break;
+ }
+ else if (start != hold)
+ goto ignore_parts;
+
+ /* real job here */
+ if (base_tool_command_is_internal (start)) {
+ /* determine end of internal command */
+ const gchar *end;
+ gboolean inquotes = FALSE;
+ gchar *chunk;
+ for (end = start; *end; end++) {
+ if (*end == '\\') { /* ignore next char */
+ end++;
+ if (!*end)
+ break;
+ else
+ continue;
+ }
+
+ if (*end == '"')
+ inquotes = !inquotes;
+ else if ((*end == '\n') && !inquotes)
+ break;
+ }
+ if (inquotes) {
+ /* error: quotes non closed */
+ g_set_error (error, GDA_SQL_PARSER_ERROR, GDA_SQL_PARSER_SYNTAX_ERROR,
+ _("Syntax error"));
+ gchar **res;
+ res = (gchar **) g_array_free (parts, FALSE);
+ g_strfreev (res);
+ return NULL;
+ }
+ chunk = g_strndup (start, end - start);
+ g_array_append_val (parts, chunk);
+
+ if (*end)
+ start = end + 1;
+ else
+ break;
+ }
+ else {
+ /* parse statement */
+ GdaStatement *stmt;
+ const gchar *remain = NULL;
+ GdaSqlParser *parser = NULL;
+ if (console)
+ parser = t_connection_get_parser (t_context_get_connection (console));
+ else
+ parser = gda_sql_parser_new ();
+ stmt = gda_sql_parser_parse_string (parser, start, &remain, error);
+ if (!console)
+ g_object_unref (parser);
+
+ if (stmt) {
+ g_object_unref (stmt);
+
+ gchar *chunk;
+ if (remain)
+ chunk = g_strndup (start, remain - start);
+ else
+ chunk = g_strdup (start);
+ g_array_append_val (parts, chunk);
+
+ if (remain)
+ start = remain;
+ else
+ break;
+ }
+ else {
+ gchar **res;
+ res = (gchar **) g_array_free (parts, FALSE);
+ g_strfreev (res);
+ return NULL;
+ }
+ }
+ }
+
+ if (parts)
+ return (gchar **) g_array_free (parts, FALSE);
+ else
+ return NULL;
+}
+
+/**
* t_utils_command_is_complete:
+ * @console: a #TContext
+ * @command: a string
+ *
+ * Determines if @command represents one or more (internal or SQL) complete commands, used by interactive
+ * sessions.
+ *
+ * Returns: %TRUE if @command is determined to be complete
*/
gboolean
-t_utils_command_is_complete (const gchar *command)
+t_utils_command_is_complete (TContext *console, const gchar *command)
{
if (!command || !(*command))
return FALSE;
- if ((*command == '\\') || (*command == '.')) {
- /* internal command */
- return TRUE;
- }
- else if (*command == '#') {
- /* comment, to be ignored */
- return TRUE;
- }
- else {
- gint i, len;
- len = strlen (command);
- for (i = len - 1; i > 0; i--) {
- if ((command [i] != ' ') && (command [i] != '\n') && (command [i] != '\r'))
- break;
+
+ gchar **parts;
+ parts = t_utils_split_text_into_single_commands (console, command, NULL);
+ if (!parts)
+ return FALSE;
+
+ guint i;
+ gboolean retval = FALSE;
+ for (i = 0; parts [i]; i++);
+ if (i > 0) {
+ const gchar *str;
+ str = parts [i-1];
+
+ if (*str) {
+ if (base_tool_command_is_internal (str))
+ retval = TRUE;
+ else {
+ guint l;
+ for (l = strlen (str) - 1; (l > 0) && isspace (str[l]); l--); /* ignore spaces
+ * at the end */
+ if (str[l] == ';')
+ retval = TRUE; /* we consider the end of the command when there is a
';' */
+ }
}
- if (command [i] == ';')
- return TRUE;
- else
- return FALSE;
- }
+ }
+ g_strfreev (parts);
+ return retval;
}
/**
diff --git a/tools/common/t-utils.h b/tools/common/t-utils.h
index fc671a4..20ae02b 100644
--- a/tools/common/t-utils.h
+++ b/tools/common/t-utils.h
@@ -25,7 +25,9 @@
const gchar *t_utils_fk_policy_to_string (GdaMetaForeignKeyPolicy policy);
gchar *t_utils_compute_prompt (TContext *console, gboolean in_command, gboolean for_readline,
ToolOutputFormat format);
-gboolean t_utils_command_is_complete (const gchar *command);
+
+gchar **t_utils_split_text_into_single_commands (TContext *console, const gchar *commands, GError
**error);
+gboolean t_utils_command_is_complete (TContext *console, const gchar *command);
gboolean t_utils_check_shell_argument (const gchar *arg);
diff --git a/tools/common/web-server.c b/tools/common/web-server.c
index 31b104c..320f20d 100644
--- a/tools/common/web-server.c
+++ b/tools/common/web-server.c
@@ -1868,7 +1868,7 @@ gda_sql_console_execute (WebServer *server, TContext *console, const gchar *comm
loc_cmde = g_strdup (command);
g_strchug (loc_cmde);
if (*loc_cmde) {
- if (t_utils_command_is_complete (loc_cmde)) {
+ if (t_utils_command_is_complete (console, loc_cmde)) {
/* execute command */
ToolCommandResult *res;
diff --git a/tools/test-splitter.c b/tools/test-splitter.c
new file mode 100644
index 0000000..e5dc6e5
--- /dev/null
+++ b/tools/test-splitter.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "common/t-utils.h"
+
+typedef struct {
+ gchar *commands;
+ gchar *exp_result[];
+} TestData;
+
+TestData td0 = {
+ "\\cmde \\\\\"and arg\"",
+ {"\\cmde \\\\\"and arg\"", NULL}
+};
+TestData td1 = {
+ ".d\nSELECT * from table;\n.cmd \"arg\narg more\"",
+ {".d", "SELECT * from table;", ".cmd \"arg\narg more\"", NULL}
+};
+TestData td2 = {
+ ".cmde and arg",
+ {".cmde and arg", NULL}
+};
+TestData td3 = {
+ "\\cmde \"and arg",
+ {NULL}
+};
+TestData td4 = {
+ ".cmde and arg\n\\othercommand",
+ {".cmde and arg", "\\othercommand", NULL}
+};
+TestData td5 = {
+ "\\cmde \"and arg\"",
+ {"\\cmde \"and arg\"", NULL}
+};
+TestData td6 = {
+ "\\cmde \\\"and arg\"",
+ {NULL}
+};
+TestData td7 = {
+ "\\cmde \\\\\"and arg",
+ {NULL}
+};
+TestData td8 = {
+ ".d",
+ {".d", NULL}
+};
+TestData td9 = {
+ " ",
+ {NULL}
+};
+
+
+TestData *data[] = {&td0, &td1, &td2, &td3, &td4, &td5, &td6, &td7, &td8, &td9, NULL};
+
+int
+main (int argc, char * argv[])
+{
+ gchar **parts;
+ GError *error = NULL;
+ guint nb, nfailed = 0;
+
+ for (nb = 0; data[nb]; nb++) {
+ TestData *td;
+ td = data[nb];
+ g_print ("== Test %u ==\n", nb);
+ parts = t_utils_split_text_into_single_commands (NULL, td->commands, &error);
+ if (parts) {
+ guint i;
+ for (i = 0; td->exp_result[i] && parts[i]; i++) {
+ if (strcmp (parts[i], td->exp_result[i])) {
+ g_print ("Error: got [%s] and expected [%s]\n", parts[i],
td->exp_result[i]);
+ nfailed ++;
+ }
+ }
+ if (td->exp_result[i] || parts[i]) {
+ g_print ("Error: got [%s] and expected [%s]\n", parts[i], td->exp_result[i]);
+ nfailed ++;
+ }
+
+ g_strfreev (parts);
+ }
+ else {
+ if (td->exp_result [0]) {
+ g_print ("Error: got a parser error and expected [%s] at first\n",
td->exp_result[0]);
+ nfailed ++;
+ }
+ }
+ g_clear_error (&error);
+ }
+
+ if (nfailed)
+ g_print ("%u test(s) failed out of %u\n", nfailed, nb);
+ else
+ g_print ("%u tests Ok\n", nb);
+
+ return nfailed ? 0 : 1;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]