1 (uiop:define-package
#:lw2.components
2 (:use
#:cl
#:alexandria
#:lw2.utils
#:lw2.csrf
)
4 #:standard-component
#:prepare-function
9 #:find-component
#:delete-component
#:define-component
#:renderer
10 #:component-value-bind
)
12 #:standard-component
#:wrap-http-bindings
))
14 (in-package #:lw2.components
)
16 (defvar *components
* nil
)
18 (defun make-binding-form (additional-vars body
&aux var-bindings additional-declarations additional-preamble
)
19 (loop for x in additional-vars
20 when
(not (member (first (ensure-list x
)) '(* &without-csrf-check
)))
22 (destructuring-bind (name &key member type default required
(request-type '(:post
:get
)) (real-name (string-downcase name
)) passthrough
)
27 `(or ,.
(mapcar (lambda (rt)
28 (list (if (eq rt
:post
) 'hunchentoot
:post-parameter
'hunchentoot
:get-parameter
)
30 (ensure-list request-type
)))))
34 `(let* ((raw-value ,inner-form
)
35 (sym (find-symbol (string-upcase raw-value
) ,(find-package '#:keyword
))))
37 (if (member sym
,member
)
39 (error "The ~A parameter has an unrecognized value." ',name
)))))
40 ((and type
(subtypep type
'integer
))
41 `(let ((,name
,inner-form
))
42 (declare (type (or null simple-string
) ,name
))
43 (if ,name
(parse-integer ,name
))))
46 (if (eq type
'boolean
)
47 `(let ((,name
,inner-form
))
49 (truthy-string-p ,name
)
52 `(or ,inner-form
,default
)
55 (push `(unless (and ,name
(not (equal ,name
""))) (error "Missing required parameter: ~A" ,real-name
))
58 (if type
(error "Cannot specify both member and type.")
59 (push `(type (or null symbol
) ,name
) additional-declarations
))
61 (push `(type (or null
,type
) ,name
) additional-declarations
)
62 (push `(type (or null simple-string
) ,name
) additional-declarations
)))
64 (push `(,name
,inner-form
) var-bindings
)))))
66 ,@(unless (member '&without-csrf-check additional-vars
)
68 (let ,(nreverse var-bindings
)
69 (declare ,.
(nreverse additional-declarations
))
70 ,.
(nreverse additional-preamble
)
73 (defmacro with-http-args
(http-args &body body
)
74 (make-binding-form http-args body
))
76 (defun wrap-prepare-code (http-args lambda-list body
)
77 (with-gensyms (renderer-callback)
78 `(lambda (,renderer-callback
,@lambda-list
)
79 (macrolet ((renderer ((&rest lambda-list
) &body body
)
80 `(funcall ,',renderer-callback
(lambda ,lambda-list
(block nil
(locally ,@body
))))))
81 ,(make-binding-form http-args body
)))))
83 (defun find-component (name)
84 (or (second (find name
*components
* :key
#'car
))
85 (error "Undefined component: ~A" name
)))
87 (defun delete-component (name)
88 (setf *components
* (delete name
*components
* :key
#'car
)))
90 (defmacro define-component
(name lambda-list
(&key http-args
) &body body
)
93 (alist :prepare-function
,(wrap-prepare-code http-args lambda-list body
))))
94 (delete-component ',name
)
95 (push (list ',name component
) *components
*))))
97 (defun prepare-function (component)
98 (cdr (assoc :prepare-function component
)))
100 (defmacro component-value-bind
((&rest binding-forms
) &body body
)
101 (let ((output-form `(locally ,@body
)))
102 (dolist (b (reverse binding-forms
))
103 (destructuring-bind (binding-vars prepare-form
&key as
) b
104 (destructuring-bind (name &rest args
) (ensure-list prepare-form
)
105 (let ((binding-vars (ensure-list binding-vars
)))
107 `(let ((,(or as name
) nil
))
108 (multiple-value-bind ,binding-vars
(funcall (load-time-value (prepare-function (find-component ',name
)))
109 (lambda (renderer) (setf ,(or as name
) renderer
))