fileman.c revision 75406
1/* fileman.c -- A tiny application which demonstrates how to use the
2   GNU Readline library.  This application interactively allows users
3   to manipulate files and their modes. */
4
5#ifdef HAVE_CONFIG_H
6#  include <config.h>
7#endif
8
9#include <sys/types.h>
10#ifdef HAVE_SYS_FILE_H
11#  include <sys/file.h>
12#endif
13#include <sys/stat.h>
14
15#ifdef HAVE_UNISTD_H
16#  include <unistd.h>
17#endif
18
19#include <fcntl.h>
20#include <stdio.h>
21#include <errno.h>
22
23#if defined (HAVE_STRING_H)
24#  include <string.h>
25#else /* !HAVE_STRING_H */
26#  include <strings.h>
27#endif /* !HAVE_STRING_H */
28
29#ifdef HAVE_STDLIB_H
30#  include <stdlib.h>
31#endif
32
33#ifdef READLINE_LIBRARY
34#  include "readline.h"
35#  include "history.h"
36#else
37#  include <readline/readline.h>
38#  include <readline/history.h>
39#endif
40
41extern char *xmalloc ();
42
43/* The names of functions that actually do the manipulation. */
44int com_list __P((char *));
45int com_view __P((char *));
46int com_rename __P((char *));
47int com_stat __P((char *));
48int com_pwd __P((char *));
49int com_delete __P((char *));
50int com_help __P((char *));
51int com_cd __P((char *));
52int com_quit __P((char *));
53
54/* A structure which contains information on the commands this program
55   can understand. */
56
57typedef struct {
58  char *name;			/* User printable name of the function. */
59  rl_icpfunc_t *func;		/* Function to call to do the job. */
60  char *doc;			/* Documentation for this function.  */
61} COMMAND;
62
63COMMAND commands[] = {
64  { "cd", com_cd, "Change to directory DIR" },
65  { "delete", com_delete, "Delete FILE" },
66  { "help", com_help, "Display this text" },
67  { "?", com_help, "Synonym for `help'" },
68  { "list", com_list, "List files in DIR" },
69  { "ls", com_list, "Synonym for `list'" },
70  { "pwd", com_pwd, "Print the current working directory" },
71  { "quit", com_quit, "Quit using Fileman" },
72  { "rename", com_rename, "Rename FILE to NEWNAME" },
73  { "stat", com_stat, "Print out statistics on FILE" },
74  { "view", com_view, "View the contents of FILE" },
75  { (char *)NULL, (rl_icpfunc_t *)NULL, (char *)NULL }
76};
77
78/* Forward declarations. */
79char *stripwhite ();
80COMMAND *find_command ();
81
82/* The name of this program, as taken from argv[0]. */
83char *progname;
84
85/* When non-zero, this global means the user is done using this program. */
86int done;
87
88char *
89dupstr (s)
90     char *s;
91{
92  char *r;
93
94  r = xmalloc (strlen (s) + 1);
95  strcpy (r, s);
96  return (r);
97}
98
99main (argc, argv)
100     int argc;
101     char **argv;
102{
103  char *line, *s;
104
105  progname = argv[0];
106
107  initialize_readline ();	/* Bind our completer. */
108
109  /* Loop reading and executing lines until the user quits. */
110  for ( ; done == 0; )
111    {
112      line = readline ("FileMan: ");
113
114      if (!line)
115        break;
116
117      /* Remove leading and trailing whitespace from the line.
118         Then, if there is anything left, add it to the history list
119         and execute it. */
120      s = stripwhite (line);
121
122      if (*s)
123        {
124          add_history (s);
125          execute_line (s);
126        }
127
128      free (line);
129    }
130  exit (0);
131}
132
133/* Execute a command line. */
134int
135execute_line (line)
136     char *line;
137{
138  register int i;
139  COMMAND *command;
140  char *word;
141
142  /* Isolate the command word. */
143  i = 0;
144  while (line[i] && whitespace (line[i]))
145    i++;
146  word = line + i;
147
148  while (line[i] && !whitespace (line[i]))
149    i++;
150
151  if (line[i])
152    line[i++] = '\0';
153
154  command = find_command (word);
155
156  if (!command)
157    {
158      fprintf (stderr, "%s: No such command for FileMan.\n", word);
159      return (-1);
160    }
161
162  /* Get argument to command, if any. */
163  while (whitespace (line[i]))
164    i++;
165
166  word = line + i;
167
168  /* Call the function. */
169  return ((*(command->func)) (word));
170}
171
172/* Look up NAME as the name of a command, and return a pointer to that
173   command.  Return a NULL pointer if NAME isn't a command name. */
174COMMAND *
175find_command (name)
176     char *name;
177{
178  register int i;
179
180  for (i = 0; commands[i].name; i++)
181    if (strcmp (name, commands[i].name) == 0)
182      return (&commands[i]);
183
184  return ((COMMAND *)NULL);
185}
186
187/* Strip whitespace from the start and end of STRING.  Return a pointer
188   into STRING. */
189char *
190stripwhite (string)
191     char *string;
192{
193  register char *s, *t;
194
195  for (s = string; whitespace (*s); s++)
196    ;
197
198  if (*s == 0)
199    return (s);
200
201  t = s + strlen (s) - 1;
202  while (t > s && whitespace (*t))
203    t--;
204  *++t = '\0';
205
206  return s;
207}
208
209/* **************************************************************** */
210/*                                                                  */
211/*                  Interface to Readline Completion                */
212/*                                                                  */
213/* **************************************************************** */
214
215char *command_generator __P((const char *, int));
216char **fileman_completion __P((const char *, int, int));
217
218/* Tell the GNU Readline library how to complete.  We want to try to complete
219   on command names if this is the first word in the line, or on filenames
220   if not. */
221initialize_readline ()
222{
223  /* Allow conditional parsing of the ~/.inputrc file. */
224  rl_readline_name = "FileMan";
225
226  /* Tell the completer that we want a crack first. */
227  rl_attempted_completion_function = fileman_completion;
228}
229
230/* Attempt to complete on the contents of TEXT.  START and END bound the
231   region of rl_line_buffer that contains the word to complete.  TEXT is
232   the word to complete.  We can use the entire contents of rl_line_buffer
233   in case we want to do some simple parsing.  Return the array of matches,
234   or NULL if there aren't any. */
235char **
236fileman_completion (text, start, end)
237     const char *text;
238     int start, end;
239{
240  char **matches;
241
242  matches = (char **)NULL;
243
244  /* If this word is at the start of the line, then it is a command
245     to complete.  Otherwise it is the name of a file in the current
246     directory. */
247  if (start == 0)
248    matches = rl_completion_matches (text, command_generator);
249
250  return (matches);
251}
252
253/* Generator function for command completion.  STATE lets us know whether
254   to start from scratch; without any state (i.e. STATE == 0), then we
255   start at the top of the list. */
256char *
257command_generator (text, state)
258     const char *text;
259     int state;
260{
261  static int list_index, len;
262  char *name;
263
264  /* If this is a new word to complete, initialize now.  This includes
265     saving the length of TEXT for efficiency, and initializing the index
266     variable to 0. */
267  if (!state)
268    {
269      list_index = 0;
270      len = strlen (text);
271    }
272
273  /* Return the next name which partially matches from the command list. */
274  while (name = commands[list_index].name)
275    {
276      list_index++;
277
278      if (strncmp (name, text, len) == 0)
279        return (dupstr(name));
280    }
281
282  /* If no names matched, then return NULL. */
283  return ((char *)NULL);
284}
285
286/* **************************************************************** */
287/*                                                                  */
288/*                       FileMan Commands                           */
289/*                                                                  */
290/* **************************************************************** */
291
292/* String to pass to system ().  This is for the LIST, VIEW and RENAME
293   commands. */
294static char syscom[1024];
295
296/* List the file(s) named in arg. */
297com_list (arg)
298     char *arg;
299{
300  if (!arg)
301    arg = "";
302
303  sprintf (syscom, "ls -FClg %s", arg);
304  return (system (syscom));
305}
306
307com_view (arg)
308     char *arg;
309{
310  if (!valid_argument ("view", arg))
311    return 1;
312
313#if defined (__MSDOS__)
314  /* more.com doesn't grok slashes in pathnames */
315  sprintf (syscom, "less %s", arg);
316#else
317  sprintf (syscom, "more %s", arg);
318#endif
319  return (system (syscom));
320}
321
322com_rename (arg)
323     char *arg;
324{
325  too_dangerous ("rename");
326  return (1);
327}
328
329com_stat (arg)
330     char *arg;
331{
332  struct stat finfo;
333
334  if (!valid_argument ("stat", arg))
335    return (1);
336
337  if (stat (arg, &finfo) == -1)
338    {
339      perror (arg);
340      return (1);
341    }
342
343  printf ("Statistics for `%s':\n", arg);
344
345  printf ("%s has %d link%s, and is %d byte%s in length.\n",
346	  arg,
347          finfo.st_nlink,
348          (finfo.st_nlink == 1) ? "" : "s",
349          finfo.st_size,
350          (finfo.st_size == 1) ? "" : "s");
351  printf ("Inode Last Change at: %s", ctime (&finfo.st_ctime));
352  printf ("      Last access at: %s", ctime (&finfo.st_atime));
353  printf ("    Last modified at: %s", ctime (&finfo.st_mtime));
354  return (0);
355}
356
357com_delete (arg)
358     char *arg;
359{
360  too_dangerous ("delete");
361  return (1);
362}
363
364/* Print out help for ARG, or for all of the commands if ARG is
365   not present. */
366com_help (arg)
367     char *arg;
368{
369  register int i;
370  int printed = 0;
371
372  for (i = 0; commands[i].name; i++)
373    {
374      if (!*arg || (strcmp (arg, commands[i].name) == 0))
375        {
376          printf ("%s\t\t%s.\n", commands[i].name, commands[i].doc);
377          printed++;
378        }
379    }
380
381  if (!printed)
382    {
383      printf ("No commands match `%s'.  Possibilties are:\n", arg);
384
385      for (i = 0; commands[i].name; i++)
386        {
387          /* Print in six columns. */
388          if (printed == 6)
389            {
390              printed = 0;
391              printf ("\n");
392            }
393
394          printf ("%s\t", commands[i].name);
395          printed++;
396        }
397
398      if (printed)
399        printf ("\n");
400    }
401  return (0);
402}
403
404/* Change to the directory ARG. */
405com_cd (arg)
406     char *arg;
407{
408  if (chdir (arg) == -1)
409    {
410      perror (arg);
411      return 1;
412    }
413
414  com_pwd ("");
415  return (0);
416}
417
418/* Print out the current working directory. */
419com_pwd (ignore)
420     char *ignore;
421{
422  char dir[1024], *s;
423
424  s = getcwd (dir, sizeof(dir) - 1);
425  if (s == 0)
426    {
427      printf ("Error getting pwd: %s\n", dir);
428      return 1;
429    }
430
431  printf ("Current directory is %s\n", dir);
432  return 0;
433}
434
435/* The user wishes to quit using this program.  Just set DONE non-zero. */
436com_quit (arg)
437     char *arg;
438{
439  done = 1;
440  return (0);
441}
442
443/* Function which tells you that you can't do this. */
444too_dangerous (caller)
445     char *caller;
446{
447  fprintf (stderr,
448           "%s: Too dangerous for me to distribute.  Write it yourself.\n",
449           caller);
450}
451
452/* Return non-zero if ARG is a valid argument for CALLER, else print
453   an error message and return zero. */
454int
455valid_argument (caller, arg)
456     char *caller, *arg;
457{
458  if (!arg || !*arg)
459    {
460      fprintf (stderr, "%s: Argument required.\n", caller);
461      return (0);
462    }
463
464  return (1);
465}
466