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