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 *
input_from_stream(FILE * stream)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
chars_for_readline_cb(G_GNUC_UNUSED GIOChannel * ioc,G_GNUC_UNUSED GIOCondition condition,G_GNUC_UNUSED gpointer data)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
line_read_handler(char * line)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
init_input(TreatLineFunc treat_line_func,ComputePromptFunc prompt_func,gpointer data)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
end_input(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
determine_max_prefix_len(gchar ** array)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 **
_tool_completion(const char * text,int start,int end)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
tool_input_set_completion_func(ToolCommandGroup * group,CompletionFunc func,gchar * start_chars_to_ignore)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
input_get_size(gint * width,gint * height)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
sanitize_env(gchar * str)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
init_history()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
add_to_history(const gchar * txt)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
save_history(const gchar * file,GError ** error)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