1 /* commandmode.c - Interpreter and completion for the interactive mode.
2  *
3  * Copyright (C) 2001, 2002, 2004, 2005, 2007, 2008 Oskar Liljeblad
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 #include <config.h>
21 #include <sys/types.h>	    	    	/* POSIX */
22 #if HAVE_SYS_WAIT_H
23 # include <sys/wait.h>	    	    	/* POSIX */
24 #endif
25 #include <unistd.h> 	    	    	/* gnulib (POSIX) */
26 #if HAVE_WORDEXP_H
27 #include <wordexp.h>	    	    	/* POSIX */
28 #endif
29 #include <signal.h> 	    	    	/* gnulib (POSIX) */
30 #include <errno.h>  	    	    	/* C89 */
31 #include <stdio.h>  	    	    	/* C89 */
32 #include <stdlib.h> 	    	    	/* C89 */
33 #include <readline/readline.h>	    	/* GNU Readline */
34 #include <readline/history.h>	    	/* GNU Readline */
35 #include <stdbool.h>			/* gnulib (POSIX) */
36 #include <gettext.h> 	    	    	/* gnulib (gettext) */
37 #define _(s) gettext(s)
38 #define N_(s) (s)
39 #include "progname.h"			/* gnulib */
40 #include "quotearg.h"	    	    	/* gnulib */
41 #include "xalloc.h"			/* gnulib */
42 #include "xvasprintf.h"			/* gnulib */
43 #include "version-etc.h"		/* gnulib */
44 #include "common/common.h"
45 #include "common/comparison.h"
46 #include "common/string-utils.h"
47 #include "common/error.h"
48 #include "qcmd.h"
49 
50 struct Command {
51     char *word;
52     void (*handler)(char **);
53 };
54 
55 static int command_compare(const struct Command *c1, const struct Command *c2);
56 static char *command_generator(const char *text, int state);
57 static char **completion(const char *text, int start, int end);
58 static void edit_command(char **args);
59 static void help_command(char **args);
60 static void quit_command(char **args);
61 static void version_command(char **args);
62 static void apply_command(char **args);
63 static void plan_command(char **args);
64 
65 /* This array must be sorted by the `word' field */
66 static struct Command commands[] = {
67     { "apply",	    apply_command },
68     { "ed",	    edit_command },
69     { "edit",	    edit_command },
70     { "exit",	    quit_command },
71     { "help",	    help_command },
72     { "import",	    import_command },
73     { "list",	    list_command },
74     { "ls",	    list_command },
75     { "plan",	    plan_command },
76     { "quit",	    quit_command },
77     { "set",	    set_command },
78     { "show",	    show_command },
79     { "version",    version_command },
80 };
81 static bool running = true;
82 static bool exit_warned = false;
83 
84 static int
command_compare(const struct Command * c1,const struct Command * c2)85 command_compare(const struct Command *c1, const struct Command *c2)
86 {
87     return strcmp(c1->word, c2->word);
88 }
89 
90 static char *
command_generator(const char * text,int state)91 command_generator(const char *text, int state)
92 {
93     static int c;
94 
95     if (state == 0)
96 	c = 0;
97 
98     while (c < sizeof(commands)/sizeof(*commands)) {
99 	char *name = commands[c].word;
100 	c++;
101 	if (starts_with(name, text))
102 	    return xstrdup(name);
103     }
104 
105     return NULL;
106 }
107 
108 static char **
completion(const char * text,int start,int end)109 completion(const char *text, int start, int end)
110 {
111     int idx = word_get_index(rl_line_buffer, start);
112 
113     if (idx == 0) {
114 	return rl_completion_matches(text, command_generator);
115     } else if (idx == 1) {
116 	char *command = word_get(rl_line_buffer, 0);
117 	if (strcmp(command, "set") == 0 || strcmp(command, "show") == 0) {
118 	    free(command);
119 	    return rl_completion_matches(text, variable_generator);
120 	}
121 	free(command);
122     } else if (idx == 2) {
123 	char *command = word_get(rl_line_buffer, 0);
124 	if (strcmp(command, "set") == 0) {
125 	    char *variable = word_get(rl_line_buffer, 1);
126 	    if (strcmp(variable, "format") == 0) {
127 		free(command);
128 		free(variable);
129 		return rl_completion_matches(text, edit_format_generator);
130 	    }
131 	    else if (strcmp(variable, "options") == 0) {
132 		free(command);
133 		free(variable);
134 		return rl_completion_matches(text, format->option_generator);
135 	    }
136 	    free(variable);
137 	}
138 	free(command);
139     }
140 
141     return NULL;
142 }
143 
144 void
display_commandmode_header(void)145 display_commandmode_header(void)
146 {
147     printf(_("%s (%s) %s\n\
148 %s.\n\
149 This program is free software, covered by the GNU General Public License,\n\
150 and you are welcome to change it and/or distribute copies of it under\n\
151 certain conditions. There is absolutely no warranty for this program.\n\
152 See the COPYING file for details.\n"),
153 	program, PACKAGE, VERSION, version_etc_copyright);
154 }
155 
156 void
terminate_program(void)157 terminate_program(void)
158 {
159     bool valid = false;
160 
161     if (plan != NULL && llist_is_empty(plan->error)) {
162 //	Iterator *it;
163 
164 	valid = true;
165 	if (!llist_is_empty(plan->ok)) {
166 	    valid = false;
167 /*	    for (it = llist_iterator(plan->ok); iterator_has_next(it); ) {
168 		FileSpec *spec = iterator_next(it);
169 		if (spec->status != APPLY_COMPLETE) {
170 		    valid = false;
171 		    break;
172 		}
173 FIXME
174 	    }
175 	    iterator_free(it);*/
176 	}
177     }
178     if (valid || plan == NULL || exit_warned) {
179 	running = false;
180 	puts("");
181 	return;
182     }
183     puts("exit");
184     printf(_("There are unapplied changes.\n"));
185     exit_warned = true;
186 }
187 
188 static void
int_signal_handler(int signal)189 int_signal_handler(int signal)
190 {
191     puts("");
192     rl_line_buffer[0] = '\0';
193     rl_point = 0;
194     rl_end = 0;
195     rl_forced_update_display();
196 }
197 
198 void
commandmode_mainloop(void)199 commandmode_mainloop(void)
200 {
201     struct sigaction action;
202     char *prompt;
203 
204     rl_readline_name = program_name;
205     rl_attempted_completion_function = completion;
206     rl_basic_word_break_characters = " \t\n\"\\'`@$><=;|&{(,";
207 
208     memset(&action, 0, sizeof(sigaction));
209     action.sa_handler = int_signal_handler;
210     action.sa_flags = SA_RESTART;
211     if (sigaction(SIGINT, &action, NULL) < 0)
212 	die(_("cannot register signal handler: %s"), errstr);
213     action.sa_handler = SIG_IGN;
214     if (sigaction(SIGQUIT, &action, NULL) < 0)
215 	die(_("cannot register signal handler: %s"), errstr);
216 
217     prompt = xasprintf("%s> ", program);
218 
219     while (running) {
220 	char *line;
221 	char **words;
222 	wordexp_t result;
223 	int rc;
224 
225 	line = readline(prompt);
226 	if (line == NULL) {
227 	    terminate_program();
228 	    continue;
229 	}
230 	add_history(line);
231 	rc = wordexp(line, &result, WRDE_NOCMD|WRDE_UNDEF);
232 	if (rc != 0) {
233 	    free(line);
234 	    switch (rc) {
235 	    case WRDE_BADCHAR:
236 		warn(_("input contains unquoted invalid character"));
237 		break;
238 	    case WRDE_BADVAL:
239 		warn(_("variable reference using dollar sign ($) is not allowed"));
240 		break;
241 	    case WRDE_CMDSUB:
242 		warn(_("command substitution using backticks (``) is not allowed"));
243 		break;
244 	    case WRDE_NOSPACE:
245 		errno = ENOMEM;
246 		die("%s", errstr);
247 		break;
248 	    case WRDE_SYNTAX:
249 		warn(_("syntax error in input"));
250 		break;
251 	    }
252 	    continue;
253 	}
254 	words = result.we_wordv;
255 	if (words != NULL && *words != NULL) {
256 	    struct Command *result;
257 	    struct Command target;
258 
259 	    target.word = words[0];
260 	    result = bsearch(&target, commands,
261 		    sizeof(commands)/sizeof(*commands),
262 		    sizeof(*commands), (comparison_fn_t) command_compare);
263 	    if (result == NULL)
264 		warn(_("undefined command `%s'. Try `help'."), quotearg(words[0]));
265 	    else
266 		result->handler(words);
267 	}
268 	if (words != NULL)
269 	    wordfree(&result);
270 	free(line);
271     }
272 
273     free(prompt);
274 }
275 
276 static void
apply_command(char ** args)277 apply_command(char **args)
278 {
279     if (plan == NULL) {
280     	printf(_("No plan - use `list' and `edit' first.\n"));
281 	return;
282     }
283     apply_plan(plan);
284 }
285 
286 static void
plan_command(char ** args)287 plan_command(char **args)
288 {
289     if (plan == NULL) {
290 	printf(_("No plan - use `list' and `edit' first.\n"));
291 	return;
292     }
293     display_plan(plan);
294 }
295 
296 static void
edit_command(char ** args)297 edit_command(char **args)
298 {
299     bool all;
300     bool force;
301 
302     if (llist_is_empty(work_files)) {
303 	printf(_("No files to edit - use `list' first.\n"));
304 	return;
305     }
306     all = (args[1] != NULL && strcmp(args[1], "all") == 0);
307     force = false;
308     if (edit_files(all, force)) {
309 	if (plan != NULL)
310 	    free_plan(plan);
311 	plan = make_plan(work_files);
312 	if (plan != NULL)
313     	    display_plan(plan);
314     }
315 }
316 
317 static void
quit_command(char ** args)318 quit_command(char **args)
319 {
320     running = false;
321 }
322 
323 static FILE *
launch_pager(pid_t * child_pid)324 launch_pager(pid_t *child_pid)
325 {
326     FILE *pager;
327     pid_t pid;
328     int pager_fd[2];
329 
330     *child_pid = -1;
331     if (pipe(pager_fd) != 0) {
332 	warn(_("cannot start pager: %s"), errstr);
333 	return stdout;
334     }
335 
336     pid = fork();
337     if (pid < 0) {
338 	warn(_("cannot start pager: %s"), errstr);
339 	return stdout;
340     }
341     if (pid == 0) {
342 	if (close(pager_fd[1]) < 0)
343 	    die(_("cannot start pager: %s"), errstr);
344 	if (dup2(pager_fd[0], STDIN_FILENO) < 0)
345 	    die(_("cannot start pager: %s"), errstr);
346 	execlp("pager", "pager", NULL);
347 	if (errno == ENOENT)
348 	    execlp("more", "more", NULL);
349 	if (errno == ENOENT)
350 	    execlp("cat", "cat", NULL);
351 	die(_("cannot start pager: %s"), errstr);
352     }
353     *child_pid = pid;
354 
355     if (close(pager_fd[0]) < 0) {
356 	warn(_("cannot start pager: %s"), errstr);
357 	return stdout;
358     }
359 
360     pager = fdopen(pager_fd[1], "w");
361     if (pager == NULL) {
362 	warn(_("cannot start pager: %s"), errstr);
363 	return stdout;
364     }
365     return pager;
366 }
367 
368 static bool
close_pager(FILE * pager,pid_t child_pid)369 close_pager(FILE *pager, pid_t child_pid)
370 {
371     if (pager != stdout && fclose(pager) < 0) {
372 	warn(_("cannot terminate pager: %s"), errstr);
373 	return false;
374     }
375     if (child_pid != -1 && waitpid(child_pid, NULL, 0) != child_pid) {
376 	warn(_("cannot terminate pager: %s"), errstr);
377 	return false;
378     }
379     return true;
380 }
381 
382 static void
help_command(char ** args)383 help_command(char **args)
384 {
385     FILE *pager;
386     pid_t child;
387 
388     if (args[1] == NULL) {
389 	pager = launch_pager(&child);
390 	fprintf(pager, _("\
391 ls, list [OPTIONS].. [FILES]..\n\
392   Select files to rename. If no files are specified, select all files in\n\
393   current directory. Use `help ls' to display a list of allowed options.\n\
394 import FILE\n\
395   Read files to rename from a text file. Each line should correspond to an\n\
396   existing file to rename.\n\
397 ed, edit [all]\n\
398   Edit renames in a text editor. If this command has been run before, and\n\
399   not `all' is specified, only edit renames with errors in.\n\
400 plan\n\
401   Display the current rename-plan. (This plan is created after `edit'.)\n\
402 apply\n\
403   Apply the current plan, i.e. rename files. Only those files marked as OK\n\
404   in the plan will be renamed.\n\
405 show [VARIABLE]\n\
406   Display the value of the specified variable, or all variables if none\n\
407   specified.\n\
408 set VARIABLE VALUE\n\
409   Set the value of a variable.\n\
410 exit, quit\n\
411   Exit this program\n\
412 help [ls|usage]\n\
413   If `ls' is specified, display list options. If `usage' is specified,\n\
414   display accepted command line options. Otherwise display this help\n\
415   message.\n\
416 version\n\
417   Display version information for this program.\n"));
418 	close_pager(pager, child);
419     } else if (strcmp(args[1], "ls") == 0) {
420 	pager = launch_pager(&child);
421 	display_ls_help(pager);
422 	close_pager(pager, child);
423     } else if (strcmp(args[1], "usage") == 0) {
424 	pager = launch_pager(&child);
425 	display_help(pager);
426 	close_pager(pager, child);
427     } else {
428 	printf(_("Usage: help [ls|usage]\n"));
429     }
430 }
431 
432 static void
version_command(char ** args)433 version_command(char **args)
434 {
435     display_version();
436     printf(_("\nSend bug reports and questions to <%s>.\n"), PACKAGE_BUGREPORT);
437 }
438