├── lisp ├── README.md ├── src │ ├── package.lisp │ └── electron-sbcl-sqlite.lisp └── electron-sbcl-sqlite.asd ├── .gitignore ├── clean.sh ├── package.json ├── index.html ├── main.js └── README.adoc /lisp/README.md: -------------------------------------------------------------------------------- 1 | # electron-sbcl-sqlite 2 | 3 | The process that implements application logic for 4 | electron-sbcl-sqlite. 5 | -------------------------------------------------------------------------------- /lisp/src/package.lisp: -------------------------------------------------------------------------------- 1 | ;;;; package.lisp 2 | 3 | (defpackage #:electron-sbcl-sqlite 4 | (:use #:cl #:cl-who #:sqlite)) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lispapp 3 | lispapp.exe 4 | electron-lisp-boilerplate-darwin-x64 5 | electron-lisp-boilerplate-win32-x64 6 | package-lock.json 7 | 8 | 9 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f lispapp 4 | rm -f lispapp.exe 5 | rm -rf electron-sbcl-sqlite-darwin-x64 6 | rm -rf electron-sbcl-sqlite-linux-x64 7 | rm -rf electron-sbcl-sqlite-win32-x64 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-sbcl-sqlite", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "electron": "^11.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World! 6 | 7 | 8 | 9 | 10 |

Hello World!

