Skip to content

Instantly share code, notes, and snippets.

@ordnungswidrig
Last active November 16, 2023 01:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ordnungswidrig/6605483ac429a399a644024a6736ecf0 to your computer and use it in GitHub Desktop.
Save ordnungswidrig/6605483ac429a399a644024a6736ecf0 to your computer and use it in GitHub Desktop.
A very basic shell implemented in babashka
#!/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