git.fiddlerwoaroof.com
Browse code

feat: demo, other stuff

Ed Langley authored on 29/10/2020 20:33:30
Showing 5 changed files
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 101
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+This is the stub README.txt for the "araneus-demo" project.
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
+
0 14
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+;;;; araneus-demo.lisp
2
+
3
+(in-package #:araneus-demo)
4
+
5
+;;; "araneus-demo" goes here. Hacks and glory await!
6
+
7
+(define-controller )
0 8
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+;;;; package.lisp
2
+
3
+(defpackage #:araneus-demo
4
+  (:use #:cl)
5
+  (:import-from #:araneus #:define-controller #:define-view #:define-spinneret-view))
6
+