1 /* 2 * Copyright (C) 2007 - 2011 Vivien Malerba <malerba@gnome-db.org> 3 * Copyright (C) 2010 David King <davidk@openismus.com> 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 2 8 * of the License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 */ 19 20 #include "tool-errors.h" 21 #include "tool-input.h" 22 #include "tool-command.h" 23 #include <glib/gi18n-lib.h> 24 #include <glib/gstdio.h> 25 #include <string.h> 26 #include <stdlib.h> 27 #include <errno.h> 28 #include <unistd.h> 29 #ifndef G_OS_WIN32 30 #include <sys/ioctl.h> 31 #endif 32 33 #define TO_IMPLEMENT g_print ("Implementation missing: %s() in %s line %d\n", __FUNCTION__, __FILE__,__LINE__) 34 35 #ifdef HAVE_READLINE 36 #include <readline/readline.h> 37 #endif 38 #ifdef HAVE_HISTORY 39 #include <readline/history.h> 40 #endif 41 42 #include "tool-defines.h" 43 44 #ifdef HAVE_HISTORY 45 static gboolean history_init_done = FALSE; 46 gchar *history_file = NULL; 47 #endif 48 49 static void init_history (); 50 51 /** 52 * input_from_stream 53 * 54 * returns: a new string read from @stream 55 */ 56 gchar * 57 input_from_stream (FILE *stream) 58 { 59 #define LINE_SIZE 65535 60 gchar line [LINE_SIZE]; 61 gchar *result; 62 63 result = fgets (line, LINE_SIZE, stream); 64 if (!result) 65 return NULL; 66 else { 67 gint len = strlen (line); 68 if (line [len - 1] == '\n') 69 line [len - 1] = 0; 70 return g_strdup (line); 71 } 72 } 73 74 static TreatLineFunc line_cb_func = NULL; 75 static gpointer line_cb_func_data = NULL; 76 static ComputePromptFunc line_prompt_func = NULL; 77 static GIOChannel *ioc = NULL; 78 79 static gboolean 80 chars_for_readline_cb (G_GNUC_UNUSED GIOChannel *ioc, G_GNUC_UNUSED GIOCondition condition, 81 G_GNUC_UNUSED gpointer data) 82 { 83 #ifdef HAVE_READLINE 84 rl_callback_read_char (); 85 #else 86 gchar *line; 87 gsize tpos; 88 GError *error = NULL; 89 GIOStatus st; 90 st = g_io_channel_read_line (ioc, &line, NULL, &tpos, &error); 91 switch (st) { 92 case G_IO_STATUS_NORMAL: 93 line [tpos] = 0; 94 if (line_cb_func (line, line_cb_func_data) == TRUE) { 95 /* print prompt for next line */ 96 g_print ("%s", line_prompt_func ()); 97 } 98 g_free (line); 99 break; 100 case G_IO_STATUS_ERROR: 101 g_warning ("Error reading from STDIN: %s\n", 102 error && error->message ? error->message : _("No detail")); 103 if (error) 104 g_error_free (error); 105 break; 106 case G_IO_STATUS_EOF: 107 /* send the Quit command */ 108 line_cb_func (".q", line_cb_func_data); 109 return FALSE; 110 break; 111 default: 112 break; 113 } 114 #endif 115 return TRUE; 116 } 117 118 #ifdef HAVE_READLINE 119 static void 120 line_read_handler (char *line) 121 { 122 line_cb_func (line, line_cb_func_data); /* we don't care about the return status */ 123 free (line); 124 rl_set_prompt (line_prompt_func ()); 125 } 126 #endif 127 128 /** 129 * init_input 130 * 131 * Initializes input 132 */ 133 void 134 init_input (TreatLineFunc treat_line_func, ComputePromptFunc prompt_func, gpointer data) 135 { 136 /* init readline related features */ 137 line_cb_func = treat_line_func; 138 line_cb_func_data = data; 139 line_prompt_func = prompt_func; 140 141 #ifdef HAVE_READLINE 142 rl_catch_signals = 1; 143 rl_set_signals (); 144 rl_readline_name = "gda-sql"; 145 rl_callback_handler_install (prompt_func (), line_read_handler); 146 #else 147 g_print ("%s", line_prompt_func ()); 148 #endif 149 if (!ioc) { 150 ioc = g_io_channel_unix_new (STDIN_FILENO); 151 g_io_add_watch (ioc, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) chars_for_readline_cb, NULL); 152 } 153 154 /* init history */ 155 init_history (); 156 157 /* completion init */ 158 #ifdef HAVE_READLINE 159 rl_basic_word_break_characters = " \t\n\\'`@$><=;|&{("; 160 rl_completer_word_break_characters = " \t\n\\'`@$><=;|&{("; 161 #endif 162 } 163 164 /** 165 * end_input 166 * 167 * Releases any data related to the input and allocated during init_input() 168 */ 169 void 170 end_input (void) 171 { 172 #ifdef HAVE_READLINE 173 rl_callback_handler_remove (); 174 #endif 175 if (ioc) { 176 g_io_channel_shutdown (ioc, TRUE, NULL); 177 g_io_channel_unref (ioc); 178 } 179 } 180 181 static gsize 182 determine_max_prefix_len (gchar **array) 183 { 184 gsize max; 185 g_assert (array[0]); 186 g_assert (array[1]); 187 188 for (max = 1; ; max++) { 189 gint i; 190 gchar *ref; 191 ref = array[0]; 192 for (i = 1; array[i]; i++) { 193 if (strncmp (ref, array[i], max)) 194 break; 195 if (!array[i][max]) { 196 max ++; 197 break; 198 } 199 } 200 if (array[i]) 201 break; 202 } 203 204 return max - 1; 205 } 206 207 static ToolCommandGroup *user_defined_completion_group = NULL; 208 static CompletionFunc user_defined_completion_func = NULL; 209 static gchar *user_defined_chars_to_ignore = NULL; 210 211 #ifdef HAVE_READLINE 212 static char ** 213 _tool_completion (const char *text, int start, int end) 214 { 215 GArray *array = NULL; 216 gchar *match, *prematch; 217 gboolean treated = FALSE; 218 match = g_strndup (rl_line_buffer + start, end - start); 219 g_assert (!strcmp (text, match)); 220 prematch = g_strndup (rl_line_buffer, start); 221 g_strstrip (prematch); 222 g_strstrip (match); 223 224 if (start == 0) { 225 /* user needs to enter a command */ 226 if (!user_defined_completion_group) 227 goto out; 228 229 gchar *cmd_match; 230 gchar start_char = 0; 231 cmd_match = match; 232 233 /* maybe ignore some start chars */ 234 if (user_defined_chars_to_ignore) { 235 gchar *ptr; 236 for (ptr = user_defined_chars_to_ignore; *ptr; ptr++) { 237 if (*cmd_match == *ptr) { 238 cmd_match ++; 239 start_char = *ptr; 240 break; 241 } 242 } 243 } 244 245 if (!user_defined_chars_to_ignore || (start_char && user_defined_chars_to_ignore)) { 246 GSList *commands; 247 commands = tool_command_get_commands (user_defined_completion_group, cmd_match); 248 if (commands) { 249 array = g_array_new (TRUE, FALSE, sizeof (gchar*)); 250 GSList *list; 251 for (list = commands; list; list = list->next) { 252 ToolCommand *tc = (ToolCommand*) list->data; 253 const gchar *tmp; 254 if (start_char) 255 tmp = g_strdup_printf ("%c%s", start_char, tc->name); 256 else 257 tmp = g_strdup (tc->name); 258 g_array_append_val (array, tmp); 259 } 260 g_slist_free (commands); 261 } 262 rl_completion_append_character = ' '; 263 treated = TRUE; 264 } 265 } 266 267 if (!treated && user_defined_completion_func) { 268 gchar **vals; 269 ToolCommand *tc = NULL; 270 if (*prematch && user_defined_completion_group) { 271 gchar *tmp = prematch; 272 if (user_defined_chars_to_ignore) { 273 gchar *ptr; 274 for (ptr = user_defined_chars_to_ignore; *ptr; ptr++) { 275 if (*tmp == *ptr) { 276 tmp ++; 277 break; 278 } 279 } 280 } 281 tc = tool_command_group_find (user_defined_completion_group, tmp, NULL); 282 } 283 if (tc && tc->completion_func) 284 vals = tc->completion_func (text); 285 else 286 vals = user_defined_completion_func (text, rl_line_buffer, start, end); 287 288 if (vals) { 289 guint i; 290 for (i = 0; vals [i]; i++) { 291 if (!array) 292 array = g_array_new (TRUE, FALSE, sizeof (gchar*)); 293 gchar *tmp; 294 tmp = vals[i]; 295 g_array_append_val (array, tmp); 296 } 297 g_free (vals); /* and not g_strfreev() */ 298 rl_completion_append_character = 0; 299 } 300 } 301 302 out: 303 g_free (match); 304 g_free (prematch); 305 if (array) { 306 if (array->len > 1) { 307 /* determine max string in common to all possible completions */ 308 gsize len; 309 len = determine_max_prefix_len ((gchar**) array->data); 310 if (len > 0) { 311 gchar *tmp; 312 tmp = g_strndup (g_array_index (array, gchar*, 0), len); 313 g_array_prepend_val (array, tmp); 314 } 315 else { 316 gchar *tmp; 317 tmp = g_strdup (""); 318 g_array_prepend_val (array, tmp); 319 } 320 } 321 return (gchar**) g_array_free (array, FALSE); 322 } 323 else 324 return NULL; 325 } 326 #endif /* HAVE_READLINE */ 327 328 /** 329 * tool_input_set_completion_func: 330 * @group: (allow-none): a #ToolCommandGroup, or %NULL 331 * @func: (allow-none): a #CompletionFunc, or %NULL 332 * @start_chars_to_ignore: (allow-none): a list of characters to ignore at the beginning of commands 333 * 334 * Defines the completion function. 335 */ 336 void 337 tool_input_set_completion_func (ToolCommandGroup *group, CompletionFunc func, gchar *start_chars_to_ignore) 338 { 339 user_defined_completion_group = group; 340 user_defined_completion_func = func; 341 g_free (user_defined_chars_to_ignore); 342 user_defined_chars_to_ignore = start_chars_to_ignore ? g_strdup (start_chars_to_ignore) : NULL; 343 344 #ifdef HAVE_READLINE 345 if (group || func) 346 rl_attempted_completion_function = _tool_completion; 347 else 348 rl_attempted_completion_function = NULL; 349 #endif 350 } 351 352 /* 353 * input_get_size 354 * 355 * Get the size of the input term, if possible, otherwise returns -1 356 */ 357 void 358 input_get_size (gint *width, gint *height) 359 { 360 int tty = fileno (stdin); 361 int cols = -1, rows = -1; 362 363 #ifdef TIOCGWINSZ 364 struct winsize window_size; 365 if (ioctl (tty, TIOCGWINSZ, &window_size) == 0) { 366 cols = (int) window_size.ws_col; 367 rows = (int) window_size.ws_row; 368 } 369 370 if (cols <= 1) 371 cols = -1; 372 if (rows <= 0) 373 rows = -1; 374 #endif 375 376 if (width) 377 *width = cols; 378 if (height) 379 *height = rows; 380 381 /*g_print ("Screen: %dx%d\n", cols, rows);*/ 382 } 383 384 static void 385 sanitize_env (gchar *str) 386 { 387 gchar *ptr; 388 for (ptr = str; *ptr; ptr++) { 389 if (! g_ascii_isprint (*ptr) || (*ptr == G_DIR_SEPARATOR)) 390 *ptr = '_'; 391 } 392 } 393 394 /** 395 * init_history 396 * 397 * Loads the contents of the history file, if supported 398 */ 399 static void 400 init_history () 401 { 402 #ifdef HAVE_HISTORY 403 if (history_init_done) 404 return; 405 if (getenv (TOOL_HISTORY_ENV_NAME)) { 406 const gchar *ename; 407 ename = getenv (TOOL_HISTORY_ENV_NAME); 408 if (!ename || !*ename || !strcmp (ename, "NO_HISTORY")) { 409 history_init_done = TRUE; 410 return; 411 } 412 history_file = g_strdup (ename); 413 sanitize_env (history_file); 414 } 415 else { 416 gchar *cache_dir; 417 #ifdef LIBGDA_ABI_NAME 418 cache_dir = g_build_filename (g_get_user_cache_dir (), "libgda", NULL); 419 #else 420 gchar *tmp; 421 tmp = g_utf8_strdown (TOOL_NAME, -1); 422 cache_dir = g_build_filename (g_get_user_cache_dir (), tmp, NULL); 423 g_free (tmp); 424 #endif 425 history_file = g_build_filename (cache_dir, TOOL_HISTORY_FILE, NULL); 426 if (!g_file_test (cache_dir, G_FILE_TEST_EXISTS)) { 427 if (g_mkdir_with_parents (cache_dir, 0700)) { 428 g_free (history_file); 429 history_file = NULL; 430 } 431 } 432 g_free (cache_dir); 433 } 434 if (history_file) { 435 using_history (); 436 read_history (history_file); 437 history_init_done = TRUE; 438 } 439 #endif 440 } 441 442 /** 443 * add_to_history 444 */ 445 void 446 add_to_history (const gchar *txt) 447 { 448 #ifdef HAVE_HISTORY 449 if (!history_init_done) 450 init_history (); 451 if (!txt || !(*txt)) 452 return; 453 454 HIST_ENTRY *current; 455 456 current = history_get (history_length); 457 if (current && current->line && !strcmp (current->line, txt)) 458 return; 459 add_history (txt); 460 #endif 461 } 462 463 /** 464 * save_history 465 */ 466 gboolean 467 save_history (const gchar *file, GError **error) 468 { 469 #ifdef HAVE_HISTORY 470 int res; 471 if (!history_init_done || !history_file) 472 return FALSE; 473 res = append_history (1, file ? file : history_file); 474 if (res == ENOENT) 475 res = write_history (file ? file : history_file); 476 if (res != 0) { 477 g_set_error (error, TOOL_ERRORS, TOOL_STORED_DATA_ERROR, 478 _("Could not save history file to '%s': %s"), 479 file ? file : history_file, strerror (errno)); 480 return FALSE; 481 } 482 /*if (res == 0) 483 history_truncate_file (history_file, 500);*/ 484 #endif 485 return TRUE; 486 } 487