Doing the tutorial on writing a blog using PLT-Scheme opened my eyes to some of the possibilities. Working lately primary on webapps using web.py, sqlalchemy I wanted to see what could be done using scheme. In this case browsing PLT's available libraries. After scanning PLT's PLaneT I have found some interesting libs opensourced by a company called untyped.

The libs I found interesting where:

I decided to take a closer look at them and how untyped implemented its magic.

Dispatch, declare what you want

Dispatch is essentially syntax to declare routes and controllers. I wanted to know more so I analysed an example given by the documentation.

Define your site

#lang scheme/base

;; automaticly grab packages from PLaneT
(require (planet untyped/dispatch))

(define-site blog
             ([(url "/") index]
              [(url "/posts/" (string-arg)) review-post]
              [(url "/archive/" (integer-arg) "/" (integer-arg))
               review-archive]))

Essentially stating that /archive/10 will call review-archive with one parameter 10.

Controllers

Controllers for the rules above may be defined with:

(define-controller (controller request args ...)
    exp ... )

An example being:

; request string -> html-response
(define-controller (review-post request slug)
  `(html (head (title ,slug))
         (body (h1 "You are viewing " ,(format "~s" slug))
               (p "And now for some content..."))))

Implementation

To find out what happens behind the syntax it helps to expand the syntax (or macro). The declarative statements above expand to:

   (require (planet untyped/dispatch))
   (begin
     (define-values (blog index review-post review-archive)
       (let-values (((blog controllers)
                     (make-site 'blog '(index review-post review-archive))))
         (let-values (((index review-post review-archive)
                       (apply values controllers)))
           (set-site-rules!
             blog
             (list
               (make-rule (make-pattern "/") index)
               (make-rule (make-pattern "/posts/" (string-arg)) review-post)
               (make-rule
                 (make-pattern "/archive/" (integer-arg) "/" (integer-arg))
                 review-archive)))
           (values blog index review-post review-archive)))))

Effectively defining blog and the controllers index review-post review-archive on module level by the function make-site. This procedure also assigns an undefined-controller to the controllers index review-post review-archive to catch the undefined ones. These syntax-extentions are defined in syntax.ss.

One can use the export-helper in a provide as such (provide (site-out blog)) to export the defined site and the configured controllers.

Rule based routing

There is a function defined (controller-url controller args ..) which can be used to translate back from controller to path.

Rules

Rules like [(url "/posts/" (string-arg)) review-post] are declared when configuring the site. This rule effectively states that /posts/([^/]+) maps to controller review-post. This rule will be used to dispatch this request, but will also be used to translate (controller-url 'review-post 10) to the following link /posts/10.

Arguments

How is this implemented. In the files pattern.ss and arg.ss and struct-private.ss contain statements which describes elements to build reqexp matches and encode and decode arguments between the url and scheme domain. The args and eventually custom args are defined in the following struct. (i removed the display routines for clarity)

(define-struct arg
  (id      ;; symbol to be used declaration 
   pattern ;; regexp pattern
   decoder ;; how to decode from captured arg to lisp domain
   encoder ;; how to encode from lisp domain to captured arg
  #:transparent)

A working example is string-arg:

(define (string-arg)
  (make-arg 
   'string                      ;; id
   "[^/]+"                      ;; regexp
   (lambda (raw)                ;; decode
     (uri-decode raw))
   (lambda (arg)                ;; encode
     (if (string? arg)
         (uri-encode arg)
         (raise-exn exn:fail:dispatch
           (format "Expected string, given: ~s" arg))))))
Adding arguments

Adding cutom arguments is made trivial by this setup since one can just build a structure with encode and decode routines and can use it.

Snooze, ORM for Postgresql or Sqlite

Will come later ..

Mirrors, xhtml/xml generation or javascript. Whatever you want ..

Will come later ..


Fork me on GitHub