Browse code
feat: demo, other stuff
Ed Langley authored on 29/10/2020 20:33:30
Showing 5 changed files
Showing 5 changed files
- junk-drawer/araneus-2.lisp
- junk-drawer/araneus-demo/README.txt
- junk-drawer/araneus-demo/araneus-demo.asd
- junk-drawer/araneus-demo/araneus-demo.lisp
- junk-drawer/araneus-demo/package.lisp
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,100 @@ |
1 |
+;; This is a minimal web-framework built on ningle that attempts to decouple |
|
2 |
+;; views from controllers and make life more interesting. |
|
3 |
+(in-package :araneus-2) |
|
4 |
+ |
|
5 |
+(defgeneric run-route (name params &rest r) |
|
6 |
+ (:documentation "specialized on NAME with an EQL-specializer. This generic |
|
7 |
+ function defines the way a specific route is to be processed")) |
|
8 |
+ |
|
9 |
+(defgeneric controller (route action &key) |
|
10 |
+ (:documentation "specialized on ROUTE to indicate which route is involved, ACTION to pick the action. |
|
11 |
+ |
|
12 |
+This generic function picks out the model that the view renders for the user. Normally, this |
|
13 |
+is specialized using the DEFINE-CONTROLLER macro. A route can specify additional")) |
|
14 |
+ |
|
15 |
+(defgeneric view (name model) |
|
16 |
+ (:documentation "specialized on NAME with an EQL-specializer. This generic function |
|
17 |
+ renders the model picked out by the controller. Normally, this is |
|
18 |
+ specialized using the DEFINE-VIEW macr")) |
|
19 |
+ |
|
20 |
+(defmacro defroutes (app &body routes) |
|
21 |
+ "Define a set of routes for given paths. the ROUTES parameter expects this format: |
|
22 |
+ ((\"/path/to/{route}\" :method :POST) route-callback) the AS-ROUTE macro helps one |
|
23 |
+ avoid binding function values to the route for flexibility." |
|
24 |
+ (alexandria:once-only (app) |
|
25 |
+ `(setfs |
|
26 |
+ ,@(loop for ((target &key method) callback) in routes |
|
27 |
+ collect `((ningle:route ,app ,target :method ,(or method :GET)) ,callback))))) |
|
28 |
+ |
|
29 |
+ |
|
30 |
+(defvar *current-route*) |
|
31 |
+(defun call-current-view (model) |
|
32 |
+ "Call the currently running view with a new model. |
|
33 |
+ |
|
34 |
+ Useful if one view name is specialized many times on different model classes: the controller can |
|
35 |
+ pass the container and then the view can recall itself on the contained items." |
|
36 |
+ (view *current-route* model)) |
|
37 |
+ |
|
38 |
+;; DEPRECATED? |
|
39 |
+(defmacro as-route (route action &rest r &key &allow-other-keys) |
|
40 |
+ "Create a lambda directing requests to the route for NAME. This uses the |
|
41 |
+ generic function RUN-ROUTE internally whose default implementation relies on |
|
42 |
+ appropriate implementations of CONTROLLER and VIEW. The RUN-ROUTE method receives |
|
43 |
+ the parameters ningle passes to other functions as a first parameter, and then it |
|
44 |
+ receives a bunch of arguments according to the arguments passed to this macro." |
|
45 |
+ `(lambda (params) |
|
46 |
+ (run-route ,route ,action ,@r))) |
|
47 |
+ |
|
48 |
+(defun %compose-route (controller controller-args view) |
|
49 |
+ (declare (optimize (debug 3) (speed 0) (space 0) (safety 3))) |
|
50 |
+ (lambda (params) |
|
51 |
+ (declare (optimize (debug 3) (speed 0) (space 0) (safety 3))) |
|
52 |
+ (let ((*current-route* view)) |
|
53 |
+ (apply #'view |
|
54 |
+ (list view |
|
55 |
+ (apply #'controller |
|
56 |
+ (list* controller |
|
57 |
+ params |
|
58 |
+ controller-args))))))) |
|
59 |
+ |
|
60 |
+(defmacro compose-route ((controller &rest controller-args) view) |
|
61 |
+ `(%compose-route ',controller ,controller-args ',view)) |
|
62 |
+ |
|
63 |
+(defun switch-view (view-name) |
|
64 |
+ (format t "~&Switching view to: ~a~&" view-name) |
|
65 |
+ (alexandria:if-let ((switch-view-restart (find-restart 'switch-view))) |
|
66 |
+ (invoke-restart switch-view-restart view-name) |
|
67 |
+ (cerror "ignore this error" |
|
68 |
+ "Can only call switch-view while the route is being processed"))) |
|
69 |
+ |
|
70 |
+(defmethod run-route (route action &rest r) |
|
71 |
+ (let ((*current-route* route)) |
|
72 |
+ (fw.lu:let-each (:be *) |
|
73 |
+ (list* route action r) |
|
74 |
+ (restart-bind ((switch-view (lambda (new-view) |
|
75 |
+ (format t "~%SWITCHING VIEW: ~a" new-view) |
|
76 |
+ (setf *current-route* new-view)))) |
|
77 |
+ (apply #'controller *)) |
|
78 |
+ (view *current-route* *)))) |
|
79 |
+ |
|
80 |
+; The default controller just passes its parameters directly to the view |
|
81 |
+(defmethod controller (route action &key) |
|
82 |
+ action) |
|
83 |
+ |
|
84 |
+(defun render-mustache (fn data) |
|
85 |
+ (with-open-file (s (truename fn)) |
|
86 |
+ (let ((template (make-string (file-length s)))) |
|
87 |
+ (read-sequence template s) |
|
88 |
+ (mustache:render* template data)))) |
|
89 |
+ |
|
90 |
+(defmacro mustache ((template lambda-list data) &body body) |
|
91 |
+ "Template specifies the template to be render, lambda-list is used to destructure data |
|
92 |
+ and body transforms the destructured data into an alist for use in the template" |
|
93 |
+ (alexandria:once-only (template) |
|
94 |
+ `(destructuring-bind ,lambda-list ,data |
|
95 |
+ (render-mustache ,template |
|
96 |
+ (list |
|
97 |
+ ,@(loop for (k v) on body by #'cddr |
|
98 |
+ if (or k v) |
|
99 |
+ collect `(cons ,k ,v))))))) |
|
100 |
+ |
0 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,13 @@ |
1 |
+;;;; araneus-demo.asd |
|
2 |
+ |
|
3 |
+(asdf:defsystem #:araneus-demo |
|
4 |
+ :description "Describe araneus-demo here" |
|
5 |
+ :author "Your Name <your.name@example.com>" |
|
6 |
+ :license "Specify license here" |
|
7 |
+ :depends-on (#:alexandria |
|
8 |
+ #:serapeum |
|
9 |
+ #:araneus) |
|
10 |
+ :serial t |
|
11 |
+ :components ((:file "package") |
|
12 |
+ (:file "araneus-demo"))) |
|
13 |
+ |