Scripting and the CLI

GoLisp compiles to a single static binary, which makes it a good fit for command-line tools and scripts. You can run a .glsp file directly, parse options declaratively, shell out to other programs, and walk the filesystem — all from core, with no imports.

Running Scripts

For quick iteration, run a file without producing any artifacts:

glisp run stats.glsp 3 1 4 1 5
glisp run --watch server.glsp     # re-run on every save

Add a shebang and a file is directly executable:

#!/usr/bin/env glisp
(ns main)

(defn main [] -> void
  (println "ahoy"))
chmod +x ahoy.glsp
./ahoy.glsp

When you want a distributable binary instead, glisp build stats.glsp produces one.

Arguments and Environment

The sys/ namespace fronts the process and its environment:

(rest (sys/args))           ; arguments after the program name
(sys/env "PORT")            ; "" if unset
(sys/env "PORT" "8080")     ; with a default
(sys/exit 1)                ; exit with a status code

Parsing Options

cli/parse-opts turns raw arguments into options and positionals. Each spec is a map: :long (required), :short, :desc, :default, :flag (a boolean that consumes no value), and :int (parse the value as an integer):

(def specs []any
  [{:long "--name" :short "-n" :desc "Who to greet" :default "world"}
   {:long "--loud" :short "-l" :desc "Shout"        :flag true}])

(defn main [] -> void
  (let [parsed (cli/parse-opts (rest (sys/args)) specs)
        opts (:options parsed)
        msg  (str "hello, " (:name opts))]
    (when (not (empty? (:errors parsed)))
      (doseq [e (:errors parsed)] (println "error:" e))
      (println (:summary parsed))
      (sys/exit 1))
    (println (if (:loud opts) (str/upper msg) msg))
    (println "extra args:" (:arguments parsed))))

The result is a map: :options (keyed by each long name without --, so :name and :loud), :arguments (the positionals), :errors (unknown options, missing values, bad integers), and :summary (generated help text). --name value, --name=value, and -n value are all accepted; -- ends option parsing.

Running Other Programs

proc/run executes a command directly; proc/sh runs a string through sh -c so you get pipes, globs, and redirection. Both return {:out :err :exit :ok}:

(let [r (proc/run "git" "rev-parse" "HEAD")]
  (if (:ok r)
    (println "commit:" (str/trim (:out r)))
    (println "git failed (" (:exit r) "):" (:err r))))

(str/trim (:out (proc/sh "ls -1 | wc -l")))   ; count files via the shell

Files and Paths

slurp reads a whole file, spit writes one, and lines splits text on newlines. Path helpers front path/filepath, and glob/walk find files:

(spit "/tmp/note.txt" "hello\nworld")

(if-err [text err] (slurp "/tmp/note.txt")
  (println "read failed:" err)
  (doseq [l (lines text)] (println "line:" l)))

(path/join "src" "main.glsp")     ; "src/main.glsp"
(path/base "src/main.glsp")       ; "main.glsp"
(path/ext  "main.glsp")           ; ".glsp"

(glob "*.txt")                    ; matching files in the current dir
(doseq [f (walk "examples")]      ; every file under a tree, recursively
  (when (str/ends-with? f ".glsp")
    (println (path/base f))))

That is the whole scripting kit: read input, transform it, shell out where it's easier, and write the result — compiled to one binary you can drop anywhere.