Last active
November 16, 2023 01:52
-
-
Save ordnungswidrig/6605483ac429a399a644024a6736ecf0 to your computer and use it in GitHub Desktop.
A very basic shell implemented in babashka
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env rlwrap bb -i | |
;; needs babashka beta | |
(defn ^sun.misc.Signal ->signal | |
"Convert a keyword to an appropriate Signal instance." | |
[signal] | |
(sun.misc.Signal. (-> signal name .toUpperCase))) | |
(defn ^Long signal->number | |
"Find out a signal's number" | |
[signal] | |
(-> signal ->signal .getNumber)) | |
(defn ^clojure.lang.Keyword signal->kw | |
"Translate a signal to a keyword" | |
[^sun.misc.Signal s] | |
(-> s .getName .toLowerCase keyword)) | |
(defn ^sun.misc.SignalHandler ->handler | |
"Convert class to signal handler." | |
[handler] | |
(proxy [sun.misc.SignalHandler] [] | |
(handle [sig] (handler (signal->kw sig))))) | |
(defn on-signal | |
"Execute handler when signal is caught" | |
[signal handler] | |
(sun.misc.Signal/handle (->signal signal) (->handler handler))) | |
(def waiting-for-input (atom true)) | |
(def ^:dynamic *handlers* | |
{:_ (fn [sig] (println "SIGNAL: " sig)) | |
:int (fn [sig] | |
(when @waiting-for-input | |
(println "Use quit to exit.")))}) | |
(defn handle-signal [sig] | |
(when-let [h (or (get *handlers* sig) | |
(get *handlers* :_))] | |
(h sig))) | |
(defmacro with-handlers | |
[handlers & body] | |
`(binding [*handlers* (merge *handlers* ~handlers)] | |
~@body)) | |
(doseq [s [:hup :int :quit :pipe :alrm :term :usr1 :usr2 :chld :ttin :ttou :winch]] | |
(on-signal s handle-signal)) | |
(require '[babashka.process :refer [process check]]) | |
(require '[clojure.string :as str]) | |
(require '[babashka.fs :as fs]) | |
;; babash(ka) sh(ell) | |
(println "Welcome to babash...") | |
(def cwd (atom (-> (io/file".") fs/absolutize (fs/canonicalize {:nofollow-links true})str))) | |
(defn prompt! [] | |
(let [home (str (fs/home)) | |
d (str/replace @cwd home "~")] | |
(print "ℬ" d "> ")) (flush)) | |
(defn cmd-cd [cmd_ args_] | |
(if (> (count args_) 1) | |
(println (format "%s: %s" cmd_ "too many arguments")) | |
(let [target (or (first args_) (str (fs/home))) | |
dir (if (fs/absolute? target) target | |
(io/file @cwd target))] | |
(if (fs/exists? dir) | |
(if (fs/directory? dir) | |
(do | |
(reset! cwd (-> dir | |
(fs/canonicalize {:nofollow-links true}) | |
str))) | |
(println (format "%s: %s: %s" cmd_ "not a directory" (first args_)))) | |
(println (format "%s: %s: %s" cmd_ "no such file or directory" (first args_))))))) | |
(prompt!) | |
(doseq [i *input*] | |
(let [i (.trim i) | |
[cmd_ & args_] (str/split i #"\s+")] | |
(try | |
(cond | |
(= "" i) | |
:nop | |
(= "quit" i) | |
(do | |
(println "Tschüß...") | |
(System/exit 0)) | |
(= "pwd" cmd_) | |
(println @cwd) | |
(= "cd" cmd_) | |
(cmd-cd cmd_ args_) | |
:else | |
;; tokenize with edn | |
(do | |
(let [reader (java.io.PushbackReader. (java.io.StringReader. i)) | |
cmd i | |
#_(take-while #(not= ::eof %) | |
(repeatedly #(edn/read {:eof ::eof} reader)))] | |
;; (println "Running" (pr-str cmd)) | |
(try (reset! waiting-for-input true) | |
(.flush *out*) (.flush *err*) | |
(-> (let [o @(process cmd {:dir @cwd :inherit true})] | |
(.flush *out*) (.flush *err*))) | |
(catch java.io.IOException e | |
(binding [*out* *err*] | |
(println (.getMessage e)))) | |
(finally | |
(reset! waiting-for-input false)))))) | |
(prompt!) | |
(catch Exception e | |
(println "GOT" e))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment