Browse code
(init)
Ed Langley authored on 25/06/2020 17:11:52
Showing 10 changed files
Showing 10 changed files
- .gitignore
- files/api.js
- files/components.js
- files/index.html
- files/jsconfig.json
- files/main.js
- files/store.js
- main.lisp
- package.lisp
- web-lisp.asd
0 | 3 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,11 @@ |
1 |
+export function selectPackage(dispatchers, pkg) { |
|
2 |
+ fetch(`/packages/${pkg}`) |
|
3 |
+ .then((v) => v.json()) |
|
4 |
+ .then((it) => dispatchers.updateSymbols(pkg, it)); |
|
5 |
+} |
|
6 |
+ |
|
7 |
+export function updatePackages(dispatchers) { |
|
8 |
+ fetch("/packages") |
|
9 |
+ .then((v) => v.json()) |
|
10 |
+ .then((it) => dispatchers.updatePackages(it)); |
|
11 |
+} |
0 | 12 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,62 @@ |
1 |
+import { |
|
2 |
+ html, |
|
3 |
+ render, |
|
4 |
+} from "https://unpkg.com/lit-html@1.2.1/lit-html.js?module"; |
|
5 |
+ |
|
6 |
+const PackageSymbol = ({ |
|
7 |
+ name, |
|
8 |
+ "value-bound": v, |
|
9 |
+ "function-bound": f, |
|
10 |
+ "macro-bound": m, |
|
11 |
+}) => |
|
12 |
+ html` |
|
13 |
+ <tr> |
|
14 |
+ <td>${name}</td> |
|
15 |
+ <td>${v}</td> |
|
16 |
+ <td>${f}</td> |
|
17 |
+ <td>${m}</td> |
|
18 |
+ </tr> |
|
19 |
+ `; |
|
20 |
+ |
|
21 |
+const Package = ({ pkg, symbols }) => html` |
|
22 |
+ <div> |
|
23 |
+ <table id="package"> |
|
24 |
+ <thead> |
|
25 |
+ <th>Symbol Name</th> |
|
26 |
+ <th>Value Bound?</th> |
|
27 |
+ <th>Function Bound?</th> |
|
28 |
+ <th>Macro Bound?</th> |
|
29 |
+ </thead> |
|
30 |
+ <tbody> |
|
31 |
+ ${R.map( |
|
32 |
+ (v) => PackageSymbol(v), |
|
33 |
+ R.sortBy( |
|
34 |
+ R.compose(R.toLower, ({ name }) => name), |
|
35 |
+ symbols |
|
36 |
+ ) |
|
37 |
+ )} |
|
38 |
+ </tbody> |
|
39 |
+ </table> |
|
40 |
+ </div> |
|
41 |
+`; |
|
42 |
+ |
|
43 |
+export const PackageList = ( |
|
44 |
+ dispatchers, |
|
45 |
+ { packages, currentPackage, symbols } |
|
46 |
+) => |
|
47 |
+ html` |
|
48 |
+ <main> |
|
49 |
+ <ul id="packages"> |
|
50 |
+ ${R.map( |
|
51 |
+ (pkg) => |
|
52 |
+ html`<li> |
|
53 |
+ <button @click=${(_) => selectPackage(dispatchers, pkg)}> |
|
54 |
+ ${pkg} |
|
55 |
+ </button> |
|
56 |
+ </li>`, |
|
57 |
+ R.sortBy(R.toLower, packages) |
|
58 |
+ )} |
|
59 |
+ </ul> |
|
60 |
+ ${Package({ package: currentPackage, symbols })} |
|
61 |
+ </main> |
|
62 |
+ `; |
0 | 63 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,46 @@ |
1 |
+<!DOCTYPE html> |
|
2 |
+<html lang="en"> |
|
3 |
+ <head> |
|
4 |
+ <meta charset="UTF-8" /> |
|
5 |
+ <title>Document</title> |
|
6 |
+ <style> |
|
7 |
+ * { |
|
8 |
+ box-sizing: border-box; |
|
9 |
+ } |
|
10 |
+ html, |
|
11 |
+ body { |
|
12 |
+ margin: 0; |
|
13 |
+ padding: 0; |
|
14 |
+ } |
|
15 |
+ ul { |
|
16 |
+ margin: 0; |
|
17 |
+ } |
|
18 |
+ #root { |
|
19 |
+ height: 100vh; |
|
20 |
+ } |
|
21 |
+ main { |
|
22 |
+ display: flex; |
|
23 |
+ height: 100%; |
|
24 |
+ } |
|
25 |
+ main > * { |
|
26 |
+ width: 50%; |
|
27 |
+ max-height: 100%; |
|
28 |
+ overflow-y: auto; |
|
29 |
+ } |
|
30 |
+ </style> |
|
31 |
+ </head> |
|
32 |
+ <body> |
|
33 |
+ <div id="root"></div> |
|
34 |
+ <script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script> |
|
35 |
+ <script> |
|
36 |
+ // HACK(keanulee): The Redux package assumes `process` exists - mock it here before |
|
37 |
+ // the module is loaded. |
|
38 |
+ window.process = { |
|
39 |
+ env: { |
|
40 |
+ NODE_ENV: "production", |
|
41 |
+ }, |
|
42 |
+ }; |
|
43 |
+ </script> |
|
44 |
+ <script src="/public/main.js" type="module"></script> |
|
45 |
+ </body> |
|
46 |
+</html> |
0 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
1 |
+import { |
|
2 |
+ html, |
|
3 |
+ render, |
|
4 |
+} from "https://unpkg.com/lit-html@1.2.1/lit-html.js?module"; |
|
5 |
+ |
|
6 |
+import { store, dispatchers } from "./store.js"; |
|
7 |
+import { selectPackage, updatePackages } from "./api.js"; |
|
8 |
+import { PackageList } from "./components.js"; |
|
9 |
+ |
|
10 |
+store.subscribe((_) => { |
|
11 |
+ render( |
|
12 |
+ PackageList(dispatchers, store.getState()), |
|
13 |
+ document.querySelector("#root") |
|
14 |
+ ); |
|
15 |
+}); |
|
16 |
+ |
|
17 |
+store.dispatch({ type: "@@start" }); |
|
18 |
+updatePackages(dispatchers); |
0 | 19 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,42 @@ |
1 |
+import * as Redux from "https://unpkg.com/redux@4.0.5?module"; |
|
2 |
+ |
|
3 |
+const initialState = { |
|
4 |
+ foo: "asdf", |
|
5 |
+ packages: [], |
|
6 |
+ currentPackage: "", |
|
7 |
+ symbols: [], |
|
8 |
+}; |
|
9 |
+ |
|
10 |
+function reducer(state = initialState, { type, payload }) { |
|
11 |
+ if (type === "UPDATE_PACKAGES") { |
|
12 |
+ return { ...state, packages: payload }; |
|
13 |
+ } else if (type === "UPDATE_SYMBOLS") { |
|
14 |
+ return { |
|
15 |
+ ...state, |
|
16 |
+ symbols: payload.symbols, |
|
17 |
+ currentPackage: payload.currentPackage, |
|
18 |
+ }; |
|
19 |
+ } |
|
20 |
+ return state; |
|
21 |
+} |
|
22 |
+ |
|
23 |
+export const store = Redux.createStore( |
|
24 |
+ reducer, |
|
25 |
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() |
|
26 |
+); |
|
27 |
+ |
|
28 |
+export const dispatchers = { |
|
29 |
+ updateFoo(payload) { |
|
30 |
+ console.log("foo"); |
|
31 |
+ store.dispatch({ type: "UPDATE_FOO", payload }); |
|
32 |
+ }, |
|
33 |
+ updatePackages(payload) { |
|
34 |
+ store.dispatch({ type: "UPDATE_PACKAGES", payload }); |
|
35 |
+ }, |
|
36 |
+ updateSymbols(_, { package: currentPackage, symbols }) { |
|
37 |
+ store.dispatch({ |
|
38 |
+ type: "UPDATE_SYMBOLS", |
|
39 |
+ payload: { currentPackage, symbols }, |
|
40 |
+ }); |
|
41 |
+ }, |
|
42 |
+}; |
0 | 43 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,79 @@ |
1 |
+(cl:in-package :fwoar.web-lisp) |
|
2 |
+ |
|
3 |
+(defclass web-lisp-app (ningle:<app>) |
|
4 |
+ ()) |
|
5 |
+ |
|
6 |
+(defun string-return (s) |
|
7 |
+ `(200 () (,s))) |
|
8 |
+ |
|
9 |
+(defmacro with-json-string ((s &rest args &key indent) &body body) |
|
10 |
+ "Set up a JSON streaming encoder context, then evaluate BODY. |
|
11 |
+Return a string with the generated JSON output." |
|
12 |
+ (declare (ignore indent)) |
|
13 |
+ `(with-output-to-string (,s) |
|
14 |
+ (with-open-stream (,s (yason:make-json-output-stream s ,@args)) |
|
15 |
+ ,@body))) |
|
16 |
+ |
|
17 |
+(deftype simple-json-type () |
|
18 |
+ '(or |
|
19 |
+ (member yason:true yason:false t) |
|
20 |
+ real string null)) |
|
21 |
+(deftype compound-json-type () |
|
22 |
+ '(or |
|
23 |
+ (array *) |
|
24 |
+ list |
|
25 |
+ hash-table)) |
|
26 |
+(deftype json-type () |
|
27 |
+ '(or simple-json-type compound-json-type)) |
|
28 |
+(defun encodable (it) |
|
29 |
+ (typep it |
|
30 |
+ 'json-type)) |
|
31 |
+ |
|
32 |
+(defmethod araneus:routes progn ((app web-lisp-app)) |
|
33 |
+ (setf (ningle:route app "/systems" :method :get) |
|
34 |
+ (lambda (params) |
|
35 |
+ (declare (ignore params)) |
|
36 |
+ (string-return |
|
37 |
+ (with-json-string (s :indent t) |
|
38 |
+ (yason:encode (asdf:registered-systems) |
|
39 |
+ s)))) |
|
40 |
+ |
|
41 |
+ (ningle:route app "/packages" :method :get) |
|
42 |
+ (lambda (params) |
|
43 |
+ (declare (ignore params)) |
|
44 |
+ (string-return |
|
45 |
+ (with-json-string (s :indent t) |
|
46 |
+ (yason:encode (mapcar 'package-name |
|
47 |
+ (list-all-packages)) |
|
48 |
+ s)))) |
|
49 |
+ |
|
50 |
+ (ningle:route app "/packages/:package-name" :method :get) |
|
51 |
+ (lambda (params) |
|
52 |
+ (let ((package-name (serapeum:assocdr :package-name params |
|
53 |
+ :test 'equal))) |
|
54 |
+ (string-return |
|
55 |
+ (yason:with-output-to-string* (:indent t) |
|
56 |
+ (yason:with-object () |
|
57 |
+ (yason:encode-object-element "package" package-name) |
|
58 |
+ (yason:with-object-element ("symbols") |
|
59 |
+ (yason:with-array () |
|
60 |
+ (do-external-symbols (sym package-name) |
|
61 |
+ (yason:with-object () |
|
62 |
+ (yason:encode-object-elements |
|
63 |
+ "name" (symbol-name sym) |
|
64 |
+ "value-bound" (boundp sym) |
|
65 |
+ "function-bound" (fboundp sym) |
|
66 |
+ "macro-bound" (if (macro-function sym) |
|
67 |
+ 'yason:true |
|
68 |
+ 'yason:false))))))))))) |
|
69 |
+ |
|
70 |
+ (ningle:route app "/packages/:package-name/describe/:symbol-name" :method :get) |
|
71 |
+ (lambda (params) |
|
72 |
+ (let ((package-name (serapeum:assocdr :package-name params |
|
73 |
+ :test 'equal)) |
|
74 |
+ (symbol-name (serapeum:assocdr :symbol-name params |
|
75 |
+ :test 'equal))) |
|
76 |
+ (string-return |
|
77 |
+ (with-output-to-string (s) |
|
78 |
+ (describe (find-symbol symbol-name package-name) |
|
79 |
+ s))))))) |
0 | 10 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,17 @@ |
1 |
+;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; Package: ASDF-USER -*- |
|
2 |
+(in-package :asdf-user) |
|
3 |
+ |
|
4 |
+(defsystem :web-lisp |
|
5 |
+ :description "" |
|
6 |
+ :author "Ed L <edward@elangley.org>" |
|
7 |
+ :license "MIT" |
|
8 |
+ :depends-on (#:alexandria |
|
9 |
+ #:uiop |
|
10 |
+ #:serapeum |
|
11 |
+ #:clack |
|
12 |
+ #:araneus |
|
13 |
+ #:yason) |
|
14 |
+ :serial t |
|
15 |
+ :components ((:file "package") |
|
16 |
+ (:file "main") |
|
17 |
+ )) |