trivial-toplevel-commands
2024-10-12
Trivial Toplevel Commands allows to define toplevel commands available on most implementations in a portable fashion.
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 debuggercommands. Trivial Toplevel Commands only defines debugger command tonot interfere with default SBCL REPL. If you want to access definedcommands, 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-likeREPL. Trivial Toplevel Commands supports it.
- You can also
- CCL commands only accept evaluated arguments
- macro-like commandsare thus not possible. If you want to have maximum portability, onlyuse
define-toplevel-command/eval
or constant arguments. Contributea 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'thave access to these implementations. Like, at all. Thanks.
1Getting 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
2Examples
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)
.
- Documentation for the command can be fetched with
You can also see the practical uses for commands in my config.
3Similar Libraries
- Peder Klingenberg's commands.lisp
- CMUCL-specific, could possiblywork on SBCL.
- Paul Foley's Toplevel
- CMUCL-specific.