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