editline/examples/fileman.c

483 lines
11 KiB
C
Raw Normal View History

/* fileman.c -- A tiny application which demonstrates how to use the
GNU Readline library. This application interactively allows users
to manipulate files and their modes.
NOTE: this was taken from the GNU Readline documentation and ported
to libedit. A command to output the history list was added.
*/
#include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <locale.h>
#include <time.h>
#include "editline.h"
void too_dangerous(char *caller);
void initialize_readline(const char *prompt);
int execute_line(char *line);
int valid_argument(char *caller, char *arg);
/* The names of functions that actually do the manipulation. */
int com_list(char *);
int com_view(char *);
int com_history(char *);
int com_rename(char *);
int com_stat(char *);
int com_pwd(char *);
int com_delete(char *);
int com_help(char *);
int com_cd(char *);
int com_quit(char *);
struct cmd {
char *name; /* User printable name of the function. */
int (*func)(char *); /* Function to call to do the job. */
char *doc; /* Documentation for this function. */
};
struct cmd commands[] = {
{ "cd", com_cd, "Change to directory DIR"},
{ "delete", com_delete, "Delete FILE"},
{ "help", com_help, "Display this text"},
{ "?", com_help, "Synonym for `help'"},
{ "list", com_list, "List files in DIR"},
{ "ls", com_list, "Synonym for `list'"},
{ "pwd", com_pwd, "Print the current working directory"},
{ "quit", com_quit, "Quit using Fileman"},
{ "rename", com_rename, "Rename FILE to NEWNAME"},
{ "stat", com_stat, "Print out statistics on FILE"},
{ "view", com_view, "View the contents of FILE"},
{ "history", com_history, "List editline history"},
{ NULL, NULL, NULL },
};
/* Forward declarations. */
char *stripwhite(char *string);
struct cmd *find_command(char *name);
/* ~/.fileman_history */
char *fileman_history;
/* Prompt base and current */
const char *prompt_init;
char *prompt_curr;
/* When non-zero, this means the user is done using this program. */
int done;
int main(int argc, char **argv)
{
char *line, *s;
setlocale(LC_CTYPE, "");
initialize_readline("(FileMan)");
/* Loop reading and executing lines until the user quits. */
for (; done == 0;) {
line = readline(NULL);
if (!line)
break;
/* Remove leading and trailing whitespace from the line.
Then, if there is anything left, add it to the history list
and execute it. */
s = stripwhite(line);
#if 0
if (*s) {
char *expansion;
int result;
result = history_expand(s, &expansion);
if (result < 0 || result == 2) {
fprintf(stderr, "%s\n", expansion);
} else {
add_history(expansion);
execute_line(expansion);
}
free(expansion);
}
#else
execute_line(s);
#endif
free(line);
}
puts("");
write_history(fileman_history);
free(fileman_history);
return 0;
}
/* Execute a command line. */
int execute_line(char *line)
{
int i;
struct cmd *command;
char *word;
/* Isolate the command word. */
i = 0;
while (line[i] && isspace(line[i]))
i++;
word = line + i;
while (line[i] && !isspace(line[i]))
i++;
if (line[i])
line[i++] = '\0';
command = find_command(word);
if (!command) {
fprintf(stderr, "%s: No such command for FileMan.\n", word);
return -1;
}
/* Get argument to command, if any. */
while (isspace(line[i]))
i++;
word = line + i;
/* Call the function. */
return command->func(word);
}
/* Look up NAME as the name of a command, and return a pointer to that
command. Return a NULL pointer if NAME isn't a command name. */
struct cmd *find_command(char *name)
{
int i;
for (i = 0; commands[i].name; i++)
if (strcmp(name, commands[i].name) == 0)
return &commands[i];
return NULL;
}
/*
* Strip whitespace from the start and end of STRING. Return a pointer
* into STRING.
*/
char *stripwhite(char *string)
{
char *s, *t;
for (s = string; isspace(*s); s++) ;
if (*s == 0)
return s;
t = s + strlen(s) - 1;
while (t > s && isspace(*t))
t--;
*++t = '\0';
return s;
}
/* **************************************************************** */
/* */
/* Interface to Readline Completion */
/* */
/* **************************************************************** */
char *command_generator(const char *, int);
char **fileman_completion(const char *, int, int);
void fileman_prompt(void);
/*
* Tell the GNU Readline library how to complete. We want to try to
* complete on command names if this is the first word in the line, or
* on filenames if not.
*/
void initialize_readline(const char *prompt)
{
const char *home;
size_t len;
/* Allow conditional parsing of the ~/.inputrc file. */
rl_readline_name = "FileMan";
/* Tell the completer that we want a crack first. */
rl_attempted_completion_function = fileman_completion;
/* Restore command history */
home = getenv("HOME");
len = (home ? strlen(home) : 0) + 14;
fileman_history = malloc(len);
assert(fileman_history);
snprintf(fileman_history, len, "%s/.fileman_history", home ? home : ".");
read_history(fileman_history);
/* Prompt is updated when moving around in the tree */
prompt_init = prompt;
fileman_prompt();
}
/*
* Update prompt when changing directory. Use an allocated string to
* show off the rl_set_prompt() API for issue #51.
*/
void fileman_prompt(void)
{
char cwd[1024];
size_t len;
if (prompt_curr)
free(prompt_curr);
assert(getcwd(cwd, sizeof(cwd)));
len = strlen(prompt_init) + strlen(cwd) + 10;
prompt_curr = malloc(len);
assert(prompt_curr);
snprintf(prompt_curr, len, "%s %s/> ", prompt_init, cwd);
rl_set_prompt(prompt_curr);
}
/*
* Attempt to complete on the contents of TEXT. START and END
* bound the region of rl_line_buffer that contains the word to
* complete. TEXT is the word to complete. We can use the entire
* contents of rl_line_buffer in case we want to do some simple
* parsing. Returnthe array of matches, or NULL if there aren't any.
*/
char **fileman_completion(const char *text, int start, int end)
{
char **matches = NULL;
/* If this word is at the start of the line, then it is a command
to complete. Otherwise it is the name of a file in the current
directory. */
if (start == 0)
matches = rl_completion_matches(text, command_generator);
return matches;
}
/* Generator function for command completion. STATE lets us
know whether to start from scratch; without any state
(i.e. STATE == 0), then we start at the top of the list. */
char *command_generator(const char *text, int state)
{
static int list_index, len;
char *name;
/* If this is a new word to complete, initialize now. This
includes saving the length of TEXT for efficiency, and
initializing the index variable to 0. */
if (!state) {
list_index = 0;
len = strlen(text);
}
/* Return the next name which partially matches from the
command list. */
while ((name = commands[list_index].name)) {
list_index++;
if (strncmp(name, text, len) == 0)
return strdup(name);
}
/* If no names matched, then return NULL. */
return NULL;
}
/* **************************************************************** */
/* */
/* FileMan Commands */
/* */
/* **************************************************************** */
/* String to pass to system (). This is for the LIST, VIEW and RENAME
commands. */
static char syscom[1024];
/* List the file(s) named in arg. */
int com_list(char *arg)
{
if (!arg)
arg = "";
sprintf(syscom, "ls -FClg %s", arg);
return system(syscom);
}
int com_view(char *arg)
{
if (!valid_argument("view", arg))
return 1;
sprintf(syscom, "more %s", arg);
return system(syscom);
}
int com_history(char *arg)
{
const char *he;
/* rewind history */
while (el_prev_hist()) ;
for (he = el_next_hist(); he != NULL; he = el_next_hist())
printf("%s\n", he);
return 0;
}
int com_rename(char *arg)
{
too_dangerous("rename");
return 1;
}
int com_stat(char *arg)
{
struct stat finfo;
if (!valid_argument("stat", arg))
return 1;
if (stat(arg, &finfo) == -1) {
perror(arg);
return 1;
}
printf("Statistics for `%s':\n", arg);
printf("%s has %ld link%s, and is %lld byte%s in length.\n", arg,
(long)finfo.st_nlink,
(finfo.st_nlink == 1) ? "" : "s",
(long long)finfo.st_size, (finfo.st_size == 1) ? "" : "s");
printf("Inode Last Change at: %s", ctime(&finfo.st_ctime));
printf(" Last access at: %s", ctime(&finfo.st_atime));
printf(" Last modified at: %s", ctime(&finfo.st_mtime));
return 0;
}
int com_delete(char *arg)
{
too_dangerous("delete");
return 1;
}
/* Print out help for ARG, or for all of the commands if ARG is
not present. */
int com_help(char *arg)
{
int i;
int printed = 0;
for (i = 0; commands[i].name; i++) {
if (!*arg || (strcmp(arg, commands[i].name) == 0)) {
printf("%s\t\t%s.\n", commands[i].name,
commands[i].doc);
printed++;
}
}
if (!printed) {
printf("No commands match `%s'. Possibilties are:\n", arg);
for (i = 0; commands[i].name; i++) {
/* Print in six columns. */
if (printed == 6) {
printed = 0;
printf("\n");
}
printf("%s\t", commands[i].name);
printed++;
}
if (printed)
printf("\n");
}
return 0;
}
/* Change to the directory ARG. */
int com_cd(char *arg)
{
if (chdir(arg) == -1) {
perror(arg);
return 1;
}
//com_pwd("");
fileman_prompt();
return 0;
}
/* Print out the current working directory. */
int com_pwd(char *ignore)
{
char dir[1024], *s;
s = getcwd(dir, sizeof(dir) - 1);
if (!s) {
printf("Error getting pwd: %s\n", dir);
return 1;
}
printf("Current directory is %s\n", dir);
return 0;
}
/* The user wishes to quit using this program. Just set DONE
non-zero. */
int com_quit(char *arg)
{
done = 1;
return 0;
}
/* Function which tells you that you can't do this. */
void too_dangerous(char *caller)
{
fprintf(stderr, "%s: Too dangerous for me to distribute.\n", caller);
fprintf(stderr, "Write it yourself.\n");
}
/* Return non-zero if ARG is a valid argument for CALLER,
else print an error message and return zero. */
int valid_argument(char *caller, char *arg)
{
if (!arg || !*arg) {
fprintf(stderr, "%s: Argument required.\n", caller);
return 0;
}
return 1;
}
/**
* Local Variables:
* c-file-style: "k&r"
* c-basic-offset: 4
* End:
*/