summary refs log tree commit diff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/.gitignore1
-rw-r--r--bin/Makefile2
-rw-r--r--bin/ish.c105
3 files changed, 107 insertions, 1 deletions
diff --git a/bin/.gitignore b/bin/.gitignore
index 8960977b..35d9a3af 100644
--- a/bin/.gitignore
+++ b/bin/.gitignore
@@ -2,6 +2,7 @@ atch
 bri
 dtch
 hnel
+ish
 jrp
 pbcopy
 pbd
diff --git a/bin/Makefile b/bin/Makefile
index f2112e29..e3f7053c 100644
--- a/bin/Makefile
+++ b/bin/Makefile
@@ -1,4 +1,4 @@
-BINS = atch bri dtch hnel jrp pbcopy pbd pbpaste typo wake watch xx
+BINS = atch bri dtch hnel ish jrp pbcopy pbd pbpaste typo wake watch xx
 CFLAGS += -Wall -Wextra -Wpedantic
 LDLIBS = -lcurses -ledit -lutil
 
diff --git a/bin/ish.c b/bin/ish.c
new file mode 100644
index 00000000..5812e76d
--- /dev/null
+++ b/bin/ish.c
@@ -0,0 +1,105 @@
+#include <err.h>
+#include <histedit.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#define UNUSED __attribute__((unused))
+
+static int builtinCd(int argc, const char *argv[]) {
+    const char *path = getenv("HOME");
+    if (argc > 1) path = argv[1];
+    int error = chdir(path);
+    if (error) warn("%s", path);
+    return error;
+}
+
+static int builtinExec(int argc, const char *argv[]) {
+    if (argc < 2) return 0;
+    execvp(argv[1], (char *const *)argv + 1);
+    err(EX_UNAVAILABLE, "%s", argv[1]);
+}
+
+static const struct {
+    const char *name;
+    int (*fn)(int argc, const char *argv[]);
+} BUILTINS[] = {
+    { "cd", builtinCd },
+    { "exec", builtinExec },
+};
+#define BUILTINS_LEN (sizeof(BUILTINS) / sizeof(BUILTINS[0]))
+
+static char *prompt(EditLine *editLine UNUSED) {
+    return "$ ";
+}
+
+static char *rprompt(EditLine *editLine UNUSED) {
+    static char buf[PATH_MAX];
+    char *cwd = getcwd(buf, sizeof(buf));
+    if (!cwd) err(EX_OSERR, "getcwd");
+    return cwd;
+}
+
+int main(int argc, const char *argv[]) {
+    EditLine *editLine = el_init(argv[0], stdin, stdout, stderr);
+    History *hist = history_init();
+    Tokenizer *tokenizer = tok_init(NULL);
+
+    el_set(editLine, EL_SIGNAL, 1);
+    el_set(editLine, EL_HIST, history, hist);
+    el_set(editLine, EL_PROMPT, prompt);
+    el_set(editLine, EL_RPROMPT, rprompt);
+
+    HistEvent histEvent;
+    history(hist, &histEvent, H_SETSIZE, 1000);
+    history(hist, &histEvent, H_SETUNIQUE, 1);
+    // TODO: History persistence.
+
+    el_source(editLine, NULL);
+
+    for (;;) {
+        int count;
+        const char *line = el_gets(editLine, &count);
+        if (count < 0) err(EX_IOERR, "el_gets");
+        if (!line) break;
+
+        history(hist, &histEvent, H_ENTER, line);
+
+        int tok = tok_line(tokenizer, el_line(editLine), &argc, &argv, NULL, NULL);
+        if (tok < 0) errx(EX_SOFTWARE, "tok_line");
+        if (tok > 0) continue; // TODO: Change prompt.
+
+        bool builtin = false;
+        for (size_t i = 0; i < BUILTINS_LEN; ++i) {
+            if (strcmp(argv[0], BUILTINS[i].name)) continue;
+            builtin = true;
+            BUILTINS[i].fn(argc, argv);
+            break;
+        }
+
+        if (!builtin) {
+            pid_t pid = fork();
+            if (pid < 0) err(EX_OSERR, "fork");
+
+            if (!pid) {
+                execvp(argv[0], (char *const *)argv);
+                err(EX_UNAVAILABLE, "%s", argv[0]);
+            }
+
+            int status;
+            pid_t dead = wait(&status);
+            if (dead < 0) err(EX_OSERR, "wait");
+        }
+
+        tok_reset(tokenizer);
+    }
+
+    el_end(editLine);
+    history_end(hist);
+    tok_end(tokenizer);
+    return EX_OK;
+}