factor/extra/furnace/furnace.factor

216 lines
5.7 KiB
Factor

! Copyright (C) 2006, 2008 Slava Pestov, Doug Coleman
! See http://factorcode.org/license.txt for BSD license.
USING: arrays assocs calendar debugger furnace.sessions
furnace.validator hashtables heaps html.elements http
http.server.responders http.server.templating io.files kernel
math namespaces quotations sequences splitting words strings
vectors webapps.callback continuations tuples classes vocabs
html io ;
IN: furnace
: code>quotation ( word/quot -- quot )
dup word? [ 1quotation ] when ;
SYMBOL: default-action
SYMBOL: template-path
: render-template ( template -- )
template-path get swap path+
".furnace" append resource-path
run-template-file ;
: define-action ( word hash -- )
over t "action" set-word-prop
"action-params" set-word-prop ;
: define-form ( word1 word2 hash -- )
dupd define-action
swap code>quotation "form-failed" set-word-prop ;
: default-values ( word hash -- )
"default-values" set-word-prop ;
SYMBOL: request-params
SYMBOL: current-action
SYMBOL: validators-errored
SYMBOL: validation-errors
: action-link ( query action -- url )
[
"/responder/" %
dup word-vocabulary "webapps." ?head drop %
"/" %
word-name %
] "" make swap build-url ;
: action-param ( hash paramsepc -- obj error/f )
unclip rot at swap >quotation apply-validators ;
: query>seq ( hash word -- seq )
"action-params" word-prop [
dup first -rot
action-param [
t validators-errored >session
rot validation-errors session> set-at
] [
nip
] if*
] with map ;
: expire-sessions ( -- )
sessions get-global
[ nip session-last-seen 20 minutes ago <=> 0 > ]
[ 2drop ] heap-pop-while ;
: lookup-session ( hash -- session )
"furnace-session-id" over at sessions get-global at [
nip
] [
new-session rot "furnace-session-id" swap set-at
] if* ;
: quot>query ( seq action -- hash )
>r >array r> "action-params" word-prop
[ first swap 2array ] 2map >hashtable ;
PREDICATE: word action "action" word-prop ;
: action-call? ( quot -- ? )
>vector dup pop action? >r [ word? not ] all? r> and ;
: unclip* dup 1 head* swap peek ;
: quot-link ( quot -- url )
dup action-call? [
unclip* [ quot>query ] keep action-link
] [
t register-html-callback
] if ;
: replace-variables ( quot -- quot )
[ dup string? [ request-params session> at ] when ] map ;
: furnace-session-id ( -- hash )
"furnace-session-id" request-params session> at
"furnace-session-id" associate ;
: redirect-to-action ( -- )
current-action session>
"form-failed" word-prop replace-variables
quot-link furnace-session-id build-url permanent-redirect ;
: if-form-page ( if then -- )
current-action session> "form-failed" word-prop -rot if ;
: do-action
current-action session> [ query>seq ] keep add >quotation call ;
: process-form ( -- )
H{ } clone validation-errors >session
request-params session> current-action session> query>seq
validators-errored session> [
drop redirect-to-action
] [
current-action session> add >quotation call
] if ;
: page-submitted ( -- )
[ process-form ] [ request-params session> do-action ] if-form-page ;
: action-first-time ( -- )
request-params session> current-action session>
[ "default-values" word-prop swap union request-params >session ] keep
request-params session> do-action ;
: page-not-submitted ( -- )
[ redirect-to-action ] [ action-first-time ] if-form-page ;
: setup-call-action ( hash word -- )
over lookup-session session set
current-action >session
request-params session> swap union
request-params >session
f validators-errored >session ;
: call-action ( hash word -- )
setup-call-action
"furnace-form-submitted" request-params session> at
[ page-submitted ] [ page-not-submitted ] if ;
: responder-vocab ( str -- newstr )
"webapps." swap append ;
: lookup-action ( str webapp -- word )
responder-vocab lookup dup [
dup "action" word-prop [ drop f ] unless
] when ;
: truncate-url ( str -- newstr )
CHAR: / over index [ head ] when* ;
: parse-action ( str -- word/f )
dup empty? [ drop default-action get ] when
truncate-url "responder" get lookup-action ;
: service-request ( hash str -- )
parse-action [
[ call-action ] [ <pre> print-error </pre> ] recover
] [
"404 no such action: " "argument" get append httpd-error
] if* ;
: service-get
"query" get swap service-request ;
: service-post
"response" get swap service-request ;
: web-app ( name defaul path -- )
[
template-path set
default-action set
"responder" set
[ service-get ] "get" set
[ service-post ] "post" set
] make-responder ;
: explode-tuple ( tuple -- )
dup tuple-slots swap class "slot-names" word-prop
[ set ] 2each ;
SYMBOL: model
: with-slots ( model quot -- )
[
>r [ dup model set explode-tuple ] when* r> call
] with-scope ;
: render-component ( model template -- )
swap [ render-template ] with-slots ;
: browse-webapp-source ( vocab -- )
<a f >vocab-link browser-link-href =href a>
"Browse source" write
</a> ;
: send-resource ( name -- )
template-path get swap path+ resource-path <file-reader>
stdio get stream-copy ;
: render-link ( quot name -- )
<a swap quot-link =href a> write </a> ;
: session-var ( str -- newstr )
request-params session> at ;
: render ( str -- )
request-params session> at [ write ] when* ;
: render-error ( str error-str -- )
swap validation-errors session> at validation-error? [
write
] [
drop
] if ;