[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Chicken-users] Suggestion for new egg: Wings!
Re: [Chicken-users] Suggestion for new egg: Wings!
Fri, 13 Jul 2007 23:55:38 +0100
On 6 Jul 2007, at 1:23 am, Alaric Snell-Pym wrote:
I propose to create an egg called 'wings' to contain general utility
functions for access .wings metadata files (there'll be other uses
for the files in future, so I plan to create a generic framework),
the definition of the wings-environment parameter and utility
procedures for accessing same, utility and support functions for the
data source API using the wings environment, then the aforementioned
page argument handling tools.
Here's a progress report:
- Wings environment works. You can bind stuff into the global
environment, access bindings with (wings:get 'foo), and dynamically
bind things with (wings:let (('foo 'bar)) (wings:get 'foo)).
- Metadata file parsing works. (wings:call-with-metadata "/path/to/
file" thunk) will look for a file called "/path/to/file.wings" (the
extension being configurable with a parameter). If it's not present,
it just calls the thunk.
But if it is present, it loads it and evals it within a quasiquote -
so it's literal data, except you can use , to include bits of live
Scheme code. It's treated as an alist of things to bind into the
Wings environment during the dynamic scope of calling the thunk. If
there's a entry in the alist called 'wrapper' then it's expected to
be a closure, and is applied to the thunk. If not, the thunk is just
The "wrapper" key is meant as a catch-all to control the dynamic
environment of the thunk, where parameters other than the wings
environment can be changed, exceptions handled, etc.
Now, the normal use of wings:call-with-metadata is to wrap Spiffy's
handling of files, so we provide a function called wings:handler-
wrap. If a Spiffy file extension handler is passed to this function,
it returns a new one that wraps invocation of the original handler in
a wings:call-with-metadata. So you can have .wings metadata handling
added to your web-scheme, ssp, cgi, or whatever.
- URL argument parsing is in progress. I've talked with Peter Bex
about how to get Spiffy to support path info so I can have positional
parameters in URLs, and while he's looking into that, I've pressed
ahead with named query-string params.
This is quite cool. I've written it so that it can handle nested
compound argument types.
Here's a sample argument declaration that demonstrates the atomic types:
'((a "a" optional string)
(b "b" symbol)
(c "c" symbol a b c)
(d "d" number)
(e "e" integer)
(f "f" string)
(g "g" boolean)
(h "h" boolean "yeah" "nope")
The symbol at the head of each list is the name the argument will be
bound to; the next element, the string, is the name to expect in the
URL query string. The rest is typing information.
If "optional" is not specified, then failing to provide this argument
signals a condition that will, when I've written the higher-level
driver, cause a 400 Bad Request to be sent back. If "optional" is
specified, as with the "a" argument above, then omitting it causes #f
to be bound to the name.
Declaring something to be of type "string" just means that whatever
the URL contains is passed back, unchanged. Type "symbol" on its own
just passes it through string->symbol, but "symbol a b c" constrains
it to be a symbol from the list (a b c). This is useful for things
like the available options in a <select> form field. Anything outside
of the list signals a condition, again.
"number" causes a string->number, and if it returns #f, signals a
"integer" does the same as "number", but also requires the result to
be an exact integer. This is useful for IDs of things in SQL
"boolean" alone interprets 1, t, yes, and y (case insensitive) as #t
and 0, f, no, and n as #f, and signals a condition on anything else.
Or you can write 'boolean "yeah" "nope"' to provide your own yes/no
strings. Or you can provide lists of yes/no strings: 'boolean ("yeah"
But where we start to leave Rails in the dust is with the compound
The simplest compound type is "set". Set just collects multiple
instances of the argument in the query string and makes a list. If
you declare an argument "x" to be of type "set symbol a b c", for
example, then the parser will interpret a query string such as
x=a&x=c as '(a c) - possibly in some other order. Set arguments are
implicitly optional; no instances of an argument makes for an empty
The next one I got working was "list", which looks for multiple
arguments of the form "<argname>[<index>]" and collects them into a
list, with the integer indices starting at 0. The list is long enough
to fit the highest index, and any missing positions in the list are
filled with #f.
So if you defined an argument "x" as "list symbol" then the parser
would see x=foo&x=bar as '(bar #f foo).
But if you define an argument "x" as "list set symbol" then the
parser will see x=foo&x=bar&x=baz as '((bar) #f (foo baz)) -
it's a list of sets, apart from the #f.
And you can do things like "list list list integer", which will pick
up query strings like "x=1&...".
Now, you can't nest a set inside a set - set can only wrap atomic
types, since set has no ability to distinguish structure in the
arguments it matches, unlike list.
We also have alist, which generalises list somewhat; the indices are
replaced with symbols. x[a]=1&x[b]=2 -> ((a . 1) (b . 2)). Lists and
alists can nest as deeply as you like.
And the best thing is, it seems to work. At least, the low level
procedure that takes an argument type declaration and the alist of
URL query string params as produced by the uri egg satisfactorily
returns an alist binding argument name symbols to their parsed values.
If you're interested, I've comitted what I have so far into svn
(https://galinha.ucpel.tche.br/svn/chicken-eggs/wings/trunk) - no
documentation yet, but the unit tests in tests/run.scm provide useful