Hunchentoot handler for dynamic amount of parameters - common-lisp

I want to be able to handle a form which uses a dynamic amount of form fields
e.g.
(form :action "/theaction"
:method "post"
(input :type "text" :name "firstinput") (:br)
(dotimes (i n)
(:input :type "text" :name (FORMAT nil "INPUT~a" i)))
(:input :type "submit" :value "submit" :name "submit"))
how can I define the handler as the &rest is not accepted and would not allow me to access the variable names which I obviously need for further processing.
(define-easy-handler (theaction :uri "/theaction") (&special-rest all-params)
(log-message* :INFO "~a" all-params))
-> "(("firstinput" . "value") ("INPUT0" . "value") ("INPUT1" . "value") ...)"
A possibility would be to pre-define all variables up to e.g. 100, but this would seem rather cumbersome and unintuitive.

The lambda-list of define-easy-handler is just a shortcut for using lower-level calls. You can get more extensive access to parameters by using functions like like GET-PARAMETER and POST-PARAMETER. You can get an alist of all parameters by using get-parameters* (or post-parameters*) in the body of the handler.

Related

How to produce HTML from a list

The usual way to generate HTML using CL-WHO is by using the macros with-html-output and with-html-output-to-string. This uses special syntax. For example:
(let ((id "greeting")
(message "Hello!"))
(cl-who:with-html-output-to-string (*standard-output*)
((:p :id id) message)))
Is it possible to write the data ((:p :id id) message) as a list instead of using the macro syntax shown above? For example, I would like to define the HTML as a list like this:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
;; What should I do here to generate HTML using CL-WHO?
)
Can CL-WHO take a normal Lisp list and produce HTML from the list?
You want to insert code into an expression.
Actually you would need eval:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
(eval `(cl-who:with-html-output-to-string (*standard-output*)
,the-html)))
But this is not good to use eval.
But a macro contains an implicite eval.
I would define a macro for this and call the macro:
(defun html (&body body)
`(cl-who:with-html-output-to-string (*standard-output*)
,#body))
;; but still one doesn't get rid of the `eval` ...
;; one has to call:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
(eval `(html ,the-html)))

Using CLOS class instances as hash-table keys?

I have the following class:
(defclass category ()
((cat-channel-name
:accessor cat-channel-name :initarg :cat-channel-name :initform "" :type string
:documentation "Name of the channel of this category")
(cat-min
:accessor cat-min :initarg :min :initform 0 :type number
:documentation "Mininum value of category")
(cat-max
:accessor cat-max :initarg :max :initform 1 :type number
:documentation "Maximum value of category"))
(:documentation "A category"))
Now, I would like to use this class as a key for a hash-table. The addresses of instances can be easily compared with eq. The problem is however, there might be multiple identical instances of this category class and I would like the hash-table to recognize this as a key as well.
So, I was trying to overwrite the :test argument of the make-hash-table function like this:
(make-hash-table :test #'(lambda (a b) (and (equal (cat-channel-name a) (cat-channel-name b))
(eq (cat-min a) (cat-min b))
(eq (cat-max a) (cat-max b)))
Unfortunately, this is not allowed. :test needs to be a designator for one of the functions eq, eql, equal, or equalp.
One way to solve this would be to turn the class category into a struct, but I need it to be a class. Is there any way I can solve this?
Many Common Lisp implementations provide extensions to the ANSI Common Lisp standard to support different test and hash functions (and a lot more).
CL-CUSTOM-HASH-TABLE is a compatibility layer.
You can use a more extensible hash table library, as explained in coredump's answer, but you could also use the approach that Common Lisp takes toward symbols: you can intern them. In this case, you just need an appropriate interning function that takes enough of a category to produce a canonical instance, and a hash table to store them. E.g., with a simplified category class:
(defclass category ()
((name :accessor cat-name :initarg :name)
(number :accessor cat-number :initarg :number)))
(defparameter *categories*
(make-hash-table :test 'equalp))
(defun intern-category (name number)
(let ((key (list name number)))
(multiple-value-bind (category presentp)
(gethash key *categories*)
(if presentp category
(setf (gethash key *categories*)
(make-instance 'category
:name name
:number number))))))
Then, you can call intern-category with the same arguments and get the same object back, which you can safely use as a hash table key:
(eq (intern-category "foo" 45)
(intern-category "foo" 45))
;=> T
Don't compare numbers with eq, use eql or =. From eq (emphasis mine):
Objects that appear the same when printed are not necessarily eq to each other. [...]
An implementation is permitted to make "copies" of characters and numbers at any time. The effect is that Common Lisp makes no guarantee that eq is true even when both its arguments are "the same thing" if that thing is a character or number.
You can use the genhash library. First, you define a new hash function (see also sxhash) and a test function for your type and you associate it with a test designator:
(genhash:register-test-designator
'category=
(lambda (category) <hashing>)
(lambda (a b)
(and (equal ... ...)
(= ... ...)
(= ... ...))))
Then, you can define a new table:
(genhash:make-generic-hashtable :test 'category=)

Custom slot options don't apply any reduction to its argument

Say if I define a metaclass that enhances standard slots with a validator slot, when I pass :validator (clavier:valid-email "The email is invalid") as an option, instead of storing the result of of the expression, which is a funcallable, it stores the expression itself. Am I'm missing a step when extending the standard slots? How do I ensure the expression is evaluated before stored? I'm using SBCL 1.2.11 btw. Here is the code in question
(unless (find-package 'clavier)
(ql:quickload :clavier))
(unless (find-package 'c2mop)
(ql:quickload :c2mop))
(defpackage #:clos2web/validation
(:use #:cl)
(:import-from #:c2mop
#:standard-class
#:standard-direct-slot-definition
#:standard-effective-slot-definition
#:validate-superclass
#:direct-slot-definition-class
#:effective-slot-definition-class
#:compute-effective-slot-definition
#:slot-value-using-class))
(in-package #:clos2web/validation)
(defun true (value)
"Always return true."
(declare (ignore value))
t)
(defclass validation-class (standard-class)
()
(:documentation "Meta-class for objects whose slots know how to validate
their values."))
(defmethod validate-superclass
((class validation-class) (super standard-class))
t)
(defmethod validate-superclass
((class standard-class) (super validation-class))
t)
(defclass validation-slot (c2mop:standard-slot-definition)
((validator :initarg :validator :accessor validator :initform #'true
:documentation "The function to determine if the value is
valid. It takes as a parameter the value.")))
(defclass validation-direct-slot (validation-slot
standard-direct-slot-definition)
())
(defclass validation-effective-slot (validation-slot
standard-effective-slot-definition)
())
(defmethod direct-slot-definition-class ((class validation-class) &rest initargs)
(declare (ignore initargs))
(find-class 'validation-direct-slot))
(defmethod effective-slot-definition-class ((class validation-class) &rest initargs)
(declare (ignore initargs))
(find-class 'validation-effective-slot))
(defmethod compute-effective-slot-definition
((class validation-class) slot-name direct-slot-definitions)
(let ((effective-slot-definition (call-next-method)))
(setf (validator effective-slot-definition)
(some #'validator direct-slot-definitions))
effective-slot-definition))
(defmethod (setf slot-value-using-class) :before
(new (class validation-class) object (slot validation-effective-slot))
(when (slot-boundp slot 'validator)
(multiple-value-bind (validp msg)
(funcall (validator slot) new)
(unless validp
(error msg)))))
;; Example usage
(defclass user ()
((name :initarg :name)
(email :initarg :email :validator (clavier:valid-email "The email is invalid") :accessor email))
(:metaclass validation-class))
(let ((pepe (make-instance 'user :name "Pepe" :email "pepe#tumadre.com")))
(setf (email pepe) "FU!")) ;; should throw
The code fails when making an instance as (CLAVIER:VALID-EMAIL "The email is invalid") is not a funcallable.
(CLAVIER:VALID-EMAIL
"The email is invalid") fell through ETYPECASE expression.
Wanted one of (FUNCTION SYMBOL).
[Condition of type SB-KERNEL:CASE-FAILURE]
Like the comment above says, defclass does not evaluate arguments (it is a macro). While the usual advice is to avoid eval, I think that eval in this circumstance might be exactly what you want. While usually you would splice the form directly into some macro body, with defclass I think the answer is to eval the form in slot initialization and store the evaluation (if it has not yet been evaled).
This would probably occur in:
(defmethod initialize-instance :after ((obj validation-slot)
&key &allow-other-keys)
#| ... |#)
Optionally you could also store the :validation-message and :validation-fn as two separate arguments then call:
(multiple-value-bind (validp msg)
(funcall (funcall (validator-fn slot)
(validator-message slot))
new)
(unless validp
(error msg)))
Another alternative would be to store the evaluation of the form and pass that to the macro:
(defvar *email-validator* (CLAVIER:VALID-EMAIL "The email is invalid"))
(defun email-validator (val)
(funcall *email-validator* val))
Then pass email-validator to defclass.
Additionally I might suggest that your validation functions signal slot-validation-error type conditions instead of error type conditions. Then your condition could contain references to the validator that failed, the value, the slot and the instance. This could give you much better control than the raw error. You could also add some restarts (abort to skip setting the slot, use-value to provide a different value).
Depending on your setup, it might also make more sense for your validation function to signal these directly instead of returning multiple values that are then coerced to signals.

function using cl-who:with-html-output ignoring parameter

I'm not sure whether this is an issue with my use of cl-who (specifically with-html-output-to-string and with-html-output) or an issue with my understanding of Common Lisp (as this is my first project using Lisp).
I created a function to create form fields:
(defun form-field (type name label)
(cl-who:with-html-output (*standard-output* nil)
(:div :class "field"
(:label :for name label)
(:input :type type :name name))))
When using this function, ie: (form-field "text" "username" "Username") the parameter label seems to be ignored... the HTML output is:
<div class="field"><label for="username"></label>
<input type="text" name="username"/></div>
instead of the expected output:
<div class="field"><label for="username">Username</label>
<input type="text" name="username"/></div>
If I modify the function and add a print statement:
(defun form-field (type name label)
(cl-who:with-html-output (*standard-output* nil)
(print label)
(:div :class "field"
(:label :for name label)
(:input :type type :name name))))
The "Username" string is successfully output (but still ignored in the HTML)... any ideas what might cause this?
Keep in mind, I'm calling this function within a cl-who:with-html-output-to-string for use with hunchentoot.
This situation is described in the CL-WHO evaluation rules under "A form which is neither a string nor a keyword..." (:label :for name label) falls under that rule, and it's just evaluated, but it doesn't output anything so it has no effect. One easy fix: use (str label) instead.

Hunchentoot/cl-who page composition

Hunchentoot/cl-who Page Composition
I'm trying to put together a few pages in hunchentoot as an experiment, and I'm running into an unexpected wall. As an example, I have the following template macro.
(defmacro page-template ((&key title) &body body)
`(with-html-output-to-string
(*standard-output* nil :prologue t :indent t)
(:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en"
(:head (:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8")
(:title ,(format nil "~#[~A - ~]Test Site" title)))
(:body ,#body))))
Now when I have a pure text page, or one filled with html literals like
(define-easy-handler (test-page :uri "/") ()
(page-template (:title "Splash Page") (:p "Testing testing")))
everything is a-ok. The page outputs properly and I can see te efforts of my code instantly. However, when I have a page which is made up of redundant elements, it's not as simple. For example, lets say I have a page on which for whatever reason I want to display three RSS newsfeeds. This is a sufficiently complex component that I want to abstract it out, so to my minnd, I should be able to do something like
(define-easy-handler (test-feed :uri "/feeds") ()
(page-template (:title "Splash Page")
(publish-newsfeed "http://nf-one.html")
(publish-newsfeed "http://nf-two.html")
(publish-newsfeed "http://nf-three.html")))
(defmacro publish-newsfeed (url &optional (item-limit 5))
(flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
(let ((rss-feed (xmls:parse (drakma:http-request url))))
`(:div :class "rss-feed"
(:a :href ,(get-text rss-feed '("channel" "link")) :target "_top" (:h1 ,(get-text rss-feed '("channel" "title"))))
(:ul ,#(mapcar #'(lambda (item)
`(:li (:a :href ,(get-text item '("link")) :target "_top" (:h2 ,(get-text item '("title"))))
(:p :class "date" ,(get-text item '("pubDate")))
(:p ,(get-text item '("description")))))
(let ((items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item")))
(if (> (length items) item-limit) (subseq items 0 item-limit) items))))))))
But the result of the above is a "Server Error" page. I'm not quire sure why; page-template is a macro so the calls to publish-newsfeed shouldn't be expanded until they're in the context of with-html-output-to-string. Can anyone tell me what I'm doing wrong?
Also, on closer inspection of the various Hunchentoot/cl-who tutorials, none of them seems to do this kind of page composition. Can anyone with some Hunchentoot experience tell me what the correct/canonical way of decomposing a page into components is?
EDIT:
Correct response by Ramarren below; the with-html-output macros work under different evaluation rules. The version of publish-newsfeed that would actually work in this situation is actually
(defun publish-newsfeed (url &optional (item-limit 5))
(flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
(let* ((rss-feed (xmls:parse (drakma:http-request url)))
(items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item"))
(ltd-items (if (> (length items) item-limit) (subseq items 0 item-limit) items)))
(with-html-output
(*standard-output* nil :indent t)
(:div :class "rss-feed"
(:a :href (get-text rss-feed '("channel" "link")) :target "_top" (:h1 (str (get-text rss-feed '("channel" "title")))))
(:ul (dolist (item ltd-items)
(htm (:li (:h2 (:a :href (get-text item '("link")) :target "_top" (str (get-text item '("title")))))
(:p :class "date" (str (get-text item '("pubDate"))))
(:p (str (get-text item '("description")))))))))))))
Note the removal of mapcar for dolist (I'm a Schemer, don't give me too much of a hard time about liking lambdas, but they weren't the right choice here), and the use of htm to escape blocks of html s-exps (h-exps?) that wouldn't otherwise be in context for with-html-output. Finally, I had to wrap text but NOT :href properties in (str ) to get them to expand dynamically.
The macro with-html-output-to-string expands its body using special evaluation rules. In particular, any not recognized forms are left as is, which means macros are not expanded before the html generating code is generated, which means by the time your publish-newsfeed macro is expanded by the standard compiler it is no longer in context ofwith-html-output-to-string. This is clear when expanding the macro manually, especially using slime macroexpansion feature.
To make it work you should make publish-newsfeed a function and use with-html-output inside it with the same stream (either assume `standard-output everywhere or pass the stream explicitly).

Resources