-
Notifications
You must be signed in to change notification settings - Fork 0
/
navi.lisp
87 lines (69 loc) · 2.75 KB
/
navi.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
(in-package #:navi)
(defvar *hot-reload-p* nil)
(defvar *output-dir* (fad:merge-pathnames-as-file (uiop:getcwd) "out/"))
(defvar *pages* (make-hash-table))
(defvar *assets* nil)
(defvar *pages-dirty-p* nil)
;; These settings are ensuring that we don't mess with spaces when displaying inlined links.
;; More info: https://github.com/ruricolist/spinneret/issues/37
(setf spinneret:*html-style* :human)
(setf spinneret:*suppress-inserted-spaces* t)
(setf spinneret:*fill-column* 999)
(defclass page ()
((path :initarg :path :accessor page-path)
(body :initarg :body :accessor page-body)
(builder :initarg :builder :accessor page-builder)))
(defmacro define-page (name (&key path) &body body)
`(add-page ',name ,path ',body (lambda () (spinneret:with-html (:doctype) ,@body))))
(defun should-reload-p ()
(and *hot-reload-p* (navi/server:running-p)))
(defun add-page (name path body builder)
(setf (gethash name *pages*)
(make-instance 'page
:path path
:body body
:builder builder))
(when (should-reload-p)
(build-pages)))
(defmacro define-tag (name (body attrs-var &rest ll) &body tag)
`(progn
(spinneret:deftag ,name (,body ,attrs-var ,@ll) ,@tag)
;; TODO do this only if tree is actually changed
(setf *pages-dirty-p* t)
(when (should-reload-p)
(build-pages))))
(defun recompile-page (page)
(format t "Recompiling page... ~A~%" (page-path page))
(setf (page-builder page)
(lambda () (eval `(spinneret:with-html
(:doctype)
,@(page-body page))))))
(defun add-asset (path)
(pushnew path *assets*))
(defun build-pages (&optional (output-dir *output-dir*))
(ensure-directories-exist output-dir)
(when *pages-dirty-p*
(loop for page being the hash-value of *pages*
do (recompile-page page))
(setf *pages-dirty-p* nil))
(loop for page being the hash-value of *pages*
for path = (fad:merge-pathnames-as-file output-dir (page-path page))
do (format t "Writing ~A~%" path)
(with-open-file (spinneret:*html* path :direction :output :if-exists :supersede)
(funcall (page-builder page))))
(navi/style:compile-styles (fad:merge-pathnames-as-file output-dir "style.css"))
(loop for path in *assets*
do (uiop:copy-file path (fad:merge-pathnames-as-file output-dir (path:basename path))))
(when *hot-reload-p*
(navi/socket:reload-browser)))
(defun start ()
(when *hot-reload-p*
(uiop:copy-file (asdf:system-relative-pathname 'navi "socket.js")
(fad:merge-pathnames-as-file *output-dir* "socket.js"))
(navi/socket:start))
(when *pages-dirty-p*
(build-pages))
(navi/server:start *output-dir*))
(defun stop ()
(navi/server:stop)
(navi/socket:stop))