[tbnl-devel] New macro: create-groups-bind-regex-dispatcher

Alceste Scalas alceste.scalas at gmx.net
Sat Aug 27 15:11:41 UTC 2005


Hello,

first of all, congratulations for TBNL :-)

Here's a small macro (with a big ugly name) for TBNL, that allows to
bind portions of a request URI to arguments passed to dispatch
functions. (see attachment)

I'm using it to create RESTful [1] URIs and web APIs for a web
application I'm playing with.  For example, let's say I want to publish
a news archive.  The "usual" way for accessing the news for August 27th,
2005 would be an URI like:

    http://www.news.tld/archive?year=2005&month=08&day=27

The proposed add-on allows to easily (well, actually *more* easily)
manage URIs like this:

    http://www.news.tld/archive/2005/08/27/

The task is performed by a macro called
create-groups-bind-regex-dispatcher, which takes three arguments:

    1. a CL-PPCRE regex (a string, an s-expression or a scanner)
       with one or more register groups.  It will be matched against
       the request URI;

    2. a list of variable names that will be bound to the register
       groups above iff the regex matches;

    3. a dispatch function that accepts keyword arguments named like
       the variables above.

A code sample for the news archive:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(defun archive-page (&key year month day)
  "Archive page"
  (format nil "Year: ~A; Month: ~A; Day: ~A" year month day))

(setq *dispatch-table*
      (list
        (create-groups-bind-regex-dispatcher
	   "^\\/archive\\/(\\d{4})\\/(\\d{1,2})\\/(\\d{1,2})\\/?$"
	   (year month day) archive-page)
        #'default-dispatcher))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Under the hood, the list of variables provided to
create-groups-bind-regex-dispatcher is used with
cl-ppcre:register-groups-bind --- so you can apply some voodoo to the
matched variables:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(defun archive-page (&key year month day)
  "Archive page with integer keyword parameters"
  (format nil "(+ year month day) => ~D" (+ year month day)))

(setq *dispatch-table*
      (list
        (create-groups-bind-regex-dispatcher
	   "^\\/archive\\/(\\d{4})\\/(\\d{1,2})\\/(\\d{1,2})\\/?$"
	   ((#'parse-integer year month day)) archive-page)
        #'default-dispatcher))
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

That's all.  I hope it will be useful for you as well...

Comments are welcome (please don't be too rude, it's one of my first
attempts with CL :-)

Regards,

alceste

  References:
    [1] http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
-- 
Alceste Scalas <alceste.scalas at gmx.net>
-------------- next part --------------
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: TBNL; Base: 10 -*-
;;; $Id$

;;; Copyright (c) 2005, Alceste Scalas. All rights reserved.

;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:

;;;   * Redistributions of source code must retain the above copyright
;;;     notice, this list of conditions and the following disclaimer.

;;;   * Redistributions in binary form must reproduce the above
;;;     copyright notice, this list of conditions and the following
;;;     disclaimer in the documentation and/or other materials
;;;     provided with the distribution.

;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

(in-package #:tbnl)

(defmacro create-groups-bind-regex-dispatcher (regex var-list page-function)
  "Works like CREATE-REGEX-DISPATCHER, but PAGE-FUNCTION will be
called with the variables in VAR-LIST used as additional keyword
arguments bound to the corresponding register groups of REGEX.
Note: VAR-LIST has the same format of its omonymous in
CL-PPCRE:REGISTER-GROUPS-BIND, so you can apply some magic here."
  `(let ((scanner (cl-ppcre:create-scanner ,regex)))
     (lambda (request)
       (cl-ppcre:register-groups-bind
	   ,var-list
	   (scanner (script-name request))
	 ;; Iff the regex matches, we return a closure that calls the
	 ;; dispatch function with the proper keyword arguments
	 (lambda () (,page-function ,@(build-keyword-list var-list)))))))

(defun build-keyword-list (var-list)
  "Utility function for CREATE-GROUPS-BIND-REGEX-DISPATCHER.
Build a keyword list out of the var-list, ready to be used to
invoke the dispatcher function"
  (mapcan (lambda (var)
	    (list (intern (symbol-name var) :keyword) var))
	  (cleanup-var-list var-list)))

(defun cleanup-var-list (var-list)
  "Flatten all the \(FUNCTION VAR) entries in VAR-LIST, leaving
only variable names."
  (loop for element in var-list
        if (consp element)
          nconc (loop for var in (rest element)
                      collect var)
        else
          collect element))
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 189 bytes
Desc: Questa parte del messaggio ? firmata
URL: <https://mailman.common-lisp.net/pipermail/tbnl-devel/attachments/20050827/2d7272e8/attachment.sig>


More information about the Tbnl-devel mailing list