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