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