Simple embedded shell with runtime pluggable commands.

Dependents:   DataBus2018

Implements a simple unix-like shell for embedded systems with a pluggable command architecture.

Revision:
21:5d7ac1f0b842
Parent:
20:53f0b5dc30f9
Child:
23:b1e49cfcaef6
--- a/SimpleShell.cpp	Mon Dec 24 04:31:43 2018 +0000
+++ b/SimpleShell.cpp	Mon Dec 24 17:42:43 2018 +0000
@@ -0,0 +1,324 @@
+#include "SimpleShell.h"
+#include <ctype.h>
+#include <string>
+
+#define ESC     0x1b
+#define UP      0x41
+#define DOWN    0x42
+#define RIGHT   0x43
+#define LEFT    0x44
+#define HOME    0x31
+#define INS     0x32
+#define DEL     0x33
+#define PGUP    0x35
+#define PGDN    0x36
+#define TAIL    0x7e
+
+
+char *SimpleShell::canon(char *path) {
+    static char result[MAXBUF];
+
+    if (path[0] != '/') {
+        strncpy(result, _cwd, MAXBUF);
+        strcat(result, "/");
+    }
+    strcat(result, path);
+
+    return result;
+}
+
+
+SimpleShell::SimpleShell()
+{
+    lookupEnd = 0;
+
+    // Built-in shell commands
+    attach(callback(this, &SimpleShell::help), "help");
+    attach(callback(this, &SimpleShell::pwd), "pwd");
+    attach(callback(this, &SimpleShell::cat), "cat");
+    attach(callback(this, &SimpleShell::cd), "cd");
+    attach(callback(this, &SimpleShell::rm), "rm");
+    attach(callback(this, &SimpleShell::touch), "touch");
+    attach(callback(this, &SimpleShell::ls), "ls");
+}
+
+
+void SimpleShell::help(int argc, char **argv)
+{
+    printf("Available commands: ");
+    for (int i=0; i < lookupEnd; i++) {
+        printf("%s ", lookup[i].command);
+    }
+    printf("\n\n");
+}
+
+
+void SimpleShell::cd(int argc, char **argv)
+{
+    if (argc == 2) {
+        strncpy(_cwd, argv[1], MAXBUF);
+    } else {
+        puts("usage: cd directory");
+    }
+    return;
+}
+
+
+void SimpleShell::pwd(int argc, char **argv)
+{
+    puts(_cwd);
+    return;
+}
+
+
+void SimpleShell::ls(int argc, char **argv)
+{
+    DIR *d;
+    struct dirent *p;
+    char *path;
+
+    if (argc == 1) {
+        path = _cwd;
+    } else if (argc == 2) {
+        path = argv[1];
+    } else {
+        puts("usage: ls [directory]");
+        return;
+    }
+
+    int cols=0;
+    if ((d = opendir(path)) != NULL) {
+        while ((p = readdir(d)) != NULL) {
+            if (p->d_name && p->d_name[0] != 0xff) {
+                if (cols++ > 3) {
+                    putc('\n', stdout);
+                    cols = 0;
+                }
+                printf("%-15s  ", p->d_name);
+            }
+        }
+        putc('\n', stdout);
+        if (cols < 3)
+            putc('\n', stdout);
+        closedir(d);
+    } else {
+        puts(path);
+        puts(": No such directory\n");
+    }
+
+    return;
+}
+
+
+void SimpleShell::rm(int argc, char **argv)
+{
+    if (argc >= 2) {
+        for (int i=1; i < argc; i++) {
+            if (remove(canon(argv[i]))) {
+                printf("%s: cannot remove\n", argv[i]);
+            }
+        }
+    } else {
+        puts("usage: rm [file1 [file2 ...]]");
+    }
+}
+
+
+void SimpleShell::touch(int argc, char **argv)
+{
+    FILE *fp;
+
+    if (argc >= 2) {
+        for (int i=1; i < argc; i++) {
+            if ((fp = fopen(canon(argv[i]), "w")) != NULL) {
+                fclose(fp);
+            } else {
+                printf("%s: cannot touch\n", argv[1]);
+            }
+        }
+    } else {
+        puts("usage: touch [file1 [file2 ...]]");
+    }
+}
+
+
+void SimpleShell::cat(int argc, char **argv)
+{
+    FILE *fp;
+    //int status=0;
+    char *buf = new char[MAXBUF];
+
+    for (int i=1; i < argc; i++) {
+        //resolveDirectory(path, arg);
+        if ((fp = fopen(canon(argv[i]), "r")) != NULL) {
+            while (!feof(fp)) {
+                fgets(buf, MAXBUF-1, fp);
+                fputs(buf, stdout);
+            }
+            fclose(fp);
+        } else {
+            fputs(argv[i], stdout);
+            fputs(": No such file\n", stdout);
+            //status = 1;
+        }
+    }
+    delete[] buf;
+
+    return;
+}
+
+
+void SimpleShell::run()
+{
+    bool done=false;
+    callback_t cb;
+    //int status; // TODO implement command status return
+    std::string x;
+
+    // Set current working directory
+    strncpy(_cwd, "/etc", MAXBUF);
+
+    printf("Type help for assistance.\n");
+    help(0, NULL);
+    while (!done) {
+        printPrompt();
+        readCommand();
+        if (argv[0]) { // skip blank command
+            if (cb = findCommand()) {
+                cb.call(argc, argv);
+            } else {
+                printf("command <%s> not found\n", argv[0]);
+            }
+        }
+    }
+    puts("exiting shell\n");
+
+    return;
+}
+
+
+void SimpleShell::attach(callback_t cb, char *command)
+{
+    if (lookupEnd < MAXLOOKUP) {
+        lookup[lookupEnd].cb = cb;
+        lookup[lookupEnd].command = command;
+        lookupEnd++;
+    }
+
+    return;
+}
+
+
+SimpleShell::callback_t SimpleShell::findCommand()
+{
+    SimpleShell::callback_t cb=NULL;
+
+    for (int i=0; i < lookupEnd; i++) {
+        if (strncmp(argv[0], lookup[i].command, MAXBUF) == 0) {
+            cb = lookup[i].cb;
+            break;
+        }
+    }
+
+    return cb;
+}
+
+
+void SimpleShell::printPrompt()
+{
+    fputc('(', stdout);
+    fputs(_cwd, stdout);
+    fputs(")# ", stdout);
+
+    return;
+}
+
+
+void SimpleShell::readCommand()
+{
+    int i=0;
+    char c;
+    bool done = false;
+    static char cmd[MAXBUF];
+
+    memset(cmd, 0, MAXBUF);
+    do {
+        cmd[i] = 0;
+        c = fgetc(stdin);
+        if (c == '\r') { // if return is hit, we're done, don't add \r to cmd
+            done = true;
+        } else if (c == ESC) { // keyboard escape codes (arrow keys, etc)
+            int c2 = getchar();
+            int c3 = getchar();
+
+            if (c2 == 0x4f && c3 == 0x46) {
+                printf("<END>");
+            } else if (c2 == 0x5b) {
+
+                if (c3 == UP) {
+                    printf("<UP>");
+                } else if (c3 == DOWN) {
+                    printf("<DOWN>");
+                } else if (c3 == RIGHT) {
+                    printf("<RIGHT>");
+                } else if (c3 == LEFT) {
+                    printf("<LEFT>");
+                } else if (c3 == HOME || c3 == INS || c3 == DEL ||
+                           c3 == PGUP || c3 == PGDN) {
+                    char c4 = getchar();
+                    if (c4 == TAIL) {
+                        if (c4 == HOME) {
+                            printf("<HOME>");
+                        } else if (c4 == INS) {
+                            printf("<INS>");
+                        } else if (c4 == DEL) {
+                            printf("<DEL>");
+                        } else if (c4 == PGUP) {
+                            printf("<PGUP>");
+                        } else if (c4 == PGDN) {
+                            printf("<PGDN>");
+                        }//if
+                    }//if
+                }//if
+            }//if
+            //printf("\n");
+        } else if (i < MAXBUF-1) {
+            if (c == 0x7f || c == '\b') { // backspace or delete
+                if (i > 0) { // if we're at the beginning, do nothing
+                    i--;
+                    fputs("\b \b", stdout);
+                }
+            } else {
+                if (isprint(c))
+                    fputc(c, stdout);
+                cmd[i++] = c;
+            }
+        }
+
+    } while (!done);
+    fputc('\n', stdout);
+
+    // remove leading/trailing whitespace
+    char *s = cmd;
+    while (isspace(*s)) {
+        s++;
+    }
+    for (int j=i; j >= 0 && isspace(cmd[j]); j--) {
+        cmd[j] = '\0';
+    }
+
+    // split into command and arguments
+    argc = 0;
+    char *t;
+
+    for (int i=0; i < MAXARGS; i++) {
+        argv[i] = NULL;
+    }
+    t = strtok(s, " ");
+    while (t && argc < 10) {
+        argv[argc++] = t;
+        t = strtok(NULL, " ");
+    }
+
+    return;
+}
\ No newline at end of file