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:
- untyped/dispatch
- untyped/snooze
- untyped/mirrors
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 ..