Embedding data is very good because when shiping some software you
only need to give singgle executable, and the client just run that
executable, in C i usually using xxd
command to generate header
file, now that i program in Common Lisp i need way to embed some data
into lisp image, the first thing that came up in my mind is generate
some lisp file then i can load that file.
(defun blob->lisp (input output &key (var '*blob*))
(with-open-file (in input :element-type '(unsigned-byte 8))
(let* ((size (file-length in))
(data (make-array size :element-type '(unsigned-byte 8))))
(read-sequence data in)
(with-open-file (out output
:direction :output
:if-exists :supersede)
(format out "(defparameter ~A~% ~
(make-array ~D~% ~
:element-type '(unsigned-byte 8)~% ~
:initial-contents '(" var size)
(dotimes (i (length data))
(format out "~D " (aref data i)))
(format out ")))~%~%")
(format out "(defmacro with-~A ((var ~A) &body body)~% ~
`(uiop:with-temporary-file (:pathname ,var)~% ~
(with-open-file (out ,var :direction :output~% ~
:if-exists :supersede~% ~
:element-type '(unsigned-byte 8))~% ~
(write-sequence ,',~A out))~% ~
,@body))~%"
(string-downcase (subseq (symbol-name var) 1))
var
var))))
(uiop:file-exists-p output))
that lisp code above generate lisp file, but the problem is i just apply my C mind to Common Lisp, i need to think like Lisp Hacker.
The way of lisp hacker do is using macro system, just like previous code, but it generate at compile time.
(defmacro define-blob (name path)
(with-open-file (in path :element-type '(unsigned-byte 8))
(let* ((size (file-length in))
(data (make-array size :element-type '(unsigned-byte 8)))
(macro-name (intern (format nil "WITH-BLOB-~A" (string-upcase (symbol-name name)))
(symbol-package name))))
(read-sequence data in)
`(progn
(defparameter ,name
(make-array ,size :element-type '(unsigned-byte 8)
:initial-contents ',(coerce data 'list)))
(defmacro ,macro-name ((var) &body body)
`(uiop:with-temporary-file (:pathname ,var)
(with-open-file (out ,var :direction :output
:if-exists :supersede
:element-type '(unsigned-byte 8))
(write-sequence ,,name out))
,@body))))))
embedding some data can be very usefull for example, you create some web app, but you only want ship shinggle executable and works offline, you can embed the assets (eg. bootstrap, fontawesome)
(define-blob *bootstrap-min-css-blob*
"blob/bootstrap-5.3.8-dist/css/bootstrap.min.css")
(tbnl:define-easy-handler (bootstrap-css :uri "/bootstrap.min.css") ()
(setf (tbnl:content-type*) "text/css"
(tbnl:header-out "Cache-Control") "public, max-age=31536000"
(tbnl:header-out "ETag") "\"bootstrap-5.3.8-css\"")
(if (string= (tbnl:header-in* "if-none-match") "\"bootstrap-5.3.8-css\"")
(setf (tbnl:return-code*) 304)
(write-sequence *bootstrap-min-css-blob* (tbnl:send-headers))))
(define-blob *bootstrap-bundle-min-js-blob*
"blob/bootstrap-5.3.8-dist/js/bootstrap.bundle.min.js")
(tbnl:define-easy-handler (bootstrap-js :uri "/bootstrap.bundle.min.js") ()
(setf (tbnl:content-type*) "application/javascript"
(tbnl:header-out "Cache-Control") "public, max-age=31536000"
(tbnl:header-out "ETag") "\"bootstrap-5.3.8-js\"")
(if (string= (tbnl:header-in* "if-none-match") "\"bootstrap-5.3.8-js\"")
(setf (tbnl:return-code*) 304)
(write-sequence *bootstrap-bundle-min-js-blob* (tbnl:send-headers))))
now you only need to give single executable to your client.