[libgda] Tools: better handle commands entered in consoles: correctly identify the parts



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]