Skip to content

aartaka/trivial-toplevel-commands

Repository files navigation

Trivial Toplevel Commands

Most CL implementations provide convenient toplevel commands, like

;; ECL command to load a Lisp file
:ld file.lisp

These commands tend to start with a colon and end with a newline, possibly including space-separated arguments. Such commands are too similar to not want a portable way to define them. Trivial Toplevel Commands is such a portability layer. It allows to define and remove toplevel commands on SBCL, CCL, ECL, ABCL, CLISP, and Allegro CL.

Couple of caveats:

There are no toplevel commands on SBCL
there are only debugger commands. Trivial Toplevel Commands only defines debugger command to not interfere with default SBCL REPL. If you want to access defined commands, enter the debugger by signaling some condition ((break), at least) or contribute a more involved REPL to SBCL directly.
  • You can also (require "sb-aclrepl") to enable an Allegro-like REPL. Trivial Toplevel Commands supports it.
CCL commands only accept evaluated arguments
macro-like commands are thus not possible. If you want to have maximum portability, only use define-toplevel-command/eval or constant arguments. Contribute a new REPL to CCL if you want to change that.
ECL /read and /string commands remove a layer of quotes from strings
I.e. “”hello” hello” will be read as “hello hello”. Plan accordingly. /eval commands are unaffected.
Help with making it work on CLASP, CMUCL, and LispWorks
I don’t have access to these implementations. Like, at all. Thanks.

Getting Started

Clone the Git repository:

git clone --recursive https://github.com/aartaka/trivial-toplevel-commands ~/common-lisp/

And then load :trivial-toplevel-commands in the REPL:

(asdf:load-system :trivial-toplevel-commands)
;; or, if you use Quicklisp
(ql:quickload :trivial-toplevel-commands)

You can also install Trivial Toplevel Commands via Guix, using the bundled guix.scm file:

guix package -f guix.scm

Examples

A simple shell-invoking command mimicking ruricolist/cmd. Obviously quite primitive:

(tpl-cmds:define-command/eval (:cmd :!) (command)
  "Shell invocation shortcut."
  (uiop:launch-program command))

;; Used like:
:! "touch ~/cmd-test.txt"

;; With an apparent result
(uiop:file-exists-p #p"~/cmd-test.txt")
;; => #P"~/cmd-test.txt"

A much more involved command processing raw string argument, say, for your MP3 player setup:

(tpl-cmds:define-command/string (:list-mp3s :lm) (path)
  "Recursively list all MP3 files in PATH, be it directory or file."
  (labels ((ls (file)
             (cond
               ((uiop:directory-pathname-p file)
                (format t "~&Directory ~a..." file)
                (mapc #'ls (uiop:subdirectories file))
                (mapc #'ls (uiop:directory-files file)))
               ((equalp "mp3" (pathname-type file))
                (format t "~&File ~a..." file)))))
    (ls (uiop:merge-pathnames* (uiop:parse-native-namestring path) (uiop:getcwd)))))

;; Used like:
:list-mp3s /path/to/your/mp3/files
;; or
:lm /path/to/your/mp3/files

;; Directory ...
;; Directory ...
;; File ...
;; Directory ...
;; Directory ...
;; File ...
;; File ...
;; File ...
;; File ...

You can also undefine previously defined commands with remove-command:

;; By alias:
(tpl-cmds:remove-command :lm)
;; Or by full name:
(tpl-cmds:remove-command :list-mp3s)

Most of the information for command is accessible too, via helper functions:

Command char for implementation (setf-able)
command-char.
Command alias by name
command-alias.
And vice versa
command-name.
The handler function for the command
command-handler.
  • Documentation for the command can be fetched with (documentation (command-handler :command) 'function).

You can also see the practical uses for commands in my config.

Similar Libraries

Peder Klingenberg’s commands.lisp
CMUCL-specific, could possibly work on SBCL.
Paul Foley’s Toplevel
CMUCL-specific.