├── 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 |
--------------------------------------------------------------------------------