11 | We are using node , 12 | Chrome , 13 | and Electron . 14 | 15 | 16 | -------------------------------------------------------------------------------- /lisp/electron-sbcl-sqlite.asd: -------------------------------------------------------------------------------- 1 | ;;;; electron-sbcl-sqlite.asd 2 | 3 | #-quicklisp 4 | (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" 5 | (user-homedir-pathname)))) 6 | (when (probe-file quicklisp-init) 7 | (load quicklisp-init))) 8 | 9 | (ql:quickload :hunchentoot) 10 | (ql:quickload :cl-who) 11 | (ql:quickload :sqlite) 12 | 13 | (asdf:defsystem #:electron-sbcl-sqlite 14 | :description "Describe electron-sbcl-sqlite here" 15 | :author "Your Name " 16 | :license "Specify license here" 17 | :version "0.0.1" 18 | :serial t 19 | :depends-on (:sqlite :hunchentoot :cl-who) 20 | :components ((:module "src" 21 | :serial t 22 | :components ((:file "package") 23 | (:file "electron-sbcl-sqlite"))))) 24 | 25 | ;;; (asdf:load-system :electron-sbcl-sqlite) 26 | 27 | (defun buildapp () 28 | (asdf:load-system :electron-sbcl-sqlite) 29 | (save-lisp-and-die "lispapp" 30 | :toplevel 'cl-user::main 31 | :executable t)) 32 | 33 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, dialog } = require('electron'); 2 | //const { spawn } = require('child_process'); 3 | const execFile = require('child_process').execFile; 4 | const path = require('path'); 5 | 6 | const MAIN_URL = 'http://localhost:8000'; 7 | 8 | function createWindow () { 9 | // Create the browser window. 10 | let win = new BrowserWindow({ 11 | width: 800, 12 | height: 600, 13 | webPreferences: { 14 | nodeIntegration: true 15 | } 16 | }); 17 | 18 | // and load the index.html of the app. 19 | win.loadURL(MAIN_URL); 20 | } 21 | 22 | var lispProcess = null; 23 | 24 | function runapp () { 25 | lispExePath = path.resolve(__dirname, "lispapp.exe"); 26 | console.log(lispExePath); 27 | // lispProcess = spawn(lispExePath, [], { cwd: __dirname, shell: true}); 28 | lispProcess = execFile(lispExePath, function(err, data) { 29 | if(err) { 30 | console.error(err); 31 | return; 32 | } 33 | console.log("\n"); 34 | console.log(data.toString()); 35 | }); 36 | createWindow(); 37 | } 38 | 39 | function quit() { 40 | if (lispProcess !== null) { 41 | lispProcess.kill("SIGKILL"); 42 | lispProcess = null; 43 | } 44 | app.exit(0); 45 | } 46 | 47 | app.on("ready", runapp); 48 | app.on("quit", quit); 49 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = electron-sbcl-sqlite 2 | v0.0.1 3 | mikel evins 4 | :toc: 5 | 6 | == Overview 7 | 8 | A very simple Electron boilerplate that sets up an app whose application logic is handled by a Lisp child process, whose UI is handled by Electron, and whose persistence solution is SQLite. Uses sbcl with quicklisp and Hunchentoot to build the Lisp helper process, and electron-packager to build delivered app. 9 | 10 | Assumes that a suitable version of SQLite3 is installed and findable by CFFI. 11 | 12 | Tested on macOS Big Sur with SBCL 2.0.11 and SQlite3 3.32.3. 13 | 14 | == Building and Running the App 15 | 16 | 1. Ensure that these dependencies are properly installed: 17 | . https://nodejs.org/en/download/[A recent version of node and npm] 18 | . https://github.com/electron/electron-packager[electron-packager] 19 | . http://www.sbcl.org/platform-table.html[SBCL] 20 | . https://www.quicklisp.org/beta/[Quicklisp] 21 | 22 | 2. Using git, clone this repository. 23 | 3. cd into the cloned repository and run sh ./build.sh 24 | 4. Run the built app: 25 | `open electron-sbcl-sqlite-darwin-x64/electron-sbcl-sqlite.app` 26 | 27 | == Extending and Adapting It 28 | 29 | The simple project's purpose is to set up a bare minimum framework showing how you can build an Electron app on Lisp and SQLite. It doesn't do much more than that. The launched app fetches SBCL's `\*FEATURES*` list and uses the SQLite interface to get SQLite3's version string and its list of compile_options. To do more than that, you'll have to explore the tools and APIs yourself. 30 | 31 | A good starting place might be to look at the easy-handler definition for "/" in the file "electron-sbcl-sqlite/lisp/src/electron-sbcl-sqlite.lisp". Its `LET*` bindings use the `SQLITE` library's APIs to execute a simple SQL statement and a pragma to get the SQLite data that is displayed in the window. 32 | 33 | -------------------------------------------------------------------------------- /lisp/src/electron-sbcl-sqlite.lisp: -------------------------------------------------------------------------------- 1 | ;;;; electron-sbcl-sqlite.lisp 2 | 3 | (in-package #:electron-sbcl-sqlite) 4 | 5 | (defparameter *server* nil) 6 | 7 | (defun start-server (port) 8 | (setf *server* 9 | (make-instance 'hunchentoot:easy-acceptor :port port)) 10 | (hunchentoot:start *server*)) 11 | 12 | (hunchentoot:define-easy-handler (landing :uri "/") () 13 | (setf (hunchentoot:content-type*) "text/html") 14 | (with-open-database (db ":memory:") 15 | (let* ((sbcl-version (lisp-implementation-version)) 16 | (sbcl-features *features*) 17 | (sbcl-feature-count (length sbcl-features)) 18 | (sqlite-version (execute-single db "SELECT sqlite_version()")) 19 | (sqlite-compile-options (execute-to-list db "pragma compile_options"))) 20 | (with-html-output-to-string (out nil :prologue t) 21 | (:html 22 | (:head 23 | (:link :rel "preconnect" :href "https://cdn.jsdelivr.net") 24 | (:link :rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/@native-elements/core@1/dist/native-elements.css")) 25 | (:body 26 | (:script :src "https://unpkg.com/htmx.org@0.0.4") 27 | (:h1 "electron-sbcl-sqlite") 28 | (:div 29 | (:h4 (fmt "Running Hunchentoot on SBCL v~A with SQLite v~A" 30 | sbcl-version sqlite-version)) 31 | (:h5 "SBCL *features*:") 32 | (:p :font-size "8pt" (str sbcl-features)) 33 | (:h5 "SQLite compile_options:") 34 | (:p :font-size "8pt" (str sqlite-compile-options))) 35 | (:div 36 | (:form 37 | (:input :type "text" :name "msg") 38 | (:br) 39 | (:button :class "btn" 40 | :hx-post "/btnclick" 41 | :hx-target "#response" 42 | "Send message"))) 43 | (:div :id "response"))) 44 | (values))))) 45 | 46 | 47 | 48 | (hunchentoot:define-easy-handler (btnclick :uri "/btnclick") (msg) 49 | (setf (hunchentoot:content-type*) "text/plain") 50 | (format nil "Received: ~A" msg)) 51 | 52 | (defun stop-server () 53 | (hunchentoot:stop *server*) 54 | (setf *server* nil)) 55 | 56 | ;;; (start-server 8000) 57 | ;;; (stop-server) 58 | 59 | (in-package #:cl-user) 60 | 61 | (defun main (&optional (port 8000)) 62 | (electron-sbcl-sqlite::start-server port) 63 | (sb-impl::toplevel-repl nil)) 64 | 65 | --------------------------------------------------------------------------------