A Lisp server executing software on demand
|Stanislav Kondratyev 3e775d90b3 *path*: wild pathnames instead of directories||1 year ago|
|client||4 years ago|
|server||1 year ago|
|zsh||4 years ago|
|.gitignore||6 years ago|
|Makefile||4 years ago|
|README.md||1 year ago|
|quick-setup.sh||1 year ago|
Download the tarball and unpack it under
$ cd ~/quicklisp/local-projects; wget -O lserver.tar.gz https://notabug.org/quasus/lserver/archive/master.tar.gz && tar xvf lserver.tar.gz && cd lserver
As you are in the
lserver directory, build the server and the client.
Install the server and client binaries. You can specify a location by means of prefix, e. g.
$ make install prefix="$HOME/usr"
will install both under
By default, the home directory of the server is
$HOME/.lserver. This can be
overridden by means of the
LSERVER_HOME environment variable.
LSERVER_SOCKET variable overrides the socket name, which is
Instead of building a standalone executable, you can install the portable Common Lisp development environment Portacle and run the server from there. This way you let Portacle take care of SBCL and quicklisp and moreover, you get live introspection and hacking with Slime.
In this case you don’t need to build the server.
cd into the Portacle directory.
Download the tarball and unpack it under
$ cd all/quicklisp/local-projects; wget -O lserver.tar.gz https://notabug.org/quasus/lserver/archive/master.tar.gz && tar xvf lserver.tar.gz && cd lserver
Build and install the client:
$ cd client $ make $ make install prefix="$HOME/usr"
Of course, you can use a different prefix or no prefix altogether if you
install it under
To run the server, start Portacle, load
lserver by writing
CL-USER> (ql:quickload "lserver")
at the prompt. When you do this for the first time,
quicklisp will fetch
dependencies, so you will need an internet connection. Then run the server by
CL-USER> (lserver:run-server :background t)
You can specify an alternative home directory and/or socket name like this:
CL-USER> (lserver:run-server :background t :home "~/alternative-home" :socket "mysock")
The system definition is found in the
server directory. You build the client
and run the server as explained in the section Using from Portacle.
$ lcli --list-commands $ lcli [command] [command-arguments]
commandis the name of the piece of software to run.
command-argumentsare CLI options for the command.
No commands are defined out of the box, so the server must be configured in
order to be of any use. You do that by editing the
residing in the server home directory.
You can run the
quick-setup.sh script in order to obtain a simple file-based
command system described below together with the
This doesn’t mean that this is the preferable way of managing commands, that
say commands must be generally available or that
lserver::file-command is an ‘official’ way of distributing software.
lserverrc.lisp file is a regular Lisp file loaded when the server starts.
Single-line comments start with a semicolon and multi-line comments go between
You can use the
add-command function to directly define commands in the
configuration file. Here is a small configuration:
(in-package #:lserver) (add-command "eval" (lambda (args) (dolist (arg args t) (eval (with-standard-io-syntax (let ((*package* (find-package "LSERVER"))) (read-from-string arg)))))) "Evaluate the arguments as Lisp expressions") (add-command "say" (lambda (args) (loop for value in (multiple-value-list (eval (with-standard-io-syntax (let ((*package* (find-package "LSERVER"))) (read-from-string (first args)))))) do (format t "~A~%" value) finally (finish-output) (return t))))
The first line specifies the namespace containing
lserver-specific tools. Put
it at the top of the configuration file unless you know what you are doing.
add-command function accepts a command name, a
accepting a list of command-line options, and an optional description. The
commands defined in the snippet evaluate arbitrary Lisp expressions and
optionally print the results, e. g.
$ lcli eval '(write-line "Hello, world!")' Hello, world! $ lcli say '(+ 1 2)' 3
Of course, you don’t want that if
lserver is to be used as a system-wide daemon.
If you want to define commands in individual files, you can use the following setup:
(defun file-command (function &optional description) (add-command (pathname-name *load-truename*) function description)) (defparameter *path* (list (merge-pathnames #p"commands/*.lisp" (lserver-homedir-pathname)))) (defun commands-from-path () (dolist (glob *path*) (dolist (pathname (directory glob)) (with-standard-io-syntax (let ((*package* (find-package "LSERVER"))) (load pathname)))))) (commands-from-path)
Instead of defining the
say commands directly in the configuration
file, you can create the following files
say.lisp under the
;;;; eval.lisp (in-package #:lserver) (file-command (lambda (args) (dolist (arg args t) (eval (with-standard-io-syntax (let ((*package* (find-package "LSERVER"))) (read-from-string arg)))))) "Evaluate arguments as Lisp forms.") ;;;; say.lisp (in-package #:lserver) (file-command (lambda (args) (loop for value in (multiple-value-list (eval (with-standard-io-syntax (let ((*package* (find-package "LSERVER"))) (read-from-string (first args)))))) do (format t "~A~%" value) finally (finish-output) (return t))) "Evaluate the argument as a Lisp form and print the values on separate lines.")
file-command names commands after files, so you can easily rename commands by just renaming files.
If you add new command files while the server is running, you can run
$ lcli eval '(commands-from-path)'
for a live update.
lserver commands can be defined on top of
lserver-unaware functions. When
the command is executed, the associated function is applied to the list of
provided command arguments with the following dynamic bindings:
*standard-error*are identified with client’s standard streams;
*query-io*corresponds to stdin and stderr;
*default-pathname-defaults*is client’s working directory,
The exit code is determined by the returned value: if the function returns an integer, it is used as the exit code, if the function returns a non-integer true value, the exit code is 0 and 1 otherwise. If something goes wrong, the client dies with a nonzero exit code, so such codes are not quite reliable.