fileman.c revision 58310
121308Sache/* fileman.c -- A tiny application which demonstrates how to use the
221308Sache   GNU Readline library.  This application interactively allows users
321308Sache   to manipulate files and their modes. */
421308Sache
526497Sache#ifdef HAVE_CONFIG_H
658310Sache#  include <config.h>
726497Sache#endif
826497Sache
921308Sache#include <sys/types.h>
1026497Sache#ifdef HAVE_SYS_FILE_H
1158310Sache#  include <sys/file.h>
1226497Sache#endif
1321308Sache#include <sys/stat.h>
1421308Sache
1558310Sache#ifdef HAVE_UNISTD_H
1658310Sache#  include <unistd.h>
1758310Sache#endif
1858310Sache
1958310Sache#include <fcntl.h>
2026497Sache#include <stdio.h>
2126497Sache#include <errno.h>
2221308Sache
2326497Sache#if defined (HAVE_STRING_H)
2426497Sache#  include <string.h>
2526497Sache#else /* !HAVE_STRING_H */
2626497Sache#  include <strings.h>
2726497Sache#endif /* !HAVE_STRING_H */
2826497Sache
2958310Sache#ifdef HAVE_STDLIB_H
3058310Sache#  include <stdlib.h>
3158310Sache#endif
3258310Sache
3326497Sache#ifdef READLINE_LIBRARY
3426497Sache#  include "readline.h"
3526497Sache#  include "history.h"
3626497Sache#else
3726497Sache#  include <readline/readline.h>
3826497Sache#  include <readline/history.h>
3926497Sache#endif
4026497Sache
4121308Sacheextern char *xmalloc ();
4221308Sache
4321308Sache/* The names of functions that actually do the manipulation. */
4421308Sacheint com_list (), com_view (), com_rename (), com_stat (), com_pwd ();
4521308Sacheint com_delete (), com_help (), com_cd (), com_quit ();
4621308Sache
4721308Sache/* A structure which contains information on the commands this program
4821308Sache   can understand. */
4921308Sache
5021308Sachetypedef struct {
5121308Sache  char *name;			/* User printable name of the function. */
5221308Sache  Function *func;		/* Function to call to do the job. */
5321308Sache  char *doc;			/* Documentation for this function.  */
5421308Sache} COMMAND;
5521308Sache
5621308SacheCOMMAND commands[] = {
5721308Sache  { "cd", com_cd, "Change to directory DIR" },
5821308Sache  { "delete", com_delete, "Delete FILE" },
5921308Sache  { "help", com_help, "Display this text" },
6021308Sache  { "?", com_help, "Synonym for `help'" },
6121308Sache  { "list", com_list, "List files in DIR" },
6221308Sache  { "ls", com_list, "Synonym for `list'" },
6321308Sache  { "pwd", com_pwd, "Print the current working directory" },
6421308Sache  { "quit", com_quit, "Quit using Fileman" },
6521308Sache  { "rename", com_rename, "Rename FILE to NEWNAME" },
6621308Sache  { "stat", com_stat, "Print out statistics on FILE" },
6721308Sache  { "view", com_view, "View the contents of FILE" },
6821308Sache  { (char *)NULL, (Function *)NULL, (char *)NULL }
6921308Sache};
7021308Sache
7121308Sache/* Forward declarations. */
7221308Sachechar *stripwhite ();
7321308SacheCOMMAND *find_command ();
7421308Sache
7521308Sache/* The name of this program, as taken from argv[0]. */
7621308Sachechar *progname;
7721308Sache
7821308Sache/* When non-zero, this global means the user is done using this program. */
7921308Sacheint done;
8021308Sache
8121308Sachechar *
8221308Sachedupstr (s)
8326497Sache     char *s;
8421308Sache{
8521308Sache  char *r;
8621308Sache
8721308Sache  r = xmalloc (strlen (s) + 1);
8821308Sache  strcpy (r, s);
8921308Sache  return (r);
9021308Sache}
9121308Sache
9221308Sachemain (argc, argv)
9321308Sache     int argc;
9421308Sache     char **argv;
9521308Sache{
9621308Sache  char *line, *s;
9721308Sache
9821308Sache  progname = argv[0];
9921308Sache
10021308Sache  initialize_readline ();	/* Bind our completer. */
10121308Sache
10221308Sache  /* Loop reading and executing lines until the user quits. */
10321308Sache  for ( ; done == 0; )
10421308Sache    {
10521308Sache      line = readline ("FileMan: ");
10621308Sache
10721308Sache      if (!line)
10821308Sache        break;
10921308Sache
11021308Sache      /* Remove leading and trailing whitespace from the line.
11121308Sache         Then, if there is anything left, add it to the history list
11221308Sache         and execute it. */
11321308Sache      s = stripwhite (line);
11421308Sache
11521308Sache      if (*s)
11621308Sache        {
11721308Sache          add_history (s);
11821308Sache          execute_line (s);
11921308Sache        }
12021308Sache
12121308Sache      free (line);
12221308Sache    }
12321308Sache  exit (0);
12421308Sache}
12521308Sache
12621308Sache/* Execute a command line. */
12721308Sacheint
12821308Sacheexecute_line (line)
12921308Sache     char *line;
13021308Sache{
13121308Sache  register int i;
13221308Sache  COMMAND *command;
13321308Sache  char *word;
13421308Sache
13521308Sache  /* Isolate the command word. */
13621308Sache  i = 0;
13721308Sache  while (line[i] && whitespace (line[i]))
13821308Sache    i++;
13921308Sache  word = line + i;
14021308Sache
14121308Sache  while (line[i] && !whitespace (line[i]))
14221308Sache    i++;
14321308Sache
14421308Sache  if (line[i])
14521308Sache    line[i++] = '\0';
14621308Sache
14721308Sache  command = find_command (word);
14821308Sache
14921308Sache  if (!command)
15021308Sache    {
15121308Sache      fprintf (stderr, "%s: No such command for FileMan.\n", word);
15221308Sache      return (-1);
15321308Sache    }
15421308Sache
15521308Sache  /* Get argument to command, if any. */
15621308Sache  while (whitespace (line[i]))
15721308Sache    i++;
15821308Sache
15921308Sache  word = line + i;
16021308Sache
16121308Sache  /* Call the function. */
16221308Sache  return ((*(command->func)) (word));
16321308Sache}
16421308Sache
16521308Sache/* Look up NAME as the name of a command, and return a pointer to that
16621308Sache   command.  Return a NULL pointer if NAME isn't a command name. */
16721308SacheCOMMAND *
16821308Sachefind_command (name)
16921308Sache     char *name;
17021308Sache{
17121308Sache  register int i;
17221308Sache
17321308Sache  for (i = 0; commands[i].name; i++)
17421308Sache    if (strcmp (name, commands[i].name) == 0)
17521308Sache      return (&commands[i]);
17621308Sache
17721308Sache  return ((COMMAND *)NULL);
17821308Sache}
17921308Sache
18021308Sache/* Strip whitespace from the start and end of STRING.  Return a pointer
18121308Sache   into STRING. */
18221308Sachechar *
18321308Sachestripwhite (string)
18421308Sache     char *string;
18521308Sache{
18621308Sache  register char *s, *t;
18721308Sache
18821308Sache  for (s = string; whitespace (*s); s++)
18921308Sache    ;
19021308Sache
19121308Sache  if (*s == 0)
19221308Sache    return (s);
19321308Sache
19421308Sache  t = s + strlen (s) - 1;
19521308Sache  while (t > s && whitespace (*t))
19621308Sache    t--;
19721308Sache  *++t = '\0';
19821308Sache
19921308Sache  return s;
20021308Sache}
20121308Sache
20221308Sache/* **************************************************************** */
20321308Sache/*                                                                  */
20421308Sache/*                  Interface to Readline Completion                */
20521308Sache/*                                                                  */
20621308Sache/* **************************************************************** */
20721308Sache
20821308Sachechar *command_generator ();
20921308Sachechar **fileman_completion ();
21021308Sache
21121308Sache/* Tell the GNU Readline library how to complete.  We want to try to complete
21221308Sache   on command names if this is the first word in the line, or on filenames
21321308Sache   if not. */
21421308Sacheinitialize_readline ()
21521308Sache{
21621308Sache  /* Allow conditional parsing of the ~/.inputrc file. */
21721308Sache  rl_readline_name = "FileMan";
21821308Sache
21921308Sache  /* Tell the completer that we want a crack first. */
22021308Sache  rl_attempted_completion_function = (CPPFunction *)fileman_completion;
22121308Sache}
22221308Sache
22321308Sache/* Attempt to complete on the contents of TEXT.  START and END bound the
22421308Sache   region of rl_line_buffer that contains the word to complete.  TEXT is
22521308Sache   the word to complete.  We can use the entire contents of rl_line_buffer
22621308Sache   in case we want to do some simple parsing.  Return the array of matches,
22721308Sache   or NULL if there aren't any. */
22821308Sachechar **
22921308Sachefileman_completion (text, start, end)
23021308Sache     char *text;
23121308Sache     int start, end;
23221308Sache{
23321308Sache  char **matches;
23421308Sache
23521308Sache  matches = (char **)NULL;
23621308Sache
23721308Sache  /* If this word is at the start of the line, then it is a command
23821308Sache     to complete.  Otherwise it is the name of a file in the current
23921308Sache     directory. */
24021308Sache  if (start == 0)
24121308Sache    matches = completion_matches (text, command_generator);
24221308Sache
24321308Sache  return (matches);
24421308Sache}
24521308Sache
24621308Sache/* Generator function for command completion.  STATE lets us know whether
24721308Sache   to start from scratch; without any state (i.e. STATE == 0), then we
24821308Sache   start at the top of the list. */
24921308Sachechar *
25021308Sachecommand_generator (text, state)
25121308Sache     char *text;
25221308Sache     int state;
25321308Sache{
25421308Sache  static int list_index, len;
25521308Sache  char *name;
25621308Sache
25721308Sache  /* If this is a new word to complete, initialize now.  This includes
25821308Sache     saving the length of TEXT for efficiency, and initializing the index
25921308Sache     variable to 0. */
26021308Sache  if (!state)
26121308Sache    {
26221308Sache      list_index = 0;
26321308Sache      len = strlen (text);
26421308Sache    }
26521308Sache
26621308Sache  /* Return the next name which partially matches from the command list. */
26721308Sache  while (name = commands[list_index].name)
26821308Sache    {
26921308Sache      list_index++;
27021308Sache
27121308Sache      if (strncmp (name, text, len) == 0)
27221308Sache        return (dupstr(name));
27321308Sache    }
27421308Sache
27521308Sache  /* If no names matched, then return NULL. */
27621308Sache  return ((char *)NULL);
27721308Sache}
27821308Sache
27921308Sache/* **************************************************************** */
28021308Sache/*                                                                  */
28121308Sache/*                       FileMan Commands                           */
28221308Sache/*                                                                  */
28321308Sache/* **************************************************************** */
28421308Sache
28521308Sache/* String to pass to system ().  This is for the LIST, VIEW and RENAME
28621308Sache   commands. */
28721308Sachestatic char syscom[1024];
28821308Sache
28921308Sache/* List the file(s) named in arg. */
29021308Sachecom_list (arg)
29121308Sache     char *arg;
29221308Sache{
29321308Sache  if (!arg)
29421308Sache    arg = "";
29521308Sache
29621308Sache  sprintf (syscom, "ls -FClg %s", arg);
29721308Sache  return (system (syscom));
29821308Sache}
29921308Sache
30021308Sachecom_view (arg)
30121308Sache     char *arg;
30221308Sache{
30321308Sache  if (!valid_argument ("view", arg))
30421308Sache    return 1;
30521308Sache
30658310Sache#if defined (__MSDOS__)
30758310Sache  /* more.com doesn't grok slashes in pathnames */
30858310Sache  sprintf (syscom, "less %s", arg);
30958310Sache#else
31021308Sache  sprintf (syscom, "more %s", arg);
31158310Sache#endif
31221308Sache  return (system (syscom));
31321308Sache}
31421308Sache
31521308Sachecom_rename (arg)
31621308Sache     char *arg;
31721308Sache{
31821308Sache  too_dangerous ("rename");
31921308Sache  return (1);
32021308Sache}
32121308Sache
32221308Sachecom_stat (arg)
32321308Sache     char *arg;
32421308Sache{
32521308Sache  struct stat finfo;
32621308Sache
32721308Sache  if (!valid_argument ("stat", arg))
32821308Sache    return (1);
32921308Sache
33021308Sache  if (stat (arg, &finfo) == -1)
33121308Sache    {
33221308Sache      perror (arg);
33321308Sache      return (1);
33421308Sache    }
33521308Sache
33621308Sache  printf ("Statistics for `%s':\n", arg);
33721308Sache
33826497Sache  printf ("%s has %d link%s, and is %d byte%s in length.\n",
33926497Sache	  arg,
34021308Sache          finfo.st_nlink,
34121308Sache          (finfo.st_nlink == 1) ? "" : "s",
34221308Sache          finfo.st_size,
34321308Sache          (finfo.st_size == 1) ? "" : "s");
34421308Sache  printf ("Inode Last Change at: %s", ctime (&finfo.st_ctime));
34521308Sache  printf ("      Last access at: %s", ctime (&finfo.st_atime));
34621308Sache  printf ("    Last modified at: %s", ctime (&finfo.st_mtime));
34721308Sache  return (0);
34821308Sache}
34921308Sache
35021308Sachecom_delete (arg)
35121308Sache     char *arg;
35221308Sache{
35321308Sache  too_dangerous ("delete");
35421308Sache  return (1);
35521308Sache}
35621308Sache
35721308Sache/* Print out help for ARG, or for all of the commands if ARG is
35821308Sache   not present. */
35921308Sachecom_help (arg)
36021308Sache     char *arg;
36121308Sache{
36221308Sache  register int i;
36321308Sache  int printed = 0;
36421308Sache
36521308Sache  for (i = 0; commands[i].name; i++)
36621308Sache    {
36721308Sache      if (!*arg || (strcmp (arg, commands[i].name) == 0))
36821308Sache        {
36921308Sache          printf ("%s\t\t%s.\n", commands[i].name, commands[i].doc);
37021308Sache          printed++;
37121308Sache        }
37221308Sache    }
37321308Sache
37421308Sache  if (!printed)
37521308Sache    {
37621308Sache      printf ("No commands match `%s'.  Possibilties are:\n", arg);
37721308Sache
37821308Sache      for (i = 0; commands[i].name; i++)
37921308Sache        {
38021308Sache          /* Print in six columns. */
38121308Sache          if (printed == 6)
38221308Sache            {
38321308Sache              printed = 0;
38421308Sache              printf ("\n");
38521308Sache            }
38621308Sache
38721308Sache          printf ("%s\t", commands[i].name);
38821308Sache          printed++;
38921308Sache        }
39021308Sache
39121308Sache      if (printed)
39221308Sache        printf ("\n");
39321308Sache    }
39421308Sache  return (0);
39521308Sache}
39621308Sache
39721308Sache/* Change to the directory ARG. */
39821308Sachecom_cd (arg)
39921308Sache     char *arg;
40021308Sache{
40121308Sache  if (chdir (arg) == -1)
40221308Sache    {
40321308Sache      perror (arg);
40421308Sache      return 1;
40521308Sache    }
40621308Sache
40721308Sache  com_pwd ("");
40821308Sache  return (0);
40921308Sache}
41021308Sache
41121308Sache/* Print out the current working directory. */
41221308Sachecom_pwd (ignore)
41321308Sache     char *ignore;
41421308Sache{
41521308Sache  char dir[1024], *s;
41621308Sache
41758310Sache  s = getcwd (dir, sizeof(dir) - 1);
41821308Sache  if (s == 0)
41921308Sache    {
42021308Sache      printf ("Error getting pwd: %s\n", dir);
42121308Sache      return 1;
42221308Sache    }
42321308Sache
42421308Sache  printf ("Current directory is %s\n", dir);
42521308Sache  return 0;
42621308Sache}
42721308Sache
42821308Sache/* The user wishes to quit using this program.  Just set DONE non-zero. */
42921308Sachecom_quit (arg)
43021308Sache     char *arg;
43121308Sache{
43221308Sache  done = 1;
43321308Sache  return (0);
43421308Sache}
43521308Sache
43621308Sache/* Function which tells you that you can't do this. */
43721308Sachetoo_dangerous (caller)
43821308Sache     char *caller;
43921308Sache{
44021308Sache  fprintf (stderr,
44121308Sache           "%s: Too dangerous for me to distribute.  Write it yourself.\n",
44221308Sache           caller);
44321308Sache}
44421308Sache
44521308Sache/* Return non-zero if ARG is a valid argument for CALLER, else print
44621308Sache   an error message and return zero. */
44721308Sacheint
44821308Sachevalid_argument (caller, arg)
44921308Sache     char *caller, *arg;
45021308Sache{
45121308Sache  if (!arg || !*arg)
45221308Sache    {
45321308Sache      fprintf (stderr, "%s: Argument required.\n", caller);
45421308Sache      return (0);
45521308Sache    }
45621308Sache
45721308Sache  return (1);
45821308Sache}
459