From 8dfe287e90c0e63f42afe9787d1510f86079f603 Mon Sep 17 00:00:00 2001
From: Bruno Deferrari <utizoc@gmail.com>
Date: Sat, 12 Apr 2008 18:24:31 -0300
Subject: [PATCH 01/66] qualified: fixing docs a bit.

---
 extra/qualified/qualified-docs.factor | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/extra/qualified/qualified-docs.factor b/extra/qualified/qualified-docs.factor
index d336d31114..49ff4e9374 100755
--- a/extra/qualified/qualified-docs.factor
+++ b/extra/qualified/qualified-docs.factor
@@ -15,19 +15,31 @@ HELP: QUALIFIED-WITH:
 
 HELP: FROM:
 { $syntax "FROM: vocab => words ... ;" }
+<<<<<<< HEAD:extra/qualified/qualified-docs.factor
+{ $description "Imports " { $snippet "words" } " from " { $snippet "vocab" } "." }
+=======
 { $description "Imports the specified words from vocab." }
+>>>>>>> 04e914c... qualified: fixing docs a bit.:extra/qualified/qualified-docs.factor
 { $examples { $code
     "FROM: math.parser => bin> hex> ; ! imports only bin> and hex>" } } ;
 
 HELP: EXCLUDE:
 { $syntax "EXCLUDE: vocab => words ... ;" }
+<<<<<<< HEAD:extra/qualified/qualified-docs.factor
+{ $description "Imports everything from " { $snippet "vocab" } " excluding " { $snippet "words" } "." }
+=======
 { $description "Imports everything from vocab excluding the specified words" }
+>>>>>>> 04e914c... qualified: fixing docs a bit.:extra/qualified/qualified-docs.factor
 { $examples { $code
     "EXCLUDE: math.parser => bin> hex> ; ! imports everythin but bin> and hex>" } } ;
 
 HELP: RENAME:
 { $syntax "RENAME: word vocab => newname " }
+<<<<<<< HEAD:extra/qualified/qualified-docs.factor
+{ $description "Imports " { $snippet "word" } " from " { $snippet "vocab" } ", but renamed to " { $snippet "newname" } "." }
+=======
 { $description "Imports word from vocab, but renamed to newname." }
+>>>>>>> 04e914c... qualified: fixing docs a bit.:extra/qualified/qualified-docs.factor
 { $examples { $code
     "RENAME: + math => -"
     "2 3 - ! => 5" } } ;

From 7ef3109ff142c6cb5fa514432d4dbabf4457d4ec Mon Sep 17 00:00:00 2001
From: Bruno Deferrari <utizoc@gmail.com>
Date: Sat, 12 Apr 2008 18:27:04 -0300
Subject: [PATCH 02/66] qualified: another docs fix

---
 extra/qualified/qualified-docs.factor | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/extra/qualified/qualified-docs.factor b/extra/qualified/qualified-docs.factor
index 49ff4e9374..2c707c5bae 100755
--- a/extra/qualified/qualified-docs.factor
+++ b/extra/qualified/qualified-docs.factor
@@ -8,8 +8,8 @@ HELP: QUALIFIED:
     "QUALIFIED: math\n1 2 math:+ ! ==> 3" } } ;
 
 HELP: QUALIFIED-WITH:
-{ $syntax "QUALIFIED-WITH: vocab prefix" }
-{ $description "Works like " { $link POSTPONE: QUALIFIED: } " but uses the specified prefix." }
+{ $syntax "QUALIFIED-WITH: vocab word-prefix" }
+{ $description "Works like " { $link POSTPONE: QUALIFIED: } " but uses " { $snippet "word-prefix" } " as prefix." }
 { $examples { $code
     "QUALIFIED-WITH: math m\n1 2 m:+ ! ==> 3" } } ;
 

From 2f4cec443c229b99457741b36c7ae85331150b42 Mon Sep 17 00:00:00 2001
From: Bruno Deferrari <utizoc@gmail.com>
Date: Sat, 12 Apr 2008 19:09:46 -0300
Subject: [PATCH 03/66] qualified: docs fix

---
 extra/qualified/qualified-docs.factor | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/extra/qualified/qualified-docs.factor b/extra/qualified/qualified-docs.factor
index 2c707c5bae..c5cb088db3 100755
--- a/extra/qualified/qualified-docs.factor
+++ b/extra/qualified/qualified-docs.factor
@@ -15,31 +15,19 @@ HELP: QUALIFIED-WITH:
 
 HELP: FROM:
 { $syntax "FROM: vocab => words ... ;" }
-<<<<<<< HEAD:extra/qualified/qualified-docs.factor
 { $description "Imports " { $snippet "words" } " from " { $snippet "vocab" } "." }
-=======
-{ $description "Imports the specified words from vocab." }
->>>>>>> 04e914c... qualified: fixing docs a bit.:extra/qualified/qualified-docs.factor
 { $examples { $code
     "FROM: math.parser => bin> hex> ; ! imports only bin> and hex>" } } ;
 
 HELP: EXCLUDE:
 { $syntax "EXCLUDE: vocab => words ... ;" }
-<<<<<<< HEAD:extra/qualified/qualified-docs.factor
 { $description "Imports everything from " { $snippet "vocab" } " excluding " { $snippet "words" } "." }
-=======
-{ $description "Imports everything from vocab excluding the specified words" }
->>>>>>> 04e914c... qualified: fixing docs a bit.:extra/qualified/qualified-docs.factor
 { $examples { $code
     "EXCLUDE: math.parser => bin> hex> ; ! imports everythin but bin> and hex>" } } ;
 
 HELP: RENAME:
 { $syntax "RENAME: word vocab => newname " }
-<<<<<<< HEAD:extra/qualified/qualified-docs.factor
 { $description "Imports " { $snippet "word" } " from " { $snippet "vocab" } ", but renamed to " { $snippet "newname" } "." }
-=======
-{ $description "Imports word from vocab, but renamed to newname." }
->>>>>>> 04e914c... qualified: fixing docs a bit.:extra/qualified/qualified-docs.factor
 { $examples { $code
     "RENAME: + math => -"
     "2 3 - ! => 5" } } ;

From a96deb5995a5d67cca1f0ccab2b4372bea71a9da Mon Sep 17 00:00:00 2001
From: Bruno Deferrari <utizoc@gmail.com>
Date: Wed, 7 May 2008 17:15:31 -0300
Subject: [PATCH 04/66] Fix typo

---
 extra/qualified/qualified-docs.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extra/qualified/qualified-docs.factor b/extra/qualified/qualified-docs.factor
index c5cb088db3..d62f696a74 100755
--- a/extra/qualified/qualified-docs.factor
+++ b/extra/qualified/qualified-docs.factor
@@ -23,7 +23,7 @@ HELP: EXCLUDE:
 { $syntax "EXCLUDE: vocab => words ... ;" }
 { $description "Imports everything from " { $snippet "vocab" } " excluding " { $snippet "words" } "." }
 { $examples { $code
-    "EXCLUDE: math.parser => bin> hex> ; ! imports everythin but bin> and hex>" } } ;
+    "EXCLUDE: math.parser => bin> hex> ; ! imports everything but bin> and hex>" } } ;
 
 HELP: RENAME:
 { $syntax "RENAME: word vocab => newname " }

From e8815e7bb2faefea760faccd3462b9f7298f3042 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 22 May 2008 22:41:23 -0500
Subject: [PATCH 05/66] Moving validators to their own vocabulary

---
 extra/validators/validators-tests.factor |  70 +++++++++++
 extra/validators/validators.factor       | 142 +++++++++++++++++++++++
 2 files changed, 212 insertions(+)
 create mode 100644 extra/validators/validators-tests.factor
 create mode 100644 extra/validators/validators.factor

diff --git a/extra/validators/validators-tests.factor b/extra/validators/validators-tests.factor
new file mode 100644
index 0000000000..6ed0e0363a
--- /dev/null
+++ b/extra/validators/validators-tests.factor
@@ -0,0 +1,70 @@
+IN: validators.tests
+USING: kernel sequences tools.test validators accessors ;
+
+[ "foo" v-number ] must-fail
+[ 123 ] [ "123" v-number ] unit-test
+
+[ "slava@factorcode.org" ] [
+    "slava@factorcode.org" v-email
+] unit-test
+
+[ "slava+foo@factorcode.org" ] [
+    "slava+foo@factorcode.org" v-email
+] unit-test
+
+[ "slava@factorcode.o" v-email ]
+[ "invalid e-mail" = ] must-fail-with
+
+[ "sla@@factorcode.o" v-email ]
+[ "invalid e-mail" = ] must-fail-with
+
+[ "slava@factorcodeorg" v-email ]
+[ "invalid e-mail" = ] must-fail-with
+
+[ "http://www.factorcode.org" ]
+[ "http://www.factorcode.org" v-url ] unit-test
+
+[ "http:/www.factorcode.org" v-url ]
+[ "invalid URL" = ] must-fail-with
+
+[ 14 V{ } ] [
+    [
+        "14" "age" [ drop v-number 13 v-min-value 100 v-max-value ] validate
+    ] with-validation
+] unit-test
+
+[ f t ] [
+    [
+        "140" "age" [ drop v-number 13 v-min-value 100 v-max-value ] validate
+    ] with-validation first
+    [ first "age" = ]
+    [ second validation-error? ]
+    [ second value>> "140" = ]
+    tri and and
+] unit-test
+
+TUPLE: person name age ;
+
+person {
+    { "name" [ v-required ] }
+    { "age" [ v-number 13 v-min-value 100 v-max-value ] }
+} define-validators
+
+[ 14 V{ } ] [
+    [
+        person new dup
+        { { "age" "14" } }
+        deposit-slots
+        age>>
+    ] with-validation
+] unit-test
+
+[ t ] [
+    [
+        { { "age" "" } } required-values
+    ] with-validation first
+    [ first "age" = ]
+    [ second validation-error? ]
+    [ second message>> "required" = ]
+    tri and and
+] unit-test
diff --git a/extra/validators/validators.factor b/extra/validators/validators.factor
new file mode 100644
index 0000000000..23bda8cb6c
--- /dev/null
+++ b/extra/validators/validators.factor
@@ -0,0 +1,142 @@
+! Copyright (C) 2006, 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
+USING: kernel continuations sequences math namespaces sets
+math.parser assocs regexp fry unicode.categories sequences
+arrays hashtables words combinators mirrors classes quotations ;
+IN: validators
+
+: v-default ( str def -- str )
+    over empty? spin ? ;
+
+: v-required ( str -- str )
+    dup empty? [ "required" throw ] when ;
+
+: v-optional ( str quot -- str )
+    over empty? [ 2drop f ] [ call ] if ; inline
+
+: v-min-length ( str n -- str )
+    over length over < [
+        [ "must be at least " % # " characters" % ] "" make
+        throw
+    ] [
+        drop
+    ] if ;
+
+: v-max-length ( str n -- str )
+    over length over > [
+        [ "must be no more than " % # " characters" % ] "" make
+        throw
+    ] [
+        drop
+    ] if ;
+
+: v-number ( str -- n )
+    dup string>number [ ] [ "must be a number" throw ] ?if ;
+
+: v-integer ( n -- n )
+    dup integer? [ "must be an integer" throw ] unless ;
+
+: v-min-value ( x n -- x )
+    2dup < [
+        [ "must be at least " % # ] "" make throw
+    ] [
+        drop
+    ] if ;
+
+: v-max-value ( x n -- x )
+    2dup > [
+        [ "must be no more than " % # ] "" make throw
+    ] [
+        drop
+    ] if ;
+
+: v-regexp ( str what regexp -- str )
+    >r over r> matches?
+    [ drop ] [ "invalid " prepend throw ] if ;
+
+: v-email ( str -- str )
+    #! From http://www.regular-expressions.info/email.html
+    "e-mail"
+    R' [A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'i
+    v-regexp ;
+
+: v-url ( str -- str )
+    "URL"
+    R' (ftp|http|https)://(\w+:?\w*@)?(\S+)(:[0-9]+)?(/|/([\w#!:.?+=&%@!\-/]))?'
+    v-regexp ;
+
+: v-captcha ( str -- str )
+    dup empty? [ "must remain blank" throw ] unless ;
+
+: v-one-line ( str -- str )
+    dup "\r\n" intersect empty?
+    [ "must be a single line" throw ] unless ;
+
+: v-one-word ( str -- str )
+    dup [ alpha? ] all?
+    [ "must be a single word" throw ] unless ;
+
+SYMBOL: validation-messages
+
+: with-validation ( quot -- messages )
+    V{ } clone [
+        validation-messages rot with-variable
+    ] keep ; inline
+
+: (validation-message) ( obj -- )
+    validation-messages get push ;
+
+: (validation-message-for) ( obj name -- )
+    swap 2array (validation-message) ;
+
+TUPLE: validation-message message ;
+
+C: <validation-message> validation-message
+
+: validation-message ( string -- )
+    <validation-message> (validation-message) ;
+
+: validation-message-for ( string name -- )
+    [ <validation-message> ] dip (validation-message-for) ;
+
+TUPLE: validation-error value message ;
+
+C: <validation-error> validation-error
+
+: validation-error ( reason -- )
+    f <validation-error> (validation-message) ;
+
+: validation-error-for ( reason value name -- )
+    [ <validation-error> ] dip (validation-message-for) ;
+
+: validation-failed? ( -- ? )
+    validation-messages get [
+        dup pair? [ second ] when validation-error?
+    ] contains? ;
+
+: define-validators ( class validators -- )
+    >hashtable "validators" set-word-prop ;
+
+: validate ( value name quot -- result )
+    [ swap validation-error-for f ] recover ; inline
+
+: validate-value ( value name validators -- result )
+    '[
+        , at {
+            { [ dup pair? ] [ first ] }
+            { [ dup quotation? ] [ ] }
+        } cond call
+    ] validate ;
+
+: required-values ( assoc -- )
+    [ swap [ drop v-required ] validate drop ] assoc-each ;
+
+: validate-values ( assoc validators -- assoc' )
+    '[ over , validate-value ] assoc-map ;
+
+: deposit-values ( destination assoc validators -- )
+    validate-values update ;
+
+: deposit-slots ( tuple assoc -- )
+    [ [ <mirror> ] [ class "validators" word-prop ] bi ] dip
+    swap deposit-values ;

From 3ee56c3a68eea3570d0cb0cb61df30ff84ba2831 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 22 May 2008 22:41:48 -0500
Subject: [PATCH 06/66] Add extract-keys word

---
 core/assocs/assocs-tests.factor                    | 14 ++++++++++++++
 core/assocs/assocs.factor                          |  3 +++
 .../standard/engines/predicate/predicate.factor    |  3 +--
 core/inference/class/class.factor                  | 12 ++++++------
 4 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/core/assocs/assocs-tests.factor b/core/assocs/assocs-tests.factor
index 30f2ec23c4..43a1bac82d 100755
--- a/core/assocs/assocs-tests.factor
+++ b/core/assocs/assocs-tests.factor
@@ -104,3 +104,17 @@ unit-test
         2drop
     ] { } make
 ] unit-test
+
+[
+    H{
+        { "bangers" "mash" }
+        { "fries" "onion rings" }
+    }
+] [
+    { "bangers" "fries" } H{
+        { "fish" "chips" }
+        { "bangers" "mash" }
+        { "fries" "onion rings" }
+        { "nachos" "cheese" }
+    } extract-keys
+] unit-test
diff --git a/core/assocs/assocs.factor b/core/assocs/assocs.factor
index 92db38573a..6b0798f2e3 100755
--- a/core/assocs/assocs.factor
+++ b/core/assocs/assocs.factor
@@ -150,6 +150,9 @@ M: assoc assoc-clone-like ( assoc exemplar -- newassoc )
 : map>assoc ( seq quot exemplar -- assoc )
     >r [ 2array ] compose { } map-as r> assoc-like ; inline
 
+: extract-keys ( seq assoc -- subassoc )
+    [ [ dupd at ] curry ] keep map>assoc ;
+
 M: assoc >alist [ 2array ] { } assoc>map ;
 
 : value-at ( value assoc -- key/f )
diff --git a/core/generic/standard/engines/predicate/predicate.factor b/core/generic/standard/engines/predicate/predicate.factor
index b1bfc659df..9c810592a0 100644
--- a/core/generic/standard/engines/predicate/predicate.factor
+++ b/core/generic/standard/engines/predicate/predicate.factor
@@ -22,8 +22,7 @@ C: <predicate-dispatch-engine> predicate-dispatch-engine
     } cond ;
 
 : sort-methods ( assoc -- assoc' )
-    [ keys sort-classes ]
-    [ [ dupd at ] curry ] bi { } map>assoc ;
+    >alist [ keys sort-classes ] keep extract-keys ;
 
 M: predicate-dispatch-engine engine>quot
     methods>> clone
diff --git a/core/inference/class/class.factor b/core/inference/class/class.factor
index 933710aaca..dc632425fe 100755
--- a/core/inference/class/class.factor
+++ b/core/inference/class/class.factor
@@ -152,16 +152,16 @@ M: pair apply-constraint
 M: pair constraint-satisfied?
     first constraint-satisfied? ;
 
-: extract-keys ( seq assoc -- newassoc )
-    [ dupd at ] curry H{ } map>assoc [ nip ] assoc-filter f assoc-like ;
+: valid-keys ( seq assoc -- newassoc )
+    extract-keys [ nip ] assoc-filter f assoc-like ;
 
 : annotate-node ( node -- )
     #! Annotate the node with the currently-inferred set of
     #! value classes.
     dup node-values {
-        [ value-intervals get extract-keys >>intervals ]
-        [ value-classes   get extract-keys >>classes   ]
-        [ value-literals  get extract-keys >>literals  ]
+        [ value-intervals get valid-keys >>intervals ]
+        [ value-classes   get valid-keys >>classes   ]
+        [ value-literals  get valid-keys >>literals  ]
         [ 2drop ]
     } cleave ;
 
@@ -330,7 +330,7 @@ M: #return infer-classes-around
             [ ] [ node-input-classes ] [ in-d>> [ value-class* ] map ] tri
             classes= not [
                 fixed-point? off
-                [ in-d>> value-classes get extract-keys ] keep
+                [ in-d>> value-classes get valid-keys ] keep
                 set-node-classes
             ] [ drop ] if
         ] [ call-next-method ] if

From f693c69c406a96ef03c33b821be9805d3bc51179 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 17:33:31 -0500
Subject: [PATCH 07/66] Move HTML components to html.components, refactor

---
 extra/html/components/components-tests.factor | 145 +++++++++++++++++
 extra/html/components/components.factor       | 150 ++++++++++++++++++
 extra/html/elements/elements-tests.factor     |   7 +-
 extra/html/elements/elements.factor           |  66 +++++---
 extra/html/{ => streams}/authors.txt          |   0
 .../streams-tests.factor}                     |   7 +-
 .../{html.factor => streams/streams.factor}   |  73 +--------
 extra/html/{ => streams}/summary.txt          |   0
 extra/html/{ => streams}/tags.txt             |   0
 extra/html/stylesheet.css                     |   4 -
 extra/validators/validators.factor            |   1 +
 11 files changed, 345 insertions(+), 108 deletions(-)
 create mode 100644 extra/html/components/components-tests.factor
 create mode 100644 extra/html/components/components.factor
 rename extra/html/{ => streams}/authors.txt (100%)
 rename extra/html/{html-tests.factor => streams/streams-tests.factor} (89%)
 rename extra/html/{html.factor => streams/streams.factor} (71%)
 rename extra/html/{ => streams}/summary.txt (100%)
 rename extra/html/{ => streams}/tags.txt (100%)
 delete mode 100644 extra/html/stylesheet.css

diff --git a/extra/html/components/components-tests.factor b/extra/html/components/components-tests.factor
new file mode 100644
index 0000000000..6ecd2b0fa6
--- /dev/null
+++ b/extra/html/components/components-tests.factor
@@ -0,0 +1,145 @@
+IN: html.components.tests
+USING: html.components tools.test kernel io.streams.string
+io.streams.null accessors ;
+
+[ ] [ blank-values ] unit-test
+
+[ ] [ 3 "hi" set-value ] unit-test
+
+[ 3 ] [ "hi" value ] unit-test
+
+TUPLE: color red green blue ;
+
+[ ] [ 1 2 3 color boa from-tuple ] unit-test
+
+[ 1 ] [ "red" value ] unit-test
+
+[ ] [ "jimmy" "red" set-value ] unit-test
+
+[ "123.5" ] [ 123.5 object>string ] unit-test
+
+[ "jimmy" ] [
+    [
+        "red" label render
+    ] with-string-writer
+] unit-test
+
+[ ] [ "<jimmy>" "red" set-value ] unit-test
+
+[ "&lt;jimmy&gt;" ] [
+    [
+        "red" label render
+    ] with-string-writer
+] unit-test
+
+[ "<input type='hidden' name='red' value='<jimmy>'/>" ] [
+    [
+        "red" hidden render
+    ] with-string-writer
+] unit-test
+
+[ ] [ "'jimmy'" "red" set-value ] unit-test
+
+[ "<input type='text' size='5' name='red' value='&apos;jimmy&apos;'/>" ] [
+    [
+        "red" <field> 5 >>size render
+    ] with-string-writer
+] unit-test
+
+[ "<input type='password' size='5' name='red' value=''/>" ] [
+    [
+        "red" <password> 5 >>size render
+    ] with-string-writer
+] unit-test
+
+[ ] [
+    [
+        "green" <textarea> render
+    ] with-null-writer
+] unit-test
+
+[ ] [
+    [
+        "green" <textarea> 25 >>rows 30 >>columns render
+    ] with-null-writer
+] unit-test
+
+[ ] [ blank-values ] unit-test
+
+[ ] [ "new york" "city1" set-value ] unit-test
+
+[ ] [
+    [
+        "city1"
+        <choice>
+            { "new york" "los angeles" "chicago" } >>choices
+        render
+    ] with-null-writer
+] unit-test
+
+[ ] [ { "los angeles" "new york" } "city2" set-value ] unit-test
+
+[ ] [
+    [
+        "city2"
+        <choice>
+            { "new york" "los angeles" "chicago" } >>choices
+            t >>multiple
+        render
+    ] with-null-writer
+] unit-test
+
+[ ] [
+    [
+        "city2"
+        <choice>
+            { "new york" "los angeles" "chicago" } >>choices
+            t >>multiple
+            5 >>size
+        render
+    ] with-null-writer
+] unit-test
+
+[ ] [ blank-values ] unit-test
+
+[ ] [ t "delivery" set-value ] unit-test
+
+[ "<input type='checkbox' name='delivery' selected='true'>Delivery</input>" ] [
+    [
+        "delivery"
+        <checkbox>
+            "Delivery" >>label
+        render
+    ] with-string-writer
+] unit-test
+
+[ ] [ f "delivery" set-value ] unit-test
+
+[ "<input type='checkbox' name='delivery'>Delivery</input>" ] [
+    [
+        "delivery"
+        <checkbox>
+            "Delivery" >>label
+        render
+    ] with-string-writer
+] unit-test
+
+SINGLETON: link-test
+
+M: link-test link-title drop "<Link Title>" ;
+
+M: link-test link-href drop "http://www.apple.com/foo&bar" ;
+
+[ ] [ link-test "link" set-value ] unit-test
+
+[ "<a href='http://www.apple.com/foo&amp;bar'>&lt;Link Title&gt;</a>" ] [
+    [ "link" link render ] with-string-writer
+] unit-test
+
+[ ] [
+    "<html>arbitrary <b>markup</b> for the win!</html>" "html" set-value
+] unit-test
+
+[ "<html>arbitrary <b>markup</b> for the win!</html>" ] [
+    [ "html" html render ] with-string-writer
+] unit-test
diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
new file mode 100644
index 0000000000..ef4def8ddb
--- /dev/null
+++ b/extra/html/components/components.factor
@@ -0,0 +1,150 @@
+! Copyright (C) 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
+USING: accessors kernel namespaces io math.parser assocs classes
+classes.tuple words arrays sequences splitting mirrors
+hashtables combinators continuations math strings
+fry locals calendar calendar.format xml.entities validators
+html.elements ;
+IN: html.components
+
+SYMBOL: values
+
+: value values get at ;
+
+: set-value values get set-at ;
+
+: blank-values H{ } clone values set ;
+
+: from-tuple <mirror> values set ;
+
+: values-tuple values get object>> ;
+
+: object>string ( object -- string )
+    {
+        { [ dup real? ] [ number>string ] }
+        { [ dup timestamp? ] [ timestamp>string ] }
+        { [ dup string? ] [ ] }
+        { [ dup not ] [ drop "" ] }
+    } cond ;
+
+GENERIC: render* ( value name render -- )
+
+: render ( name renderer -- )
+    over validation-messages get at [
+        [ value>> ] [ message>> ] bi
+        [ -rot render* ] dip
+        render-error
+    ] [
+        [ [ value ] keep ] dip render*
+    ] if* ;
+
+<PRIVATE
+
+: render-input ( value name type -- )
+    <input =type =name object>string =value input/> ;
+
+PRIVATE>
+
+SINGLETON: label
+
+M: label render* 2drop object>string escape-string write ;
+
+SINGLETON: hidden
+
+M: hidden render* drop "hidden" render-input ;
+
+: render-field ( value name size type -- )
+    <input
+        =type
+        [ number>string =size ] when*
+        =name
+        object>string =value
+    input/> ;
+
+TUPLE: field size ;
+
+: <field> ( -- field )
+    field new ;
+
+M: field render* size>> "text" render-field ;
+
+TUPLE: password size ;
+
+: <password> ( -- password )
+    password new ;
+
+M: password render*
+    #! Don't send passwords back to the user
+    [ drop "" ] 2dip size>> "password" render-field ;
+
+! Text areas
+TUPLE: textarea rows columns ;
+
+: <textarea> ( -- renderer )
+    textarea new ;
+
+M: textarea render*
+    <textarea
+        [ rows>> [ number>string =rows ] when* ]
+        [ columns>> [ number>string =cols ] when* ] bi
+        =name
+    textarea>
+        object>string escape-string write
+    </textarea> ;
+
+! Choice
+TUPLE: choice size choices multiple ;
+
+: <choice> ( -- choice )
+    choice new ;
+
+: render-option ( text selected? -- )
+    <option [ "true" =selected ] when option>
+        escape-string write
+    </option> ;
+
+: render-options ( options selected -- )
+    '[ dup , member? render-option ] each ;
+
+M: choice render*
+    <select
+        swap =name
+        dup size>> [ number>string =size ] when*
+        dup multiple>> [ "true" =multiple ] when
+    select>
+        [ choices>> ] [ multiple>> ] bi
+        [ swap ] [ swap 1array ] if
+        render-options
+    </select> ;
+
+! Checkboxes
+TUPLE: checkbox label ;
+
+: <checkbox> ( -- checkbox )
+    checkbox new ;
+
+M: checkbox render*
+    <input
+        "checkbox" =type
+        swap =name
+        swap [ "true" =selected ] when
+    input>
+        label>> escape-string write
+    </input> ;
+
+! Link components
+GENERIC: link-title ( obj -- string )
+GENERIC: link-href ( obj -- url )
+
+SINGLETON: link
+
+M: link render*
+    2drop
+    <a dup link-href =href a>
+        link-title object>string escape-string write
+    </a> ;
+
+! HTML component
+SINGLETON: html
+
+M: html render* 2drop write ;
diff --git a/extra/html/elements/elements-tests.factor b/extra/html/elements/elements-tests.factor
index aa6a017540..1178deab38 100644
--- a/extra/html/elements/elements-tests.factor
+++ b/extra/html/elements/elements-tests.factor
@@ -1,8 +1,5 @@
 IN: html.elements.tests
-USING: tools.test html html.elements io.streams.string ;
-
-: make-html-string
-    [ with-html-stream ] with-string-writer ;
+USING: tools.test html.elements io.streams.string ;
 
 [ "<a href='h&amp;o'>" ]
-[ [ <a "h&o" =href a> ] make-html-string ] unit-test
+[ [ <a "h&o" =href a> ] with-string-writer ] unit-test
diff --git a/extra/html/elements/elements.factor b/extra/html/elements/elements.factor
index 49782fa305..e5377cedf8 100644
--- a/extra/html/elements/elements.factor
+++ b/extra/html/elements/elements.factor
@@ -57,6 +57,8 @@ SYMBOL: html
 : print-html ( str -- )
     write-html "\n" write-html ;
 
+<<
+
 : html-word ( name def effect -- )
     #! Define 'word creating' word to allow
     #! dynamically creating words.
@@ -137,30 +139,46 @@ SYMBOL: html
     dup "=" prepend swap
     [ write-attr ] curry attribute-effect html-word ;
 
+! Define some closed HTML tags
 [
-    ! Define some closed HTML tags
-    [
-        "h1" "h2" "h3" "h4" "h5" "h6" "h7" "h8" "h9"
-        "ol" "li" "form" "a" "p" "html" "head" "body" "title"
-        "b" "i" "ul" "table" "tbody" "tr" "td" "th" "pre" "textarea"
-        "script" "div" "span" "select" "option" "style" "input"
-    ] [ define-closed-html-word ] each
+    "h1" "h2" "h3" "h4" "h5" "h6" "h7" "h8" "h9"
+    "ol" "li" "form" "a" "p" "html" "head" "body" "title"
+    "b" "i" "ul" "table" "tbody" "tr" "td" "th" "pre" "textarea"
+    "script" "div" "span" "select" "option" "style" "input"
+] [ define-closed-html-word ] each
 
-    ! Define some open HTML tags
-    [
-        "input"
-        "br"
-        "link"
-        "img"
-    ] [ define-open-html-word ] each
+! Define some open HTML tags
+[
+    "input"
+    "br"
+    "link"
+    "img"
+] [ define-open-html-word ] each
 
-    ! Define some attributes
-    [
-        "method" "action" "type" "value" "name"
-        "size" "href" "class" "border" "rows" "cols"
-        "id" "onclick" "style" "valign" "accesskey"
-        "src" "language" "colspan" "onchange" "rel"
-        "width" "selected" "onsubmit" "xmlns" "lang" "xml:lang"
-        "media" "title" "multiple"
-    ] [ define-attribute-word ] each
-] with-compilation-unit
+! Define some attributes
+[
+    "method" "action" "type" "value" "name"
+    "size" "href" "class" "border" "rows" "cols"
+    "id" "onclick" "style" "valign" "accesskey"
+    "src" "language" "colspan" "onchange" "rel"
+    "width" "selected" "onsubmit" "xmlns" "lang" "xml:lang"
+    "media" "title" "multiple"
+] [ define-attribute-word ] each
+
+>>
+
+: xhtml-preamble ( -- )
+    "<?xml version=\"1.0\"?>" write-html
+    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" write-html ;
+
+: simple-page ( title quot -- )
+    #! Call the quotation, with all output going to the
+    #! body of an html page with the given title.
+    xhtml-preamble
+    <html "http://www.w3.org/1999/xhtml" =xmlns "en" =xml:lang "en" =lang html>
+        <head> <title> swap write </title> </head>
+        <body> call </body>
+    </html> ;
+
+: render-error ( message -- )
+    <span "error" =class span> escape-string write </span> ;
diff --git a/extra/html/authors.txt b/extra/html/streams/authors.txt
similarity index 100%
rename from extra/html/authors.txt
rename to extra/html/streams/authors.txt
diff --git a/extra/html/html-tests.factor b/extra/html/streams/streams-tests.factor
similarity index 89%
rename from extra/html/html-tests.factor
rename to extra/html/streams/streams-tests.factor
index 9f1ce6b689..2084c7db18 100644
--- a/extra/html/html-tests.factor
+++ b/extra/html/streams/streams-tests.factor
@@ -1,6 +1,7 @@
-USING: html http io io.streams.string io.styles kernel
-namespaces tools.test xml.writer sbufs sequences html.private ;
-IN: html.tests
+USING: html.streams html.streams.private
+io io.streams.string io.styles kernel
+namespaces tools.test xml.writer sbufs sequences ;
+IN: html.streams.tests
 
 : make-html-string
     [ with-html-stream ] with-string-writer ; inline
diff --git a/extra/html/html.factor b/extra/html/streams/streams.factor
similarity index 71%
rename from extra/html/html.factor
rename to extra/html/streams/streams.factor
index 71862b0d01..b35f383bdc 100755
--- a/extra/html/html.factor
+++ b/extra/html/streams/streams.factor
@@ -4,7 +4,7 @@ USING: generic assocs help http io io.styles io.files continuations
 io.streams.string kernel math math.order math.parser namespaces
 quotations assocs sequences strings words html.elements
 xml.entities sbufs continuations destructors ;
-IN: html
+IN: html.streams
 
 GENERIC: browser-link-href ( presented -- href )
 
@@ -192,76 +192,5 @@ M: html-stream make-cell-stream ( style stream -- stream' )
 M: html-stream stream-nl ( stream -- )
     dup test-last-div? [ drop ] [ [ <br/> ] with-output-stream* ] if ;
 
-! Utilities
 : with-html-stream ( quot -- )
     output-stream get <html-stream> swap with-output-stream* ; inline
-
-: xhtml-preamble
-    "<?xml version=\"1.0\"?>" write-html
-    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" write-html ;
-
-: html-document ( body-quot head-quot -- )
-    #! head-quot is called to produce output to go
-    #! in the html head portion of the document.
-    #! body-quot is called to produce output to go
-    #! in the html body portion of the document.
-    xhtml-preamble
-    <html "http://www.w3.org/1999/xhtml" =xmlns "en" =xml:lang "en" =lang html>
-        <head> call </head>
-        <body> call </body>
-    </html> ;
-
-: default-css ( -- )
-    <link
-    "stylesheet" =rel "text/css" =type
-    "/responder/resources/extra/html/stylesheet.css" =href
-    link/> ;
-
-: simple-html-document ( title quot -- )
-    swap [
-        <title> write </title>
-        default-css
-    ] html-document ;
-
-: vertical-layout ( list -- )
-    #! Given a list of HTML components, arrange them vertically.
-    <table>
-    [ <tr> <td> call </td> </tr> ] each
-    </table> ;
-
-: horizontal-layout ( list -- )
-    #! Given a list of HTML components, arrange them horizontally.
-    <table>
-     <tr "top" =valign tr> [ <td> call </td> ] each </tr>
-    </table> ;
-
-: button ( label -- )
-    #! Output an HTML submit button with the given label.
-    <input "submit" =type =value input/> ;
-
-: paragraph ( str -- )
-    #! Output the string as an html paragraph
-    <p> write </p> ;
-
-: simple-page ( title quot -- )
-    #! Call the quotation, with all output going to the
-    #! body of an html page with the given title.
-    <html>
-        <head> <title> swap write </title> </head>
-        <body> call </body>
-    </html> ;
-
-: styled-page ( title stylesheet-quot quot -- )
-    #! Call the quotation, with all output going to the
-    #! body of an html page with the given title. stylesheet-quot
-    #! is called to generate the required stylesheet.
-    <html>
-        <head>
-             <title> rot write </title>
-             swap call
-        </head>
-        <body> call </body>
-    </html> ;
-
-: render-error ( message -- )
-    <span "error" =class span> escape-string write </span> ;
diff --git a/extra/html/summary.txt b/extra/html/streams/summary.txt
similarity index 100%
rename from extra/html/summary.txt
rename to extra/html/streams/summary.txt
diff --git a/extra/html/tags.txt b/extra/html/streams/tags.txt
similarity index 100%
rename from extra/html/tags.txt
rename to extra/html/streams/tags.txt
diff --git a/extra/html/stylesheet.css b/extra/html/stylesheet.css
deleted file mode 100644
index a1afce7c9f..0000000000
--- a/extra/html/stylesheet.css
+++ /dev/null
@@ -1,4 +0,0 @@
-a:link { text-decoration: none; color: black; }
-a:visited { text-decoration: none; color: black; }
-a:active { text-decoration: none; color: black; }
-a:hover { text-decoration: underline; color: black; }
diff --git a/extra/validators/validators.factor b/extra/validators/validators.factor
index 23bda8cb6c..9d6c4bed90 100644
--- a/extra/validators/validators.factor
+++ b/extra/validators/validators.factor
@@ -56,6 +56,7 @@ IN: validators
 
 : v-email ( str -- str )
     #! From http://www.regular-expressions.info/email.html
+    60 v-max-length
     "e-mail"
     R' [A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'i
     v-regexp ;

From a251556024462da9e36135558f256649c4102b75 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 17:33:57 -0500
Subject: [PATCH 08/66] Add failing unit tests

---
 core/classes/classes-tests.factor | 9 +++++++++
 core/parser/parser-tests.factor   | 7 +++++++
 2 files changed, 16 insertions(+)

diff --git a/core/classes/classes-tests.factor b/core/classes/classes-tests.factor
index bb9fbd0167..8d20da78b5 100755
--- a/core/classes/classes-tests.factor
+++ b/core/classes/classes-tests.factor
@@ -160,3 +160,12 @@ TUPLE: flat-mx-2-1 ; INSTANCE: flat-mx-2-1 flat-mx-2
 [ t ] [ 3 number instance? ] unit-test
 [ f ] [ 3 null instance? ] unit-test
 [ t ] [ "hi" \ hi-tag instance? ] unit-test
+
+! Regression
+GENERIC: method-forget-test
+TUPLE: method-forget-class ;
+M: method-forget-class method-forget-test ;
+
+[ f ] [ \ method-forget-test "methods" assoc-empty? ] unit-test
+[ ] [ [ \ method-forget-class forget ] with-compilation-unit ] unit-test
+[ t ] [ \ method-forget-test "methods" assoc-empty? ] unit-test
diff --git a/core/parser/parser-tests.factor b/core/parser/parser-tests.factor
index 3df9dc9cb2..6f31b0ad7c 100755
--- a/core/parser/parser-tests.factor
+++ b/core/parser/parser-tests.factor
@@ -460,3 +460,10 @@ must-fail-with
     "change-combination" "parser.tests" lookup
     "methods" word-prop assoc-size
 ] unit-test
+
+[ [ ] ] [
+    2 [
+        "IN: classes.tests DEFER: twice-fails FORGET: twice-fails MIXIN: twice-fails"
+        <string-reader> "twice-fails-test" parse-stream
+    ] times
+] unit-test

From cee6ab6770f6aa90dbb085ba08c21e1139a0a8cf Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 17:45:00 -0500
Subject: [PATCH 09/66] Add unit test to prevent future screwups

---
 core/prettyprint/prettyprint-tests.factor | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/core/prettyprint/prettyprint-tests.factor b/core/prettyprint/prettyprint-tests.factor
index ed6b2f3c3c..f5ec263f11 100755
--- a/core/prettyprint/prettyprint-tests.factor
+++ b/core/prettyprint/prettyprint-tests.factor
@@ -342,3 +342,5 @@ INTERSECTION: intersection-see-test sequence number ;
 
 [ ] [ \ compose see ] unit-test
 [ ] [ \ curry see ] unit-test
+
+[ "POSTPONE: [" ] [ \ [ unparse ] unit-test

From 3a6532a9f89e91a04389d885119de255780cc4f9 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 17:45:14 -0500
Subject: [PATCH 10/66] Update html.streams usages

---
 extra/farkup/farkup-tests.factor       |  2 +-
 extra/farkup/farkup.factor             | 10 ++++------
 extra/xmode/code2html/code2html.factor |  8 ++++----
 3 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/extra/farkup/farkup-tests.factor b/extra/farkup/farkup-tests.factor
index 7176486f8e..4d235a054c 100755
--- a/extra/farkup/farkup-tests.factor
+++ b/extra/farkup/farkup-tests.factor
@@ -54,7 +54,7 @@ IN: farkup.tests
 [ "<p>=</p><h2>foo</h2>" ] [ "===foo==" convert-farkup ] unit-test
 [ "<h1>foo</h1><p>=</p>" ] [ "=foo==" convert-farkup ] unit-test
 
-[ "<div style='white-space: pre; font-family: monospace; '><span class='KEYWORD3'>int</span> <span class='FUNCTION'>main</span><span class='OPERATOR'>(</span><span class='OPERATOR'>)</span><br/></div>" ]
+[ "<pre><span class='KEYWORD3'>int</span> <span class='FUNCTION'>main</span><span class='OPERATOR'>(</span><span class='OPERATOR'>)</span>\n</pre>" ]
 [ "[c{int main()}]" convert-farkup ] unit-test
 
 [ "<p><img src=\"lol.jpg\"/></p>" ] [ "[[image:lol.jpg]]" convert-farkup ] unit-test
diff --git a/extra/farkup/farkup.factor b/extra/farkup/farkup.factor
index 15b7b4b72c..860fdba3af 100755
--- a/extra/farkup/farkup.factor
+++ b/extra/farkup/farkup.factor
@@ -2,7 +2,7 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: arrays io io.styles kernel memoize namespaces peg
 sequences strings html.elements xml.entities xmode.code2html
-splitting io.streams.string html peg.parsers html.elements
+splitting io.streams.string peg.parsers
 sequences.deep unicode.categories ;
 IN: farkup
 
@@ -56,11 +56,9 @@ MEMO: eq ( -- parser )
 : render-code ( string mode -- string' )
     >r string-lines r>
     [
-        [
-            H{ { wrap-margin f } } [
-                htmlize-lines
-            ] with-nesting
-        ] with-html-stream
+        <pre>
+            htmlize-lines
+        </pre>
     ] with-string-writer ;
 
 : check-url ( href -- href' )
diff --git a/extra/xmode/code2html/code2html.factor b/extra/xmode/code2html/code2html.factor
index 3977f4277c..a9384ad861 100755
--- a/extra/xmode/code2html/code2html.factor
+++ b/extra/xmode/code2html/code2html.factor
@@ -1,12 +1,12 @@
 USING: xmode.tokens xmode.marker xmode.catalog kernel html
 html.elements io io.files sequences words io.encodings.utf8
-namespaces ;
+namespaces xml.entities ;
 IN: xmode.code2html
 
 : htmlize-tokens ( tokens -- )
     [
         dup token-str swap token-id [
-            <span word-name =class span> write </span>
+            <span word-name =class span> escape-string write </span>
         ] [
             write
         ] if*
@@ -21,7 +21,7 @@ IN: xmode.code2html
 : default-stylesheet ( -- )
     <style>
         "resource:extra/xmode/code2html/stylesheet.css"
-        utf8 file-contents write
+        utf8 file-contents escape-string write
     </style> ;
 
 : htmlize-stream ( path stream -- )
@@ -29,7 +29,7 @@ IN: xmode.code2html
     <html>
         <head>
             default-stylesheet
-            <title> dup write </title>
+            <title> dup escape-string write </title>
         </head>
         <body>
             <pre>

From 23c0d0fc9324f91e449d096429248fcb432dce5b Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 17:45:33 -0500
Subject: [PATCH 11/66] Another html.streasm usage

---
 extra/http/server/server.factor | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/extra/http/server/server.factor b/extra/http/server/server.factor
index 4e561220f9..c1684c4ed2 100755
--- a/extra/http/server/server.factor
+++ b/extra/http/server/server.factor
@@ -1,10 +1,10 @@
 ! Copyright (C) 2003, 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: assocs kernel namespaces io io.timeouts strings splitting
-threads sequences prettyprint io.server logging calendar
-http html html.elements accessors math.parser combinators.lib
+threads sequences prettyprint io.server logging calendar http
+html.streams html.elements accessors math.parser combinators.lib
 tools.vocabs debugger continuations random combinators
-destructors io.encodings.8-bit fry classes words ;
+destructors io.encodings.8-bit fry classes words math ;
 IN: http.server
 
 ! path is a sequence of path component strings
@@ -274,9 +274,11 @@ SYMBOL: exit-continuation
     ] with-destructors ;
 
 : httpd ( port -- )
-    internet-server "http.server"
-    latin1 [ handle-client ] with-server ;
+    dup integer? [ internet-server ] when
+    "http.server" latin1
+    [ handle-client ] with-server ;
 
-: httpd-main ( -- ) 8888 httpd ;
+: httpd-main ( -- )
+    8888 httpd ;
 
 MAIN: httpd-main

From 8327449a65a95f95139cf3bd8eeeeddf637e1a72 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 19:16:21 -0500
Subject: [PATCH 12/66] Move templates to html vocabulary

---
 extra/html/components/components-tests.factor | 10 ++-
 extra/html/components/components.factor       |  8 +-
 .../templates}/chloe/chloe-tests.factor       | 10 +--
 .../templates}/chloe/chloe.factor             | 78 ++++++++++-------
 .../templates}/chloe/test/test1.xml           |  0
 .../templates}/chloe/test/test2.xml           |  0
 .../templates}/chloe/test/test3-aux.xml       |  0
 .../templates}/chloe/test/test3.xml           |  0
 .../templates}/chloe/test/test4.xml           |  2 +-
 .../templates}/chloe/test/test5.xml           |  2 +-
 .../templates}/chloe/test/test6.xml           |  2 +-
 .../templates}/chloe/test/test7.xml           |  2 +-
 .../templates}/fhtml/authors.txt              |  0
 .../templates}/fhtml/fhtml-tests.factor       |  6 +-
 .../templates}/fhtml/fhtml.factor             | 43 +++-------
 .../templates}/fhtml/test/bug.fhtml           |  0
 .../templates}/fhtml/test/bug.html            |  0
 .../templates}/fhtml/test/example.fhtml       |  0
 .../templates}/fhtml/test/example.html        |  0
 .../templates}/fhtml/test/stack.fhtml         |  0
 .../templates}/fhtml/test/stack.html          |  0
 extra/html/templates/templates.factor         | 85 +++++++++++++++++++
 .../http/server/templating/templating.factor  | 27 ------
 extra/validators/validators.factor            |  7 +-
 24 files changed, 169 insertions(+), 113 deletions(-)
 rename extra/{http/server/templating => html/templates}/chloe/chloe-tests.factor (84%)
 rename extra/{http/server/templating => html/templates}/chloe/chloe.factor (80%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test1.xml (100%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test2.xml (100%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test3-aux.xml (100%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test3.xml (100%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test4.xml (62%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test5.xml (62%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test6.xml (62%)
 rename extra/{http/server/templating => html/templates}/chloe/test/test7.xml (62%)
 rename extra/{http/server/templating => html/templates}/fhtml/authors.txt (100%)
 rename extra/{http/server/templating => html/templates}/fhtml/fhtml-tests.factor (74%)
 rename extra/{http/server/templating => html/templates}/fhtml/fhtml.factor (62%)
 rename extra/{http/server/templating => html/templates}/fhtml/test/bug.fhtml (100%)
 rename extra/{http/server/templating => html/templates}/fhtml/test/bug.html (100%)
 rename extra/{http/server/templating => html/templates}/fhtml/test/example.fhtml (100%)
 rename extra/{http/server/templating => html/templates}/fhtml/test/example.html (100%)
 rename extra/{http/server/templating => html/templates}/fhtml/test/stack.fhtml (100%)
 rename extra/{http/server/templating => html/templates}/fhtml/test/stack.html (100%)
 create mode 100644 extra/html/templates/templates.factor
 delete mode 100644 extra/http/server/templating/templating.factor

diff --git a/extra/html/components/components-tests.factor b/extra/html/components/components-tests.factor
index 6ecd2b0fa6..0bd5410a3b 100644
--- a/extra/html/components/components-tests.factor
+++ b/extra/html/components/components-tests.factor
@@ -60,7 +60,7 @@ TUPLE: color red green blue ;
 
 [ ] [
     [
-        "green" <textarea> 25 >>rows 30 >>columns render
+        "green" <textarea> 25 >>rows 30 >>cols render
     ] with-null-writer
 ] unit-test
 
@@ -68,11 +68,13 @@ TUPLE: color red green blue ;
 
 [ ] [ "new york" "city1" set-value ] unit-test
 
+[ ] [ { "new york" "los angeles" "chicago" } "cities" set-value ] unit-test
+
 [ ] [
     [
         "city1"
         <choice>
-            { "new york" "los angeles" "chicago" } >>choices
+            "cities" >>choices
         render
     ] with-null-writer
 ] unit-test
@@ -83,7 +85,7 @@ TUPLE: color red green blue ;
     [
         "city2"
         <choice>
-            { "new york" "los angeles" "chicago" } >>choices
+            "cities" >>choices
             t >>multiple
         render
     ] with-null-writer
@@ -93,7 +95,7 @@ TUPLE: color red green blue ;
     [
         "city2"
         <choice>
-            { "new york" "los angeles" "chicago" } >>choices
+            "cities" >>choices
             t >>multiple
             5 >>size
         render
diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
index ef4def8ddb..df1d1faa72 100644
--- a/extra/html/components/components.factor
+++ b/extra/html/components/components.factor
@@ -78,7 +78,7 @@ M: password render*
     [ drop "" ] 2dip size>> "password" render-field ;
 
 ! Text areas
-TUPLE: textarea rows columns ;
+TUPLE: textarea rows cols ;
 
 : <textarea> ( -- renderer )
     textarea new ;
@@ -86,14 +86,14 @@ TUPLE: textarea rows columns ;
 M: textarea render*
     <textarea
         [ rows>> [ number>string =rows ] when* ]
-        [ columns>> [ number>string =cols ] when* ] bi
+        [ cols>> [ number>string =cols ] when* ] bi
         =name
     textarea>
         object>string escape-string write
     </textarea> ;
 
 ! Choice
-TUPLE: choice size choices multiple ;
+TUPLE: choice size multiple choices ;
 
 : <choice> ( -- choice )
     choice new ;
@@ -112,7 +112,7 @@ M: choice render*
         dup size>> [ number>string =size ] when*
         dup multiple>> [ "true" =multiple ] when
     select>
-        [ choices>> ] [ multiple>> ] bi
+        [ choices>> value ] [ multiple>> ] bi
         [ swap ] [ swap 1array ] if
         render-options
     </select> ;
diff --git a/extra/http/server/templating/chloe/chloe-tests.factor b/extra/html/templates/chloe/chloe-tests.factor
similarity index 84%
rename from extra/http/server/templating/chloe/chloe-tests.factor
rename to extra/html/templates/chloe/chloe-tests.factor
index 61f72a2f14..3c52153eee 100644
--- a/extra/http/server/templating/chloe/chloe-tests.factor
+++ b/extra/html/templates/chloe/chloe-tests.factor
@@ -1,8 +1,8 @@
-USING: http.server.templating http.server.templating.chloe
-http.server.components http.server.boilerplate tools.test
-io.streams.string kernel sequences ascii boxes namespaces xml
+USING: html.templates html.templates.chloe
+tools.test io.streams.string kernel sequences ascii boxes
+namespaces xml
 splitting ;
-IN: http.server.templating.chloe.tests
+IN: html.templates.chloe.tests
 
 [ f ] [ f parse-query-attr ] unit-test
 
@@ -26,7 +26,7 @@ IN: http.server.templating.chloe.tests
     "?>" split1 nip ; inline
 
 : test-template ( name -- template )
-    "resource:extra/http/server/templating/chloe/test/"
+    "resource:extra/html/templates/chloe/test/"
     swap
     ".xml" 3append <chloe> ;
 
diff --git a/extra/http/server/templating/chloe/chloe.factor b/extra/html/templates/chloe/chloe.factor
similarity index 80%
rename from extra/http/server/templating/chloe/chloe.factor
rename to extra/html/templates/chloe/chloe.factor
index c3d93f5909..a01d424eb9 100644
--- a/extra/http/server/templating/chloe/chloe.factor
+++ b/extra/html/templates/chloe/chloe.factor
@@ -3,13 +3,14 @@
 USING: accessors kernel sequences combinators kernel namespaces
 classes.tuple assocs splitting words arrays memoize
 io io.files io.encodings.utf8 io.streams.string
-unicode.case tuple-syntax html html.elements
+unicode.case tuple-syntax mirrors fry
 multiline xml xml.data xml.writer xml.utilities
+html.elements
+html.components
 http.server
 http.server.auth
 http.server.flows
 http.server.actions
-http.server.components
 http.server.sessions
 http.server.templating
 http.server.boilerplate ;
@@ -52,8 +53,11 @@ MEMO: chloe-name ( string -- name )
 : optional-attr ( tag name -- value )
     chloe-name swap at ;
 
+: process-tag-children ( tag -- )
+    [ process-template ] each ;
+
 : children>string ( tag -- string )
-    [ [ process-template ] each ] with-string-writer ;
+    [ process-tag-children ] with-string-writer ;
 
 : title-tag ( tag -- )
     children>string set-title ;
@@ -89,18 +93,6 @@ MEMO: chloe-name ( string -- name )
         atom-feed get value>> second write
     ] if ;
 
-: component-attr ( tag -- name )
-    "component" required-attr ;
-
-: view-tag ( tag -- )
-    component-attr component render-view ;
-
-: edit-tag ( tag -- )
-    component-attr component render-edit ;
-
-: summary-tag ( tag -- )
-    component-attr component render-summary ;
-
 : parse-query-attr ( string -- assoc )
     dup empty?
     [ drop f ] [ "," split [ dup value ] H{ } map>assoc ] if ;
@@ -133,9 +125,6 @@ MEMO: chloe-name ( string -- name )
         a>
     ] with-scope ;
 
-: process-tag-children ( tag -- )
-    [ process-template ] each ;
-
 : a-tag ( tag -- )
     [ a-start-tag ]
     [ process-tag-children ]
@@ -156,7 +145,7 @@ MEMO: chloe-name ( string -- name )
             form>
         ] [
             hidden-form-field
-            "for" optional-attr [ component render-edit ] when*
+            "for" optional-attr [ hidden render ] when*
         ] bi
     ] with-scope ;
 
@@ -180,9 +169,9 @@ STRING: button-tag-markup
 : button-tag ( tag -- )
     button-tag-markup string>xml delegate
     {
-        [ >r tag-attrs chloe-attrs-only r> add-tag-attrs ]
-        [ >r tag-attrs non-chloe-attrs-only r> "button" tag-named add-tag-attrs ]
-        [ >r children>string 1array r> "button" tag-named set-tag-children ]
+        [ [ tag-attrs chloe-attrs-only ] dip add-tag-attrs ]
+        [ [ tag-attrs non-chloe-attrs-only ] dip "button" tag-named add-tag-attrs ]
+        [ [ children>string 1array ] dip "button" tag-named set-tag-children ]
         [ nip ]
     } 2cleave process-chloe-tag ;
 
@@ -211,27 +200,58 @@ STRING: button-tag-markup
 : error-message-tag ( tag -- )
     children>string render-error ;
 
+: validation-messages-tag ( tag -- )
+    drop render-validation-messages ;
+
+: singleton-component-tag ( tag class -- )
+    [ "name" required-attr ] dip render ;
+
+: attrs>slots ( tag tuple -- )
+    [ attrs>> ] [ <mirror> ] bi* '[ swap tag>> , set-at ] assoc-each ;
+
+: tuple-component-tag ( tag class -- )
+    [ drop "name" required-attr ]
+    [ new [ attrs>slots ] keep ]
+    2bi render ;
+
 : process-chloe-tag ( tag -- )
     dup name-tag {
-        { "chloe" [ [ process-template ] each ] }
+        { "chloe" [ process-tag-children ] }
+
+        ! HTML head
         { "title" [ title-tag ] }
         { "write-title" [ write-title-tag ] }
         { "style" [ style-tag ] }
         { "write-style" [ write-style-tag ] }
         { "atom" [ atom-tag ] }
         { "write-atom" [ write-atom-tag ] }
-        { "view" [ view-tag ] }
-        { "edit" [ edit-tag ] }
-        { "summary" [ summary-tag ] }
+
+        ! HTML elements
         { "a" [ a-tag ] }
-        { "form" [ form-tag ] }
         { "button" [ button-tag ] }
+
+        ! Components
+        { "label" [ label singleton-component-tag ] }
+        { "link" [ link singleton-component-tag ] }
+        { "html" [ html singleton-component-tag ] }
+
+        ! Forms
+        { "form" [ form-tag ] }
         { "error-message" [ error-message-tag ] }
-        { "validation-message" [ drop render-validation-message ] }
+        { "validation-messages" [ validation-messages-tag ] }
+        { "hidden" [ hidden singleton-component-tag ] }
+        { "field" [ field tuple-component-tag ] }
+        { "password" [ password tuple-component-tag ] }
+        { "textarea" [ textarea tuple-component-tag ] }
+        { "choice" [ choice tuple-component-tag ] }
+        { "checkbox" [ checkbox tuple-component-tag ] }
+
+        ! Control flow
         { "if" [ if-tag ] }
         { "comment" [ drop ] }
         { "call-next-template" [ drop call-next-template ] }
-        [ "Unknown chloe tag: " swap append throw ]
+
+        [ "Unknown chloe tag: " prepend throw ]
     } case ;
 
 : process-tag ( tag -- )
diff --git a/extra/http/server/templating/chloe/test/test1.xml b/extra/html/templates/chloe/test/test1.xml
similarity index 100%
rename from extra/http/server/templating/chloe/test/test1.xml
rename to extra/html/templates/chloe/test/test1.xml
diff --git a/extra/http/server/templating/chloe/test/test2.xml b/extra/html/templates/chloe/test/test2.xml
similarity index 100%
rename from extra/http/server/templating/chloe/test/test2.xml
rename to extra/html/templates/chloe/test/test2.xml
diff --git a/extra/http/server/templating/chloe/test/test3-aux.xml b/extra/html/templates/chloe/test/test3-aux.xml
similarity index 100%
rename from extra/http/server/templating/chloe/test/test3-aux.xml
rename to extra/html/templates/chloe/test/test3-aux.xml
diff --git a/extra/http/server/templating/chloe/test/test3.xml b/extra/html/templates/chloe/test/test3.xml
similarity index 100%
rename from extra/http/server/templating/chloe/test/test3.xml
rename to extra/html/templates/chloe/test/test3.xml
diff --git a/extra/http/server/templating/chloe/test/test4.xml b/extra/html/templates/chloe/test/test4.xml
similarity index 62%
rename from extra/http/server/templating/chloe/test/test4.xml
rename to extra/html/templates/chloe/test/test4.xml
index dd9b232d73..55612360a5 100644
--- a/extra/http/server/templating/chloe/test/test4.xml
+++ b/extra/html/templates/chloe/test/test4.xml
@@ -2,7 +2,7 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:if t:code="http.server.templating.chloe.tests:test4-aux?">
+	<t:if t:code="html.templates.chloe.tests:test4-aux?">
 		True
 	</t:if>
 
diff --git a/extra/http/server/templating/chloe/test/test5.xml b/extra/html/templates/chloe/test/test5.xml
similarity index 62%
rename from extra/http/server/templating/chloe/test/test5.xml
rename to extra/html/templates/chloe/test/test5.xml
index 3bd39e45bd..edcbe8f3b1 100644
--- a/extra/http/server/templating/chloe/test/test5.xml
+++ b/extra/html/templates/chloe/test/test5.xml
@@ -2,7 +2,7 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:if t:code="http.server.templating.chloe.tests:test5-aux?">
+	<t:if t:code="html.templates.chloe.tests:test5-aux?">
 		True
 	</t:if>
 
diff --git a/extra/http/server/templating/chloe/test/test6.xml b/extra/html/templates/chloe/test/test6.xml
similarity index 62%
rename from extra/http/server/templating/chloe/test/test6.xml
rename to extra/html/templates/chloe/test/test6.xml
index 56234a5f0d..b3f649333f 100644
--- a/extra/http/server/templating/chloe/test/test6.xml
+++ b/extra/html/templates/chloe/test/test6.xml
@@ -2,7 +2,7 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:if t:var="http.server.templating.chloe.tests:test6-aux?">
+	<t:if t:var="html.templates.chloe.tests:test6-aux?">
 		True
 	</t:if>
 
diff --git a/extra/http/server/templating/chloe/test/test7.xml b/extra/html/templates/chloe/test/test7.xml
similarity index 62%
rename from extra/http/server/templating/chloe/test/test7.xml
rename to extra/html/templates/chloe/test/test7.xml
index a4f8e06e7d..338595e556 100644
--- a/extra/http/server/templating/chloe/test/test7.xml
+++ b/extra/html/templates/chloe/test/test7.xml
@@ -2,7 +2,7 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:if t:var="http.server.templating.chloe.tests:test7-aux?">
+	<t:if t:var="html.templates.chloe.tests:test7-aux?">
 		True
 	</t:if>
 
diff --git a/extra/http/server/templating/fhtml/authors.txt b/extra/html/templates/fhtml/authors.txt
similarity index 100%
rename from extra/http/server/templating/fhtml/authors.txt
rename to extra/html/templates/fhtml/authors.txt
diff --git a/extra/http/server/templating/fhtml/fhtml-tests.factor b/extra/html/templates/fhtml/fhtml-tests.factor
similarity index 74%
rename from extra/http/server/templating/fhtml/fhtml-tests.factor
rename to extra/html/templates/fhtml/fhtml-tests.factor
index 42bec43570..43ea28fa55 100755
--- a/extra/http/server/templating/fhtml/fhtml-tests.factor
+++ b/extra/html/templates/fhtml/fhtml-tests.factor
@@ -1,10 +1,10 @@
 USING: io io.files io.streams.string io.encodings.utf8
-http.server.templating http.server.templating.fhtml kernel
+html.templates html.templates.fhtml kernel
 tools.test sequences parser ;
-IN: http.server.templating.fhtml.tests
+IN: html.templates.fhtml.tests
 
 : test-template ( path -- ? )
-    "resource:extra/http/server/templating/fhtml/test/"
+    "resource:extra/html/templates/fhtml/test/"
     prepend
     [
         ".fhtml" append <fhtml> [ call-template ] with-string-writer
diff --git a/extra/http/server/templating/fhtml/fhtml.factor b/extra/html/templates/fhtml/fhtml.factor
similarity index 62%
rename from extra/http/server/templating/fhtml/fhtml.factor
rename to extra/html/templates/fhtml/fhtml.factor
index 2cc053a0ca..74e5c37ef1 100755
--- a/extra/http/server/templating/fhtml/fhtml.factor
+++ b/extra/html/templates/fhtml/fhtml.factor
@@ -4,12 +4,10 @@
 USING: continuations sequences kernel namespaces debugger
 combinators math quotations generic strings splitting
 accessors assocs fry
-parser io io.files io.streams.string io.encodings.utf8 source-files
-html html.elements
-http.server.static http.server http.server.templating ;
-IN: http.server.templating.fhtml
-
-: templating-vocab ( -- vocab-name ) "http.server.templating.fhtml" ;
+parser io io.files io.streams.string io.encodings.utf8
+html.elements
+html.templates ;
+IN: html.templates.fhtml
 
 ! We use a custom lexer so that %> ends a token even if not
 ! followed by whitespace
@@ -35,7 +33,7 @@ DEFER: <% delimiter
 : found-<% ( accum lexer col -- accum )
     [
         over line-text>>
-        >r >r column>> r> r> subseq parsed
+        [ column>> ] 2dip subseq parsed
         \ write-html parsed
     ] 2keep 2 + >>column drop ;
 
@@ -62,37 +60,20 @@ DEFER: <% delimiter
 
 : parse-template ( string -- quot )
     [
-        use [ clone ] change
-        templating-vocab use+
+        "quiet" on
+        parser-notes off
+        "html.templates.fhtml" use+
         string-lines parse-template-lines
-    ] with-scope ;
+    ] with-file-vocabs ;
 
-: eval-template ( string -- ) parse-template call ;
-
-: html-error. ( error -- )
-    <pre> error. </pre> ;
+: eval-template ( string -- )
+    parse-template call ;
 
 TUPLE: fhtml path ;
 
 C: <fhtml> fhtml
 
 M: fhtml call-template* ( filename -- )
-    '[
-        , path>> [
-            "quiet" on
-            parser-notes off
-            templating-vocab use+
-            ! so that reload works properly
-            dup source-file file set
-            utf8 file-contents
-            [ eval-template ] [ html-error. drop ] recover
-        ] with-file-vocabs
-    ] assert-depth ;
-
-! file responder integration
-: enable-fhtml ( responder -- responder )
-    [ <fhtml> serve-template ]
-    "application/x-factor-server-page"
-    pick special>> set-at ;
+    '[ , path>> utf8 file-contents eval-template ] assert-depth ;
 
 INSTANCE: fhtml template
diff --git a/extra/http/server/templating/fhtml/test/bug.fhtml b/extra/html/templates/fhtml/test/bug.fhtml
similarity index 100%
rename from extra/http/server/templating/fhtml/test/bug.fhtml
rename to extra/html/templates/fhtml/test/bug.fhtml
diff --git a/extra/http/server/templating/fhtml/test/bug.html b/extra/html/templates/fhtml/test/bug.html
similarity index 100%
rename from extra/http/server/templating/fhtml/test/bug.html
rename to extra/html/templates/fhtml/test/bug.html
diff --git a/extra/http/server/templating/fhtml/test/example.fhtml b/extra/html/templates/fhtml/test/example.fhtml
similarity index 100%
rename from extra/http/server/templating/fhtml/test/example.fhtml
rename to extra/html/templates/fhtml/test/example.fhtml
diff --git a/extra/http/server/templating/fhtml/test/example.html b/extra/html/templates/fhtml/test/example.html
similarity index 100%
rename from extra/http/server/templating/fhtml/test/example.html
rename to extra/html/templates/fhtml/test/example.html
diff --git a/extra/http/server/templating/fhtml/test/stack.fhtml b/extra/html/templates/fhtml/test/stack.fhtml
similarity index 100%
rename from extra/http/server/templating/fhtml/test/stack.fhtml
rename to extra/html/templates/fhtml/test/stack.fhtml
diff --git a/extra/http/server/templating/fhtml/test/stack.html b/extra/html/templates/fhtml/test/stack.html
similarity index 100%
rename from extra/http/server/templating/fhtml/test/stack.html
rename to extra/html/templates/fhtml/test/stack.html
diff --git a/extra/html/templates/templates.factor b/extra/html/templates/templates.factor
new file mode 100644
index 0000000000..ed26c9b531
--- /dev/null
+++ b/extra/html/templates/templates.factor
@@ -0,0 +1,85 @@
+! Copyright (C) 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: accessors kernel fry io io.encodings.utf8 io.files
+debugger prettyprint continuations namespaces boxes sequences
+arrays strings html.elements io.streams.string quotations ;
+IN: html.templates
+
+MIXIN: template
+
+GENERIC: call-template* ( template -- )
+
+M: string call-template* write ;
+
+M: callable call-template* call ;
+
+M: object call-template* output-stream get stream-copy ;
+
+ERROR: template-error template error ;
+
+M: template-error error.
+    "Error while processing template " write
+    [ template>> pprint ":" print nl ]
+    [ error>> error. ]
+    bi ;
+
+: call-template ( template -- )
+    [ call-template* ] [ template-error ] recover ;
+
+SYMBOL: title
+
+: set-title ( string -- )
+    title get >box ;
+
+: write-title ( -- )
+    title get value>> write ;
+
+SYMBOL: style
+
+: add-style ( string -- )
+    "\n" style get push-all
+         style get push-all ;
+
+: write-style ( -- )
+    style get >string write ;
+
+SYMBOL: atom-feed
+
+: set-atom-feed ( title url -- )
+    2array atom-feed get >box ;
+
+: write-atom-feed ( -- )
+    atom-feed get value>> [
+        <link "alternate" =rel "application/atom+xml" =type
+        [ first =title ] [ second =href ] bi
+        link/>
+    ] when* ;
+
+SYMBOL: nested-template?
+
+SYMBOL: next-template
+
+: call-next-template ( -- )
+    next-template get write-html ;
+
+M: f call-template* drop call-next-template ;
+
+: with-boilerplate ( body template -- )
+    [
+        title get [ <box> title set ] unless
+        atom-feed get [ <box> atom-feed set ] unless
+        style get [ SBUF" " clone style set ] unless
+
+        [
+            [
+                nested-template? on
+                call-template
+            ] with-string-writer
+            next-template set
+        ]
+        [ call-template ]
+        bi*
+    ] with-scope ; inline
+
+: template-convert ( template output -- )
+    utf8 [ call-template ] with-file-writer ;
diff --git a/extra/http/server/templating/templating.factor b/extra/http/server/templating/templating.factor
deleted file mode 100644
index 73f6095eae..0000000000
--- a/extra/http/server/templating/templating.factor
+++ /dev/null
@@ -1,27 +0,0 @@
-USING: accessors kernel fry io io.encodings.utf8 io.files
-http http.server debugger prettyprint continuations ;
-IN: http.server.templating
-
-MIXIN: template
-
-GENERIC: call-template* ( template -- )
-
-ERROR: template-error template error ;
-
-M: template-error error.
-    "Error while processing template " write
-    [ template>> pprint ":" print nl ]
-    [ error>> error. ]
-    bi ;
-
-: call-template ( template -- )
-    [ call-template* ] [ template-error ] recover ;
-
-M: template write-response-body* call-template ;
-
-: template-convert ( template output -- )
-    utf8 [ call-template ] with-file-writer ;
-
-! responder integration
-: serve-template ( template -- response )
-    '[ , call-template ] <html-content> ;
diff --git a/extra/validators/validators.factor b/extra/validators/validators.factor
index 9d6c4bed90..b03cc76444 100644
--- a/extra/validators/validators.factor
+++ b/extra/validators/validators.factor
@@ -122,12 +122,7 @@ C: <validation-error> validation-error
     [ swap validation-error-for f ] recover ; inline
 
 : validate-value ( value name validators -- result )
-    '[
-        , at {
-            { [ dup pair? ] [ first ] }
-            { [ dup quotation? ] [ ] }
-        } cond call
-    ] validate ;
+    '[ , at call ] validate ;
 
 : required-values ( assoc -- )
     [ swap [ drop v-required ] validate drop ] assoc-each ;

From 73a25d847167120165db844b3d28f8696f4d0328 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 19:22:31 -0500
Subject: [PATCH 13/66] Remove obsolete vocabularies

---
 extra/http/server/components/code/code.factor |  20 -
 .../server/components/components-tests.factor | 133 ------
 .../http/server/components/components.factor  | 401 ------------------
 .../server/components/farkup/farkup.factor    |  17 -
 .../components/inspector/inspector.factor     |  17 -
 extra/http/server/components/test/form.fhtml  |   1 -
 extra/http/server/forms/forms.factor          |  79 ----
 .../server/validators/validators-tests.factor |  29 --
 .../http/server/validators/validators.factor  |  85 ----
 9 files changed, 782 deletions(-)
 delete mode 100644 extra/http/server/components/code/code.factor
 delete mode 100755 extra/http/server/components/components-tests.factor
 delete mode 100755 extra/http/server/components/components.factor
 delete mode 100755 extra/http/server/components/farkup/farkup.factor
 delete mode 100644 extra/http/server/components/inspector/inspector.factor
 delete mode 100755 extra/http/server/components/test/form.fhtml
 delete mode 100644 extra/http/server/forms/forms.factor
 delete mode 100755 extra/http/server/validators/validators-tests.factor
 delete mode 100755 extra/http/server/validators/validators.factor

diff --git a/extra/http/server/components/code/code.factor b/extra/http/server/components/code/code.factor
deleted file mode 100644
index 19fc8c5ca8..0000000000
--- a/extra/http/server/components/code/code.factor
+++ /dev/null
@@ -1,20 +0,0 @@
-! Copyright (C) 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: splitting kernel io sequences xmode.code2html accessors
-http.server.components html xml.entities ;
-IN: http.server.components.code
-
-TUPLE: code-renderer < text-renderer mode ;
-
-: <code-renderer> ( mode -- renderer )
-    code-renderer new-text-renderer
-        swap >>mode ;
-
-M: code-renderer render-view*
-    [
-        [ string-lines ] [ mode>> value ] bi* htmlize-lines
-    ] with-html-stream ;
-
-: <code> ( id mode -- component )
-    swap <text>
-        swap <code-renderer> >>renderer ;
diff --git a/extra/http/server/components/components-tests.factor b/extra/http/server/components/components-tests.factor
deleted file mode 100755
index ff87bb71fb..0000000000
--- a/extra/http/server/components/components-tests.factor
+++ /dev/null
@@ -1,133 +0,0 @@
-IN: http.server.components.tests
-USING: http.server.components http.server.forms
-http.server.validators namespaces tools.test kernel accessors
-tuple-syntax mirrors
-http http.server.actions http.server.templating.fhtml
-io.streams.string io.streams.null ;
-
-validation-failed? off
-
-[ 3 ] [ "3" "n" <number> validate ] unit-test
-
-[ 123 ] [
-    ""
-    "n" <number>
-        123 >>default
-    validate
-] unit-test
-
-[ f ] [ validation-failed? get ] unit-test
-
-[ t ] [ "3x" "n" <number> validate validation-error? ] unit-test
-
-[ t ] [ validation-failed? get ] unit-test
-
-[ "" ] [ "" "email" <email> validate ] unit-test
-
-[ "slava@jedit.org" ] [ "slava@jedit.org" "email" <email> validate ] unit-test
-
-[ "slava@jedit.org" ] [
-    "slava@jedit.org"
-    "email" <email>
-        t >>required
-    validate
-] unit-test
-
-[ t ] [
-    "a"
-    "email" <email>
-        t >>required
-    validate validation-error?
-] unit-test
-
-[ t ] [ "a" "email" <email> validate validation-error? ] unit-test
-
-TUPLE: test-tuple text number more-text ;
-
-: <test-tuple> test-tuple new ;
-
-: <test-form> ( -- form )
-    "test" <form>
-        "resource:extra/http/server/components/test/form.fhtml" <fhtml> >>view-template
-        "resource:extra/http/server/components/test/form.fhtml" <fhtml> >>edit-template
-        "text" <string>
-            t >>required
-            add-field
-        "number" <number>
-            123 >>default
-            t >>required
-            0 >>min-value
-            10 >>max-value
-            add-field
-        "more-text" <text>
-            "hi" >>default
-            add-field ;
-
-[ ] [ <test-tuple> <mirror> values set <test-form> view-form write-response-body drop ] unit-test
-
-[ ] [ <test-tuple> <mirror> values set <test-form> edit-form write-response-body drop ] unit-test
-
-[ TUPLE{ test-tuple number: 123 more-text: "hi" } ] [
-    <test-tuple> from-tuple
-    <test-form> set-defaults
-    values-tuple
-] unit-test
-
-[
-    H{
-        { "text" "fdafsa" }
-        { "number" "xxx" }
-        { "more-text" "" }
-    } params set
-
-    H{ } clone values set
-
-    [ t ] [ <test-form> (validate-form) ] unit-test
-
-    [ "fdafsa" ] [ "text" value ] unit-test
-
-    [ t ] [ "number" value validation-error? ] unit-test
-] with-scope
-
-[
-    [ ] [
-        "n" <number>
-            0 >>min-value
-            10 >>max-value
-        "n" set
-    ] unit-test
-
-    [ "123" ] [
-        "123" "n" get validate value>>
-    ] unit-test
-    
-    [ ] [ "i" <integer> "i" set ] unit-test
-
-    [ 3 ] [
-        "3" "i" get validate
-    ] unit-test
-    
-    [ t ] [
-        "3.9" "i" get validate validation-error?
-    ] unit-test
-
-    H{ } clone values set
-
-    [ ] [ 3 "i" set-value ] unit-test
-
-    [ "3" ] [ [ "i" get render-view ] with-string-writer ] unit-test
-
-    [ ] [ [ "i" get render-edit ] with-null-stream ] unit-test
-
-    [ ] [ "t" <text> "t" set ] unit-test
-
-    [ ] [ "hello world" "t" set-value ] unit-test
-
-    [ ] [ [ "t" get render-edit ] with-null-stream ] unit-test
-] with-scope
-
-[ t ] [ "wake up sheeple" dup "n" <text> validate = ] unit-test
-
-[ ] [ "password" <password> "p" set ] unit-test
-
-[ ] [ "pub-date" <date> "d" set ] unit-test
diff --git a/extra/http/server/components/components.factor b/extra/http/server/components/components.factor
deleted file mode 100755
index 7f2a5a9ce1..0000000000
--- a/extra/http/server/components/components.factor
+++ /dev/null
@@ -1,401 +0,0 @@
-! Copyright (C) 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: accessors namespaces kernel io math.parser assocs classes
-words classes.tuple arrays sequences splitting mirrors
-hashtables fry locals combinators continuations math
-calendar.format html html.elements xml.entities
-http.server.validators ;
-IN: http.server.components
-
-! Renderer protocol
-GENERIC: render-summary* ( value renderer -- )
-GENERIC: render-view* ( value renderer -- )
-GENERIC: render-edit* ( value id renderer -- )
-
-M: object render-summary* render-view* ;
-
-TUPLE: field type ;
-
-C: <field> field
-
-M: field render-view*
-    drop escape-string write ;
-
-M: field render-edit*
-    <input type>> =type =name =value input/> ;
-
-TUPLE: hidden < field ;
-
-: hidden ( -- renderer ) T{ hidden f "hidden" } ; inline
-
-! Component protocol
-SYMBOL: components
-
-TUPLE: component id required default renderer ;
-
-: component ( name -- component )
-    dup components get at
-    [ ] [ "No such component: " prepend throw ] ?if ;
-
-GENERIC: init ( component -- component )
-
-M: component init ;
-
-GENERIC: validate* ( value component -- result )
-GENERIC: component-string ( value component -- string )
-
-SYMBOL: values
-
-: value values get at ;
-
-: set-value values get set-at ;
-
-: blank-values H{ } clone values set ;
-
-: from-tuple <mirror> values set ;
-
-: values-tuple values get mirror-object ;
-
-: render-view-or-summary ( component -- value renderer )
-    [ id>> value ] [ component-string ] [ renderer>> ] tri ;
-
-: render-view ( component -- )
-    render-view-or-summary render-view* ;
-
-: render-summary ( component -- )
-    render-view-or-summary render-summary* ;
-
-<PRIVATE
-
-: render-edit-string ( string component -- )
-    [ id>> ] [ renderer>> ] bi render-edit* ;
-
-: render-edit-error ( component -- )
-    [ id>> value ] keep
-    [ [ value>> ] dip render-edit-string ]
-    [ drop reason>> render-error ] 2bi ;
-
-: value-or-default ( component -- value )
-    [ id>> value ] [ default>> ] bi or ;
-
-: render-edit-value ( component -- )
-    [ value-or-default ]
-    [ component-string ]
-    [ render-edit-string ]
-    tri ;
-
-PRIVATE>
-
-: render-edit ( component -- )
-    dup id>> value validation-error?
-    [ render-edit-error ] [ render-edit-value ] if ;
-
-: validate ( value component -- result )
-    '[
-        ,
-        over empty? [
-            [ default>> [ v-default ] when* ]
-            [ required>> [ v-required ] when ]
-            bi
-        ] [ validate* ] if
-    ] with-validator ;
-
-: new-component ( id class renderer -- component )
-    swap new
-        swap >>renderer
-        swap >>id
-        init ; inline
-
-! String input fields
-TUPLE: string < component one-line min-length max-length ;
-
-: new-string ( id class -- component )
-    "text" <field> new-component
-        t >>one-line ; inline
-
-: <string> ( id -- component )
-    string new-string ;
-
-M: string validate*
-    [   one-line>> [ v-one-line   ] when  ]
-    [ min-length>> [ v-min-length ] when* ]
-    [ max-length>> [ v-max-length ] when* ]
-    tri ;
-
-M: string component-string
-    drop ;
-
-! Username fields
-TUPLE: username < string ;
-
-M: username init
-    2 >>min-length
-    20 >>max-length ;
-
-: <username> ( id -- component )
-    username new-string ;
-
-M: username validate*
-    call-next-method v-one-word ;
-
-! E-mail fields
-TUPLE: email < string ;
-
-: <email> ( id -- component )
-    email new-string
-        5 >>min-length
-        60 >>max-length ;
-
-M: email validate*
-    call-next-method dup empty? [ v-email ] unless ;
-
-! URL fields
-TUPLE: url < string ;
-
-: <url> ( id -- component )
-    url new-string
-        5 >>min-length
-        60 >>max-length ;
-
-M: url validate*
-    call-next-method dup empty? [ v-url ] unless ;
-
-! Don't send passwords back to the user
-TUPLE: password-renderer < field ;
-
-: password-renderer T{ password-renderer f "password" } ;
-
-: blank-password >r >r drop "" r> r> ;
-
-M: password-renderer render-edit*
-    blank-password call-next-method ;
-
-! Password fields
-TUPLE: password < string ;
-
-M: password init
-    6 >>min-length
-    60 >>max-length ;
-
-: <password> ( id -- component )
-    password new-string
-        password-renderer >>renderer ;
-
-M: password validate*
-    call-next-method v-one-word ;
-
-! Number fields
-TUPLE: number < string min-value max-value ;
-
-: <number> ( id -- component )
-    number new-string ;
-
-M: number validate*
-    [ v-number ] [
-        [ min-value>> [ v-min-value ] when* ]
-        [ max-value>> [ v-max-value ] when* ]
-        bi
-    ] bi* ;
-
-M: number component-string
-    drop dup [ number>string ] when ;
-
-! Integer fields
-TUPLE: integer < number ;
-
-: <integer> ( id -- component )
-    integer new-string ;
-
-M: integer validate*
-    call-next-method v-integer ;
-
-! Simple captchas
-TUPLE: captcha < string ;
-
-: <captcha> ( id -- component )
-    captcha new-string ;
-
-M: captcha validate*
-    drop v-captcha ;
-
-! Text areas
-TUPLE: text-renderer rows cols ;
-
-: new-text-renderer ( class -- renderer )
-    new
-        60 >>cols
-        20 >>rows ;
-
-: <text-renderer> ( -- renderer )
-    text-renderer new-text-renderer ;
-
-M: text-renderer render-view*
-    drop escape-string write ;
-
-M: text-renderer render-edit*
-    <textarea
-        [ rows>> [ number>string =rows ] when* ]
-        [ cols>> [ number>string =cols ] when* ] bi
-        [ =id   ]
-        [ =name ] bi
-    textarea>
-        escape-string write
-    </textarea> ;
-
-TUPLE: text < string ;
-
-: new-text ( id class -- component )
-    new-string
-        f >>one-line
-        <text-renderer> >>renderer ;
-
-: <text> ( id -- component )
-    text new-text ;
-
-! HTML text component
-TUPLE: html-text-renderer < text-renderer ;
-
-: <html-text-renderer> ( -- renderer )
-    html-text-renderer new-text-renderer ;
-
-M: html-text-renderer render-view*
-    drop escape-string write ;
-
-TUPLE: html-text < text ;
-
-: <html-text> ( id -- component )
-    html-text new-text
-        <html-text-renderer> >>renderer ;
-
-! Date component
-TUPLE: date < string ;
-
-: <date> ( id -- component )
-    date new-string ;
-
-M: date component-string
-    drop timestamp>string ;
-
-! Link components
-
-GENERIC: link-title ( obj -- string )
-GENERIC: link-href ( obj -- url )
-
-SINGLETON: link-renderer
-
-M: link-renderer render-view*
-    drop <a dup link-href =href a> link-title escape-string write </a> ;
-
-TUPLE: link < string ;
-
-: <link> ( id -- component )
-    link new-string
-        link-renderer >>renderer ;
-
-! List components
-SYMBOL: +plain+
-SYMBOL: +ordered+
-SYMBOL: +unordered+
-
-TUPLE: list-renderer component type ;
-
-C: <list-renderer> list-renderer
-
-: render-plain-list ( seq component quot -- )
-    '[ , component>> renderer>> @ ] each ; inline
-
-: render-li-list ( seq component quot -- )
-    '[ <li> @ </li> ] render-plain-list ; inline
-
-: render-ordered-list ( seq quot component -- )
-    <ol> render-li-list </ol> ; inline
-
-: render-unordered-list ( seq quot component -- )
-    <ul> render-li-list </ul> ; inline
-
-: render-list ( value renderer quot -- )
-    over type>> {
-        { +plain+     [ render-plain-list ] }
-        { +ordered+   [ render-ordered-list ] }
-        { +unordered+ [ render-unordered-list ] }
-    } case ; inline
-
-M: list-renderer render-view*
-    [ render-view* ] render-list ;
-
-M: list-renderer render-summary*
-    [ render-summary* ] render-list ;
-
-TUPLE: list < component ;
-
-: <list> ( id component type -- list )
-    <list-renderer> list swap new-component ;
-
-M: list component-string drop ;
-
-! Choice
-TUPLE: choice-renderer choices ;
-
-C: <choice-renderer> choice-renderer
-
-M: choice-renderer render-view*
-    drop escape-string write ;
-
-: render-option ( text selected? -- )
-    <option [ "true" =selected ] when option>
-        escape-string write
-    </option> ;
-
-: render-options ( options selected -- )
-    '[ dup , member? render-option ] each ;
-
-M: choice-renderer render-edit*
-    <select swap =name select>
-        choices>> swap 1array render-options
-    </select> ;
-
-TUPLE: choice < string ;
-
-: <choice> ( id choices -- component )
-    swap choice new-string
-        swap <choice-renderer> >>renderer ;
-
-! Menu
-TUPLE: menu-renderer choices size ;
-
-: <menu-renderer> ( choices -- renderer )
-    5 menu-renderer boa ;
-
-M:: menu-renderer render-edit* ( value id renderer -- )
-    <select
-        renderer size>> [ number>string =size ] when*
-        id =name
-        "true" =multiple
-    select>
-        renderer choices>> value render-options
-    </select> ;
-
-TUPLE: menu < string ;
-
-: <menu> ( id choices -- component )
-    swap menu new-string
-        swap <menu-renderer> >>renderer ;
-
-! Checkboxes
-TUPLE: checkbox-renderer label ;
-
-C: <checkbox-renderer> checkbox-renderer
-
-M: checkbox-renderer render-edit*
-    <input
-        "checkbox" =type
-        swap =id
-        swap [ "true" =selected ] when
-    input>
-        label>> escape-string write
-    </input> ;
-
-TUPLE: checkbox < string ;
-
-: <checkbox> ( id label -- component )
-    checkbox swap <checkbox-renderer> new-component ;
diff --git a/extra/http/server/components/farkup/farkup.factor b/extra/http/server/components/farkup/farkup.factor
deleted file mode 100755
index 87b7170bbf..0000000000
--- a/extra/http/server/components/farkup/farkup.factor
+++ /dev/null
@@ -1,17 +0,0 @@
-! Copyright (C) 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: splitting kernel io sequences farkup accessors
-http.server.components xml.entities ;
-IN: http.server.components.farkup
-
-TUPLE: farkup-renderer < text-renderer ;
-
-: <farkup-renderer> ( -- renderer )
-    farkup-renderer new-text-renderer ;
-
-M: farkup-renderer render-view*
-    drop string-lines "\n" join convert-farkup write ;
-
-: <farkup> ( id -- component )
-    <text>
-        <farkup-renderer> >>renderer ;
diff --git a/extra/http/server/components/inspector/inspector.factor b/extra/http/server/components/inspector/inspector.factor
deleted file mode 100644
index 42366b57e4..0000000000
--- a/extra/http/server/components/inspector/inspector.factor
+++ /dev/null
@@ -1,17 +0,0 @@
-! Copyright (C) 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: splitting kernel io sequences inspector accessors
-http.server.components xml.entities html ;
-IN: http.server.components.inspector
-
-SINGLETON: inspector-renderer
-
-M: inspector-renderer render-view*
-    drop [ describe ] with-html-stream ;
-
-TUPLE: inspector < component ;
-
-M: inspector component-string drop ;
-
-: <inspector> ( id -- component )
-    inspector inspector-renderer new-component ;
diff --git a/extra/http/server/components/test/form.fhtml b/extra/http/server/components/test/form.fhtml
deleted file mode 100755
index d3f5a12faa..0000000000
--- a/extra/http/server/components/test/form.fhtml
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/extra/http/server/forms/forms.factor b/extra/http/server/forms/forms.factor
deleted file mode 100644
index 92fb25bb16..0000000000
--- a/extra/http/server/forms/forms.factor
+++ /dev/null
@@ -1,79 +0,0 @@
-! Copyright (C) 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: kernel accessors assocs namespaces io.files sequences fry
-http.server
-http.server.actions
-http.server.components
-http.server.validators
-http.server.templating ;
-IN: http.server.forms
-
-TUPLE: form < component
-view-template edit-template summary-template
-components ;
-
-M: form init V{ } clone >>components ;
-
-: <form> ( id -- form )
-    form f new-component
-        dup >>renderer ;
-
-: add-field ( form component -- form )
-    dup id>> pick components>> set-at ;
-
-: set-components ( form -- )
-    components>> components set ;
-
-: with-form ( form quot -- )
-    [ [ set-components ] [ call ] bi* ] with-scope ; inline
-
-: set-defaults ( form -- )
-    [
-        components get [
-            swap values get [
-                swap default>> or
-            ] change-at
-        ] assoc-each
-    ] with-form ;
-
-: <form-response> ( form template -- response )
-    [ components>> components set ] [ <html-content> ] bi* ;
-
-: view-form ( form -- response )
-    dup view-template>> <form-response> ;
-
-: edit-form ( form -- response )
-    dup edit-template>> <form-response> ;
-
-: validate-param ( id component -- )
-    [ [ params get at ] [ validate ] bi* ]
-    [ drop set-value ] 2bi ;
-
-: (validate-form) ( form -- error? )
-    [
-        validation-failed? off
-        components get [ validate-param ] assoc-each
-        validation-failed? get
-    ] with-form ;
-
-: validate-form ( form -- )
-    (validate-form) [ validation-failed ] when ;
-
-: render-form ( value form template -- )
-    [
-        [ from-tuple ]
-        [ set-components ]
-        [ call-template ]
-        tri*
-    ] with-scope ;
-
-M: form component-string drop ;
-
-M: form render-summary*
-    dup summary-template>> render-form ;
-
-M: form render-view*
-    dup view-template>> render-form ;
-
-M: form render-edit*
-    nip dup edit-template>> render-form ;
diff --git a/extra/http/server/validators/validators-tests.factor b/extra/http/server/validators/validators-tests.factor
deleted file mode 100755
index 5e845705ab..0000000000
--- a/extra/http/server/validators/validators-tests.factor
+++ /dev/null
@@ -1,29 +0,0 @@
-IN: http.server.validators.tests
-USING: kernel sequences tools.test http.server.validators
-accessors ;
-
-[ "foo" v-number ] must-fail
-[ 123 ] [ "123" v-number ] unit-test
-
-[ "slava@factorcode.org" ] [
-    "slava@factorcode.org" v-email
-] unit-test
-
-[ "slava+foo@factorcode.org" ] [
-    "slava+foo@factorcode.org" v-email
-] unit-test
-
-[ "slava@factorcode.o" v-email ]
-[ "invalid e-mail" = ] must-fail-with
-
-[ "sla@@factorcode.o" v-email ]
-[ "invalid e-mail" = ] must-fail-with
-
-[ "slava@factorcodeorg" v-email ]
-[ "invalid e-mail" = ] must-fail-with
-
-[ "http://www.factorcode.org" ]
-[ "http://www.factorcode.org" v-url ] unit-test
-
-[ "http:/www.factorcode.org" v-url ]
-[ "invalid URL" = ] must-fail-with
diff --git a/extra/http/server/validators/validators.factor b/extra/http/server/validators/validators.factor
deleted file mode 100755
index 7415787c79..0000000000
--- a/extra/http/server/validators/validators.factor
+++ /dev/null
@@ -1,85 +0,0 @@
-! Copyright (C) 2006, 2008 Slava Pestov
-! See http://factorcode.org/license.txt for BSD license.
-USING: kernel continuations sequences math namespaces sets
-math.parser assocs regexp fry unicode.categories sequences ;
-IN: http.server.validators
-
-SYMBOL: validation-failed?
-
-TUPLE: validation-error value reason ;
-
-C: <validation-error> validation-error
-
-: with-validator ( value quot -- result )
-    [ validation-failed? on <validation-error> ] recover ; inline
-
-: v-default ( str def -- str )
-    over empty? spin ? ;
-
-: v-required ( str -- str )
-    dup empty? [ "required" throw ] when ;
-
-: v-optional ( str quot -- str )
-    over empty? [ 2drop f ] [ call ] if ; inline
-
-: v-min-length ( str n -- str )
-    over length over < [
-        [ "must be at least " % # " characters" % ] "" make
-        throw
-    ] [
-        drop
-    ] if ;
-
-: v-max-length ( str n -- str )
-    over length over > [
-        [ "must be no more than " % # " characters" % ] "" make
-        throw
-    ] [
-        drop
-    ] if ;
-
-: v-number ( str -- n )
-    dup string>number [ ] [ "must be a number" throw ] ?if ;
-
-: v-integer ( n -- n )
-    dup integer? [ "must be an integer" throw ] unless ;
-
-: v-min-value ( x n -- x )
-    2dup < [
-        [ "must be at least " % # ] "" make throw
-    ] [
-        drop
-    ] if ;
-
-: v-max-value ( x n -- x )
-    2dup > [
-        [ "must be no more than " % # ] "" make throw
-    ] [
-        drop
-    ] if ;
-
-: v-regexp ( str what regexp -- str )
-    >r over r> matches?
-    [ drop ] [ "invalid " prepend throw ] if ;
-
-: v-email ( str -- str )
-    #! From http://www.regular-expressions.info/email.html
-    "e-mail"
-    R' [A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'i
-    v-regexp ;
-
-: v-url ( str -- str )
-    "URL"
-    R' (ftp|http|https)://(\w+:?\w*@)?(\S+)(:[0-9]+)?(/|/([\w#!:.?+=&%@!\-/]))?'
-    v-regexp ;
-
-: v-captcha ( str -- str )
-    dup empty? [ "must remain blank" throw ] unless ;
-
-: v-one-line ( str -- str )
-    dup "\r\n" intersect empty?
-    [ "must be a single line" throw ] unless ;
-
-: v-one-word ( str -- str )
-    dup [ alpha? ] all?
-    [ "must be a single word" throw ] unless ;

From 376c73c7c86e69ec84ecab4fd212f342ef16b7d5 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 22:20:27 -0500
Subject: [PATCH 14/66] Convert HTML streams to use inheritance and new
 accessors, fix a bug

---
 extra/html/streams/streams-tests.factor |  4 +-
 extra/html/streams/streams.factor       | 93 ++++++++++++-------------
 2 files changed, 49 insertions(+), 48 deletions(-)

diff --git a/extra/html/streams/streams-tests.factor b/extra/html/streams/streams-tests.factor
index 2084c7db18..14f1621346 100644
--- a/extra/html/streams/streams-tests.factor
+++ b/extra/html/streams/streams-tests.factor
@@ -1,6 +1,6 @@
 USING: html.streams html.streams.private
 io io.streams.string io.styles kernel
-namespaces tools.test xml.writer sbufs sequences ;
+namespaces tools.test xml.writer sbufs sequences inspector ;
 IN: html.streams.tests
 
 : make-html-string
@@ -70,3 +70,5 @@ M: funky browser-link-href
 ] [
     [ H{ } [ ] with-nesting nl ] make-html-string
 ] unit-test
+
+[ ] [ [ { 1 2 3 } describe ] with-html-stream ] unit-test
diff --git a/extra/html/streams/streams.factor b/extra/html/streams/streams.factor
index b35f383bdc..e3f45e4c25 100755
--- a/extra/html/streams/streams.factor
+++ b/extra/html/streams/streams.factor
@@ -1,50 +1,44 @@
-! Copyright (C) 2004, 2006 Slava Pestov.
+! Copyright (C) 2004, 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: generic assocs help http io io.styles io.files continuations
 io.streams.string kernel math math.order math.parser namespaces
 quotations assocs sequences strings words html.elements
-xml.entities sbufs continuations destructors ;
+xml.entities sbufs continuations destructors accessors ;
 IN: html.streams
 
 GENERIC: browser-link-href ( presented -- href )
 
 M: object browser-link-href drop f ;
 
-TUPLE: html-stream last-div? ;
+TUPLE: html-stream stream last-div ;
 
-! A hack: stream-nl after with-nesting or tabular-output is
-! ignored, so that HTML stream output looks like UI pane output
-: test-last-div? ( stream -- ? )
-    dup html-stream-last-div?
-    f rot set-html-stream-last-div? ;
+! stream-nl after with-nesting or tabular-output is
+! ignored, so that HTML stream output looks like
+! UI pane output
+: last-div? ( stream -- ? )
+    [ f ] change-last-div drop ;
 
 : not-a-div ( stream -- stream )
-    dup test-last-div? drop ; inline
+    f >>last-div ; inline
 
 : a-div ( stream -- straem )
-    t over set-html-stream-last-div? ; inline
+    t >>last-div ; inline
 
 : <html-stream> ( stream -- stream )
-    html-stream construct-delegate ;
+    f html-stream boa ;
 
 <PRIVATE
 
-TUPLE: html-sub-stream style stream ;
+TUPLE: html-sub-stream < html-stream style parent ;
 
-: (html-sub-stream) ( style stream -- stream )
-    html-sub-stream boa
-    512 <sbuf> <html-stream> over set-delegate ;
-
-: <html-sub-stream> ( style stream class -- stream )
-    >r (html-sub-stream) r> construct-delegate ; inline
+: new-html-sub-stream ( style stream class -- stream )
+    new
+        512 <sbuf> >>stream
+        swap >>parent
+        swap >>style ; inline
 
 : end-sub-stream ( substream -- string style stream )
-    dup delegate >string
-    over html-sub-stream-style
-    rot html-sub-stream-stream ;
-
-: delegate-write ( string -- )
-    output-stream get delegate stream-write ;
+    [ stream>> >string ] [ style>> ] [ parent>> ] tri ;
 
 : object-link-tag ( style quot -- )
     presented pick at [
@@ -99,11 +93,11 @@ TUPLE: html-sub-stream style stream ;
     ] if ; inline
 
 : format-html-span ( string style stream -- )
-    [
-        [ [ drop delegate-write ] span-tag ] object-link-tag
+    stream>> [
+        [ [ drop write ] span-tag ] object-link-tag
     ] with-output-stream* ;
 
-TUPLE: html-span-stream ;
+TUPLE: html-span-stream < html-sub-stream ;
 
 M: html-span-stream dispose
     end-sub-stream not-a-div format-html-span ;
@@ -132,11 +126,11 @@ M: html-span-stream dispose
     ] if ; inline
 
 : format-html-div ( string style stream -- )
-    [
-        [ [ delegate-write ] div-tag ] object-link-tag
+    stream>> [
+        [ [ write ] div-tag ] object-link-tag
     ] with-output-stream* ;
 
-TUPLE: html-block-stream ;
+TUPLE: html-block-stream < html-sub-stream ;
 
 M: html-block-stream dispose ( quot style stream -- )
     end-sub-stream a-div format-html-div ;
@@ -159,38 +153,43 @@ M: html-block-stream dispose ( quot style stream -- )
 PRIVATE>
 
 ! Stream protocol
-M: html-stream stream-write1 ( char stream -- )
+M: html-stream stream-flush
+    stream>> stream-flush ;
+
+M: html-stream stream-write1
     >r 1string r> stream-write ;
 
-M: html-stream stream-write ( str stream -- )
-    not-a-div >r escape-string r> delegate stream-write ;
+M: html-stream stream-write
+    not-a-div >r escape-string r> stream>> stream-write ;
 
-M: html-stream make-span-stream ( style stream -- stream' )
-    html-span-stream <html-sub-stream> ;
-
-M: html-stream stream-format ( str style stream -- )
+M: html-stream stream-format
     >r html over at [ >r escape-string r> ] unless r>
     format-html-span ;
 
-M: html-stream make-block-stream ( style stream -- stream' )
-    html-block-stream <html-sub-stream> ;
+M: html-stream stream-nl
+    dup last-div? [ drop ] [ [ <br/> ] with-output-stream* ] if ;
 
-M: html-stream stream-write-table ( grid style stream -- )
-    a-div [
+M: html-stream make-span-stream
+    html-span-stream new-html-sub-stream ;
+
+M: html-stream make-block-stream
+    html-block-stream new-html-sub-stream ;
+
+M: html-stream make-cell-stream
+    html-sub-stream new-html-sub-stream ;
+
+M: html-stream stream-write-table
+    a-div stream>> [
         <table dup table-attrs table> swap [
             <tr> [
                 <td "top" =valign swap table-style =style td>
-                    >string write-html
+                    stream>> >string write
                 </td>
             ] with each </tr>
         ] with each </table>
     ] with-output-stream* ;
 
-M: html-stream make-cell-stream ( style stream -- stream' )
-    (html-sub-stream) ;
-
-M: html-stream stream-nl ( stream -- )
-    dup test-last-div? [ drop ] [ [ <br/> ] with-output-stream* ] if ;
+M: html-stream dispose stream>> dispose ;
 
 : with-html-stream ( quot -- )
     output-stream get <html-stream> swap with-output-stream* ; inline

From 53857fdfee998019c90d8d8b1d126f2a0db17cc8 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 22:32:39 -0500
Subject: [PATCH 15/66] Move farkup, code, inspector components to
 html.components

---
 extra/html/components/components-tests.factor | 27 +++++++++++++++++--
 extra/html/components/components.factor       | 23 +++++++++++++++-
 2 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/extra/html/components/components-tests.factor b/extra/html/components/components-tests.factor
index 0bd5410a3b..d09f8b6b42 100644
--- a/extra/html/components/components-tests.factor
+++ b/extra/html/components/components-tests.factor
@@ -1,6 +1,7 @@
 IN: html.components.tests
-USING: html.components tools.test kernel io.streams.string
-io.streams.null accessors ;
+USING: tools.test kernel io.streams.string
+io.streams.null accessors inspector html.streams
+html.components ;
 
 [ ] [ blank-values ] unit-test
 
@@ -145,3 +146,25 @@ M: link-test link-href drop "http://www.apple.com/foo&bar" ;
 [ "<html>arbitrary <b>markup</b> for the win!</html>" ] [
     [ "html" html render ] with-string-writer
 ] unit-test
+
+[ ] [ "int x = 4;" "code" set-value ] unit-test
+
+[ ] [ "java" "mode" set-value ] unit-test
+
+[ "<span class='KEYWORD3'>int</span> x <span class='OPERATOR'>=</span> <span class='DIGIT'>4</span>;\n" ] [
+    [ "code" <code> "mode" >>mode render ] with-string-writer
+] unit-test
+
+[ ] [ "-foo\n-bar" "farkup" set-value ] unit-test
+
+[ "<ul><li>foo</li><li>bar</li></ul>" ] [
+    [ "farkup" farkup render ] with-string-writer
+] unit-test
+
+[ ] [ { 1 2 3 } "object" set-value ] unit-test
+
+[ t ] [
+    [ "object" inspector render ] with-string-writer
+    [ "object" value [ describe ] with-html-stream ] with-string-writer
+    =
+] unit-test
diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
index df1d1faa72..9c762eaa3a 100644
--- a/extra/html/components/components.factor
+++ b/extra/html/components/components.factor
@@ -4,7 +4,7 @@ USING: accessors kernel namespaces io math.parser assocs classes
 classes.tuple words arrays sequences splitting mirrors
 hashtables combinators continuations math strings
 fry locals calendar calendar.format xml.entities validators
-html.elements ;
+html.elements html.streams xmode.code2html farkup inspector ;
 IN: html.components
 
 SYMBOL: values
@@ -144,6 +144,27 @@ M: link render*
         link-title object>string escape-string write
     </a> ;
 
+! XMode code component
+TUPLE: code mode ;
+
+: <code> ( -- code )
+    code new ;
+
+M: code render*
+    [ string-lines ] [ drop ] [ mode>> value ] tri* htmlize-lines ;
+
+! Farkup component
+SINGLETON: farkup
+
+M: farkup render*
+    2drop string-lines "\n" join convert-farkup write ;
+
+! Inspector component
+SINGLETON: inspector
+
+M: inspector render*
+    2drop [ describe ] with-html-stream ;
+
 ! HTML component
 SINGLETON: html
 

From 6a1f38581692af69731eeefd7f4be570f0a94364 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Fri, 23 May 2008 22:37:55 -0500
Subject: [PATCH 16/66] Add failing unit test

---
 extra/locals/locals-tests.factor | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/extra/locals/locals-tests.factor b/extra/locals/locals-tests.factor
index c5adaa5e5e..87bc49f366 100755
--- a/extra/locals/locals-tests.factor
+++ b/extra/locals/locals-tests.factor
@@ -254,3 +254,14 @@ M:: fixnum next-method-test ( a -- b ) a call-next-method 1 + ;
 [ 3 ] [ 1 [let | a [ ] | 3 ] ] unit-test
 
 [ ] [ 1 2 [let | a [ ] b [ ] | ] ] unit-test
+
+:: a-word-with-locals ( a b -- ) ;
+
+: new-definition "IN: locals.tests\nUSING: math ;\n: a-word-with-locals ( -- x ) 2 3 + ;\n" ;
+
+[ ] [ new-definition eval ] unit-test
+
+[ t ] [
+    [ \ a-word-with-locals see ] with-string-writer
+    new-definition =
+] unit-test

From 9100398adfb1343506a4762dc7ec993d3c4e4e75 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Sat, 24 May 2008 01:28:48 -0500
Subject: [PATCH 17/66] Add more chloe tags, and tests

---
 extra/html/components/components.factor       | 13 ++++---
 extra/html/templates/chloe/chloe-tests.factor | 38 ++++++++++++++++++-
 extra/html/templates/chloe/chloe.factor       | 16 +++++---
 extra/html/templates/chloe/test/test8.xml     | 27 +++++++++++++
 4 files changed, 83 insertions(+), 11 deletions(-)
 create mode 100644 extra/html/templates/chloe/test/test8.xml

diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
index 9c762eaa3a..faef9ca1b5 100644
--- a/extra/html/components/components.factor
+++ b/extra/html/components/components.factor
@@ -15,6 +15,9 @@ SYMBOL: values
 
 : blank-values H{ } clone values set ;
 
+: prepare-value ( name object -- value name object )
+    [ [ value ] keep ] dip ; inline
+
 : from-tuple <mirror> values set ;
 
 : values-tuple values get object>> ;
@@ -35,7 +38,7 @@ GENERIC: render* ( value name render -- )
         [ -rot render* ] dip
         render-error
     ] [
-        [ [ value ] keep ] dip render*
+        prepare-value render*
     ] if* ;
 
 <PRIVATE
@@ -56,7 +59,7 @@ M: hidden render* drop "hidden" render-input ;
 : render-field ( value name size type -- )
     <input
         =type
-        [ number>string =size ] when*
+        [ object>string =size ] when*
         =name
         object>string =value
     input/> ;
@@ -85,8 +88,8 @@ TUPLE: textarea rows cols ;
 
 M: textarea render*
     <textarea
-        [ rows>> [ number>string =rows ] when* ]
-        [ cols>> [ number>string =cols ] when* ] bi
+        [ rows>> [ object>string =rows ] when* ]
+        [ cols>> [ object>string =cols ] when* ] bi
         =name
     textarea>
         object>string escape-string write
@@ -109,7 +112,7 @@ TUPLE: choice size multiple choices ;
 M: choice render*
     <select
         swap =name
-        dup size>> [ number>string =size ] when*
+        dup size>> [ object>string =size ] when*
         dup multiple>> [ "true" =multiple ] when
     select>
         [ choices>> value ] [ multiple>> ] bi
diff --git a/extra/html/templates/chloe/chloe-tests.factor b/extra/html/templates/chloe/chloe-tests.factor
index 3c52153eee..4d8d15c581 100644
--- a/extra/html/templates/chloe/chloe-tests.factor
+++ b/extra/html/templates/chloe/chloe-tests.factor
@@ -1,6 +1,6 @@
 USING: html.templates html.templates.chloe
 tools.test io.streams.string kernel sequences ascii boxes
-namespaces xml
+namespaces xml html.components
 splitting ;
 IN: html.templates.chloe.tests
 
@@ -87,3 +87,39 @@ SYMBOL: test7-aux?
         "test7" test-template call-template
     ] run-template
 ] unit-test
+
+[ ] [ blank-values ] unit-test
+
+[ ] [ "A label" "label" set-value ] unit-test
+
+SINGLETON: link-test
+
+M: link-test link-title drop "<Link Title>" ;
+
+M: link-test link-href drop "http://www.apple.com/foo&bar" ;
+
+[ ] [ link-test "link" set-value ] unit-test
+
+[ ] [ "int x = 5;" "code" set-value ] unit-test
+
+[ ] [ "c" "mode" set-value ] unit-test
+
+[ ] [ { 1 2 3 } "inspector" set-value ] unit-test
+
+[ ] [ "<p>a paragraph</p>" "html" set-value ] unit-test
+
+[ ] [ "sheeple" "field" set-value ] unit-test
+
+[ ] [ "a password" "password" set-value ] unit-test
+
+[ ] [ "a\nb\nc" "textarea" set-value ] unit-test
+
+[ ] [ "new york" "choice" set-value ] unit-test
+
+[ ] [ { "new york" "detroit" "minneapolis" } "choices" set-value ] unit-test
+    
+[ ] [
+    [
+        "test8" test-template call-template
+    ] run-template drop
+] unit-test
diff --git a/extra/html/templates/chloe/chloe.factor b/extra/html/templates/chloe/chloe.factor
index a01d424eb9..43834f896e 100644
--- a/extra/html/templates/chloe/chloe.factor
+++ b/extra/html/templates/chloe/chloe.factor
@@ -7,14 +7,13 @@ unicode.case tuple-syntax mirrors fry
 multiline xml xml.data xml.writer xml.utilities
 html.elements
 html.components
+html.templates
 http.server
 http.server.auth
 http.server.flows
 http.server.actions
-http.server.sessions
-http.server.templating
-http.server.boilerplate ;
-IN: http.server.templating.chloe
+http.server.sessions ;
+IN: html.templates.chloe
 
 ! Chloe is Ed's favorite web designer
 
@@ -207,7 +206,11 @@ STRING: button-tag-markup
     [ "name" required-attr ] dip render ;
 
 : attrs>slots ( tag tuple -- )
-    [ attrs>> ] [ <mirror> ] bi* '[ swap tag>> , set-at ] assoc-each ;
+    [ attrs>> ] [ <mirror> ] bi*
+    '[
+        swap tag>> dup "name" =
+        [ 2drop ] [ , set-at ] if
+    ] assoc-each ;
 
 : tuple-component-tag ( tag class -- )
     [ drop "name" required-attr ]
@@ -233,6 +236,9 @@ STRING: button-tag-markup
         ! Components
         { "label" [ label singleton-component-tag ] }
         { "link" [ link singleton-component-tag ] }
+        { "code" [ code tuple-component-tag ] }
+        { "farkup" [ farkup singleton-component-tag ] }
+        { "inspector" [ inspector singleton-component-tag ] }
         { "html" [ html singleton-component-tag ] }
 
         ! Forms
diff --git a/extra/html/templates/chloe/test/test8.xml b/extra/html/templates/chloe/test/test8.xml
new file mode 100644
index 0000000000..8e2ff2e8ad
--- /dev/null
+++ b/extra/html/templates/chloe/test/test8.xml
@@ -0,0 +1,27 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:label t:name="label" />
+
+	<t:link t:name="link" />
+
+	<t:code t:name="code" mode="mode" />
+
+	<t:farkup t:name="farkup" />
+
+	<t:inspector t:name="inspector" />
+
+	<t:html t:name="html" />
+
+	<t:field t:name="field" t:size="13" />
+
+	<t:password t:name="password" t:size="10" />
+
+	<t:textarea t:name="textarea" t:rows="5" t:cols="10" />
+
+	<t:choice t:name="choice" t:choices="choices" />
+
+	<t:checkbox t:name="checkbox">Checkbox</t:checkbox>
+
+</t:chloe>

From 3104cdb511537f6ba1f37925534520b6c6a0d92b Mon Sep 17 00:00:00 2001
From: Matthew Willis <matthew.willis@mac.com>
Date: Sat, 17 May 2008 17:07:40 -0700
Subject: [PATCH 18/66] Changed the unicode cairo sample to use actual unicode
 instead of a byte array.

---
 extra/cairo/samples/samples.factor | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/extra/cairo/samples/samples.factor b/extra/cairo/samples/samples.factor
index 402c3881f4..0805cd41c1 100644
--- a/extra/cairo/samples/samples.factor
+++ b/extra/cairo/samples/samples.factor
@@ -116,11 +116,11 @@ IN: cairo.samples
     cr cairo_fill ;
 
 : utf8 ( -- )
-    cr "Serif" CAIRO_FONT_SLANT_NORMAL CAIRO_FONT_WEIGHT_NORMAL
-    cairo_select_font_face
+    ! cr "kochi" CAIRO_FONT_SLANT_NORMAL CAIRO_FONT_WEIGHT_NORMAL
+    ! cairo_select_font_face
     cr 50 cairo_set_font_size
     "cairo_text_extents_t" malloc-object
-    cr B{ 230 151 165 230 156 172 232 170 158 } pick cairo_text_extents
+    cr "日本語" pick cairo_text_extents
     cr over
     [ cairo_text_extents_t-width 2 / ]
     [ cairo_text_extents_t-x_bearing ] bi +
@@ -129,7 +129,7 @@ IN: cairo.samples
     [ cairo_text_extents_t-y_bearing ] bi +
     128 swap - cairo_move_to
     free
-    cr B{ 230 151 165 230 156 172 232 170 158 } cairo_show_text
+    cr "日本語" cairo_show_text
     
     cr 1 0.2 0.2 0.6 cairo_set_source_rgba
     cr 6 cairo_set_line_width

From 06843caee91a4c9ee0a0d068e9fe024199c47d66 Mon Sep 17 00:00:00 2001
From: Matthew Willis <matthew.willis@mac.com>
Date: Sun, 18 May 2008 01:59:11 -0700
Subject: [PATCH 19/66] Fixed mistake introduced in the last patch.

---
 extra/cairo/samples/samples.factor | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/extra/cairo/samples/samples.factor b/extra/cairo/samples/samples.factor
index 0805cd41c1..3cc63922f8 100644
--- a/extra/cairo/samples/samples.factor
+++ b/extra/cairo/samples/samples.factor
@@ -116,8 +116,8 @@ IN: cairo.samples
     cr cairo_fill ;
 
 : utf8 ( -- )
-    ! cr "kochi" CAIRO_FONT_SLANT_NORMAL CAIRO_FONT_WEIGHT_NORMAL
-    ! cairo_select_font_face
+    cr "Sans" CAIRO_FONT_SLANT_NORMAL CAIRO_FONT_WEIGHT_NORMAL
+    cairo_select_font_face
     cr 50 cairo_set_font_size
     "cairo_text_extents_t" malloc-object
     cr "日本語" pick cairo_text_extents

From c64de056fbd9f7f060b17ffacb56755cbfe6dd47 Mon Sep 17 00:00:00 2001
From: Matthew Willis <matthew.willis@mac.com>
Date: Sat, 24 May 2008 12:06:59 -0700
Subject: [PATCH 20/66] First crack at pango.  Try "cairo.pango.gadgets" run

---
 extra/cairo/pango/gadgets/gadgets.factor |  20 +++
 extra/cairo/pango/pango.factor           | 175 +++++++++++++++++++++++
 2 files changed, 195 insertions(+)
 create mode 100644 extra/cairo/pango/gadgets/gadgets.factor
 create mode 100644 extra/cairo/pango/pango.factor

diff --git a/extra/cairo/pango/gadgets/gadgets.factor b/extra/cairo/pango/gadgets/gadgets.factor
new file mode 100644
index 0000000000..780881e872
--- /dev/null
+++ b/extra/cairo/pango/gadgets/gadgets.factor
@@ -0,0 +1,20 @@
+USING: cairo.pango cairo cairo.ffi cairo.gadgets
+alien.c-types kernel math ;
+IN: cairo.pango.gadgets
+
+: (pango-gadget) ( setup show -- gadget )
+    [ drop layout-size ]
+    [ compose [ with-pango ] curry <cached-cairo> ] 2bi ;
+
+: <pango-gadget> ( quot -- gadget )
+    [ cr layout pango_cairo_show_layout ] (pango-gadget) ;
+
+USING: prettyprint sequences ui.gadgets.panes ;
+: hello-pango ( -- )
+    50 [ 6 + ] map [
+        "Sans Bold " swap unparse append
+        [ layout-font "Hello, Pango!" layout-text ] curry
+        <pango-gadget> gadget.
+    ] each ;
+
+MAIN: hello-pango
diff --git a/extra/cairo/pango/pango.factor b/extra/cairo/pango/pango.factor
new file mode 100644
index 0000000000..789044f6e1
--- /dev/null
+++ b/extra/cairo/pango/pango.factor
@@ -0,0 +1,175 @@
+! Copyright (C) 2008 Matthew Willis.
+! See http://factorcode.org/license.txt for BSD license.
+!
+! pangocairo bindings, from pango/pangocairo.h
+
+USING: cairo.ffi alien.c-types math
+alien.syntax system combinators alien ;
+IN: cairo.pango
+
+<< "pangocairo" {
+!    { [ os winnt? ] [ "libpangocairo-1.dll" ] }
+!    { [ os macosx? ] [ "libpangocairo.dylib" ] }
+    { [ os unix? ] [ "libpangocairo-1.0.so" ] }
+} cond "cdecl" add-library >>
+
+LIBRARY: pangocairo
+
+TYPEDEF: void* PangoCairoFont
+TYPEDEF: void* PangoCairoFontMap
+TYPEDEF: void* PangoFontMap
+
+FUNCTION: PangoFontMap*
+pango_cairo_font_map_new  ( ) ;
+
+FUNCTION: PangoFontMap*
+pango_cairo_font_map_new_for_font_type ( cairo_font_type_t fonttype ) ;
+
+FUNCTION: PangoFontMap*
+pango_cairo_font_map_get_default ( ) ;
+
+FUNCTION: cairo_font_type_t
+pango_cairo_font_map_get_font_type ( PangoCairoFontMap* fontmap ) ;
+
+FUNCTION: void
+pango_cairo_font_map_set_resolution ( PangoCairoFontMap* fontmap, double dpi ) ;
+
+FUNCTION: double
+pango_cairo_font_map_get_resolution ( PangoCairoFontMap* fontmap ) ;
+
+FUNCTION: PangoContext*
+pango_cairo_font_map_create_context ( PangoCairoFontMap* fontmap ) ;
+
+FUNCTION: cairo_scaled_font_t*
+pango_cairo_font_get_scaled_font ( PangoCairoFont* font ) ;
+
+! Update a Pango context for the current state of a cairo context
+FUNCTION: void
+pango_cairo_update_context ( cairo_t* cr, PangoContext* context ) ;
+
+FUNCTION: void
+pango_cairo_context_set_font_options ( PangoContext* context, cairo_font_options_t* options ) ;
+
+FUNCTION: cairo_font_options_t*
+pango_cairo_context_get_font_options ( PangoContext* context ) ;
+
+FUNCTION: void
+pango_cairo_context_set_resolution ( PangoContext* context, double dpi ) ;
+
+FUNCTION: double
+pango_cairo_context_get_resolution ( PangoContext* context ) ;
+
+! Convenience
+FUNCTION: PangoLayout*
+pango_cairo_create_layout ( cairo_t* cr ) ;
+
+FUNCTION: void
+pango_cairo_update_layout ( cairo_t* cr, PangoLayout* layout ) ;
+
+! Rendering
+FUNCTION: void
+pango_cairo_show_glyph_string ( cairo_t* cr, PangoFont* font, PangoGlyphString* glyphs ) ;
+
+FUNCTION: void
+pango_cairo_show_layout_line ( cairo_t* cr, PangoLayoutLine* line ) ;
+
+FUNCTION: void
+pango_cairo_show_layout ( cairo_t* cr, PangoLayout* layout ) ;
+
+FUNCTION: void
+pango_cairo_show_error_underline ( cairo_t* cr, double x, double y, double width, double height ) ;
+
+! Rendering to a path
+FUNCTION: void
+pango_cairo_glyph_string_path ( cairo_t* cr, PangoFont* font, PangoGlyphString* glyphs ) ;
+
+FUNCTION: void
+pango_cairo_layout_line_path  ( cairo_t* cr, PangoLayoutLine* line ) ;
+
+FUNCTION: void
+pango_cairo_layout_path ( cairo_t* cr, PangoLayout* layout ) ;
+
+FUNCTION: void
+pango_cairo_error_underline_path ( cairo_t* cr, double x, double y, double width, double height ) ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+! Helpful functions from other parts of pango
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: PANGO_SCALE 1024 ;
+
+FUNCTION: void
+pango_layout_set_text ( PangoLayout* layout, char* text, int length ) ;
+
+FUNCTION: char*
+pango_layout_get_text ( PangoLayout* layout ) ;
+
+FUNCTION: void
+pango_layout_get_size ( PangoLayout* layout, int* width, int* height ) ;
+
+TYPEDEF: void* PangoFontDescription
+
+FUNCTION: PangoFontDescription*
+pango_font_description_from_string ( char* str ) ;
+
+FUNCTION: char*
+pango_font_description_to_string ( PangoFontDescription* desc ) ;
+
+FUNCTION: char*
+pango_font_description_to_filename ( PangoFontDescription* desc ) ;
+
+FUNCTION: void
+pango_layout_set_font_description ( PangoLayout* layout, PangoFontDescription* desc ) ;
+
+FUNCTION: PangoFontDescription*
+pango_layout_get_font_description ( PangoLayout* layout ) ;
+
+FUNCTION: void
+pango_layout_get_pixel_size ( PangoLayout* layout, int* width, int* height ) ;
+
+FUNCTION: void
+pango_font_description_free ( PangoFontDescription* desc ) ;
+
+TYPEDEF: void* gpointer
+
+FUNCTION: void
+g_object_unref ( gpointer object ) ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+! Higher level words and combinators
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+USING: destructors accessors namespaces kernel cairo ;
+
+TUPLE: pango-layout alien ;
+C: <pango-layout> pango-layout
+M: pango-layout dispose ( alien -- ) alien>> g_object_unref ;
+
+: layout ( -- pango-layout ) pango-layout get ;
+
+: (with-pango) ( layout quot -- )
+    >r alien>> pango-layout r> with-variable ; inline
+
+: with-pango ( quot -- )
+    cr pango_cairo_create_layout <pango-layout> swap
+    [ (with-pango) ] curry with-disposal ; inline
+
+: pango-layout-get-pixel-size ( layout -- width height )
+    0 <int> 0 <int> [ pango_layout_get_pixel_size ] 2keep
+    [ *int ] bi@ ;
+
+: dummy-pango ( quot -- )
+    >r CAIRO_FORMAT_ARGB32 0 0 cairo_image_surface_create
+    r> [ with-pango ] curry with-cairo-from-surface ; inline
+
+: layout-size ( quot -- width height )
+    [ layout pango-layout-get-pixel-size ] compose dummy-pango ; inline
+
+: layout-font ( str -- )
+    pango_font_description_from_string
+    dup zero? [ "pango: not a valid font." throw ] when
+    layout over pango_layout_set_font_description
+    pango_font_description_free ;
+
+: layout-text ( str -- )
+    layout swap -1 pango_layout_set_text ;

From dc1a423f88025e46ded88abe1db54f47e1786e87 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Sat, 24 May 2008 21:49:48 -0500
Subject: [PATCH 21/66] Document clumps

---
 core/sequences/sequences.factor               |   2 +-
 core/splitting/splitting-docs.factor          |  82 ++++++--
 core/splitting/splitting.factor               |   4 +-
 extra/http/server/auth/admin/admin.factor     | 179 ------------------
 extra/http/server/auth/admin/user-list.xml    |   9 -
 extra/http/server/auth/admin/user-summary.xml |   9 -
 extra/webapps/pastebin/paste-list.xml         |  15 --
 extra/webapps/pastebin/pastebin-common.xml    |  31 +++
 extra/webapps/pastebin/pastebin.xml           |  35 ++--
 .../user-admin}/edit-user.xml                 |   0
 .../admin => webapps/user-admin}/new-user.xml |   0
 extra/webapps/user-admin/user-admin.factor    | 160 ++++++++++++++++
 .../user-admin/user-admin.xml}                |   0
 extra/webapps/user-admin/user-list.xml        |  13 ++
 14 files changed, 289 insertions(+), 250 deletions(-)
 delete mode 100644 extra/http/server/auth/admin/admin.factor
 delete mode 100644 extra/http/server/auth/admin/user-list.xml
 delete mode 100644 extra/http/server/auth/admin/user-summary.xml
 delete mode 100644 extra/webapps/pastebin/paste-list.xml
 create mode 100644 extra/webapps/pastebin/pastebin-common.xml
 rename extra/{http/server/auth/admin => webapps/user-admin}/edit-user.xml (100%)
 rename extra/{http/server/auth/admin => webapps/user-admin}/new-user.xml (100%)
 create mode 100644 extra/webapps/user-admin/user-admin.factor
 rename extra/{http/server/auth/admin/admin.xml => webapps/user-admin/user-admin.xml} (100%)
 create mode 100644 extra/webapps/user-admin/user-list.xml

diff --git a/core/sequences/sequences.factor b/core/sequences/sequences.factor
index cbddfa7d28..4153430514 100755
--- a/core/sequences/sequences.factor
+++ b/core/sequences/sequences.factor
@@ -680,7 +680,7 @@ PRIVATE>
 : unclip ( seq -- rest first )
     [ rest ] [ first ] bi ;
 
-: unclip-last ( seq -- butfirst last )
+: unclip-last ( seq -- butlast last )
     [ but-last ] [ peek ] bi ;
 
 : unclip-slice ( seq -- rest first )
diff --git a/core/splitting/splitting-docs.factor b/core/splitting/splitting-docs.factor
index 5000dbf5fd..1beafc710a 100644
--- a/core/splitting/splitting-docs.factor
+++ b/core/splitting/splitting-docs.factor
@@ -1,6 +1,25 @@
 USING: help.markup help.syntax sequences strings ;
 IN: splitting
 
+ARTICLE: "groups-clumps" "Groups and clumps"
+"A virtual sequence for splitting a sequence into disjoint, fixed-length subsequences:"
+{ $subsection groups }
+{ $subsection <groups> }
+{ $subsection <sliced-groups> }
+"A virtual sequence for splitting a sequence into overlapping, fixed-length subsequences:"
+{ $subsection clumps }
+{ $subsection <clumps> }
+{ $subsection <sliced-clumps> }
+"The difference can be summarized as the following:"
+{ $list
+    { "With groups, the subsequences form the original sequence when concatenated:"
+        { $unchecked-example "dup n groups concat sequence= ." "t" }
+    }
+    { "With clumps, collecting the first element of each subsequence but the last one, together with the last subseqence, yields the original sequence:"
+        { $unchecked-example "dup n clumps unclip-last >r [ first ] map r> append sequence= ." "t" }
+    }
+} ;
+
 ARTICLE: "sequences-split" "Splitting sequences"
 "Splitting sequences at occurrences of subsequences:"
 { $subsection ?head }
@@ -9,14 +28,9 @@ ARTICLE: "sequences-split" "Splitting sequences"
 { $subsection ?tail-slice }
 { $subsection split1 }
 { $subsection split }
-"Grouping elements:"
-{ $subsection group }
-"A virtual sequence for grouping elements:"
-{ $subsection groups }
-{ $subsection <groups> }
-{ $subsection <sliced-groups> }
 "Splitting a string into lines:"
-{ $subsection string-lines } ;
+{ $subsection string-lines }
+{ $subsection "groups-clumps" } ;
 
 ABOUT: "sequences-split"
 
@@ -36,19 +50,22 @@ HELP: split
 { $examples { $example "USING: prettyprint splitting ;" "\"hello world-how are you?\" \" -\" split ." "{ \"hello\" \"world\" \"how\" \"are\" \"you?\" }" } } ;
 
 HELP: groups
-{ $class-description "Instances are virtual sequences whose elements are fixed-length subsequences or slices of an underlying sequence. Groups are mutable and resizable if the underlying sequence is mutable and resizable, respectively."
+{ $class-description "Instances are virtual sequences whose elements are disjoint fixed-length subsequences of an underlying sequence. Groups are mutable and resizable if the underlying sequence is mutable and resizable, respectively."
 $nl
 "New groups are created by calling " { $link <groups> } " and " { $link <sliced-groups> } "." }
 { $see-also group } ;
 
 HELP: group
 { $values { "seq" "a sequence" } { "n" "a non-negative integer" } { "array" "a sequence of sequences" } }
-{ $description "Splits the sequence into groups of " { $snippet "n" } " elements and collects the groups into a new array." }
-{ $notes "If the sequence length is not a multiple of " { $snippet "n" } ", the final subsequence in the list will be shorter than " { $snippet "n" } " elements." } ;
+{ $description "Splits the sequence into disjoint groups of " { $snippet "n" } " elements and collects the groups into a new array." }
+{ $notes "If the sequence length is not a multiple of " { $snippet "n" } ", the final subsequence in the list will be shorter than " { $snippet "n" } " elements." }
+{ $examples
+    { $example "USING: splitting prettyprint ;" "{ 3 1 3 3 7 } 2 group ." "{ { 3 1 } { 3 3 } { 7 } }" }
+} ;
 
 HELP: <groups>
 { $values { "seq" "a sequence" } { "n" "a non-negative integer" } { "groups" groups } }
-{ $description "Outputs a virtual sequence whose elements are subsequences consisting of groups of " { $snippet "n" } " elements from the underlying sequence." }
+{ $description "Outputs a virtual sequence whose elements are disjoint subsequences of " { $snippet "n" } " elements from the underlying sequence." }
 { $examples
     { $example
         "USING: arrays kernel prettyprint sequences splitting ;"
@@ -58,7 +75,7 @@ HELP: <groups>
 
 HELP: <sliced-groups>
 { $values { "seq" "a sequence" } { "n" "a non-negative integer" } { "groups" groups } }
-{ $description "Outputs a virtual sequence whose elements are slices consisting of groups of " { $snippet "n" } " elements from the underlying sequence." }
+{ $description "Outputs a virtual sequence whose elements are overlapping subsequences of " { $snippet "n" } " elements from the underlying sequence." }
 { $examples
     { $example
         "USING: arrays kernel prettyprint sequences splitting ;"
@@ -68,7 +85,46 @@ HELP: <sliced-groups>
     }
 } ;
 
-{ group <groups> <sliced-groups> } related-words
+HELP: clumps
+{ $class-description "Instances are virtual sequences whose elements are overlapping fixed-length subsequences o an underlying sequence. Clumps are mutable and resizable if the underlying sequence is mutable and resizable, respectively."
+$nl
+"New clumps are created by calling " { $link <clumps> } " and " { $link <sliced-clumps> } "." } ;
+
+HELP: clump
+{ $values { "seq" "a sequence" } { "n" "a non-negative integer" } { "array" "a sequence of sequences" } }
+{ $description "Splits the sequence into overlapping clumps of " { $snippet "n" } " elements and collects the clumps into a new array." }
+{ $errors "Throws an error if " { $snippet "n" } " is smaller than the length of the sequence." }
+{ $examples
+    { $example "USING: splitting prettyprint ;" "{ 3 1 3 3 7 } 2 clump ." "{ { 3 1 } { 1 3 } { 3 3 } { 3 7 } }" }
+} ;
+
+HELP: <clumps>
+{ $values { "seq" "a sequence" } { "n" "a non-negative integer" } { "clumps" clumps } }
+{ $description "Outputs a virtual sequence whose elements are overlapping subsequences of " { $snippet "n" } " elements from the underlying sequence." }
+{ $examples
+    "Running averages:"
+    { $example
+        "USING: splitting sequences math prettyprint kernel ;"
+        "IN: scratchpad"
+        ": share-price"
+        "    { 13/50 51/100 13/50 1/10 4/5 17/20 33/50 3/25 19/100 3/100 } ;"
+        ""
+        "share-price 4 <clumps> [ [ sum ] [ length ] bi / ] map ."
+        "{ 113/400 167/400 201/400 241/400 243/400 91/200 1/4 }"
+    }
+} ;
+
+HELP: <sliced-clumps>
+{ $values { "seq" "a sequence" } { "n" "a non-negative integer" } { "clumps" clumps } }
+{ $description "Outputs a virtual sequence whose elements are overlapping slices of " { $snippet "n" } " elements from the underlying sequence." } ;
+
+{ clumps groups } related-words
+
+{ clump group } related-words
+
+{ <clumps> <groups> } related-words
+
+{ <sliced-clumps> <sliced-groups> } related-words
 
 HELP: ?head
 { $values { "seq" "a sequence" } { "begin" "a sequence" } { "newseq" "a new sequence" } { "?" "a boolean" } }
diff --git a/core/splitting/splitting.factor b/core/splitting/splitting.factor
index 9f6ae75d32..62e7ef3782 100755
--- a/core/splitting/splitting.factor
+++ b/core/splitting/splitting.factor
@@ -44,7 +44,7 @@ M: sliced-groups nth group@ <slice> ;
 
 TUPLE: clumps < abstract-groups ;
 
-: <clumps> ( seq n -- groups )
+: <clumps> ( seq n -- clumps )
     clumps construct-groups ; inline
 
 M: clumps length
@@ -58,7 +58,7 @@ M: clumps group@
 
 TUPLE: sliced-clumps < groups ;
 
-: <sliced-clumps> ( seq n -- groups )
+: <sliced-clumps> ( seq n -- clumps )
     sliced-clumps construct-groups ; inline
 
 M: sliced-clumps nth group@ <slice> ;
diff --git a/extra/http/server/auth/admin/admin.factor b/extra/http/server/auth/admin/admin.factor
deleted file mode 100644
index 21e1a6181b..0000000000
--- a/extra/http/server/auth/admin/admin.factor
+++ /dev/null
@@ -1,179 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.
-! See http://factorcode.org/license.txt for BSD license.
-USING: kernel sequences accessors namespaces combinators words
-assocs locals db.tuples arrays splitting strings qualified
-
-http.server.templating.chloe
-http.server.boilerplate
-http.server.auth.providers
-http.server.auth.providers.db
-http.server.auth.login
-http.server.auth
-http.server.forms
-http.server.components.inspector
-http.server.validators
-http.server.sessions
-http.server.actions
-http.server.crud
-http.server ;
-EXCLUDE: http.server.components => string? number? ;
-IN: http.server.auth.admin
-
-: admin-template ( name -- template )
-    "resource:extra/http/server/auth/admin/" swap ".xml" 3append <chloe> ;
-
-: words>strings ( seq -- seq' )
-    [ [ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ] map ;
-
-: strings>words ( seq -- seq' )
-    [ ":" split1 swap lookup ] map ;
-
-: <capabilities> ( id -- component )
-    capabilities get words>strings <menu> ;
-
-: <new-user-form> ( -- form )
-    "user" <form>
-        "new-user" admin-template >>edit-template
-        "username" <string> add-field
-        "realname" <string> add-field
-        "new-password" <password> t >>required add-field
-        "verify-password" <password> t >>required add-field
-        "email" <email> add-field
-        "capabilities" <capabilities> add-field ;
-
-: <edit-user-form> ( -- form )
-    "user" <form>
-        "edit-user" admin-template >>edit-template
-        "user-summary" admin-template >>summary-template
-        "username" <string> hidden >>renderer add-field
-        "realname" <string> add-field
-        "new-password" <password> add-field
-        "verify-password" <password> add-field
-        "email" <email> add-field
-        "profile" <inspector> add-field
-        "capabilities" <capabilities> add-field ;
-
-: <user-list-form> ( -- form )
-    "user-list" <form>
-        "user-list" admin-template >>view-template
-        "list" <edit-user-form> +unordered+ <list> add-field ;
-
-:: <new-user-action> ( form ctor next -- action )
-    <action>
-        [
-            blank-values
-
-            "username" get ctor call
-
-            {
-                [ username>> "username" set-value ]
-                [ realname>> "realname" set-value ]
-                [ email>> "email" set-value ]
-                [ profile>> "profile" set-value ]
-            } cleave
-        ] >>init
-
-        [ form edit-form ] >>display
-
-        [
-            blank-values
-
-            form validate-form
-
-            same-password-twice
-
-            user new "username" value >>username select-tuple
-            [ user-exists ] when
-
-            "username" value <user>
-                "realname" value >>realname
-                "email" value >>email
-                "new-password" value >>encoded-password
-                H{ } clone >>profile
-
-            insert-tuple
-
-            next f <standard-redirect>
-        ] >>submit ;
-    
-:: <edit-user-action> ( form ctor next -- action )
-    <action>
-        { { "username" [ v-required ] } } >>get-params
-
-        [
-            blank-values
-
-            "username" get ctor call select-tuple
-
-            {
-                [ username>> "username" set-value ]
-                [ realname>> "realname" set-value ]
-                [ email>> "email" set-value ]
-                [ profile>> "profile" set-value ]
-                [ capabilities>> words>strings "capabilities" set-value ]
-            } cleave
-        ] >>init
-
-        [ form edit-form ] >>display
-
-        [
-            blank-values
-
-            form validate-form
-
-            "username" value <user> select-tuple
-                "realname" value >>realname
-                "email" value >>email
-
-            { "new-password" "verify-password" }
-            [ value empty? ] all? [
-                same-password-twice
-                "new-password" value >>encoded-password
-            ] unless
-
-            "capabilities" value {
-                { [ dup string? ] [ 1array ] }
-                { [ dup array? ] [ ] }
-            } cond strings>words >>capabilities
-
-            update-tuple
-
-            next f <standard-redirect>
-        ] >>submit ;
-
-:: <delete-user-action> ( ctor next -- action )
-    <action>
-        { { "username" [ ] } } >>post-params
-
-        [
-            "username" get
-            [ <user> select-tuple 1 >>deleted update-tuple ]
-            [ logout-all-sessions ]
-            bi
-
-            next f <standard-redirect>
-        ] >>submit ;
-
-TUPLE: user-admin < dispatcher ;
-
-SYMBOL: can-administer-users?
-
-can-administer-users? define-capability
-
-:: <user-admin> ( -- responder )
-    [let | ctor [ [ <user> ] ] |
-        user-admin new-dispatcher
-            <user-list-form> ctor <list-action> "" add-responder
-            <new-user-form> ctor "$user-admin" <new-user-action> "new" add-responder
-            <edit-user-form> ctor "$user-admin" <edit-user-action> "edit" add-responder
-            ctor "$user-admin" <delete-user-action> "delete" add-responder
-        <boilerplate>
-            "admin" admin-template >>template
-        { can-administer-users? } <protected>
-    ] ;
-
-: make-admin ( username -- )
-    <user>
-    select-tuple
-    [ can-administer-users? suffix ] change-capabilities
-    update-tuple ;
diff --git a/extra/http/server/auth/admin/user-list.xml b/extra/http/server/auth/admin/user-list.xml
deleted file mode 100644
index 520b7f2512..0000000000
--- a/extra/http/server/auth/admin/user-list.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:title>Users</t:title>
-
-	<t:summary t:component="list" />
-
-</t:chloe>
diff --git a/extra/http/server/auth/admin/user-summary.xml b/extra/http/server/auth/admin/user-summary.xml
deleted file mode 100644
index c426e7c072..0000000000
--- a/extra/http/server/auth/admin/user-summary.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:a t:href="$user-admin/edit" t:query="username">
-		<t:view t:component="username" />
-	</t:a>
-
-</t:chloe>
diff --git a/extra/webapps/pastebin/paste-list.xml b/extra/webapps/pastebin/paste-list.xml
deleted file mode 100644
index c91aa6fc42..0000000000
--- a/extra/webapps/pastebin/paste-list.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:title>Pastebin</t:title>
-
-	<table width="100%">
-		<th align="left" width="50%">Summary:</th>
-		<th align="left" width="100">Paste by:</th>
-		<th align="left" width="200">Date:</th>
-
-		<t:summary t:component="pastes" />
-	</table>
-
-</t:chloe>
diff --git a/extra/webapps/pastebin/pastebin-common.xml b/extra/webapps/pastebin/pastebin-common.xml
new file mode 100644
index 0000000000..b99cf28753
--- /dev/null
+++ b/extra/webapps/pastebin/pastebin-common.xml
@@ -0,0 +1,31 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:atom t:title="Pastebin - Atom" t:href="$pastebin/feed.xml" />
+
+	<t:style t:include="resource:extra/webapps/pastebin/pastebin.css" />
+
+	<div class="navbar">
+
+		  <t:a t:href="$pastebin/list">Pastes</t:a>
+		| <t:a t:href="$pastebin/new-paste">New Paste</t:a>
+		| <t:a t:href="$pastebin/feed.xml">Atom Feed</t:a>
+
+		<t:if t:code="http.server.sessions:uid">
+
+			<t:if t:code="http.server.auth.login:allow-edit-profile?">
+				| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
+			</t:if>
+
+			| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
+
+		</t:if>
+
+	</div>
+
+	<h1><t:write-title /></h1>
+
+        <t:call-next-template />
+
+</t:chloe>
diff --git a/extra/webapps/pastebin/pastebin.xml b/extra/webapps/pastebin/pastebin.xml
index 7ca4c95f8e..46604598ce 100644
--- a/extra/webapps/pastebin/pastebin.xml
+++ b/extra/webapps/pastebin/pastebin.xml
@@ -2,29 +2,20 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:atom t:title="Pastebin - Atom" t:href="$pastebin/feed.xml" />
+	<t:title>Pastebin</t:title>
 
-	<t:style t:include="resource:extra/webapps/pastebin/pastebin.css" />
+	<table width="100%">
+		<th align="left" width="50%">Summary:</th>
+		<th align="left" width="100">Paste by:</th>
+		<th align="left" width="200">Date:</th>
 
-	<div class="navbar">
-		  <t:a t:href="$pastebin/list">Pastes</t:a>
-		| <t:a t:href="$pastebin/new-paste">New Paste</t:a>
-		| <t:a t:href="$pastebin/feed.xml">Atom Feed</t:a>
-
-		<t:if t:code="http.server.sessions:uid">
-
-			<t:if t:code="http.server.auth.login:allow-edit-profile?">
-				| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
-			</t:if>
-
-			| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
-
-		</t:if>
-
-	</div>
-
-	<h1><t:write-title /></h1>
-
-        <t:call-next-template />
+		<t:each-tuple t:values="pastes">
+			<tr>
+				<td><t:a t:href="$pastebin/view-paste" t:query="id"><t:field t:name="summary" /></t:a></td>
+				<td><t:field t:name="author" /></td>
+				<td><t:field t:name="date" /></td>
+			</tr>
+		</t:each-tuple>
+	</table>
 
 </t:chloe>
diff --git a/extra/http/server/auth/admin/edit-user.xml b/extra/webapps/user-admin/edit-user.xml
similarity index 100%
rename from extra/http/server/auth/admin/edit-user.xml
rename to extra/webapps/user-admin/edit-user.xml
diff --git a/extra/http/server/auth/admin/new-user.xml b/extra/webapps/user-admin/new-user.xml
similarity index 100%
rename from extra/http/server/auth/admin/new-user.xml
rename to extra/webapps/user-admin/new-user.xml
diff --git a/extra/webapps/user-admin/user-admin.factor b/extra/webapps/user-admin/user-admin.factor
new file mode 100644
index 0000000000..172ab62c50
--- /dev/null
+++ b/extra/webapps/user-admin/user-admin.factor
@@ -0,0 +1,160 @@
+! Copyright (C) 2008 Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: kernel sequences accessors namespaces combinators words
+assocs db.tuples arrays splitting strings validators
+html.elements
+html.components
+html.templates.chloe
+http.server.boilerplate
+http.server.auth.providers
+http.server.auth.providers.db
+http.server.auth.login
+http.server.auth
+http.server.sessions
+http.server.actions
+http.server.crud
+http.server ;
+IN: webapps.user-admin
+
+: admin-template ( name -- template )
+    "resource:extra/webapps/user-admin/" swap ".xml" 3append <chloe> ;
+
+: words>strings ( seq -- seq' )
+    [ [ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ] map ;
+
+: strings>words ( seq -- seq' )
+    [ ":" split1 swap lookup ] map ;
+
+: <user-list-action> ( -- action )
+    <action>
+        [ f <user> select-tuples "users" set-value ] >>init
+        [ "user-list" admin-template <html-content> ] >>display ;
+
+: <new-user-action> ( -- action )
+    <action>
+        [
+            "username" param <user> {
+                [ username>> "username" set-value ]
+                [ realname>> "realname" set-value ]
+                [ email>> "email" set-value ]
+                [ profile>> "profile" set-value ]
+            } cleave
+
+            capabilities get "all-capabilities" set-value
+        ] >>init
+
+        [ "new-user" admin-template <html-content> ] >>display
+
+        [
+            {
+                { "username" [ v-username ] }
+                { "realname" [ v-one-line ] }
+                { "new-password" [ v-password ] }
+                { "verify-password" [ v-password ] }
+                { "email" [ [ v-email ] v-optional ] }
+                { "capabilities" [ ] }
+            } validate-params
+
+            same-password-twice
+
+            user new "username" value >>username select-tuple
+            [ user-exists ] when
+        ] >>validate
+
+        [
+            "username" value <user>
+                "realname" value >>realname
+                "email" value >>email
+                "new-password" value >>encoded-password
+                H{ } clone >>profile
+
+            insert-tuple
+
+            "$user-admin" f <standard-redirect>
+        ] >>submit ;
+    
+: <edit-user-action> ( -- action )
+    <action>
+        [
+            { { "username" [ v-username ] } } validate-params
+
+            "username" value <user> select-tuple {
+                [ username>> "username" set-value ]
+                [ realname>> "realname" set-value ]
+                [ email>> "email" set-value ]
+                [ profile>> "profile" set-value ]
+                [ capabilities>> words>strings "capabilities" set-value ]
+            } cleave
+
+            capabilities get "all-capabilities" set-value
+        ] >>init
+
+        [ "edit-user" admin-template <html-content> ] >>display
+
+        [
+            {
+                { "username" [ v-username ] }
+                { "realname" [ v-one-line ] }
+                { "new-password" [ [ v-password ] v-optional ] }
+                { "verify-password" [ [ v-password ] v-optional ] }
+                { "email" [ [ v-email ] v-optional ] }
+                { "capabilities" [ ] }
+            } validate-params
+
+            "new-password" "verify-password"
+            [ value empty? ] both? [
+                same-password-twice
+            ] unless
+        ] >>validate
+
+        [
+            "username" value <user> select-tuple
+                "realname" value >>realname
+                "email" value >>email
+
+            "new-password" value empty? [ drop ] [
+                "new-password" value >>encoded-password
+            ] if
+
+            "capabilities" value {
+                { [ dup string? ] [ 1array ] }
+                { [ dup array? ] [ ] }
+            } cond strings>words >>capabilities
+
+            update-tuple
+
+            "$user-admin" f <standard-redirect>
+        ] >>submit ;
+
+: <delete-user-action> ( -- action )
+    <action>
+        [
+            { { "username" [ v-username ] } } validate-params
+            [ <user> select-tuple 1 >>deleted update-tuple ]
+            [ logout-all-sessions ]
+            bi
+
+            "$user-admin" f <standard-redirect>
+        ] >>submit ;
+
+TUPLE: user-admin < dispatcher ;
+
+SYMBOL: can-administer-users?
+
+can-administer-users? define-capability
+
+: <user-admin> ( -- responder )
+    user-admin new-dispatcher
+        <user-list-action> "" add-responder
+        <new-user-action> "new" add-responder
+        <edit-user-action> "edit" add-responder
+        <delete-user-action> "delete" add-responder
+    <boilerplate>
+        "admin" admin-template >>template
+    { can-administer-users? } <protected> ;
+
+: make-admin ( username -- )
+    <user>
+    select-tuple
+    [ can-administer-users? suffix ] change-capabilities
+    update-tuple ;
diff --git a/extra/http/server/auth/admin/admin.xml b/extra/webapps/user-admin/user-admin.xml
similarity index 100%
rename from extra/http/server/auth/admin/admin.xml
rename to extra/webapps/user-admin/user-admin.xml
diff --git a/extra/webapps/user-admin/user-list.xml b/extra/webapps/user-admin/user-list.xml
new file mode 100644
index 0000000000..6887308754
--- /dev/null
+++ b/extra/webapps/user-admin/user-list.xml
@@ -0,0 +1,13 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>Users</t:title>
+
+	<t:each-tuple t:values="users">
+		<t:a t:href="$user-admin/edit" t:query="username">
+			<t:label t:name="username" />
+		</t:a>
+	</t:each-tuple>
+
+</t:chloe>

From 5cb13132af08a2615889fdaf0b01404905f96377 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Sun, 25 May 2008 19:44:37 -0500
Subject: [PATCH 22/66] adjoin and conjoin words added

---
 core/combinators/combinators.factor         |  4 ++--
 core/compiler/units/units.factor            |  2 +-
 core/generator/fixup/fixup.factor           |  4 ++--
 core/sequences/sequences-docs.factor        | 18 +-----------------
 core/sequences/sequences-tests.factor       |  4 ++--
 core/sequences/sequences.factor             |  2 --
 core/sets/sets-docs.factor                  | 18 ++++++++++++++++++
 core/sets/sets.factor                       | 12 ++++++++----
 extra/multi-methods/multi-methods.factor    |  2 +-
 extra/trees/splay/splay-tests.factor        |  2 +-
 extra/ui/tools/interactor/interactor.factor |  2 +-
 11 files changed, 37 insertions(+), 33 deletions(-)

diff --git a/core/combinators/combinators.factor b/core/combinators/combinators.factor
index d33edfab30..f6873429fe 100755
--- a/core/combinators/combinators.factor
+++ b/core/combinators/combinators.factor
@@ -95,10 +95,10 @@ M: hashtable hashcode*
 
 : (distribute-buckets) ( buckets pair keys -- )
     dup t eq? [
-        drop [ swap push-new ] curry each
+        drop [ swap adjoin ] curry each
     ] [
         [
-            >r 2dup r> hashcode pick length rem rot nth push-new
+            >r 2dup r> hashcode pick length rem rot nth adjoin
         ] each 2drop
     ] if ;
 
diff --git a/core/compiler/units/units.factor b/core/compiler/units/units.factor
index a31cd8de16..11c81f4097 100755
--- a/core/compiler/units/units.factor
+++ b/core/compiler/units/units.factor
@@ -14,7 +14,7 @@ TUPLE: redefine-error def ;
     { { "Continue" t } } throw-restarts drop ;
 
 : add-once ( key assoc -- )
-    2dup key? [ over redefine-error ] when dupd set-at ;
+    2dup key? [ over redefine-error ] when conjoin ;
 
 : (remember-definition) ( definition loc assoc -- )
     >r over set-where r> add-once ;
diff --git a/core/generator/fixup/fixup.factor b/core/generator/fixup/fixup.factor
index 06895cd8ac..b38d70fb80 100755
--- a/core/generator/fixup/fixup.factor
+++ b/core/generator/fixup/fixup.factor
@@ -102,13 +102,13 @@ M: frame-required fixup* drop ;
 
 M: integer fixup* , ;
 
-: push-new* ( obj table -- n )
+: adjoin* ( obj table -- n )
     2dup swap [ eq? ] curry find drop
     [ 2nip ] [ dup length >r push r> ] if* ;
 
 SYMBOL: literal-table
 
-: add-literal ( obj -- n ) literal-table get push-new* ;
+: add-literal ( obj -- n ) literal-table get adjoin* ;
 
 : add-dlsym-literals ( symbol dll -- )
     >r string>symbol r> 2array literal-table get push-all ;
diff --git a/core/sequences/sequences-docs.factor b/core/sequences/sequences-docs.factor
index 351ba89692..2c1a3b8ab9 100755
--- a/core/sequences/sequences-docs.factor
+++ b/core/sequences/sequences-docs.factor
@@ -191,7 +191,6 @@ $nl
 "Other destructive words:"
 { $subsection move }
 { $subsection exchange }
-{ $subsection push-new }
 { $subsection copy }
 { $subsection replace-slice }
 { $see-also set-nth push pop "sequences-stacks" } ;
@@ -624,22 +623,7 @@ HELP: replace-slice
 { $errors "Throws an error if " { $snippet "new" } " contains elements whose types are not permissible in " { $snippet "seq" } "." }
 { $side-effects "seq" } ;
 
-HELP: push-new
-{ $values { "elt" object } { "seq" "a resizable mutable sequence" } }
-{ $description "Removes all elements equal to " { $snippet "elt" } ", and adds " { $snippet "elt" } " at the end of the sequence." }
-{ $examples
-    { $example
-        "USING: namespaces prettyprint sequences ;"
-        "V{ \"beans\" \"salsa\" \"cheese\" } \"v\" set"
-        "\"nachos\" \"v\" get push-new"
-        "\"salsa\" \"v\" get push-new"
-        "\"v\" get ."
-        "V{ \"beans\" \"cheese\" \"nachos\" \"salsa\" }"
-    }
-}
-{ $side-effects "seq" } ;
-
-{ push push-new prefix suffix } related-words
+{ push prefix suffix } related-words
 
 HELP: suffix
 { $values { "seq" sequence } { "elt" object } { "newseq" sequence } }
diff --git a/core/sequences/sequences-tests.factor b/core/sequences/sequences-tests.factor
index 2479c125a2..0511721c18 100755
--- a/core/sequences/sequences-tests.factor
+++ b/core/sequences/sequences-tests.factor
@@ -216,10 +216,10 @@ unit-test
 ] unit-test
 
 [ V{ 1 2 3 } ]
-[ 3 V{ 1 2 } clone [ push-new ] keep ] unit-test
+[ 3 V{ 1 2 } clone [ adjoin ] keep ] unit-test
 
 [ V{ 1 2 3 } ]
-[ 3 V{ 1 3 2 } clone [ push-new ] keep ] unit-test
+[ 3 V{ 1 3 2 } clone [ adjoin ] keep ] unit-test
 
 ! erg's random tester found this one
 [ SBUF" 12341234" ] [
diff --git a/core/sequences/sequences.factor b/core/sequences/sequences.factor
index 4153430514..4854ff8001 100755
--- a/core/sequences/sequences.factor
+++ b/core/sequences/sequences.factor
@@ -499,8 +499,6 @@ M: slice equal? over slice? [ sequence= ] [ 2drop f ] if ;
 
 : delete ( elt seq -- ) 0 0 rot (delete) nip set-length drop ;
 
-: push-new ( elt seq -- ) [ delete ] 2keep push ;
-
 : prefix ( seq elt -- newseq )
     over >r over length 1+ r> [
         [ 0 swap set-nth-unsafe ] keep
diff --git a/core/sets/sets-docs.factor b/core/sets/sets-docs.factor
index f4e2557a71..97fbc973f0 100644
--- a/core/sets/sets-docs.factor
+++ b/core/sets/sets-docs.factor
@@ -16,10 +16,28 @@ $nl
 { $subsection set= }
 "A word used to implement the above:"
 { $subsection unique }
+"Adding elements to sets:"
+{ $subsection adjoin }
+{ $subsection conjoin }
 { $see-also member? memq? contains? all? "assocs-sets" } ;
 
 ABOUT: "sets"
 
+HELP: adjoin
+{ $values { "elt" object } { "seq" "a resizable mutable sequence" } }
+{ $description "Removes all elements equal to " { $snippet "elt" } ", and adds " { $snippet "elt" } " at the end of the sequence." }
+{ $examples
+    { $example
+        "USING: namespaces prettyprint sequences ;"
+        "V{ \"beans\" \"salsa\" \"cheese\" } \"v\" set"
+        "\"nachos\" \"v\" get adjoin"
+        "\"salsa\" \"v\" get adjoin"
+        "\"v\" get ."
+        "V{ \"beans\" \"cheese\" \"nachos\" \"salsa\" }"
+    }
+}
+{ $side-effects "seq" } ;
+
 HELP: unique
 { $values { "seq" "a sequence" } { "assoc" "an assoc" } }
 { $description "Outputs a new assoc where the keys and values are equal." }
diff --git a/core/sets/sets.factor b/core/sets/sets.factor
index b0d26e0f30..5fbec9a7c8 100644
--- a/core/sets/sets.factor
+++ b/core/sets/sets.factor
@@ -3,10 +3,14 @@
 USING: assocs hashtables kernel sequences vectors ;
 IN: sets
 
+: adjoin ( elt seq -- ) [ delete ] [ push ] 2bi ;
+
+: conjoin ( elt assoc -- ) dupd set-at ;
+
 : (prune) ( elt hash vec -- )
-    3dup drop key?
-    [ [ drop dupd set-at ] [ nip push ] [ ] 3tri ] unless
-    3drop ; inline
+    3dup drop key? [ 3drop ] [
+        [ drop conjoin ] [ nip push ] 3bi
+    ] if ; inline
 
 : prune ( seq -- newseq )
     [ ] [ length <hashtable> ] [ length <vector> ] tri
@@ -16,7 +20,7 @@ IN: sets
     [ dup ] H{ } map>assoc ;
 
 : (all-unique?) ( elt hash -- ? )
-    2dup key? [ 2drop f ] [ dupd set-at t ] if ;
+    2dup key? [ 2drop f ] [ conjoin t ] if ;
 
 : all-unique? ( seq -- ? )
     dup length <hashtable> [ (all-unique?) ] curry all? ;
diff --git a/extra/multi-methods/multi-methods.factor b/extra/multi-methods/multi-methods.factor
index 59e8049232..b1073c116d 100755
--- a/extra/multi-methods/multi-methods.factor
+++ b/extra/multi-methods/multi-methods.factor
@@ -25,7 +25,7 @@ SYMBOL: total
     ]
     [
         [ pair? ] filter
-        [ keys [ hooks get push-new ] each ] keep
+        [ keys [ hooks get adjoin ] each ] keep
     ] bi append ;
 
 : canonicalize-specializer-2 ( specializer -- specializer' )
diff --git a/extra/trees/splay/splay-tests.factor b/extra/trees/splay/splay-tests.factor
index 29ea2eee2d..29bc153030 100644
--- a/extra/trees/splay/splay-tests.factor
+++ b/extra/trees/splay/splay-tests.factor
@@ -8,7 +8,7 @@ IN: trees.splay.tests
     100 [ drop 100 random swap at drop ] with each ;
 
 : make-numeric-splay-tree ( n -- splay-tree )
-    <splay> [ [ dupd set-at ] curry each ] keep ;
+    <splay> [ [ conjoin ] curry each ] keep ;
 
 [ t ] [
     100 make-numeric-splay-tree dup randomize-numeric-splay-tree
diff --git a/extra/ui/tools/interactor/interactor.factor b/extra/ui/tools/interactor/interactor.factor
index 2e59363531..c28e8aec7c 100755
--- a/extra/ui/tools/interactor/interactor.factor
+++ b/extra/ui/tools/interactor/interactor.factor
@@ -76,7 +76,7 @@ M: interactor model-changed
     ] with-output-stream* ;
 
 : add-interactor-history ( str interactor -- )
-    over empty? [ 2drop ] [ interactor-history push-new ] if ;
+    over empty? [ 2drop ] [ interactor-history adjoin ] if ;
 
 : interactor-continue ( obj interactor -- )
     mailbox>> mailbox-put ;

From a84880d770b5e5b18c26a649bdea078e8b074ff9 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Sun, 25 May 2008 19:45:21 -0500
Subject: [PATCH 23/66] Fix parameter ordering

---
 extra/db/db.factor          | 2 +-
 extra/db/pools/pools.factor | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/extra/db/db.factor b/extra/db/db.factor
index 9514f62cf0..4b98612069 100755
--- a/extra/db/db.factor
+++ b/extra/db/db.factor
@@ -127,7 +127,7 @@ M: nonthrowable execute-statement* ( statement type -- )
 : query-map ( statement quot -- seq )
     accumulator >r query-each r> { } like ; inline
 
-: with-db ( db seq quot -- )
+: with-db ( seq class quot -- )
     >r make-db db-open db r>
     [ db get swap [ drop ] prepose with-disposal ] curry with-variable ;
     inline
diff --git a/extra/db/pools/pools.factor b/extra/db/pools/pools.factor
index 4d201c2edf..63153c451e 100644
--- a/extra/db/pools/pools.factor
+++ b/extra/db/pools/pools.factor
@@ -6,16 +6,16 @@ IN: db.pools
 
 TUPLE: db-pool < pool db params ;
 
-: <db-pool> ( db params -- pool )
+: <db-pool> ( params db -- pool )
     db-pool <pool>
-        swap >>params
-        swap >>db ;
+        swap >>db
+        swap >>params ;
 
 : with-db-pool ( db params quot -- )
     >r <db-pool> r> with-pool ; inline
 
 M: db-pool make-connection ( pool -- )
-    [ db>> ] [ params>> ] bi make-db db-open ;
+    [ params>> ] [ db>> ] bi make-db db-open ;
 
 : with-pooled-db ( pool quot -- )
     [ db swap with-variable ] curry with-pooled-connection ; inline

From d589ac19dd1f3b95f4fb31f6224fde66d99169d6 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 00:47:27 -0500
Subject: [PATCH 24/66] Reworking validation

---
 extra/html/components/components-tests.factor |  10 +
 extra/html/components/components.factor       |  42 ++-
 extra/html/templates/chloe/chloe-tests.factor |  40 ++-
 extra/html/templates/chloe/chloe.factor       |  28 +-
 extra/html/templates/chloe/test/test10.xml    |  14 +
 extra/html/templates/chloe/test/test11.xml    |  14 +
 extra/html/templates/chloe/test/test9.xml     |  11 +
 extra/html/templates/templates.factor         |   4 +-
 extra/http/client/client.factor               |   2 +-
 extra/http/http-tests.factor                  |   2 +-
 extra/http/http.factor                        |  32 +-
 .../http/server/actions/actions-tests.factor  |  10 +-
 extra/http/server/actions/actions.factor      | 122 ++++---
 extra/http/server/auth/auth.factor            |   4 +-
 extra/http/server/auth/login/edit-profile.xml |  14 +-
 extra/http/server/auth/login/login.factor     | 329 +++++++----------
 extra/http/server/auth/login/login.xml        |   6 +-
 extra/http/server/auth/login/recover-1.xml    |  32 +-
 extra/http/server/auth/login/recover-3.xml    |  18 +-
 extra/http/server/auth/login/register.xml     |  80 ++---
 .../server/auth/providers/assoc/assoc.factor  |   4 +-
 .../server/boilerplate/boilerplate.factor     |  64 +---
 extra/http/server/callbacks/callbacks.factor  |   4 +-
 extra/http/server/crud/crud.factor            |   7 +-
 extra/http/server/db/db.factor                |   2 +-
 extra/http/server/server-tests.factor         |   6 +-
 extra/http/server/server.factor               |  38 +-
 .../server/sessions/sessions-tests.factor     |   8 +-
 extra/http/server/static/static.factor        |  29 +-
 extra/webapps/counter/counter.factor          |  23 +-
 extra/webapps/counter/counter.xml             |  13 +
 .../factor-website/factor-website.factor      |  12 +-
 extra/webapps/factor-website/page.css         |  20 ++
 extra/webapps/pastebin/new-paste.xml          |  10 +-
 extra/webapps/pastebin/paste.xml              |  52 ++-
 extra/webapps/pastebin/pastebin-common.xml    |   3 -
 extra/webapps/pastebin/pastebin.factor        | 332 +++++++++---------
 extra/webapps/pastebin/pastebin.xml           |   2 +
 extra/webapps/planet/admin.xml                |  14 +-
 extra/webapps/planet/edit-blog.xml            |   7 +-
 extra/webapps/planet/mini-planet.xml          |  14 +
 extra/webapps/planet/new-blog.xml             |  32 ++
 extra/webapps/planet/planet-common.xml        |  25 ++
 extra/webapps/planet/planet.factor            | 235 +++++++------
 extra/webapps/planet/planet.xml               |  50 ++-
 extra/webapps/planet/postings-summary.xml     |   7 -
 extra/webapps/planet/postings.xml             |  19 -
 extra/webapps/todo/edit-todo.xml              |   6 +-
 extra/webapps/todo/todo-list.xml              |  29 +-
 extra/webapps/todo/todo.factor                | 127 ++++---
 extra/webapps/todo/todo.xml                   |   2 +-
 extra/webapps/todo/view-todo.xml              |   6 +-
 extra/webapps/user-admin/edit-user.xml        |  16 +-
 extra/webapps/user-admin/new-user.xml         |  14 +-
 extra/webapps/user-admin/user-admin.factor    |  59 ++--
 extra/webapps/user-admin/user-list.xml        |  16 +-
 extra/xmode/catalog/catalog.factor            |  12 +-
 .../code2html/responder/responder.factor      |  13 +-
 58 files changed, 1197 insertions(+), 949 deletions(-)
 create mode 100644 extra/html/templates/chloe/test/test10.xml
 create mode 100644 extra/html/templates/chloe/test/test11.xml
 create mode 100644 extra/html/templates/chloe/test/test9.xml
 create mode 100644 extra/webapps/counter/counter.xml
 create mode 100644 extra/webapps/planet/mini-planet.xml
 create mode 100644 extra/webapps/planet/new-blog.xml
 create mode 100644 extra/webapps/planet/planet-common.xml
 delete mode 100644 extra/webapps/planet/postings-summary.xml
 delete mode 100644 extra/webapps/planet/postings.xml

diff --git a/extra/html/components/components-tests.factor b/extra/html/components/components-tests.factor
index d09f8b6b42..f2b0049a8e 100644
--- a/extra/html/components/components-tests.factor
+++ b/extra/html/components/components-tests.factor
@@ -168,3 +168,13 @@ M: link-test link-href drop "http://www.apple.com/foo&bar" ;
     [ "object" value [ describe ] with-html-stream ] with-string-writer
     =
 ] unit-test
+
+[ ] [ blank-values ] unit-test
+
+[ ] [
+    "factor" [
+        "concatenative" "model" set-value
+    ] nest-values
+] unit-test
+
+[ H{ { "factor" H{ { "model" "concatenative" } } } } ] [ values get ] unit-test
diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
index faef9ca1b5..e6df343161 100644
--- a/extra/html/components/components.factor
+++ b/extra/html/components/components.factor
@@ -1,8 +1,8 @@
 ! Copyright (C) 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors kernel namespaces io math.parser assocs classes
-classes.tuple words arrays sequences splitting mirrors
-hashtables combinators continuations math strings
+classes.tuple words arrays sequences sequences.lib splitting
+mirrors hashtables combinators continuations math strings
 fry locals calendar calendar.format xml.entities validators
 html.elements html.streams xmode.code2html farkup inspector ;
 IN: html.components
@@ -18,22 +18,52 @@ SYMBOL: values
 : prepare-value ( name object -- value name object )
     [ [ value ] keep ] dip ; inline
 
-: from-tuple <mirror> values set ;
+: from-assoc ( assoc -- ) values get swap update ;
 
-: values-tuple values get object>> ;
+: from-tuple ( tuple -- ) <mirror> from-assoc ;
+
+: deposit-values ( destination names -- )
+    [ dup value ] H{ } map>assoc update ;
+
+: deposit-slots ( destination names -- )
+    [ <mirror> ] dip deposit-values ;
+
+: with-each-index ( seq quot -- )
+    '[
+        [
+            blank-values 1+ "index" set-value @
+        ] with-scope
+    ] each-index ; inline
+
+: with-each-value ( seq quot -- )
+    '[ "value" set-value @ ] with-each-index ; inline
+
+: with-each-assoc ( seq quot -- )
+    '[ from-assoc @ ] with-each-index ; inline
+
+: with-each-tuple ( seq quot -- )
+    '[ from-tuple @ ] with-each-index ; inline
+
+: nest-values ( name quot -- )
+    swap [
+        [
+            H{ } clone [ values set call ] keep
+        ] with-scope
+    ] dip set-value ; inline
 
 : object>string ( object -- string )
     {
         { [ dup real? ] [ number>string ] }
         { [ dup timestamp? ] [ timestamp>string ] }
         { [ dup string? ] [ ] }
+        { [ dup word? ] [ word-name ] }
         { [ dup not ] [ drop "" ] }
     } cond ;
 
 GENERIC: render* ( value name render -- )
 
 : render ( name renderer -- )
-    over validation-messages get at [
+    over named-validation-messages get at [
         [ value>> ] [ message>> ] bi
         [ -rot render* ] dip
         render-error
@@ -103,7 +133,7 @@ TUPLE: choice size multiple choices ;
 
 : render-option ( text selected? -- )
     <option [ "true" =selected ] when option>
-        escape-string write
+        object>string escape-string write
     </option> ;
 
 : render-options ( options selected -- )
diff --git a/extra/html/templates/chloe/chloe-tests.factor b/extra/html/templates/chloe/chloe-tests.factor
index 4d8d15c581..eaa0f0dc3d 100644
--- a/extra/html/templates/chloe/chloe-tests.factor
+++ b/extra/html/templates/chloe/chloe-tests.factor
@@ -1,7 +1,7 @@
 USING: html.templates html.templates.chloe
 tools.test io.streams.string kernel sequences ascii boxes
 namespaces xml html.components
-splitting ;
+splitting unicode.categories ;
 IN: html.templates.chloe.tests
 
 [ f ] [ f parse-query-attr ] unit-test
@@ -117,9 +117,45 @@ M: link-test link-href drop "http://www.apple.com/foo&bar" ;
 [ ] [ "new york" "choice" set-value ] unit-test
 
 [ ] [ { "new york" "detroit" "minneapolis" } "choices" set-value ] unit-test
-    
+
 [ ] [
     [
         "test8" test-template call-template
     ] run-template drop
 ] unit-test
+
+[ ] [ { 1 2 3 } "numbers" set-value ] unit-test
+
+[ "<ul><li>1</li><li>2</li><li>3</li></ul>" ] [
+    [
+        "test9" test-template call-template
+    ] run-template [ blank? not ] filter
+] unit-test
+
+TUPLE: person first-name last-name ;
+
+[ ] [
+    {
+        T{ person f "RBaxter" "Unknown" }
+        T{ person f "Doug" "Coleman" }
+    } "people" set-value
+] unit-test
+
+[ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [
+    [
+        "test10" test-template call-template
+    ] run-template [ blank? not ] filter
+] unit-test
+
+[ ] [
+    {
+        H{ { "first-name" "RBaxter" } { "last-name" "Unknown" } }
+        H{ { "first-name" "Doug"    } { "last-name" "Coleman" } }
+    } "people" set-value
+] unit-test
+
+[ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [
+    [
+        "test11" test-template call-template
+    ] run-template [ blank? not ] filter
+] unit-test
diff --git a/extra/html/templates/chloe/chloe.factor b/extra/html/templates/chloe/chloe.factor
index 43834f896e..4430e69336 100644
--- a/extra/html/templates/chloe/chloe.factor
+++ b/extra/html/templates/chloe/chloe.factor
@@ -3,7 +3,7 @@
 USING: accessors kernel sequences combinators kernel namespaces
 classes.tuple assocs splitting words arrays memoize
 io io.files io.encodings.utf8 io.streams.string
-unicode.case tuple-syntax mirrors fry
+unicode.case tuple-syntax mirrors fry math
 multiline xml xml.data xml.writer xml.utilities
 html.elements
 html.components
@@ -196,6 +196,27 @@ STRING: button-tag-markup
 : if-tag ( tag -- )
     dup if-satisfied? [ process-tag-children ] [ drop ] if ;
 
+: even-tag ( tag -- )
+    "index" value even? [ process-tag-children ] [ drop ] if ;
+
+: odd-tag ( tag -- )
+    "index" value odd? [ process-tag-children ] [ drop ] if ;
+
+: (each-tag) ( tag quot -- )
+    [
+        [ "values" required-attr value ] keep
+        '[ , process-tag-children ]
+    ] dip call ; inline
+
+: each-tag ( tag -- )
+    [ with-each-value ] (each-tag) ;
+
+: each-tuple-tag ( tag -- )
+    [ with-each-tuple ] (each-tag) ;
+
+: each-assoc-tag ( tag -- )
+    [ with-each-assoc ] (each-tag) ;
+
 : error-message-tag ( tag -- )
     children>string render-error ;
 
@@ -254,6 +275,11 @@ STRING: button-tag-markup
 
         ! Control flow
         { "if" [ if-tag ] }
+        { "even" [ even-tag ] }
+        { "odd" [ odd-tag ] }
+        { "each" [ each-tag ] }
+        { "each-assoc" [ each-assoc-tag ] }
+        { "each-tuple" [ each-tuple-tag ] }
         { "comment" [ drop ] }
         { "call-next-template" [ drop call-next-template ] }
 
diff --git a/extra/html/templates/chloe/test/test10.xml b/extra/html/templates/chloe/test/test10.xml
new file mode 100644
index 0000000000..afded9366f
--- /dev/null
+++ b/extra/html/templates/chloe/test/test10.xml
@@ -0,0 +1,14 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<table>
+		<t:each-tuple t:values="people">
+			<tr>
+				<td><t:label t:name="first-name"/></td>
+				<td><t:label t:name="last-name"/></td>
+			</tr>
+		</t:each-tuple>
+	</table>
+
+</t:chloe>
diff --git a/extra/html/templates/chloe/test/test11.xml b/extra/html/templates/chloe/test/test11.xml
new file mode 100644
index 0000000000..17e31b1a59
--- /dev/null
+++ b/extra/html/templates/chloe/test/test11.xml
@@ -0,0 +1,14 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<table>
+		<t:each-assoc t:values="people">
+			<tr>
+				<td><t:label t:name="first-name"/></td>
+				<td><t:label t:name="last-name"/></td>
+			</tr>
+		</t:each-assoc>
+	</table>
+
+</t:chloe>
diff --git a/extra/html/templates/chloe/test/test9.xml b/extra/html/templates/chloe/test/test9.xml
new file mode 100644
index 0000000000..bcfc468738
--- /dev/null
+++ b/extra/html/templates/chloe/test/test9.xml
@@ -0,0 +1,11 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<ul>
+		<t:each t:values="numbers">
+			<li><t:label t:name="value"/></li>
+		</t:each>
+	</ul>
+
+</t:chloe>
diff --git a/extra/html/templates/templates.factor b/extra/html/templates/templates.factor
index ed26c9b531..580af58ecc 100644
--- a/extra/html/templates/templates.factor
+++ b/extra/html/templates/templates.factor
@@ -19,12 +19,12 @@ ERROR: template-error template error ;
 
 M: template-error error.
     "Error while processing template " write
-    [ template>> pprint ":" print nl ]
+    [ template>> short. ":" print nl ]
     [ error>> error. ]
     bi ;
 
 : call-template ( template -- )
-    [ call-template* ] [ template-error ] recover ;
+    [ call-template* ] [ \ template-error boa rethrow ] recover ;
 
 SYMBOL: title
 
diff --git a/extra/http/client/client.factor b/extra/http/client/client.factor
index c455c8c5f1..7b156a4b9b 100755
--- a/extra/http/client/client.factor
+++ b/extra/http/client/client.factor
@@ -93,7 +93,7 @@ M: download-failed error.
 
 : download-to ( url file -- )
     #! Downloads the contents of a URL to a file.
-    >r http-get r> latin1 [ write ] with-file-writer ;
+    [ http-get ] dip latin1 [ write ] with-file-writer ;
 
 : download ( url -- )
     dup download-name download-to ;
diff --git a/extra/http/http-tests.factor b/extra/http/http-tests.factor
index 89480b43ba..151d1ce84f 100755
--- a/extra/http/http-tests.factor
+++ b/extra/http/http-tests.factor
@@ -237,7 +237,7 @@ test-db [
 [ ] [
     [
         <dispatcher>
-            <action> [ "text/plain" <content> [ "Hi" write ] >>body ] >>display
+            <action> [ [ "Hi" write ] <text-content> ] >>display
             <login>
             <sessions>
             "" add-responder
diff --git a/extra/http/http.factor b/extra/http/http.factor
index 7587cb0fe9..89c8f62d5c 100755
--- a/extra/http/http.factor
+++ b/extra/http/http.factor
@@ -9,7 +9,9 @@ math.parser calendar calendar.format
 io io.streams.string io.encodings.utf8 io.encodings.string
 io.sockets io.sockets.secure
 
-unicode.case unicode.categories qualified ;
+unicode.case unicode.categories qualified
+
+html.templates ;
 
 EXCLUDE: fry => , ;
 
@@ -65,14 +67,14 @@ M: https protocol>string drop "https" ;
     2dup length 2 - >= [
         2drop
     ] [
-        >r 1+ dup 2 + r> subseq  hex> [ , ] when*
+        [ 1+ dup 2 + ] dip subseq  hex> [ , ] when*
     ] if ;
 
 : url-decode-% ( index str -- index str )
-    2dup url-decode-hex >r 3 + r> ;
+    2dup url-decode-hex [ 3 + ] dip ;
 
 : url-decode-+-or-other ( index str ch -- index str )
-    dup CHAR: + = [ drop CHAR: \s ] when , >r 1+ r> ;
+    dup CHAR: + = [ drop CHAR: \s ] when , [ 1+ ] dip ;
 
 : url-decode-iter ( index str -- )
     2dup length >= [
@@ -158,7 +160,7 @@ M: https protocol>string drop "https" ;
     dup [
         "&" split H{ } clone [
             [
-                >r "=" split1 [ dup [ url-decode ] when ] bi@ swap r>
+                [ "=" split1 [ dup [ url-decode ] when ] bi@ swap ] dip
                 add-query-param
             ] curry each
         ] keep
@@ -174,7 +176,7 @@ M: https protocol>string drop "https" ;
     ] assoc-map
     [
         [
-            >r url-encode r>
+            [ url-encode ] dip
             [ url-encode "=" swap 3append , ] with each
         ] assoc-each
     ] { } make "&" join ;
@@ -342,7 +344,7 @@ SYMBOL: max-post-request
     dup "cookie" header [ parse-cookies >>cookies ] when* ;
 
 : parse-content-type-attributes ( string -- attributes )
-    " " split harvest [ "=" split1 >r >lower r> ] { } map>assoc ;
+    " " split harvest [ "=" split1 [ >lower ] dip ] { } map>assoc ;
 
 : parse-content-type ( content-type -- type encoding )
     ";" split1 parse-content-type-attributes "charset" swap at ;
@@ -521,18 +523,8 @@ body ;
     over unparse-content-type "content-type" pick set-at
     write-header ;
 
-GENERIC: write-response-body* ( body -- )
-
-M: f write-response-body* drop ;
-
-M: string write-response-body* write ;
-
-M: callable write-response-body* call ;
-
-M: object write-response-body* output-stream get stream-copy ;
-
 : write-response-body ( response -- response )
-    dup body>> write-response-body* ;
+    dup body>> call-template ;
 
 M: response write-response ( respose -- )
     write-response-version
@@ -547,10 +539,10 @@ M: response write-full-response ( request response -- )
     swap method>> "HEAD" = [ write-response-body ] unless ;
 
 : get-cookie ( request/response name -- cookie/f )
-    >r cookies>> r> '[ , _ name>> = ] find nip ;
+    [ cookies>> ] dip '[ , _ name>> = ] find nip ;
 
 : delete-cookie ( request/response name -- )
-    over cookies>> >r get-cookie r> delete ;
+    over cookies>> [ get-cookie ] dip delete ;
 
 : put-cookie ( request/response cookie -- request/response )
     [ name>> dupd get-cookie [ dupd delete-cookie ] when* ] keep
diff --git a/extra/http/server/actions/actions-tests.factor b/extra/http/server/actions/actions-tests.factor
index 5aa761603f..480cbc8e96 100755
--- a/extra/http/server/actions/actions-tests.factor
+++ b/extra/http/server/actions/actions-tests.factor
@@ -1,16 +1,10 @@
-USING: http.server.actions http.server.validators
+USING: kernel http.server.actions validators
 tools.test math math.parser multiline namespaces http
 io.streams.string http.server sequences splitting accessors ;
 IN: http.server.actions.tests
 
-[
-    "a" [ v-number ] { { "a" "123" } } validate-param
-    [ 123 ] [ "a" get ] unit-test
-] with-scope
-
 <action>
-    [ "a" get "b" get + ] >>display
-    { { "a" [ v-number ] } { "b" [ v-number ] } } >>get-params
+    [ "a" param "b" param [ string>number ] bi@ + ] >>display
 "action-1" set
 
 : lf>crlf "\n" split "\r\n" join ;
diff --git a/extra/http/server/actions/actions.factor b/extra/http/server/actions/actions.factor
index 2d73cb46a7..bcd2cbd585 100755
--- a/extra/http/server/actions/actions.factor
+++ b/extra/http/server/actions/actions.factor
@@ -1,68 +1,84 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors sequences kernel assocs combinators
-http.server http.server.validators http hashtables namespaces
-fry continuations locals boxes xml.entities html.elements io ;
+USING: accessors sequences kernel assocs combinators http.server
+validators http hashtables namespaces fry continuations locals
+boxes xml.entities html.elements html.components io arrays ;
 IN: http.server.actions
 
 SYMBOL: params
 
-SYMBOL: validation-message
-
-: render-validation-message ( -- )
-    validation-message get value>> [
-        <span "error" =class span>
-            escape-string write
-        </span>
-    ] when* ;
-
-TUPLE: action init display submit get-params post-params ;
-
-: <action>
-    action new
-        [ ] >>init
-        [ <400> ] >>display
-        [ <400> ] >>submit ;
-
-:: validate-param ( name validator assoc -- )
-    name assoc at validator with-validator name set ; inline
-
-: action-params ( validators -- error? )
-    validation-failed? off
-    params get '[ , validate-param ] assoc-each
-    validation-failed? get ;
-
-: handle-get ( -- response )
-    action get get-params>> action-params [ <400> ] [
-        action get [ init>> call ] [ display>> call ] bi
+: render-validation-messages ( -- )
+    validation-messages get
+    dup empty? [ drop ] [
+        <ul "errors" =class ul>
+            [ <li> message>> escape-string write </li> ] each
+        </ul>
     ] if ;
 
-: handle-post ( -- response )
-    action get post-params>> action-params
-    [ <400> ] [ action get submit>> call ] if ;
+TUPLE: action init display validate submit ;
+
+: new-action ( class -- action )
+    new
+        [ ] >>init
+        [ <400> ] >>display
+        [ ] >>validate
+        [ <400> ] >>submit ;
+
+: <action> ( -- action )
+    action new-action ;
+
+: handle-get ( action -- response )
+    blank-values
+    [ init>> call ]
+    [ display>> call ]
+    bi ;
 
 : validation-failed ( -- * )
-    action get display>> call exit-with ;
+    request get method>> "POST" =
+    [ action get display>> call ] [ <400> ] if exit-with ;
 
-: validation-failed-with ( string -- * )
-    validation-message get >box
-    validation-failed ;
+: handle-post ( action -- response )
+    init-validation
+    blank-values
+    [ validate>> call ]
+    [ submit>> call ] bi ;
 
 M: action call-responder* ( path action -- response )
+    dup action set
     '[
-        , [ CHAR: / = ] right-trim empty? [
-            , action set
-            request get
-            <box> validation-message set
-            [ request-params params set ]
-            [
-                method>> {
-                    { "GET" [ handle-get ] }
-                    { "HEAD" [ handle-get ] }
-                    { "POST" [ handle-post ] }
-                } case
-            ] bi
-        ] [
-            <404>
-        ] if
+        , empty? [
+            init-validation
+            ,
+            request get [ request-params params set ] [ method>> ] bi
+            {
+                { "GET" [ handle-get ] }
+                { "HEAD" [ handle-get ] }
+                { "POST" [ handle-post ] }
+            } case
+        ] [ <404> ] if
     ] with-exit-continuation ;
+
+: param ( name -- value )
+    params get at ;
+
+: check-validation ( -- )
+    validation-failed? [ validation-failed ] when ;
+
+: validate-params ( validators -- )
+    params get swap validate-values from-assoc
+    check-validation ;
+
+: validate-integer-id ( -- )
+    { { "id" [ v-number ] } } validate-params ;
+
+TUPLE: page-action < action template ;
+
+: <page-action> ( -- page )
+    page-action new-action
+        dup '[ , template>> <html-content> ] >>display ;
+
+TUPLE: feed-action < action feed ;
+
+: <feed-action> ( -- feed )
+    feed-action new
+        dup '[ , feed>> call <feed-content> ] >>display ;
diff --git a/extra/http/server/auth/auth.factor b/extra/http/server/auth/auth.factor
index 36fcff4b2e..4b34fbe804 100755
--- a/extra/http/server/auth/auth.factor
+++ b/extra/http/server/auth/auth.factor
@@ -1,6 +1,6 @@
 ! Copyright (c) 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors assocs namespaces kernel sequences
+USING: accessors assocs namespaces kernel sequences sets
 http.server
 http.server.sessions
 http.server.auth.providers ;
@@ -38,4 +38,4 @@ SYMBOL: capabilities
 
 V{ } clone capabilities set-global
 
-: define-capability ( word -- ) capabilities get push-new ;
+: define-capability ( word -- ) capabilities get adjoin ;
diff --git a/extra/http/server/auth/login/edit-profile.xml b/extra/http/server/auth/login/edit-profile.xml
index 1eaf65fa07..855dfa8469 100644
--- a/extra/http/server/auth/login/edit-profile.xml
+++ b/extra/http/server/auth/login/edit-profile.xml
@@ -10,12 +10,12 @@
 	
 	<tr>
 		<th class="field-label">User name:</th>
-		<td><t:view t:component="username" /></td>
+		<td><t:field t:name="username" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Real name:</th>
-		<td><t:edit t:component="realname" /></td>
+		<td><t:field t:name="realname" /></td>
 	</tr>
 	
 	<tr>
@@ -25,7 +25,7 @@
 	
 	<tr>
 		<th class="field-label">Current password:</th>
-		<td><t:edit t:component="password" /></td>
+		<td><t:password t:name="password" /></td>
 	</tr>
 	
 	<tr>
@@ -35,12 +35,12 @@
 	
 	<tr>
 		<th class="field-label">New password:</th>
-		<td><t:edit t:component="new-password" /></td>
+		<td><t:password t:name="new-password" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Verify:</th>
-		<td><t:edit t:component="verify-password" /></td>
+		<td><t:password t:name="verify-password" /></td>
 	</tr>
 	
 	<tr>
@@ -50,7 +50,7 @@
 	
 	<tr>
 		<th class="field-label">E-mail:</th>
-		<td><t:edit t:component="email" /></td>
+		<td><t:field t:name="email" /></td>
 	</tr>
 	
 	<tr>
@@ -62,7 +62,7 @@
 
 	<p>
 		<input type="submit" value="Update" />
-		<t:validation-message />
+		<t:validation-messages />
 	</p>
 
 	</t:form>
diff --git a/extra/http/server/auth/login/login.factor b/extra/http/server/auth/login/login.factor
index bb77532a22..e8c9bf8608 100755
--- a/extra/http/server/auth/login/login.factor
+++ b/extra/http/server/auth/login/login.factor
@@ -2,7 +2,7 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors quotations assocs kernel splitting
 combinators sequences namespaces hashtables sets
-fry arrays threads locals qualified random
+fry arrays threads qualified random validators
 io
 io.sockets
 io.encodings.utf8
@@ -12,23 +12,22 @@ continuations
 destructors
 checksums
 checksums.sha2
+validators
+html.components
 html.elements
+html.templates
+html.templates.chloe
 http
 http.server
 http.server.auth
 http.server.auth.providers
 http.server.auth.providers.db
 http.server.actions
-http.server.components
 http.server.flows
-http.server.forms
 http.server.sessions
-http.server.boilerplate
-http.server.templating
-http.server.templating.chloe
-http.server.validators ;
-IN: http.server.auth.login
+http.server.boilerplate ;
 QUALIFIED: smtp
+IN: http.server.auth.login
 
 TUPLE: login < dispatcher users checksum ;
 
@@ -65,149 +64,124 @@ M: user-saver dispose
     3append <chloe> ;
 
 ! ! ! Login
+: successful-login ( user -- )
+    username>> set-uid ;
 
-: <login-form>
-    "login" <form>
-        "login" login-template >>edit-template
-        "username" <username>
-            t >>required
-            add-field
-        "password" <password>
-            t >>required
-            add-field ;
+: login-failed ( -- * )
+    "invalid username or password" validation-error
+    validation-failed ;
 
-: successful-login ( user -- response )
-    username>> set-uid
-    "$login" end-flow ;
+: <login-action> ( -- action )
+    <action>
+        [ "login" login-template <html-content> ] >>display
 
-: login-failed "invalid username or password" validation-failed-with ;
+        [
+            {
+                { "username" [ v-required ] }
+                { "password" [ v-required ] }
+            } validate-params
 
-:: <login-action> ( -- action )
-    [let | form [ <login-form> ] |
-        <action>
-            [ blank-values ] >>init
+            "password" value
+            "username" value check-login
+            [ successful-login ] [ login-failed ] if*
+        ] >>validate
 
-            [ form edit-form ] >>display
-
-            [
-                blank-values
-
-                form validate-form
-
-                "password" value "username" value check-login
-                [ successful-login ] [ login-failed ] if*
-            ] >>submit
-    ] ;
+        [ "$login" end-flow ] >>submit ;
 
 ! ! ! New user registration
 
-: <register-form> ( -- form )
-    "register" <form>
-        "register" login-template >>edit-template
-        "username" <username>
-            t >>required
-            add-field
-        "realname" <string> add-field
-        "new-password" <password>
-            t >>required
-            add-field
-        "verify-password" <password>
-            t >>required
-            add-field
-        "email" <email> add-field
-        "captcha" <captcha> add-field ;
+: user-exists ( -- * )
+    "username taken" validation-error
+    validation-failed ;
 
-: password-mismatch "passwords do not match" validation-failed-with ;
-
-: user-exists "username taken" validation-failed-with ;
+: password-mismatch ( -- * )
+    "passwords do not match" validation-error
+    validation-failed ;
 
 : same-password-twice ( -- )
     "new-password" value "verify-password" value =
     [ password-mismatch ] unless ;
 
-:: <register-action> ( -- action )
-    [let | form [ <register-form> ] |
-        <action>
-            [ blank-values ] >>init
+: <register-action> ( -- action )
+    <page-action>
+        "register" login-template >>template
 
-            [ form edit-form ] >>display
+        [
+            {
+                { "username" [ v-username ] }
+                { "realname" [ [ v-one-line ] v-optional ] }
+                { "new-password" [ v-password ] }
+                { "verify-password" [ v-password ] }
+                { "email" [ [ v-email ] v-optional ] }
+                { "captcha" [ v-captcha ] }
+            } validate-params
 
-            [
-                blank-values
+            same-password-twice
+        ] >>validate
 
-                form validate-form
+        [
+            "username" value <user>
+                "realname" value >>realname
+                "new-password" value >>encoded-password
+                "email" value >>email
+                H{ } clone >>profile
 
-                same-password-twice
+            users new-user [ user-exists ] unless*
 
-                "username" value <user>
-                    "realname" value >>realname
-                    "new-password" value >>encoded-password
-                    "email" value >>email
-                    H{ } clone >>profile
+            login get init-user-profile
 
-                users new-user [ user-exists ] unless*
-
-                successful-login
-
-                login get init-user-profile
-            ] >>submit
-    ] ;
+            successful-login
+        ] >>submit ;
 
 ! ! ! Editing user profile
 
-: <edit-profile-form> ( -- form )
-    "edit-profile" <form>
-        "edit-profile" login-template >>edit-template
-        "username" <username> add-field
-        "realname" <string> add-field
-        "password" <password> add-field
-        "new-password" <password> add-field
-        "verify-password" <password> add-field
-        "email" <email> add-field ;
+: <edit-profile-action> ( -- action )
+    <action>
+        [
+            logged-in-user get
+            [ username>> "username" set-value ]
+            [ realname>> "realname" set-value ]
+            [ email>> "email" set-value ]
+            tri
+        ] >>init
 
-:: <edit-profile-action> ( -- action )
-    [let | form [ <edit-profile-form> ] |
-        <action>
-            [
-                blank-values
+        [ "edit-profile" login-template <html-content> ] >>display
 
-                logged-in-user get
-                [ username>> "username" set-value ]
-                [ realname>> "realname" set-value ]
-                [ email>> "email" set-value ]
-                tri
-            ] >>init
+        [
+            uid "username" set-value
 
-            [ form edit-form ] >>display
+            {
+                { "realname" [ [ v-one-line ] v-optional ] }
+                { "password" [ ] }
+                { "new-password" [ [ v-password ] v-optional ] }
+                { "verify-password" [ [ v-password ] v-optional ] } 
+                { "email" [ [ v-email ] v-optional ] }
+            } validate-params
 
-            [
-                blank-values
-                uid "username" set-value
+            { "password" "new-password" "verify-password" }
+            [ value empty? not ] contains? [
+                "password" value uid check-login
+                [ "incorrect password" validation-error ] unless
 
-                form validate-form
+                same-password-twice
+            ] when
+        ] >>validate
 
-                logged-in-user get
+        [
+            logged-in-user get
 
-                { "password" "new-password" "verify-password" }
-                [ value empty? ] all? [
-                    same-password-twice
+            "new-password" value dup empty?
+            [ drop ] [ >>encoded-password ] if
 
-                    "password" value uid check-login
-                    [ login-failed ] unless
+            "realname" value >>realname
+            "email" value >>email
 
-                    "new-password" value >>encoded-password
-                ] unless
+            t >>changed?
 
-                "realname" value >>realname
-                "email" value >>email
+            drop
 
-                t >>changed?
-
-                drop
-
-                "$login" end-flow
-            ] >>submit
-    ] ;
+            "$login" end-flow
+        ] >>submit ;
 
 ! ! ! Password recovery
 
@@ -250,92 +224,61 @@ SYMBOL: lost-password-from
     '[ , password-email smtp:send-email ]
     "E-mail send thread" spawn drop ;
 
-: <recover-form-1> ( -- form )
-    "register" <form>
-        "recover-1" login-template >>edit-template
-        "username" <username>
-            t >>required
-            add-field
-        "email" <email>
-            t >>required
-            add-field
-        "captcha" <captcha> add-field ;
+: <recover-action-1> ( -- action )
+    <action>
+        [ "recover-1" login-template <html-content> ] >>display
 
-:: <recover-action-1> ( -- action )
-    [let | form [ <recover-form-1> ] |
-        <action>
-            [ blank-values ] >>init
+        [
+            {
+                { "username" [ v-username ] }
+                { "email" [ v-email ] }
+                { "captcha" [ v-captcha ] }
+            } validate-params
+        ] >>validate
 
-            [ form edit-form ] >>display
+        [
+            "email" value "username" value
+            users issue-ticket [
+                send-password-email
+            ] when*
 
-            [
-                blank-values
+            "recover-2" login-template <html-content>
+        ] >>submit ;
 
-                form validate-form
-
-                "email" value "username" value
-                users issue-ticket [
-                    send-password-email
-                ] when*
-
-                "recover-2" login-template serve-template
-            ] >>submit
-    ] ;
-
-: <recover-form-3>
-    "new-password" <form>
-        "recover-3" login-template >>edit-template
-        "username" <username>
-            hidden >>renderer
-            t >>required
-            add-field
-        "new-password" <password>
-            t >>required
-            add-field
-        "verify-password" <password>
-            t >>required
-            add-field
-        "ticket" <string>
-            hidden >>renderer
-            t >>required
-            add-field ;
-
-:: <recover-action-3> ( -- action )
-    [let | form [ <recover-form-3> ] |
-        <action>
-            [
-                { "username" [ v-required ] }
+: <recover-action-3> ( -- action )
+    <action>
+        [
+            {
+                { "username" [ v-username ] }
                 { "ticket" [ v-required ] }
-            ] >>get-params
+            } validate-params
+        ] >>init
 
-            [
-                [
-                    "username" [ get ] keep set
-                    "ticket" [ get ] keep set
-                ] H{ } make-assoc values set
-            ] >>init
+        [ "recover-3" login-template <html-content> ] >>display
 
-            [ <recover-form-3> edit-form ] >>display
+        [
+            {
+                { "username" [ v-username ] }
+                { "ticket" [ v-required ] }
+                { "new-password" [ v-password ] }
+                { "verify-password" [ v-password ] }
+            } validate-params
 
-            [
-                blank-values
+            same-password-twice
+        ] >>validate
 
-                form validate-form
+        [
+            "ticket" value
+            "username" value
+            users claim-ticket [
+                "new-password" value >>encoded-password
+                users update-user
 
-                same-password-twice
-
-                "ticket" value
-                "username" value
-                users claim-ticket [
-                    "new-password" value >>encoded-password
-                    users update-user
-
-                    "recover-4" login-template serve-template
-                ] [
-                    <400>
-                ] if*
-            ] >>submit
-    ] ;
+                "recover-4" login-template <html-content>
+            ] [
+                <400>
+            ] if*
+        ] >>submit ;
 
 ! ! ! Logout
 : <logout-action> ( -- action )
diff --git a/extra/http/server/auth/login/login.xml b/extra/http/server/auth/login/login.xml
index d0a73a4d8b..545d7e0990 100644
--- a/extra/http/server/auth/login/login.xml
+++ b/extra/http/server/auth/login/login.xml
@@ -10,12 +10,12 @@
 
 			<tr>
 				<th class="field-label">User name:</th>
-				<td><t:edit t:component="username" /></td>
+				<td><t:field t:name="username" /></td>
 			</tr>
 
 			<tr>
 				<th class="field-label">Password:</th>
-				<td><t:edit t:component="password" /></td>
+				<td><t:password t:name="password" /></td>
 			</tr>
 
 		</table>
@@ -23,7 +23,7 @@
 		<p>
 
 			<input type="submit" value="Log in" />
-			<t:validation-message />
+			<t:validation-messages />
 
 		</p>
 
diff --git a/extra/http/server/auth/login/recover-1.xml b/extra/http/server/auth/login/recover-1.xml
index 7c72181c10..21fbe6fd39 100644
--- a/extra/http/server/auth/login/recover-1.xml
+++ b/extra/http/server/auth/login/recover-1.xml
@@ -10,25 +10,25 @@
 
 		<table>
 
-		<tr>
-		<th class="field-label">User name:</th>
-		<td><t:edit t:component="username" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">User name:</th>
+				<td><t:field t:name="username" /></td>
+			</tr>
 
-		<tr>
-		<th class="field-label">E-mail:</th>
-		<td><t:edit t:component="email" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">E-mail:</th>
+				<td><t:field t:name="email" /></td>
+			</tr>
 
-		<tr>
-		<th class="field-label">Captcha:</th>
-		<td><t:edit t:component="captcha" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">Captcha:</th>
+				<td><t:field t:name="captcha" /></td>
+			</tr>
 
-		<tr>
-		<td></td>
-		<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to e-mail you will be blocked.</td>
-		</tr>
+			<tr>
+				<td></td>
+				<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to e-mail you will be blocked.</td>
+			</tr>
 
 		</table>
 
diff --git a/extra/http/server/auth/login/recover-3.xml b/extra/http/server/auth/login/recover-3.xml
index 6c60b257a8..2e412d1f18 100644
--- a/extra/http/server/auth/login/recover-3.xml
+++ b/extra/http/server/auth/login/recover-3.xml
@@ -10,29 +10,29 @@
 
 		<table>
 
-			<t:edit t:component="username" />
-			<t:edit t:component="ticket" />
+			<t:hidden t:name="username" />
+			<t:hidden t:name="ticket" />
 
 			<tr>
-			<th class="field-label">Password:</th>
-			<td><t:edit t:component="new-password" /></td>
+				<th class="field-label">Password:</th>
+				<td><t:password t:name="new-password" /></td>
 			</tr>
 
 			<tr>
-			<th class="field-label">Verify password:</th>
-			<td><t:edit t:component="verify-password" /></td>
+				<th class="field-label">Verify password:</th>
+				<td><t:password t:name="verify-password" /></td>
 			</tr>
 
 			<tr>
-			<td></td>
-			<td>Enter your password twice to ensure it is correct.</td>
+				<td></td>
+				<td>Enter your password twice to ensure it is correct.</td>
 			</tr>
 
 		</table>
 
 		<p>
 			<input type="submit" value="Set password" />
-			<t:validation-message />
+			<t:validation-messages />
 		</p>
 
 	</t:form>
diff --git a/extra/http/server/auth/login/register.xml b/extra/http/server/auth/login/register.xml
index 9b45a7f087..4804410dde 100644
--- a/extra/http/server/auth/login/register.xml
+++ b/extra/http/server/auth/login/register.xml
@@ -8,55 +8,55 @@
 
 		<table>
 
-		<tr>
-		<th class="field-label">User name:</th>
-		<td><t:edit t:component="username" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">User name:</th>
+				<td><t:field t:name="username" /></td>
+			</tr>
 
-		<tr>
-		<th class="field-label">Real name:</th>
-		<td><t:edit t:component="realname" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">Real name:</th>
+				<td><t:field t:name="realname" /></td>
+			</tr>
 
-		<tr>
-		<td></td>
-		<td>Specifying a real name is optional.</td>
-		</tr>
+			<tr>
+				<td></td>
+				<td>Specifying a real name is optional.</td>
+			</tr>
 
-		<tr>
-		<th class="field-label">Password:</th>
-		<td><t:edit t:component="new-password" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">Password:</th>
+				<td><t:password t:name="new-password" /></td>
+			</tr>
 
-		<tr>
-		<th class="field-label">Verify:</th>
-		<td><t:edit t:component="verify-password" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">Verify:</th>
+				<td><t:password t:name="verify-password" /></td>
+			</tr>
 
-		<tr>
-		<td></td>
-		<td>Enter your password twice to ensure it is correct.</td>
-		</tr>
+			<tr>
+				<td></td>
+				<td>Enter your password twice to ensure it is correct.</td>
+			</tr>
 
-		<tr>
-		<th class="field-label">E-mail:</th>
-		<td><t:edit t:component="email" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">E-mail:</th>
+				<td><t:field t:name="email" /></td>
+			</tr>
 
-		<tr>
-		<td></td>
-		<td>Specifying an e-mail address is optional. It enables the "recover password" feature.</td>
-		</tr>
+			<tr>
+				<td></td>
+				<td>Specifying an e-mail address is optional. It enables the "recover password" feature.</td>
+			</tr>
 
-		<tr>
-		<th class="field-label">Captcha:</th>
-		<td><t:edit t:component="captcha" /></td>
-		</tr>
+			<tr>
+				<th class="field-label">Captcha:</th>
+				<td><t:field t:name="captcha" /></td>
+			</tr>
 
-		<tr>
-		<td></td>
-		<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
-		</tr>
+			<tr>
+				<td></td>
+				<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
+			</tr>
 
 		</table>
 
diff --git a/extra/http/server/auth/providers/assoc/assoc.factor b/extra/http/server/auth/providers/assoc/assoc.factor
index 54f96480bc..d6ba587aa0 100755
--- a/extra/http/server/auth/providers/assoc/assoc.factor
+++ b/extra/http/server/auth/providers/assoc/assoc.factor
@@ -15,5 +15,5 @@ M: users-in-memory get-user ( username provider -- user/f )
 M: users-in-memory update-user ( user provider -- ) 2drop ;
 
 M: users-in-memory new-user ( user provider -- user/f )
-    >r dup username>> r> assoc>>
-    2dup key? [ 3drop f ] [ pick >r set-at r> ] if ;
+    [ dup username>> ] dip assoc>>
+    2dup key? [ 3drop f ] [ pick [ set-at ] dip ] if ;
diff --git a/extra/http/server/boilerplate/boilerplate.factor b/extra/http/server/boilerplate/boilerplate.factor
index e0a4037e31..96c59edd10 100644
--- a/extra/http/server/boilerplate/boilerplate.factor
+++ b/extra/http/server/boilerplate/boilerplate.factor
@@ -1,73 +1,13 @@
 ! Copyright (c) 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors kernel namespaces boxes sequences strings
-io io.streams.string arrays locals
-html.elements
-http
-http.server
-http.server.sessions
-http.server.templating ;
+USING: accessors kernel namespaces http.server html.templates
+locals ;
 IN: http.server.boilerplate
 
 TUPLE: boilerplate < filter-responder template ;
 
 : <boilerplate> f boilerplate boa ;
 
-SYMBOL: title
-
-: set-title ( string -- )
-    title get >box ;
-
-: write-title ( -- )
-    title get value>> write ;
-
-SYMBOL: style
-
-: add-style ( string -- )
-    "\n" style get push-all
-         style get push-all ;
-
-: write-style ( -- )
-    style get >string write ;
-
-SYMBOL: atom-feed
-
-: set-atom-feed ( title url -- )
-    2array atom-feed get >box ;
-
-: write-atom-feed ( -- )
-    atom-feed get value>> [
-        <link "alternate" =rel "application/atom+xml" =type
-        [ first =title ] [ second =href ] bi
-        link/>
-    ] when* ;
-
-SYMBOL: nested-template?
-
-SYMBOL: next-template
-
-: call-next-template ( -- )
-    next-template get write-html ;
-
-M: f call-template* drop call-next-template ;
-
-: with-boilerplate ( body template -- )
-    [
-        title get [ <box> title set ] unless
-        atom-feed get [ <box> atom-feed set ] unless
-        style get [ SBUF" " clone style set ] unless
-
-        [
-            [
-                nested-template? on
-                write-response-body*
-            ] with-string-writer
-            next-template set
-        ]
-        [ call-template ]
-        bi*
-    ] with-scope ; inline
-
 M:: boilerplate call-responder* ( path responder -- )
     path responder call-next-method
     dup content-type>> "text/html" = [
diff --git a/extra/http/server/callbacks/callbacks.factor b/extra/http/server/callbacks/callbacks.factor
index 5325ee3b55..40ba540ac6 100755
--- a/extra/http/server/callbacks/callbacks.factor
+++ b/extra/http/server/callbacks/callbacks.factor
@@ -90,7 +90,7 @@ SYMBOL: current-show
     [ restore-request store-current-show ] when* ;
 
 : show-final ( quot -- * )
-    >r redirect-to-here store-current-show r>
+    [ redirect-to-here store-current-show ] dip
     call exit-with ; inline
 
 : resuming-callback ( responder request -- id )
@@ -111,7 +111,7 @@ M: callback-responder call-responder* ( path responder -- response )
     ] with-exit-continuation ;
 
 : show-page ( quot -- )
-    >r redirect-to-here store-current-show r>
+    [ redirect-to-here store-current-show ] dip
     [
         [ ] t register-callback swap call exit-with
     ] callcc1 restore-request ; inline
diff --git a/extra/http/server/crud/crud.factor b/extra/http/server/crud/crud.factor
index 28c1b02005..5fb7c15019 100755
--- a/extra/http/server/crud/crud.factor
+++ b/extra/http/server/crud/crud.factor
@@ -1,12 +1,11 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel namespaces db.tuples math.parser
-accessors fry locals hashtables
+accessors fry locals hashtables validators
 http.server
 http.server.actions
 http.server.components
-http.server.forms
-http.server.validators ;
+http.server.forms ;
 IN: http.server.crud
 
 :: <view-action> ( form ctor -- action )
@@ -18,7 +17,7 @@ IN: http.server.crud
         [ form view-form ] >>display ;
 
 : <id-redirect> ( id next -- response )
-    swap number>string "id" associate <standard-redirect> ;
+    swap "id" associate <standard-redirect> ;
 
 :: <edit-action> ( form ctor next -- action )
     <action>
diff --git a/extra/http/server/db/db.factor b/extra/http/server/db/db.factor
index d0bd449457..73d4c35e2c 100755
--- a/extra/http/server/db/db.factor
+++ b/extra/http/server/db/db.factor
@@ -6,7 +6,7 @@ IN: http.server.db
 
 TUPLE: db-persistence < filter-responder pool ;
 
-: <db-persistence> ( responder db params -- responder' )
+: <db-persistence> ( responder params db -- responder' )
     <db-pool> db-persistence boa ;
 
 M: db-persistence call-responder*
diff --git a/extra/http/server/server-tests.factor b/extra/http/server/server-tests.factor
index af27eda527..0aed425ade 100755
--- a/extra/http/server/server-tests.factor
+++ b/extra/http/server/server-tests.factor
@@ -31,7 +31,7 @@ C: <mock-responder> mock-responder
 M: mock-responder call-responder*
     nip
     path>> on
-    "text/plain" <content> ;
+    [ ] <text-content> ;
 
 : check-dispatch ( tag path -- ? )
     H{ } clone base-paths set
@@ -84,7 +84,7 @@ C: <path-check-responder> path-check-responder
 
 M: path-check-responder call-responder*
     drop
-    "text/plain" <content> swap >array >>body ;
+    >array <text-content> ;
 
 [ { "c" } ] [
     H{ } clone base-paths set
@@ -125,7 +125,7 @@ C: <base-path-check-responder> base-path-check-responder
 M: base-path-check-responder call-responder*
     2drop
     "$funny-dispatcher" resolve-base-path
-    "text/plain" <content> swap >>body ;
+    <text-content> ;
 
 [ ] [
     <dispatcher>
diff --git a/extra/http/server/server.factor b/extra/http/server/server.factor
index c1684c4ed2..d68c66b829 100755
--- a/extra/http/server/server.factor
+++ b/extra/http/server/server.factor
@@ -2,9 +2,10 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: assocs kernel namespaces io io.timeouts strings splitting
 threads sequences prettyprint io.server logging calendar http
-html.streams html.elements accessors math.parser combinators.lib
-tools.vocabs debugger continuations random combinators
-destructors io.encodings.8-bit fry classes words math ;
+html.streams html.elements accessors math.parser
+combinators.lib tools.vocabs debugger continuations random
+combinators destructors io.encodings.8-bit fry classes words
+math rss json.writer ;
 IN: http.server
 
 ! path is a sequence of path component strings
@@ -18,14 +19,27 @@ GENERIC: call-responder* ( path responder -- response )
         { "POST" [ post-data>> ] }
     } case ;
 
-: <content> ( content-type -- response )
+: <content> ( body content-type -- response )
     <response>
         200 >>code
         "Document follows" >>message
-        swap >>content-type ;
+        swap >>content-type
+        swap >>body ;
 
-: <html-content> ( quot -- response )
-    "text/html" <content> swap >>body ;
+: <text-content> ( body -- response )
+    "text/plain" <content> ;
+
+: <html-content> ( body -- response )
+    "text/html" <content> ;
+
+: <xml-content> ( body -- response )
+    "text/xml" <content> ;
+
+: <feed-content> ( feed -- response )
+    '[ , feed>xml ] "text/xml" <content> ;
+
+: <json-content> ( obj -- response )
+    '[ , >json ] "application/json" <content> ;
 
 TUPLE: trivial-responder response ;
 
@@ -86,9 +100,7 @@ SYMBOL: link-hook
 : resolve-base-path ( string -- string' )
     "$" ?head [
         [
-            "/" split1 >r
-            base-path [ "/" % % ] each "/" %
-            r> %
+            "/" split1 [ base-path [ "/" % % ] each "/" % ] dip %
         ] "" make
     ] when ;
 
@@ -115,7 +127,7 @@ SYMBOL: form-hook
     request-url ;
 
 : replace-last-component ( path with -- path' )
-    >r "/" last-split1 drop "/" r> 3append ;
+    [ "/" last-split1 drop "/" ] dip 3append ;
 
 : relative-redirect ( to query -- url )
     request get clone
@@ -128,7 +140,7 @@ SYMBOL: form-hook
     {
         { [ over "http://" head? ] [ link>string ] }
         { [ over "/" head? ] [ absolute-redirect ] }
-        { [ over "$" head? ] [ >r resolve-base-path r> derive-url ] }
+        { [ over "$" head? ] [ [ resolve-base-path ] dip derive-url ] }
         [ relative-redirect ]
     } cond ;
 
@@ -163,7 +175,7 @@ TUPLE: dispatcher default responders ;
         [ nip ] [ drop default>> ] if
     ] [
         over first over responders>> at*
-        [ >r drop rest-slice r> ] [ drop default>> ] if
+        [ [ drop rest-slice ] dip ] [ drop default>> ] if
     ] if ;
 
 M: dispatcher call-responder* ( path dispatcher -- response )
diff --git a/extra/http/server/sessions/sessions-tests.factor b/extra/http/server/sessions/sessions-tests.factor
index 0d98bf2150..8ea312dcb5 100755
--- a/extra/http/server/sessions/sessions-tests.factor
+++ b/extra/http/server/sessions/sessions-tests.factor
@@ -6,7 +6,7 @@ sequences db db.sqlite continuations ;
 
 : with-session
     [
-        >r [ save-session-after ] [ session set ] bi r> call
+        [ [ save-session-after ] [ session set ] bi ] dip call
     ] with-destructors ; inline
 
 TUPLE: foo ;
@@ -18,7 +18,7 @@ M: foo init-session* drop 0 "x" sset ;
 M: foo call-responder*
     2drop
     "x" [ 1+ ] schange
-    "text/html" <content> [ "x" sget pprint ] >>body ;
+    [ "x" sget pprint ] <html-content> ;
 
 : url-responder-mock-test
     [
@@ -44,9 +44,7 @@ M: foo call-responder*
 
 : <exiting-action>
     <action>
-        [
-            "text/plain" <content> exit-with
-        ] >>display ;
+        [ [ ] <text-content> exit-with ] >>display ;
 
 [ "auth-test.db" temp-file sqlite-db delete-file ] ignore-errors
 
diff --git a/extra/http/server/static/static.factor b/extra/http/server/static/static.factor
index 2f7a6eb221..8c0e255e21 100755
--- a/extra/http/server/static/static.factor
+++ b/extra/http/server/static/static.factor
@@ -3,7 +3,8 @@
 USING: calendar html io io.files kernel math math.order
 math.parser http http.server namespaces parser sequences strings
 assocs hashtables debugger http.mime sorting html.elements
-logging calendar.format accessors io.encodings.binary fry ;
+html.templates.fhtml logging calendar.format accessors
+io.encodings.binary fry ;
 IN: http.server.static
 
 ! special maps mime types to quots with effect ( path -- )
@@ -60,15 +61,17 @@ TUPLE: file-responder root hook special allow-listings ;
     dup <a =href a> write </a> ;
 
 : directory. ( path -- )
-    dup file-name [
-        [ <h1> file-name write </h1> ]
-        [
-            <ul>
-                directory sort-keys
-                [ <li> file. </li> ] assoc-each
-            </ul>
-        ] bi
-    ] simple-html-document ;
+    [
+        dup file-name [
+            [ <h1> file-name write </h1> ]
+            [
+                <ul>
+                    directory sort-keys
+                    [ <li> file. </li> ] assoc-each
+                </ul>
+            ] bi
+        ] simple-page
+    ] with-html-stream ;
 
 : list-directory ( directory -- response )
     file-responder get allow-listings>> [
@@ -99,3 +102,9 @@ M: file-responder call-responder* ( path responder -- response )
     file-responder set
     ".." over member?
     [ drop <400> ] [ "/" join serve-object ] if ;
+
+! file responder integration
+: enable-fhtml ( responder -- responder )
+    [ <fhtml> <html-content> ]
+    "application/x-factor-server-page"
+    pick special>> set-at ;
diff --git a/extra/webapps/counter/counter.factor b/extra/webapps/counter/counter.factor
index 3cc1eb567b..04194adb29 100644
--- a/extra/webapps/counter/counter.factor
+++ b/extra/webapps/counter/counter.factor
@@ -1,26 +1,25 @@
-USING: math kernel accessors http.server http.server.actions
-http.server.sessions http.server.templating
-http.server.templating.fhtml locals ;
+USING: math kernel accessors html.components
+http.server http.server.actions
+http.server.sessions html.templates.chloe fry ;
 IN: webapps.counter
 
 SYMBOL: count
 
 TUPLE: counter-app < dispatcher ;
 
-M: counter-app init-session*
-    drop 0 count sset ;
+M: counter-app init-session* drop 0 count sset ;
 
-:: <counter-action> ( quot -- action )
-    <action> [
-        count quot schange
-        "" f <standard-redirect>
-    ] >>display ;
+: <counter-action> ( quot -- action )
+    <action>
+        swap '[ count , schange "" f <standard-redirect> ] >>submit ;
 
 : counter-template ( -- template )
-    "resource:extra/webapps/counter/counter.fhtml" <fhtml> ;
+    "resource:extra/webapps/counter/counter.xml" <chloe> ;
 
 : <display-action> ( -- action )
-    <action> [ counter-template serve-template ] >>display ;
+    <page-action>
+        [ count sget "counter" set-value ] >>init
+        counter-template >>template ;
 
 : <counter-app> ( -- responder )
     counter-app new-dispatcher
diff --git a/extra/webapps/counter/counter.xml b/extra/webapps/counter/counter.xml
new file mode 100644
index 0000000000..75e7cf3c4b
--- /dev/null
+++ b/extra/webapps/counter/counter.xml
@@ -0,0 +1,13 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+
+	<body>
+		<h1><t:label t:name="counter" /></h1>
+
+		<t:button t:action="$counter-app/inc">++</t:button>
+		<t:button t:action="$counter-app/dec">--</t:button>
+	</body>
+
+</t:chloe>
diff --git a/extra/webapps/factor-website/factor-website.factor b/extra/webapps/factor-website/factor-website.factor
index 9b3ce57d02..1fb5d4c1a6 100644
--- a/extra/webapps/factor-website/factor-website.factor
+++ b/extra/webapps/factor-website/factor-website.factor
@@ -1,16 +1,17 @@
 ! Copyright (c) 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors kernel sequences assocs io.files io.sockets
+io.server
 namespaces db db.sqlite smtp
 http.server
 http.server.db
 http.server.flows
 http.server.sessions
-http.server.auth.admin
 http.server.auth.login
 http.server.auth.providers.db
 http.server.boilerplate
-http.server.templating.chloe
+html.templates.chloe
+webapps.user-admin
 webapps.pastebin
 webapps.planet
 webapps.todo ;
@@ -30,12 +31,13 @@ IN: webapps.factor-website
         init-annotations-table
 
         init-blog-table
+        init-postings-table
 
         init-todo-table
     ] with-db ;
 
 : <factor-website> ( -- responder )
-    <dispatcher>
+    <dispatcher> 
         <todo-list> "todo" add-responder
         <pastebin> "pastebin" add-responder
         <planet-factor> "planet" add-responder
@@ -59,7 +61,7 @@ IN: webapps.factor-website
 
     <factor-website> main-responder set-global ;
 
-: start-factor-website
+: start-factor-website ( -- )
     test-db start-expiring-sessions
-    "planet" main-responder get responders>> at test-db start-update-task
+    test-db start-update-task
     8812 httpd ;
diff --git a/extra/webapps/factor-website/page.css b/extra/webapps/factor-website/page.css
index 55721d7bef..606d574618 100644
--- a/extra/webapps/factor-website/page.css
+++ b/extra/webapps/factor-website/page.css
@@ -21,6 +21,8 @@ a:hover, .link:hover {
 
 .error { color: #a00; }
 
+.errors li { color: #a00; }
+
 .field-label {
 	text-align: right;
 }
@@ -53,3 +55,21 @@ a:hover, .link:hover {
 .description p:last-child {
 	margin-bottom: 0px;
 }
+
+.description table, .description td {
+    border-color: #666;
+    border-style: solid;
+}
+
+.description table {
+    border-width: 0 0 1px 1px;
+    border-spacing: 0;
+    border-collapse: collapse;
+}
+
+.description td {
+    margin: 0;
+    padding: 4px;
+    border-width: 1px 1px 0 0;
+}
+
diff --git a/extra/webapps/pastebin/new-paste.xml b/extra/webapps/pastebin/new-paste.xml
index 86daf09aeb..1abd4d494b 100644
--- a/extra/webapps/pastebin/new-paste.xml
+++ b/extra/webapps/pastebin/new-paste.xml
@@ -7,11 +7,11 @@
 	<t:form t:action="$pastebin/new-paste">
 
 		<table>
-			<tr><th class="field-label">Summary: </th><td><t:edit t:component="summary" /></td></tr>
-			<tr><th class="field-label">Author: </th><td><t:edit t:component="author" /></td></tr>
-			<tr><th class="field-label">Mode: </th><td><t:edit t:component="mode" /></td></tr>
-			<tr><th class="field-label big-field-label">Description: </th><td><t:edit t:component="contents" /></td></tr>
-			<tr><th class="field-label">Captcha: </th><td><t:edit t:component="captcha" /></td></tr>
+			<tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
+			<tr><th class="field-label">Author: </th><td><t:field t:name="author" /></td></tr>
+			<tr><th class="field-label">Mode: </th><td><t:choice t:name="mode" t:choices="modes" /></td></tr>
+			<tr><th class="field-label big-field-label">Description: </th><td><t:textarea t:name="contents" /></td></tr>
+			<tr><th class="field-label">Captcha: </th><td><t:captcha t:name="captcha" /></td></tr>
 			<tr>
 			<td></td>
 			<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
diff --git a/extra/webapps/pastebin/paste.xml b/extra/webapps/pastebin/paste.xml
index 9141ee4ef1..1f65ff6765 100644
--- a/extra/webapps/pastebin/paste.xml
+++ b/extra/webapps/pastebin/paste.xml
@@ -2,19 +2,59 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:title>Paste: <t:view t:component="summary" /></t:title>
+	<t:atom t:title="Paste - Atom" t:href="$pastebin/paste.atom" t:query="id" />
+
+	<t:title>Paste: <t:label t:name="summary" /></t:title>
 
 	<table>
-		<tr><th class="field-label">Author:  </th><td><t:view t:component="author"  /></td></tr>
-		<tr><th class="field-label">Mode:    </th><td><t:view t:component="mode"    /></td></tr>
-		<tr><th class="field-label">Date:    </th><td><t:view t:component="date"    /></td></tr>
+		<tr><th class="field-label">Author: </th><td><t:label t:name="author" /></td></tr>
+		<tr><th class="field-label">Mode: </th><td><t:label t:name="mode" /></td></tr>
+		<tr><th class="field-label">Date: </th><td><t:label t:name="date" /></td></tr>
 	</table>
 
-	<pre class="description"><t:view t:component="contents" /></pre>
+	<pre class="description"><t:code t:name="contents" t:mode="modes" /></pre>
 
 	<t:button t:action="$pastebin/delete-paste" t:for="id" class="link-button link">Delete Paste</t:button>
 	|
 	<t:a t:href="$pastebin/annotate" t:query="id">Annotate</t:a>
 
-	<t:view t:component="annotations" />
+	<t:each-tuple t:values="annotations">
+
+		<h2>Annotation: <t:label t:name="summary" /></h2>
+
+		<table>
+			<tr><th class="field-label">Author: </th><td><t:label t:name="author" /></td></tr>
+			<tr><th class="field-label">Mode: </th><td><t:label t:name="mode" /></td></tr>
+			<tr><th class="field-label">Date: </th><td><t:label t:name="date" /></td></tr>
+		</table>
+
+		<pre class="description"><t:code t:name="contents" t:mode="mode"/></pre>
+
+		<t:button t:action="$pastebin/delete-annotation" t:for="aid" class="link-button link">Delete Annotation</t:button>
+
+	</t:each-tuple>
+
+	<t:bind-assoc t:name="new-annotation">
+
+		<h2>New Annotation</h2>
+
+		<t:form t:action="$pastebin/new-annotation" t:for="id">
+
+			<table>
+				<tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
+				<tr><th class="field-label">Author: </th><td><t:field t:name="author" /></td></tr>
+				<tr><th class="field-label">Mode: </th><td><t:choice t:name="mode" t:choices="modes" /></td></tr>
+				<tr><th class="field-label big-field-label">Description:</th><td><t:textarea t:name="contents" /></td></tr>
+				<tr><th class="field-label">Captcha: </th><td><t:captcha t:name="captcha" /></td></tr>
+				<tr>
+				<td></td>
+				<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
+				</tr>
+			</table>
+
+			<input type="SUBMIT" value="Done" />
+		</t:form>
+
+	</t:bind-assoc>
+
 </t:chloe>
diff --git a/extra/webapps/pastebin/pastebin-common.xml b/extra/webapps/pastebin/pastebin-common.xml
index b99cf28753..f785fceb6b 100644
--- a/extra/webapps/pastebin/pastebin-common.xml
+++ b/extra/webapps/pastebin/pastebin-common.xml
@@ -2,15 +2,12 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:atom t:title="Pastebin - Atom" t:href="$pastebin/feed.xml" />
-
 	<t:style t:include="resource:extra/webapps/pastebin/pastebin.css" />
 
 	<div class="navbar">
 
 		  <t:a t:href="$pastebin/list">Pastes</t:a>
 		| <t:a t:href="$pastebin/new-paste">New Paste</t:a>
-		| <t:a t:href="$pastebin/feed.xml">Atom Feed</t:a>
 
 		<t:if t:code="http.server.sessions:uid">
 
diff --git a/extra/webapps/pastebin/pastebin.factor b/extra/webapps/pastebin/pastebin.factor
index 273b250695..0772181b00 100644
--- a/extra/webapps/pastebin/pastebin.factor
+++ b/extra/webapps/pastebin/pastebin.factor
@@ -1,46 +1,40 @@
+! Copyright (C) 2007, 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
 USING: namespaces assocs sorting sequences kernel accessors
-hashtables sequences.lib locals db.types db.tuples db
-calendar calendar.format rss xml.writer
-xmode.catalog
+hashtables sequences.lib db.types db.tuples db
+calendar calendar.format math.parser rss xml.writer
+xmode.catalog validators html.components html.templates.chloe
 http.server
-http.server.crud
 http.server.actions
-http.server.components
-http.server.components.code
-http.server.templating.chloe
 http.server.auth
 http.server.auth.login
-http.server.boilerplate
-http.server.validators
-http.server.forms ;
+http.server.boilerplate ;
 IN: webapps.pastebin
 
-: <mode> ( id -- component )
-    modes keys natural-sort <choice> ;
+! ! !
+! DOMAIN MODEL
+! ! !
 
-: pastebin-template ( name -- template )
-    "resource:extra/webapps/pastebin/" swap ".xml" 3append <chloe> ;
+TUPLE: paste id summary author mode date contents annotations ;
 
-TUPLE: paste id summary author mode date contents annotations captcha ;
-
-paste "PASTE"
+\ paste "PASTE"
 {
     { "id" "ID" INTEGER +db-assigned-id+ }
     { "summary" "SUMMARY" { VARCHAR 256 } +not-null+ }
     { "author" "AUTHOR" { VARCHAR 256 } +not-null+ }
     { "mode" "MODE" { VARCHAR 256 } +not-null+ }
-    { "date" "DATE" DATETIME +not-null+ }
+    { "date" "DATE" DATETIME +not-null+ , }
     { "contents" "CONTENTS" TEXT +not-null+ }
 } define-persistent
 
 : <paste> ( id -- paste )
-    paste new
+    \ paste new
         swap >>id ;
 
 : pastes ( -- pastes )
     f <paste> select-tuples ;
 
-TUPLE: annotation aid id summary author mode contents date captcha ;
+TUPLE: annotation aid id summary author mode contents date ;
 
 annotation "ANNOTATION"
 {
@@ -63,175 +57,165 @@ annotation "ANNOTATION"
         dup id>> f <annotation> select-tuples >>annotations
     ] unless ;
 
-: <annotation-form> ( -- form )
-    "annotation" <form>
-        "annotation" pastebin-template >>view-template
-        "id" <integer>
-            hidden >>renderer
-            add-field
-        "aid" <integer>
-            hidden >>renderer
-            add-field
-        "summary" <string> add-field
-        "author" <string> add-field
-        "mode" <mode> add-field
-        "contents" "mode" <code> add-field
-        "date" <date> add-field ;
+: paste ( id -- paste )
+    <paste> select-tuple fetch-annotations ;
 
-: <new-annotation-form> ( -- form )
-    "annotation" <form>
-        "new-annotation" pastebin-template >>edit-template
-        "id" <integer>
-            hidden >>renderer
-            t >>required add-field
-        "summary" <string>
-            t >>required add-field
-        "author" <string>
-            t >>required
-            add-field
-        "mode" <mode>
-            "factor" >>default
-            t >>required
-            add-field
-        "contents" "mode" <code>
-            t >>required add-field
-        "captcha" <captcha> add-field ;
+: <id-redirect> ( id next -- response )
+    swap "id" associate <standard-redirect> ;
 
-: <paste-form> ( -- form )
-    "paste" <form>
-        "paste" pastebin-template >>view-template
-        "paste-summary" pastebin-template >>summary-template
-        "id" <integer>
-            hidden >>renderer add-field
-        "summary" <string> add-field
-        "author" <string> add-field
-        "mode" <mode> add-field
-        "date" <date> add-field
-        "contents" "mode" <code> add-field
-        "annotations" <annotation-form> +plain+ <list> add-field ;
+! ! !
+! LINKS, ETC
+! ! !
 
-: <new-paste-form> ( -- form )
-    "paste" <form>
-        "new-paste" pastebin-template >>edit-template
-        "summary" <string>
-            t >>required add-field
-        "author" <string>
-            t >>required add-field
-        "mode" <mode>
-            "factor" >>default
-            t >>required
-            add-field
-        "contents" "mode" <code>
-            t >>required add-field
-        "captcha" <captcha> add-field ;
+: pastebin-link ( -- url )
+    "$pastebin/list" f link>string ;
 
-: <paste-list-form> ( -- form )
-    "pastebin" <form>
-        "paste-list" pastebin-template >>view-template
-        "pastes" <paste-form> +plain+ <list> add-field ;
+GENERIC: entity-link ( entity -- url )
 
-:: <paste-list-action> ( -- action )
-    [let | form [ <paste-list-form> ] |
-        <action>
-            [
-                blank-values
+M: paste entity-link
+    id>> "id" associate "$pastebin/paste" swap link>string ;
 
-                pastes "pastes" set-value
+M: annotation entity-link
+    [ id>> "id" associate "$pastebin/paste" swap link>string ]
+    [ aid>> number>string "#" prepend ] bi
+    append ;
 
-                form view-form
-            ] >>display
-    ] ;
+: pastebin-template ( name -- template )
+    "resource:extra/webapps/pastebin/" swap ".xml" 3append <chloe> ;
 
-:: <annotate-action> ( form ctor next -- action )
-    <action>
-        { { "id" [ v-number ] } } >>get-params
+! ! !
+! PASTE LIST
+! ! !
 
-        [
-            "id" get f ctor call
+: <pastebin-action> ( -- action )
+    <page-action>
+        [ pastes "pastes" set-value ] >>init
+        "pastebin" pastebin-template >>template ;
 
-            from-tuple form set-defaults
-        ] >>init
-
-        [ form edit-form ] >>display
-
-        [
-            f f ctor call from-tuple
-
-            form validate-form
-
-            values-tuple insert-tuple
-
-            "id" value next <id-redirect>
-        ] >>submit ;
-
-: pastebin-feed-entries ( -- entries )
-    pastes <reversed> 20 short head [
-        [ summary>> ]
-        [ "$pastebin/view-paste" swap id>> "id" associate link>string ]
-        [ date>> ] tri
-        f swap <entry>
+: pastebin-feed-entries ( seq -- entries )
+    <reversed> 20 short head [
+        entry new
+            swap
+            [ summary>> >>title ]
+            [ date>> >>pub-date ]
+            [ entity-link >>link ]
+            tri
     ] map ;
 
 : pastebin-feed ( -- feed )
     feed new
         "Factor Pastebin" >>title
-        "http://paste.factorcode.org" >>link
-        pastebin-feed-entries >>entries ;
+        pastebin-link >>link
+        pastes pastebin-feed-entries >>entries ;
 
-: <feed-action> ( -- action )
-    <action>
+: <pastebin-feed-action> ( -- action )
+    <feed-action> [ pastebin-feed ] >>feed ;
+
+! ! !
+! PASTES
+! ! !
+
+: <paste-action> ( -- action )
+    <page-action>
         [
-            "text/xml" <content>
-            [ pastebin-feed feed>xml write-xml ] >>body
-        ] >>display ;
+            validate-integer-id
+            "id" value paste from-tuple
 
-:: <view-paste-action> ( form ctor -- action )
-    <action>
-        { { "id" [ v-number ] } } >>get-params
-
-        [ "id" get ctor call select-tuple fetch-annotations from-tuple ] >>init
-
-        [ form view-form ] >>display ;
-
-:: <delete-paste-action> ( ctor next -- action )
-    <action>
-        { { "id" [ v-number ] } } >>post-params
-
-        [
-            "id" get ctor call delete-tuples
-
-            "id" get f <annotation> delete-tuples
-
-            next f <permanent-redirect>
-        ] >>submit ;
-
-:: <delete-annotation-action> ( ctor next -- action )
-    <action>
-        { { "aid" [ v-number ] } } >>post-params
-
-        [
-            f "aid" get ctor call select-tuple
-            [ delete-tuples ] [ id>> next <id-redirect> ] bi
-        ] >>submit ;
-
-:: <new-paste-action> ( form ctor next -- action )
-    <action>
-        [
-            f ctor call from-tuple
-
-            form set-defaults
+            "new-annotation" [
+                mode-names "modes" set-value
+                "factor" "mode" set-value
+            ] nest-values
         ] >>init
 
-        [ form edit-form ] >>display
+        "paste" pastebin-template >>template ;
+
+: paste-feed-entries ( paste -- entries )
+    fetch-annotations annotations>> pastebin-feed-entries ;
+
+: paste-feed ( paste -- feed )
+    feed new
+        swap
+        [ "Paste #" swap id>> number>string append >>title ]
+        [ entity-link >>link ]
+        [ paste-feed-entries >>entries ]
+        tri ;
+
+: <paste-feed-action> ( -- action )
+    <feed-action>
+        [ validate-integer-id ] >>init
+        [ "id" value paste annotations>> paste-feed ] >>feed ;
+
+: <new-paste-action> ( -- action )
+    <page-action>
+        [
+            "factor" "mode" set-value
+            mode-names "modes" set-value
+        ] >>init
+
+        "new-paste" pastebin-template >>template
 
         [
-            f ctor call from-tuple
+            {
+                { "summary" [ v-one-line ] }
+                { "author" [ v-one-line ] }
+                { "mode" [ v-mode ] }
+                { "contents" [ v-required ] }
+                { "captcha" [ v-captcha ] }
+            } validate-params
 
-            form validate-form
+            f <paste>
+                now >>date
+                dup { "summary" "author" "mode" "contents" } deposit-slots
+            [ insert-tuple ]
+            [ id>> "$pastebin/paste" <id-redirect> ] bi
+        ] >>submit ;
 
-            values-tuple insert-tuple
+: <delete-paste-action> ( -- action )
+    <action>
+        [ validate-integer-id ] >>validate
 
-            "id" value next <id-redirect>
+        [
+            "id" value <paste> delete-tuples
+            "id" value f <annotation> delete-tuples
+            "$pastebin/list" f <permanent-redirect>
+        ] >>submit ;
+
+! ! !
+! ANNOTATIONS
+! ! !
+
+: <new-annotation-action> ( -- action )
+    <action>
+        [
+            {
+                { "summary" [ v-one-line ] }
+                { "author" [ v-one-line ] }
+                { "mode" [ v-mode ] }
+                { "contents" [ v-required ] }
+                { "captcha" [ v-captcha ] }
+            } validate-params
+        ] >>validate
+
+        [
+            f f <annotation>
+                now >>date
+                dup { "summary" "author" "mode" "contents" } deposit-slots
+            [ insert-tuple ]
+            [
+                ! Add anchor here
+                "id" value "$pastebin/paste" <id-redirect>
+            ] bi
+        ] >>submit ;
+
+: <delete-annotation-action> ( -- action )
+    <action>
+        [ { { "aid" [ v-number ] } } validate-params ] >>validate
+
+        [
+            f "aid" value <annotation> select-tuple
+            [ delete-tuples ]
+            [ id>> "$pastebin/paste" <id-redirect> ]
+            bi
         ] >>submit ;
 
 TUPLE: pastebin < dispatcher ;
@@ -242,17 +226,17 @@ can-delete-pastes? define-capability
 
 : <pastebin> ( -- responder )
     pastebin new-dispatcher
-        <paste-list-action> "list" add-main-responder
-        <feed-action> "feed.xml" add-responder
-        <paste-form> [ <paste> ] <view-paste-action> "view-paste" add-responder
-        [ <paste> ] "$pastebin/list" <delete-paste-action> { can-delete-pastes? } <protected> "delete-paste" add-responder
-        [ <annotation> ] "$pastebin/view-paste" <delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder
-        <paste-form> [ <paste> ]    <view-paste-action>     "$pastebin/view-paste"   add-responder
-        <new-paste-form> [ <paste> now >>date ] "$pastebin/view-paste" <new-paste-action>     "new-paste"    add-responder
-        <new-annotation-form> [ <annotation> now >>date ] "$pastebin/view-paste" <annotate-action> "annotate" add-responder
+        <pastebin-action> "list" add-main-responder
+        <pastebin-feed-action> "list.atom" add-responder
+        <paste-action> "paste" add-responder
+        <paste-feed-action> "paste.atom" add-responder
+        <new-paste-action> "new-paste" add-responder
+        <delete-paste-action> { can-delete-pastes? } <protected> "delete-paste" add-responder
+        <new-annotation-action> "new-annotation" add-responder
+        <delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder
     <boilerplate>
-        "pastebin" pastebin-template >>template ;
+        "pastebin-common" pastebin-template >>template ;
 
-: init-pastes-table paste ensure-table ;
+: init-pastes-table \ paste ensure-table ;
 
 : init-annotations-table annotation ensure-table ;
diff --git a/extra/webapps/pastebin/pastebin.xml b/extra/webapps/pastebin/pastebin.xml
index 46604598ce..f0abd97c63 100644
--- a/extra/webapps/pastebin/pastebin.xml
+++ b/extra/webapps/pastebin/pastebin.xml
@@ -2,6 +2,8 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
+	<t:atom t:title="Pastebin - Atom" t:href="$pastebin/list.atom" />
+
 	<t:title>Pastebin</t:title>
 
 	<table width="100%">
diff --git a/extra/webapps/planet/admin.xml b/extra/webapps/planet/admin.xml
index c79fe2efd1..4711ca4716 100644
--- a/extra/webapps/planet/admin.xml
+++ b/extra/webapps/planet/admin.xml
@@ -4,11 +4,19 @@
 
 	<t:title>Planet Factor Administration</t:title>
 
-	<t:summary t:component="blogroll" />
+	<ul>
+		<t:each-tuple t:values="blogroll">
+			<li>
+				<t:a t:href="$planet-factor/admin/edit-blog" t:query="id">
+					<t:label t:name="name" />
+				</t:a>
+			</li>
+		</t:each-tuple>
+	</ul>
 
 	<p>
-		<t:a t:href="$planet-factor/admin/edit-blog">Add Blog</t:a>
-		| <t:a t:href="$planet-factor/admin/update">Update</t:a>
+		<t:a t:href="$planet-factor/admin/new-blog">Add Blog</t:a>
+		| <t:button t:action="$planet-factor/admin/update" class="link-button link">Update</t:button>
 	</p>
 
 </t:chloe>
diff --git a/extra/webapps/planet/edit-blog.xml b/extra/webapps/planet/edit-blog.xml
index ebfccc47de..fd9c659f59 100644
--- a/extra/webapps/planet/edit-blog.xml
+++ b/extra/webapps/planet/edit-blog.xml
@@ -10,17 +10,17 @@
 
 			<tr>
 				<th class="field-label">Blog name:</th>
-				<td><t:edit t:component="name" /></td>
+				<td><t:field t:name="name" /></td>
 			</tr>
 
 			<tr>
 				<th class="field-label">Home page:</th>
-				<td><t:edit t:component="www-url" /></td>
+				<td><t:field t:name="www-url" /></td>
 			</tr>
 
 			<tr>
 				<th class="field-label">Feed:</th>
-				<td><t:edit t:component="feed-url" /></td>
+				<td><t:field t:name="feed-url" /></td>
 			</tr>
 
 		</table>
@@ -30,4 +30,5 @@
 	</t:form>
 
 	<t:button t:action="$planet-factor/admin/delete-blog" t:for="id" class="link-button link">Delete</t:button>
+
 </t:chloe>
diff --git a/extra/webapps/planet/mini-planet.xml b/extra/webapps/planet/mini-planet.xml
new file mode 100644
index 0000000000..1338463bcf
--- /dev/null
+++ b/extra/webapps/planet/mini-planet.xml
@@ -0,0 +1,14 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:each-tuple t:values="postings">
+
+		<p class="news">
+			<strong><t:view t:component="title" /></strong> <br/>
+			<t:a value="link" t:session="none" class="more">Read More...</t:a>
+		</p>
+
+	</t:each-tuple>
+
+</t:chloe>
diff --git a/extra/webapps/planet/new-blog.xml b/extra/webapps/planet/new-blog.xml
new file mode 100644
index 0000000000..4a9638da03
--- /dev/null
+++ b/extra/webapps/planet/new-blog.xml
@@ -0,0 +1,32 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>Edit Blog</t:title>
+
+	<t:form t:action="$planet-factor/admin/new-blog">
+
+		<table>
+
+			<tr>
+				<th class="field-label">Blog name:</th>
+				<td><t:field t:name="name" /></td>
+			</tr>
+
+			<tr>
+				<th class="field-label">Home page:</th>
+				<td><t:field t:name="www-url" /></td>
+			</tr>
+
+			<tr>
+				<th class="field-label">Feed:</th>
+				<td><t:field t:name="feed-url" /></td>
+			</tr>
+
+		</table>
+
+		<input type="SUBMIT" value="Done" />
+
+	</t:form>
+
+</t:chloe>
diff --git a/extra/webapps/planet/planet-common.xml b/extra/webapps/planet/planet-common.xml
new file mode 100644
index 0000000000..29609e12ba
--- /dev/null
+++ b/extra/webapps/planet/planet-common.xml
@@ -0,0 +1,25 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:style t:include="resource:extra/webapps/planet/planet.css" />
+
+	<div class="navbar">
+		  <t:a t:href="$planet-factor/list">Front Page</t:a>
+		| <t:a t:href="$planet-factor/feed.xml">Atom Feed</t:a>
+		| <t:a t:href="$planet-factor/admin">Admin</t:a>
+
+		<t:if t:code="http.server.sessions:uid">
+			<t:if t:code="http.server.auth.login:allow-edit-profile?">
+				| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
+			</t:if>
+	
+			| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
+		</t:if>
+	</div>
+
+	<h1><t:write-title /></h1>
+
+        <t:call-next-template />
+
+</t:chloe>
diff --git a/extra/webapps/planet/planet.factor b/extra/webapps/planet/planet.factor
index c8aeab35a8..e3b5b17a32 100755
--- a/extra/webapps/planet/planet.factor
+++ b/extra/webapps/planet/planet.factor
@@ -1,22 +1,18 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: kernel accessors sequences sorting locals math math.order
+USING: kernel accessors sequences sorting math math.order
 calendar alarms logging concurrency.combinators namespaces
-sequences.lib db.types db.tuples db fry
+sequences.lib db.types db.tuples db fry locals hashtables
+html.components html.templates.chloe
 rss xml.writer
+validators
 http.server
-http.server.crud
-http.server.forms
 http.server.actions
 http.server.boilerplate
-http.server.templating.chloe
-http.server.components
 http.server.auth.login
 http.server.auth ;
 IN: webapps.planet
 
-TUPLE: planet-factor < dispatcher postings ;
-
 : planet-template ( name -- template )
     "resource:extra/webapps/planet/" swap ".xml" 3append <chloe> ;
 
@@ -34,92 +30,63 @@ blog "BLOGS"
     { "feed-url" "FEEDURL" { VARCHAR 256 } +not-null+ }
 } define-persistent
 
+! TUPLE: posting < entry id ;
+TUPLE: posting id title link description pub-date ;
+
+posting "POSTINGS"
+{
+    { "id" "ID" INTEGER +db-assigned-id+ }
+    { "title" "TITLE" { VARCHAR 256 } +not-null+ }
+    { "link" "LINK" { VARCHAR 256 } +not-null+ }
+    { "description" "DESCRIPTION" TEXT +not-null+ }
+    { "pub-date" "DATE" TIMESTAMP +not-null+ }
+} define-persistent
+
 : init-blog-table blog ensure-table ;
 
+: init-postings-table posting ensure-table ;
+
 : <blog> ( id -- todo )
     blog new
         swap >>id ;
 
 : blogroll ( -- seq )
-    f <blog> select-tuples [ [ name>> ] compare ] sort ;
+    f <blog> select-tuples
+    [ [ name>> ] compare ] sort ;
 
-: <entry-form> ( -- form )
-    "entry" <form>
-        "entry" planet-template >>view-template
-        "entry-summary" planet-template >>summary-template
-        "title" <string> add-field
-        "description" <html-text> add-field
-        "pub-date" <date> add-field ;
+: postings ( -- seq )
+    posting new select-tuples
+    [ [ pub-date>> ] compare invert-comparison ] sort ;
 
-: <blog-form> ( -- form )
-    "blog" <form>
-        "edit-blog" planet-template >>edit-template
-        "blog-admin-link" planet-template >>summary-template
-        "id" <integer>
-            hidden >>renderer
-            add-field
-        "name" <string>
-            t >>required
-            add-field
-        "www-url" <url>
-            t >>required
-            add-field
-        "feed-url" <url>
-            t >>required
-            add-field ;
+: <edit-blogroll-action> ( -- action )
+    <page-action>
+        [ blogroll "blogroll" set-value ] >>init
+        "admin" planet-template >>template ;
 
-: <planet-factor-form> ( -- form )
-    "planet-factor" <form>
-        "postings" planet-template >>view-template
-        "postings-summary" planet-template >>summary-template
-        "postings" <entry-form> +plain+ <list> add-field
-        "blogroll" "blog" <link> +unordered+ <list> add-field ;
+: <planet-action> ( -- action )
+    <page-action>
+        [
+            blogroll "blogroll" set-value
+            postings "postings" set-value
+        ] >>init
 
-: <admin-form> ( -- form )
-    "admin" <form>
-        "admin" planet-template >>view-template
-        "blogroll" <blog-form> +unordered+ <list> add-field ;
+        "planet" planet-template >>template ;
 
-:: <edit-blogroll-action> ( planet -- action )
-    [let | form [ <admin-form> ] |
-        <action>
-            [
-                blank-values
-
-                blogroll "blogroll" set-value
-
-                form view-form
-            ] >>display
-    ] ;
-
-:: <planet-action> ( planet -- action )
-    [let | form [ <planet-factor-form> ] |
-        <action>
-            [
-                blank-values
-
-                planet postings>> "postings" set-value
-                blogroll "blogroll" set-value
-
-                form view-form
-            ] >>display
-    ] ;
-
-:: planet-feed ( planet -- feed )
+: planet-feed ( -- feed )
     feed new
         "Planet Factor" >>title
         "http://planet.factorcode.org" >>link
-        planet postings>> 16 short head >>entries ;
+        postings >>entries ;
 
-:: <feed-action> ( planet -- action )
-    <action>
-        [
-            "text/xml" <content>
-            [ planet planet-feed feed>xml write-xml ] >>body
-        ] >>display ;
+: <planet-feed-action> ( -- action )
+    <feed-action> [ planet-feed ] >>feed ;
 
-: <posting> ( name entry -- entry' )
-    clone [ ": " swap 3append ] change-title ;
+:: <posting> ( entry name -- entry' )
+    posting new
+        name ": " entry title>> 3append >>title
+        entry link>> >>link
+        entry description>> >>description
+        entry pub-date>> >>pub-date ;
 
 : fetch-feed ( url -- feed )
     download-feed entries>> ;
@@ -127,55 +94,101 @@ blog "BLOGS"
 \ fetch-feed DEBUG add-error-logging
 
 : fetch-blogroll ( blogroll -- entries )
-    dup
-    [ feed-url>> fetch-feed ] parallel-map
-    [ >r name>> r> [ <posting> ] with map ] 2map concat ;
+    [ [ feed-url>> fetch-feed ] parallel-map ] [ [ name>> ] map ] bi
+    [ '[ , <posting> ] map ] 2map concat ;
 
 : sort-entries ( entries -- entries' )
-    [ [ pub-date>> ] compare ] sort <reversed> ;
+    [ [ pub-date>> ] compare invert-comparison ] sort ;
 
-: update-cached-postings ( planet -- )
-    "webapps.planet" [
-        blogroll fetch-blogroll sort-entries 8 short head
-        >>postings drop
-    ] with-logging ;
+: update-cached-postings ( -- )
+    blogroll fetch-blogroll sort-entries 8 short head [
+        posting new delete-tuples
+        [ insert-tuple ] each
+    ] with-transaction ;
 
-:: <update-action> ( planet -- action )
+: <update-action> ( -- action )
     <action>
         [
-            planet update-cached-postings
-            "" f <temporary-redirect>
-        ] >>display ;
+            update-cached-postings
+            "" f <permanent-redirect>
+        ] >>submit ;
 
-:: <planet-factor-admin> ( planet-factor -- responder )
-    [let | blog-form [ <blog-form> ]
-           blog-ctor [ [ <blog> ] ] |
-        <dispatcher>
-            planet-factor <edit-blogroll-action> >>default
+: <delete-blog-action> ( -- action )
+    <action>
+        [ validate-integer-id ] >>validate
 
-            planet-factor <update-action> "update" add-responder
+        [
+            "id" value <blog> delete-tuples
+            "$planet-factor/admin" f <standard-redirect>
+        ] >>submit ;
 
-            ! Administrative CRUD
-                      blog-ctor "$planet-factor/admin"          <delete-action> "delete-blog" add-responder
-            blog-form blog-ctor "$planet-factor/admin" <edit-action>   "edit-blog"   add-responder
-    ] ;
+: validate-blog ( -- )
+    {
+        { "name" [ v-one-line ] }
+        { "www-url" [ v-url ] }
+        { "feed-url" [ v-url ] }
+    } validate-params ;
+
+: <id-redirect> ( id next -- response )
+    swap "id" associate <standard-redirect> ;
+
+: <new-blog-action> ( -- action )
+    <page-action>
+        "new-blog" planet-template >>template
+
+        [ validate-blog ] >>validate
+
+        [
+            f <blog>
+                dup { "name" "www-url" "feed-url" } deposit-slots
+            [ insert-tuple ]
+            [ id>> "$planet-factor/admin/edit-blog" <id-redirect> ] bi
+        ] >>submit ;
+    
+: <edit-blog-action> ( -- action )
+    <page-action>
+        [
+            validate-integer-id
+            "id" value <blog> select-tuple from-tuple
+        ] >>init
+
+        "edit-blog" planet-template >>template
+
+        [
+            validate-integer-id
+            validate-blog
+        ] >>validate
+
+        [
+            f <blog>
+                dup { "id" "name" "www-url" "feed-url" } deposit-slots
+            [ update-tuple ]
+            [ id>> "$planet-factor/admin" <id-redirect> ] bi
+        ] >>submit ;
+
+TUPLE: planet-factor-admin < dispatcher ;
+
+: <planet-factor-admin> ( -- responder )
+    planet-factor-admin new-dispatcher
+        <edit-blogroll-action> "blogroll" add-main-responder
+        <update-action> "update" add-responder
+        <new-blog-action> "new-blog" add-responder
+        <edit-blog-action> "edit-blog" add-responder
+        <delete-blog-action> "delete-blog" add-responder ;
 
 SYMBOL: can-administer-planet-factor?
 
 can-administer-planet-factor? define-capability
 
+TUPLE: planet-factor < dispatcher ;
+
 : <planet-factor> ( -- responder )
     planet-factor new-dispatcher
-        dup <planet-action> "list" add-main-responder
-        dup <feed-action> "feed.xml" add-responder
-        dup <planet-factor-admin> { can-administer-planet-factor? } <protected> "admin" add-responder
+        <planet-action> "list" add-main-responder
+        <feed-action> "feed.xml" add-responder
+        <planet-factor-admin> { can-administer-planet-factor? } <protected> "admin" add-responder
     <boilerplate>
-        "planet" planet-template >>template ;
+        "planet-common" planet-template >>template ;
 
-: start-update-task ( planet db seq -- )
-    '[
-        , , , [
-            dup filter-responder? [ responder>> ] when
-            update-cached-postings
-        ] with-db
-    ] 10 minutes every drop ;
+: start-update-task ( db params -- )
+    '[ , , [ update-cached-postings ] with-db ] 10 minutes every drop ;
diff --git a/extra/webapps/planet/planet.xml b/extra/webapps/planet/planet.xml
index 29609e12ba..526a9b306b 100644
--- a/extra/webapps/planet/planet.xml
+++ b/extra/webapps/planet/planet.xml
@@ -2,24 +2,44 @@
 
 <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
 
-	<t:style t:include="resource:extra/webapps/planet/planet.css" />
+	<t:title>Planet Factor</t:title>
 
-	<div class="navbar">
-		  <t:a t:href="$planet-factor/list">Front Page</t:a>
-		| <t:a t:href="$planet-factor/feed.xml">Atom Feed</t:a>
-		| <t:a t:href="$planet-factor/admin">Admin</t:a>
+	<table width="100%" cellpadding="10">
+		<tr>
+			<td>
 
-		<t:if t:code="http.server.sessions:uid">
-			<t:if t:code="http.server.auth.login:allow-edit-profile?">
-				| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
-			</t:if>
-	
-			| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
-		</t:if>
-	</div>
+				<t:each-tuple t:values="postings">
 
-	<h1><t:write-title /></h1>
+					<h2 class="posting-title">
+						<t:a t:value="link" t:session="none"><t:label t:name="title" /></t:a>
+					</h2>
 
-        <t:call-next-template />
+					<p class="posting-body">
+						<t:html t:name="description" />
+					</p>
+
+					<p class="posting-date">
+						<t:a t:value="link" t:session="none"><t:label t:name="pub-date" /></t:a>
+					</p>
+
+				</t:each-tuple>
+
+			</td>
+
+			<td valign="top" width="25%" class="infobox">
+
+				<h2>Blogroll</h2>
+
+				<ul>
+					<t:each t:values="blogroll">
+						<li>
+							<t:link t:name="value"/>
+						</li>
+					</t:each>
+				</ul>
+
+			</td>
+		</tr>
+	</table>
 
 </t:chloe>
diff --git a/extra/webapps/planet/postings-summary.xml b/extra/webapps/planet/postings-summary.xml
deleted file mode 100644
index 765c3a8006..0000000000
--- a/extra/webapps/planet/postings-summary.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:summary t:component="postings" />
-
-</t:chloe>
diff --git a/extra/webapps/planet/postings.xml b/extra/webapps/planet/postings.xml
deleted file mode 100644
index c2c73d7e89..0000000000
--- a/extra/webapps/planet/postings.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:title>Planet Factor</t:title>
-
-	<table width="100%" cellpadding="10">
-                <tr>
-                        <td> <t:view t:component="postings" /> </td>
-  
-                        <td valign="top" width="25%" class="infobox">
-                                <h2>Blogroll</h2>
-  
-                                <t:summary t:component="blogroll" />
-                        </td>
-                </tr>
-        </table>
-
-</t:chloe>
diff --git a/extra/webapps/todo/edit-todo.xml b/extra/webapps/todo/edit-todo.xml
index e1d4c40e23..0974c8ce1b 100644
--- a/extra/webapps/todo/edit-todo.xml
+++ b/extra/webapps/todo/edit-todo.xml
@@ -6,9 +6,9 @@
 
 	<t:form t:action="$todo-list/edit" t:for="id">
 		<table>
-			<tr><th class="field-label">Summary: </th><td><t:edit t:component="summary" /></td></tr>
-			<tr><th class="field-label">Priority: </th><td><t:edit t:component="priority" /></td></tr>
-			<tr><th class="field-label big-field-label">Description:</th><td><t:edit t:component="description" /></td></tr>
+			<tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
+			<tr><th class="field-label">Priority: </th><td><t:field t:name="priority" /></td></tr>
+			<tr><th class="field-label big-field-label">Description:</th><td><t:textarea t:name="description" t:rows="20" t:cols="60" /></td></tr>
 		</table>
 
 		<input type="SUBMIT" value="Done" />
diff --git a/extra/webapps/todo/todo-list.xml b/extra/webapps/todo/todo-list.xml
index 66abeafc86..845c38dbf7 100644
--- a/extra/webapps/todo/todo-list.xml
+++ b/extra/webapps/todo/todo-list.xml
@@ -5,8 +5,33 @@
 	<t:title>My Todo List</t:title>
 
 	<table class="todo-list">
-		<tr><th>Summary</th><th>Priority</th><th>View</th><th>Edit</th></tr>
-		<t:summary t:component="list" />
+
+		<tr>
+			<th>Summary</th>
+			<th>Priority</th>
+			<th>View</th>
+			<th>Edit</th>
+		</tr>
+
+		<t:each-tuple t:values="items">
+
+			<tr>
+				<td>
+					<t:label t:name="summary" />
+				</td>
+				<td>
+					<t:label t:name="priority" />
+				</td>
+				<td>
+					<t:a t:href="$todo-list/view" t:query="id">View</t:a>
+				</td>
+				<td>
+					<t:a t:href="$todo-list/edit" t:query="id">Edit</t:a>
+				</td>
+			</tr>
+
+		</t:each-tuple>
+
 	</table>
 
 </t:chloe>
diff --git a/extra/webapps/todo/todo.factor b/extra/webapps/todo/todo.factor
index 8bfda1aad5..e3b174eaea 100755
--- a/extra/webapps/todo/todo.factor
+++ b/extra/webapps/todo/todo.factor
@@ -1,14 +1,11 @@
 ! Copyright (c) 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors kernel locals sequences namespaces
-db db.types db.tuples
+USING: accessors kernel sequences namespaces
+db db.types db.tuples validators hashtables
+html.components
+html.templates.chloe
 http.server.sessions
-http.server.components
-http.server.components.farkup
-http.server.forms
-http.server.templating.chloe
 http.server.boilerplate
-http.server.crud
 http.server.auth
 http.server.actions
 http.server.db
@@ -37,44 +34,86 @@ todo "TODO"
 : todo-template ( name -- template )
     "resource:extra/webapps/todo/" swap ".xml" 3append <chloe> ;
 
-: <todo-form> ( -- form )
-    "todo" <form>
-        "view-todo" todo-template >>view-template
-        "edit-todo" todo-template >>edit-template
-        "todo-summary" todo-template >>summary-template
-        "id" <integer>
-            hidden >>renderer
-            add-field
-        "summary" <string>
-            t >>required
-            add-field
-        "priority" <integer>
-            t >>required
-            0 >>default
-            0 >>min-value
-            10 >>max-value
-            add-field
-        "description" <farkup>
-            add-field ;
+: <view-action> ( -- action )
+    <page-action>
+        [
+            validate-integer-id
+            "id" value <todo> select-tuple from-tuple
+        ] >>init
+        
+        "view-todo" todo-template >>template ;
 
-: <todo-list-form> ( -- form )
-    "todo-list" <form>
-        "todo-list" todo-template >>view-template
-        "list" <todo-form> +plain+ <list>
-        add-field ;
+: <id-redirect> ( id next -- response )
+    swap "id" associate <standard-redirect> ;
+
+: validate-todo ( -- )
+    {
+        { "summary" [ v-one-line ] }
+        { "priority" [ v-integer 0 v-min-value 10 v-max-value ] }
+        { "description" [ v-required ] }
+    } validate-params ;
+
+: <new-action> ( -- action )
+    <page-action>
+        [ 0 "priority" set-value ] >>init
+
+        "edit-todo" todo-template >>template
+
+        [ validate-todo ] >>validate
+
+        [
+            f <todo>
+                dup { "summary" "description" } deposit-slots
+            [ insert-tuple ]
+            [ id>> "$todo-list/view" <id-redirect> ]
+            bi
+        ] >>submit ;
+
+: <edit-action> ( -- action )
+    <page-action>
+        [
+            validate-integer-id
+            "id" value <todo> select-tuple from-tuple
+        ] >>init
+
+        "edit-todo" todo-template >>template
+
+        [
+            validate-integer-id
+            validate-todo
+        ] >>validate
+
+        [
+            f <todo>
+                dup { "id" "summary" "priority" "description" } deposit-slots
+            [ update-tuple ]
+            [ id>> "$todo-list/view" <id-redirect> ]
+            bi
+        ] >>submit ;
+
+: <delete-action> ( -- action )
+    <action>
+        [ validate-integer-id ] >>validate
+
+        [
+            "id" get <todo> delete-tuples
+            "$todo-list/list" f <standard-redirect>
+        ] >>submit ;
+
+: <list-action> ( -- action )
+    <page-action>
+        [ f <todo> select-tuples "items" set-value ] >>init
+        "todo-list" todo-template >>template ;
 
 TUPLE: todo-list < dispatcher ;
 
-:: <todo-list> ( -- responder )
-    [let | todo-form [ <todo-form> ]
-           list-form [ <todo-list-form> ]
-           ctor [ [ <todo> ] ] |
-        todo-list new-dispatcher
-            list-form ctor        <list-action>   "list"   add-main-responder
-            todo-form ctor        <view-action>   "view"   add-responder
-            todo-form ctor "$todo-list/view" <edit-action>   "edit"   add-responder
-                      ctor "$todo-list/list" <delete-action> "delete" add-responder
-        <boilerplate>
-            "todo" todo-template >>template
-        f <protected>
-    ] ;
+: <todo-list> ( -- responder )
+    todo-list new-dispatcher
+        <list-action>   "list"   add-main-responder
+        <view-action>   "view"   add-responder
+        <new-action>    "new"    add-responder
+        <edit-action>   "edit"   add-responder
+        <delete-action> "delete" add-responder
+    <boilerplate>
+        "todo" todo-template >>template
+    f <protected> ;
diff --git a/extra/webapps/todo/todo.xml b/extra/webapps/todo/todo.xml
index 651e29d867..39ab5cda8b 100644
--- a/extra/webapps/todo/todo.xml
+++ b/extra/webapps/todo/todo.xml
@@ -12,7 +12,7 @@
 			| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
 		</t:if>
 
-		<t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
+		| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
 	</div>
 
 	<h1><t:write-title /></h1>
diff --git a/extra/webapps/todo/view-todo.xml b/extra/webapps/todo/view-todo.xml
index 8c90ba9056..a443528bac 100644
--- a/extra/webapps/todo/view-todo.xml
+++ b/extra/webapps/todo/view-todo.xml
@@ -5,12 +5,12 @@
 	<t:title>View Item</t:title>
 
 	<table>
-		<tr><th class="field-label">Summary:    </th><td><t:view t:component="summary"     /></td></tr>
-		<tr><th class="field-label">Priority:   </th><td><t:view t:component="priority"    /></td></tr>
+		<tr><th class="field-label">Summary: </th><td><t:label t:name="summary" /></td></tr>
+		<tr><th class="field-label">Priority: </th><td><t:label t:name="priority" /></td></tr>
 	</table>
 
 	<div class="description">
-		<t:view t:component="description" />
+		<t:farkup t:name="description" />
 	</div>
 
 	<t:a t:href="$todo-list/edit" t:query="id">Edit</t:a>
diff --git a/extra/webapps/user-admin/edit-user.xml b/extra/webapps/user-admin/edit-user.xml
index 9c0fe702bb..3f9ac8d690 100644
--- a/extra/webapps/user-admin/edit-user.xml
+++ b/extra/webapps/user-admin/edit-user.xml
@@ -10,44 +10,44 @@
 	
 	<tr>
 		<th class="field-label">User name:</th>
-		<td><t:view t:component="username" /></td>
+		<td><t:label t:name="username" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Real name:</th>
-		<td><t:edit t:component="realname" /></td>
+		<td><t:field t:name="realname" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">New password:</th>
-		<td><t:edit t:component="new-password" /></td>
+		<td><t:password t:name="new-password" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Verify:</th>
-		<td><t:edit t:component="verify-password" /></td>
+		<td><t:password t:name="verify-password" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">E-mail:</th>
-		<td><t:edit t:component="email" /></td>
+		<td><t:field t:name="email" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label big-field-label">Capabilities:</th>
-		<td><t:edit t:component="capabilities" /></td>
+		<td><t:choice t:name="capabilities" t:choices="all-capabilities" t:multiple="true" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Profile:</th>
-		<td><t:view t:component="profile" /></td>
+		<td><t:inspector t:name="profile" /></td>
 	</tr>
 
 	</table>
 	
 	<p>
 		<button type="submit" class="link-button link">Update</button>
-		<t:validation-message />
+		<t:validation-messages />
 	</p>
 
 	</t:form>
diff --git a/extra/webapps/user-admin/new-user.xml b/extra/webapps/user-admin/new-user.xml
index 2d67639985..881dca9c16 100644
--- a/extra/webapps/user-admin/new-user.xml
+++ b/extra/webapps/user-admin/new-user.xml
@@ -10,39 +10,39 @@
 	
 	<tr>
 		<th class="field-label">User name:</th>
-		<td><t:edit t:component="username" /></td>
+		<td><t:field t:name="username" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Real name:</th>
-		<td><t:edit t:component="realname" /></td>
+		<td><t:field t:name="realname" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">New password:</th>
-		<td><t:edit t:component="new-password" /></td>
+		<td><t:password t:name="new-password" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">Verify:</th>
-		<td><t:edit t:component="verify-password" /></td>
+		<td><t:password t:name="verify-password" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label">E-mail:</th>
-		<td><t:edit t:component="email" /></td>
+		<td><t:field t:name="email" /></td>
 	</tr>
 	
 	<tr>
 		<th class="field-label big-field-label">Capabilities:</th>
-		<td><t:edit t:component="capabilities" /></td>
+		<td><t:choice t:name="capabilities" t:choices="all-capabilities" t:multiple="true" /></td>
 	</tr>
 
 	</table>
 	
 	<p>
 		<button type="submit" class="link-button link">Create</button>
-		<t:validation-message />
+		<t:validation-messages />
 	</p>
 
 	</t:form>
diff --git a/extra/webapps/user-admin/user-admin.factor b/extra/webapps/user-admin/user-admin.factor
index 172ab62c50..728d5215f0 100644
--- a/extra/webapps/user-admin/user-admin.factor
+++ b/extra/webapps/user-admin/user-admin.factor
@@ -26,26 +26,22 @@ IN: webapps.user-admin
     [ ":" split1 swap lookup ] map ;
 
 : <user-list-action> ( -- action )
-    <action>
+    <page-action>
         [ f <user> select-tuples "users" set-value ] >>init
-        [ "user-list" admin-template <html-content> ] >>display ;
+        "user-list" admin-template >>template ;
 
 : <new-user-action> ( -- action )
-    <action>
+    <page-action>
         [
-            "username" param <user> {
-                [ username>> "username" set-value ]
-                [ realname>> "realname" set-value ]
-                [ email>> "email" set-value ]
-                [ profile>> "profile" set-value ]
-            } cleave
-
-            capabilities get "all-capabilities" set-value
+            "username" param <user> from-tuple
+            capabilities get words>strings "all-capabilities" set-value
         ] >>init
 
-        [ "new-user" admin-template <html-content> ] >>display
+        "new-user" admin-template >>template
 
         [
+            capabilities get words>strings "all-capabilities" set-value
+
             {
                 { "username" [ v-username ] }
                 { "realname" [ v-one-line ] }
@@ -72,26 +68,26 @@ IN: webapps.user-admin
 
             "$user-admin" f <standard-redirect>
         ] >>submit ;
-    
+
+: validate-username ( -- )
+    { { "username" [ v-username ] } } validate-params ;
+
 : <edit-user-action> ( -- action )
-    <action>
+    <page-action>
         [
-            { { "username" [ v-username ] } } validate-params
+            validate-username
 
-            "username" value <user> select-tuple {
-                [ username>> "username" set-value ]
-                [ realname>> "realname" set-value ]
-                [ email>> "email" set-value ]
-                [ profile>> "profile" set-value ]
-                [ capabilities>> words>strings "capabilities" set-value ]
-            } cleave
+            "username" value <user> select-tuple
+            [ from-tuple ] [ capabilities>> words>strings "capabilities" set-value ] bi
 
-            capabilities get "all-capabilities" set-value
+            capabilities get words>strings "all-capabilities" set-value
         ] >>init
 
-        [ "edit-user" admin-template <html-content> ] >>display
+        "edit-user" admin-template >>template
 
         [
+            capabilities get words>strings "all-capabilities" set-value
+
             {
                 { "username" [ v-username ] }
                 { "realname" [ v-one-line ] }
@@ -102,9 +98,9 @@ IN: webapps.user-admin
             } validate-params
 
             "new-password" "verify-password"
-            [ value empty? ] both? [
+            [ value empty? not ] either? [
                 same-password-twice
-            ] unless
+            ] when
         ] >>validate
 
         [
@@ -112,9 +108,9 @@ IN: webapps.user-admin
                 "realname" value >>realname
                 "email" value >>email
 
-            "new-password" value empty? [ drop ] [
+            "new-password" value empty? [
                 "new-password" value >>encoded-password
-            ] if
+            ] unless
 
             "capabilities" value {
                 { [ dup string? ] [ 1array ] }
@@ -129,7 +125,8 @@ IN: webapps.user-admin
 : <delete-user-action> ( -- action )
     <action>
         [
-            { { "username" [ v-username ] } } validate-params
+            validate-username
+
             [ <user> select-tuple 1 >>deleted update-tuple ]
             [ logout-all-sessions ]
             bi
@@ -145,12 +142,12 @@ can-administer-users? define-capability
 
 : <user-admin> ( -- responder )
     user-admin new-dispatcher
-        <user-list-action> "" add-responder
+        <user-list-action> "list" add-main-responder
         <new-user-action> "new" add-responder
         <edit-user-action> "edit" add-responder
         <delete-user-action> "delete" add-responder
     <boilerplate>
-        "admin" admin-template >>template
+        "user-admin" admin-template >>template
     { can-administer-users? } <protected> ;
 
 : make-admin ( username -- )
diff --git a/extra/webapps/user-admin/user-list.xml b/extra/webapps/user-admin/user-list.xml
index 6887308754..020d053e03 100644
--- a/extra/webapps/user-admin/user-list.xml
+++ b/extra/webapps/user-admin/user-list.xml
@@ -4,10 +4,16 @@
 
 	<t:title>Users</t:title>
 
-	<t:each-tuple t:values="users">
-		<t:a t:href="$user-admin/edit" t:query="username">
-			<t:label t:name="username" />
-		</t:a>
-	</t:each-tuple>
+	<ul>
+
+		<t:each-tuple t:values="users">
+			<li>
+				<t:a t:href="$user-admin/edit" t:query="username">
+					<t:label t:name="username" />
+				</t:a>
+			</li>
+		</t:each-tuple>
+
+	</ul>
 
 </t:chloe>
diff --git a/extra/xmode/catalog/catalog.factor b/extra/xmode/catalog/catalog.factor
index 277439c0cd..8c6025f726 100755
--- a/extra/xmode/catalog/catalog.factor
+++ b/extra/xmode/catalog/catalog.factor
@@ -1,6 +1,6 @@
 USING: xmode.loader xmode.utilities xmode.rules namespaces
 strings splitting assocs sequences kernel io.files xml memoize
-words globs combinators io.encodings.utf8 ;
+words globs combinators io.encodings.utf8 sorting ;
 IN: xmode.catalog
 
 TUPLE: mode file file-name-glob first-line-glob ;
@@ -23,17 +23,15 @@ TAGS>
         swap child-tags [ parse-mode-tag ] with each
     ] keep ;
 
-: load-catalog ( -- modes )
+MEMO: modes ( -- modes )
     "resource:extra/xmode/modes/catalog"
     file>xml parse-modes-tag ;
 
-: modes ( -- assoc )
-    \ modes get-global [
-        load-catalog dup \ modes set-global
-    ] unless* ;
+MEMO: mode-names ( -- modes )
+    modes keys natural-sort ;
 
 : reset-catalog ( -- )
-    f \ modes set-global ;
+    \ modes reset-memoized ;
 
 MEMO: (load-mode) ( name -- rule-sets )
     modes at [
diff --git a/extra/xmode/code2html/responder/responder.factor b/extra/xmode/code2html/responder/responder.factor
index 7b2bdd992a..e059aeb7ff 100755
--- a/extra/xmode/code2html/responder/responder.factor
+++ b/extra/xmode/code2html/responder/responder.factor
@@ -8,14 +8,9 @@ IN: xmode.code2html.responder
 : <sources> ( root -- responder )
     [
         drop
-        "text/html" <content> swap
-        [ "last-modified" set-header ]
-        [
-            '[
-                ,
-                dup file-name swap utf8
-                <file-reader>
+         '[
+            , [ file-name ] keep utf8 [
                 [ htmlize-stream ] with-html-stream
-            ] >>body
-        ] bi
+            ] with-file-reader
+        ] <html-content>
     ] <file-responder> ;

From 8d8cb11e2a4bbbdf14458d7aea8fd451c9494b09 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 00:48:02 -0500
Subject: [PATCH 25/66] More stuff

---
 extra/tangle/tangle.factor                | 14 ++----
 extra/validators/validators-tests.factor  | 61 +++++++++++++++++------
 extra/validators/validators.factor        | 60 +++++++++++-----------
 extra/webapps/counter/counter.fhtml       | 10 ----
 extra/webapps/pastebin/annotation.xml     | 17 -------
 extra/webapps/pastebin/new-annotation.xml | 24 ---------
 extra/webapps/pastebin/paste-summary.xml  | 11 ----
 extra/webapps/planet/blog-admin-link.xml  |  7 ---
 extra/webapps/todo/todo-summary.xml       | 20 --------
 9 files changed, 83 insertions(+), 141 deletions(-)
 delete mode 100644 extra/webapps/counter/counter.fhtml
 delete mode 100644 extra/webapps/pastebin/annotation.xml
 delete mode 100644 extra/webapps/pastebin/new-annotation.xml
 delete mode 100644 extra/webapps/pastebin/paste-summary.xml
 delete mode 100644 extra/webapps/planet/blog-admin-link.xml
 delete mode 100644 extra/webapps/todo/todo-summary.xml

diff --git a/extra/tangle/tangle.factor b/extra/tangle/tangle.factor
index 52c454f97f..8a4c6146de 100644
--- a/extra/tangle/tangle.factor
+++ b/extra/tangle/tangle.factor
@@ -19,11 +19,8 @@ C: <tangle> tangle
 : with-tangle ( tangle quot -- )
     [ [ db>> ] [ seq>> ] bi ] dip with-db ;
 
-: <text-response> ( text -- response )
-    "text/plain" <content> swap >>body ;
-
 : node-response ( id -- response )
-    load-node [ node-content <text-response> ] [ <404> ] if* ;
+    load-node [ node-content <text-content> ] [ <404> ] if* ;
 
 : display-node ( params -- response )
     [
@@ -39,7 +36,7 @@ C: <tangle> tangle
 : submit-node ( params -- response )
     [
         "node_content" swap at* [
-            create-node id>> number>string <text-response>
+            create-node id>> number>string <text-content>
         ] [
             drop <400>
         ] if
@@ -55,10 +52,7 @@ TUPLE: path-responder ;
 C: <path-responder> path-responder
 
 M: path-responder call-responder* ( path responder -- response )
-    drop path>file [ node-content <text-response> ] [ <404> ] if* ;
-
-: <json-response> ( obj -- response )
-    "application/json" <content> swap >json >>body ;
+    drop path>file [ node-content <text-content> ] [ <404> ] if* ;
 
 TUPLE: tangle-dispatcher < dispatcher tangle ;
 
@@ -67,7 +61,7 @@ TUPLE: tangle-dispatcher < dispatcher tangle ;
     <path-responder> >>default
     "resource:extra/tangle/resources" <static> "resources" add-responder
     <node-responder> "node" add-responder
-    <action> [ all-node-ids <json-response> ] >>display "all" add-responder ;
+    <action> [ all-node-ids <json-content> ] >>display "all" add-responder ;
 
 M: tangle-dispatcher call-responder* ( path dispatcher -- response )
     dup tangle>> [
diff --git a/extra/validators/validators-tests.factor b/extra/validators/validators-tests.factor
index 6ed0e0363a..a981f782d3 100644
--- a/extra/validators/validators-tests.factor
+++ b/extra/validators/validators-tests.factor
@@ -1,8 +1,28 @@
 IN: validators.tests
-USING: kernel sequences tools.test validators accessors ;
+USING: kernel sequences tools.test validators accessors
+namespaces assocs ;
+
+: with-validation ( quot -- messages )
+    [
+        init-validation
+        call
+        validation-messages get
+        named-validation-messages get >alist append
+    ] with-scope ; inline
+
+[ "" v-one-line ] must-fail
+[ "hello world" ] [ "hello world" v-one-line ] unit-test
+[ "hello\nworld" v-one-line ] must-fail
+
+[ "" v-one-word ] must-fail
+[ "hello" ] [ "hello" v-one-word ] unit-test
+[ "hello world" v-one-word ] must-fail
 
 [ "foo" v-number ] must-fail
 [ 123 ] [ "123" v-number ] unit-test
+[ 123 ] [ "123" v-integer ] unit-test
+
+[ "1.0" v-integer ] [ "must be an integer" = ] must-fail-with
 
 [ "slava@factorcode.org" ] [
     "slava@factorcode.org" v-email
@@ -29,13 +49,13 @@ USING: kernel sequences tools.test validators accessors ;
 
 [ 14 V{ } ] [
     [
-        "14" "age" [ drop v-number 13 v-min-value 100 v-max-value ] validate
+        "14" "age" [ v-number 13 v-min-value 100 v-max-value ] validate
     ] with-validation
 ] unit-test
 
 [ f t ] [
     [
-        "140" "age" [ drop v-number 13 v-min-value 100 v-max-value ] validate
+        "140" "age" [ v-number 13 v-min-value 100 v-max-value ] validate
     ] with-validation first
     [ first "age" = ]
     [ second validation-error? ]
@@ -46,25 +66,38 @@ USING: kernel sequences tools.test validators accessors ;
 TUPLE: person name age ;
 
 person {
-    { "name" [ v-required ] }
+    { "name" [ ] }
     { "age" [ v-number 13 v-min-value 100 v-max-value ] }
 } define-validators
 
-[ 14 V{ } ] [
-    [
-        person new dup
-        { { "age" "14" } }
-        deposit-slots
-        age>>
-    ] with-validation
-] unit-test
-
-[ t ] [
+[ t t ] [
     [
         { { "age" "" } } required-values
+        validation-failed?
     ] with-validation first
     [ first "age" = ]
     [ second validation-error? ]
     [ second message>> "required" = ]
     tri and and
 ] unit-test
+
+[ H{ { "a" 123 } } f V{ } ] [
+    [
+        H{
+            { "a" "123" }
+            { "b" "c" }
+            { "c" "d" }
+        }
+        H{
+            { "a" [ v-integer ] }
+        } validate-values
+        validation-failed?
+    ] with-validation
+] unit-test
+
+[ t "foo" ] [
+    [
+        "foo" validation-error
+        validation-failed?
+    ] with-validation first message>>
+] unit-test
diff --git a/extra/validators/validators.factor b/extra/validators/validators.factor
index b03cc76444..2dcc2c04f9 100644
--- a/extra/validators/validators.factor
+++ b/extra/validators/validators.factor
@@ -2,7 +2,8 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel continuations sequences math namespaces sets
 math.parser assocs regexp fry unicode.categories sequences
-arrays hashtables words combinators mirrors classes quotations ;
+arrays hashtables words combinators mirrors classes quotations
+xmode.catalog ;
 IN: validators
 
 : v-default ( str def -- str )
@@ -33,8 +34,8 @@ IN: validators
 : v-number ( str -- n )
     dup string>number [ ] [ "must be a number" throw ] ?if ;
 
-: v-integer ( n -- n )
-    dup integer? [ "must be an integer" throw ] unless ;
+: v-integer ( str -- n )
+    v-number dup integer? [ "must be an integer" throw ] unless ;
 
 : v-min-value ( x n -- x )
     2dup < [
@@ -70,25 +71,38 @@ IN: validators
     dup empty? [ "must remain blank" throw ] unless ;
 
 : v-one-line ( str -- str )
+    v-required
     dup "\r\n" intersect empty?
     [ "must be a single line" throw ] unless ;
 
 : v-one-word ( str -- str )
+    v-required
     dup [ alpha? ] all?
     [ "must be a single word" throw ] unless ;
 
-SYMBOL: validation-messages
+: v-username ( str -- str )
+    2 v-min-length 16 v-max-length v-one-word ;
 
-: with-validation ( quot -- messages )
-    V{ } clone [
-        validation-messages rot with-variable
-    ] keep ; inline
+: v-password ( str -- str )
+    6 v-min-length 40 v-max-length v-one-line ;
+
+: v-mode ( str -- str )
+    dup mode-names member? [
+        "not a valid syntax mode" throw 
+    ] unless ;
+
+SYMBOL: validation-messages
+SYMBOL: named-validation-messages
+
+: init-validation ( -- )
+    V{ } clone validation-messages set
+    H{ } clone named-validation-messages set ;
 
 : (validation-message) ( obj -- )
     validation-messages get push ;
 
 : (validation-message-for) ( obj name -- )
-    swap 2array (validation-message) ;
+    named-validation-messages get set-at ;
 
 TUPLE: validation-message message ;
 
@@ -100,39 +114,29 @@ C: <validation-message> validation-message
 : validation-message-for ( string name -- )
     [ <validation-message> ] dip (validation-message-for) ;
 
-TUPLE: validation-error value message ;
+TUPLE: validation-error message value ;
 
 C: <validation-error> validation-error
 
-: validation-error ( reason -- )
+: validation-error ( message -- )
     f <validation-error> (validation-message) ;
 
-: validation-error-for ( reason value name -- )
+: validation-error-for ( message value name -- )
     [ <validation-error> ] dip (validation-message-for) ;
 
 : validation-failed? ( -- ? )
-    validation-messages get [
-        dup pair? [ second ] when validation-error?
-    ] contains? ;
+    validation-messages get [ validation-error? ] contains?
+    named-validation-messages get [ nip validation-error? ] assoc-contains?
+    or ;
 
 : define-validators ( class validators -- )
     >hashtable "validators" set-word-prop ;
 
 : validate ( value name quot -- result )
-    [ swap validation-error-for f ] recover ; inline
-
-: validate-value ( value name validators -- result )
-    '[ , at call ] validate ;
+    '[ drop @ ] [ -rot validation-error-for f ] recover ; inline
 
 : required-values ( assoc -- )
-    [ swap [ drop v-required ] validate drop ] assoc-each ;
+    [ swap [ v-required ] validate drop ] assoc-each ;
 
 : validate-values ( assoc validators -- assoc' )
-    '[ over , validate-value ] assoc-map ;
-
-: deposit-values ( destination assoc validators -- )
-    validate-values update ;
-
-: deposit-slots ( tuple assoc -- )
-    [ [ <mirror> ] [ class "validators" word-prop ] bi ] dip
-    swap deposit-values ;
+    swap '[ [ [ dup , at ] keep ] dip validate ] assoc-map ;
diff --git a/extra/webapps/counter/counter.fhtml b/extra/webapps/counter/counter.fhtml
deleted file mode 100644
index 521096f105..0000000000
--- a/extra/webapps/counter/counter.fhtml
+++ /dev/null
@@ -1,10 +0,0 @@
-<% USING: io math.parser http.server.sessions webapps.counter ; %>
-
-<html>
-    <body>
-        <h1><% count sget number>string write %></h1>
-
-        <a href="inc">++</a>
-        <a href="dec">--</a>
-    </body>
-</html>
diff --git a/extra/webapps/pastebin/annotation.xml b/extra/webapps/pastebin/annotation.xml
deleted file mode 100644
index d5b4ea8d3a..0000000000
--- a/extra/webapps/pastebin/annotation.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<h2>Annotation: <t:view t:component="summary" /></h2>
-
-	<table>
-		<tr><th class="field-label">Author:  </th><td><t:view t:component="author"  /></td></tr>
-		<tr><th class="field-label">Mode:    </th><td><t:view t:component="mode"    /></td></tr>
-		<tr><th class="field-label">Date:    </th><td><t:view t:component="date"    /></td></tr>
-	</table>
-
-	<pre class="description"><t:view t:component="contents" /></pre>
-
-	<t:button t:action="$pastebin/delete-annotation" t:for="aid" class="link-button link">Delete Annotation</t:button>
-
-</t:chloe>
diff --git a/extra/webapps/pastebin/new-annotation.xml b/extra/webapps/pastebin/new-annotation.xml
deleted file mode 100644
index 5d18860977..0000000000
--- a/extra/webapps/pastebin/new-annotation.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:title>New Annotation</t:title>
-
-	<t:form t:action="$pastebin/annotate" t:for="id">
-
-		<table>
-			<tr><th class="field-label">Summary: </th><td><t:edit t:component="summary" /></td></tr>
-			<tr><th class="field-label">Author: </th><td><t:edit t:component="author" /></td></tr>
-			<tr><th class="field-label">Mode: </th><td><t:edit t:component="mode" /></td></tr>
-			<tr><th class="field-label big-field-label">Description:</th><td><t:edit t:component="contents" /></td></tr>
-			<tr><th class="field-label">Captcha: </th><td><t:edit t:component="captcha" /></td></tr>
-			<tr>
-			<td></td>
-			<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
-			</tr>
-		</table>
-
-		<input type="SUBMIT" value="Done" />
-	</t:form>
-
-</t:chloe>
diff --git a/extra/webapps/pastebin/paste-summary.xml b/extra/webapps/pastebin/paste-summary.xml
deleted file mode 100644
index c751b110c0..0000000000
--- a/extra/webapps/pastebin/paste-summary.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<tr>
-		<td><t:a t:href="$pastebin/view-paste" t:query="id"><t:view t:component="summary" /></t:a></td>
-		<td><t:view t:component="author" /></td>
-		<td><t:view t:component="date" /></td>
-	</tr>
-
-</t:chloe>
diff --git a/extra/webapps/planet/blog-admin-link.xml b/extra/webapps/planet/blog-admin-link.xml
deleted file mode 100644
index 8d6c890643..0000000000
--- a/extra/webapps/planet/blog-admin-link.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<t:a t:href="$planet-factor/admin/edit-blog" t:query="id"><t:view t:component="name" /></t:a>
-
-</t:chloe>
diff --git a/extra/webapps/todo/todo-summary.xml b/extra/webapps/todo/todo-summary.xml
deleted file mode 100644
index 056c9cab0a..0000000000
--- a/extra/webapps/todo/todo-summary.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version='1.0' ?>
-
-<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
-
-	<tr>
-		<td>
-			<t:view t:component="summary" />
-		</td>
-		<td>
-			<t:view t:component="priority" />
-		</td>
-		<td>
-			<t:a t:href="$todo-list/view" t:query="id">View</t:a>
-		</td>
-		<td>
-			<t:a t:href="$todo-list/edit" t:query="id">Edit</t:a>
-		</td>
-	</tr>
-
-</t:chloe>

From 1c10cb0ff155b3cbad9c6186a92b7fcfd8920eeb Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 00:48:10 -0500
Subject: [PATCH 26/66] Update for word renaming

---
 core/compiler/units/units.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/compiler/units/units.factor b/core/compiler/units/units.factor
index 11c81f4097..729cfcd179 100755
--- a/core/compiler/units/units.factor
+++ b/core/compiler/units/units.factor
@@ -1,7 +1,7 @@
 ! Copyright (C) 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel continuations assocs namespaces sequences words
-vocabs definitions hashtables init ;
+vocabs definitions hashtables init sets ;
 IN: compiler.units
 
 SYMBOL: old-definitions

From b91a314f0e503de7b9a256f32506dac7b0e1fe19 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 00:48:18 -0500
Subject: [PATCH 27/66] Another recursive fry fi

---
 extra/fry/fry-tests.factor | 10 ++++++++++
 extra/fry/fry.factor       | 17 ++++++++++++-----
 2 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/extra/fry/fry-tests.factor b/extra/fry/fry-tests.factor
index eb59ffae4e..6d6abba23c 100755
--- a/extra/fry/fry-tests.factor
+++ b/extra/fry/fry-tests.factor
@@ -52,3 +52,13 @@ sequences ;
 [ { 1 { 2 { 3 } } } ] [
     1 2 3 '[ , [ , [ , 1array ] call 2array ] call 2array ] call
 ] unit-test
+
+{ 1 1 } [ '[ [ [ , ] ] ] ] must-infer-as
+
+[ { { { 3 } } } ] [
+    3 '[ [ [ , 1array ] call 1array ] call 1array ] call
+] unit-test
+
+[ { { { 3 } } } ] [
+    3 '[ [ [ , 1array ] call 1array ] call 1array ] call
+] unit-test
diff --git a/extra/fry/fry.factor b/extra/fry/fry.factor
index 27a321ed92..4581c048fd 100755
--- a/extra/fry/fry.factor
+++ b/extra/fry/fry.factor
@@ -46,15 +46,22 @@ DEFER: (shallow-fry)
         shallow-fry
     ] if* ;
 
+: fry-specifier? ( obj -- ? ) { , namespaces:, @ } member? ;
+
+: count-inputs ( quot -- n )
+    [
+        {
+            { [ dup callable? ] [ count-inputs ] }
+            { [ dup fry-specifier? ] [ drop 1 ] }
+            [ drop 0 ]
+        } cond
+    ] map sum ;
+
 : fry ( quot -- quot' )
     [
         [
             dup callable? [
-                [
-                    [ { , namespaces:, @ } member? ] filter length
-                    \ , <repetition> %
-                ]
-                [ fry % ] bi
+                [ count-inputs \ , <repetition> % ] [ fry % ] bi
             ] [ namespaces:, ] if
         ] each
     ] [ ] make deep-fry ;

From 9d04629d4c304abb8d68803325042ec8283664a2 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 00:48:28 -0500
Subject: [PATCH 28/66] We can now parse reddit's RSS feed

---
 extra/calendar/format/format-tests.factor | 12 ++++
 extra/calendar/format/format.factor       |  8 ++-
 extra/rss/rss.factor                      | 78 ++++++++++++++---------
 3 files changed, 65 insertions(+), 33 deletions(-)

diff --git a/extra/calendar/format/format-tests.factor b/extra/calendar/format/format-tests.factor
index f4e1669178..3efe33e265 100755
--- a/extra/calendar/format/format-tests.factor
+++ b/extra/calendar/format/format-tests.factor
@@ -50,3 +50,15 @@ IN: calendar.format.tests
     "Sun May 04 07:00:00 2008 GMT" cookie-string>timestamp
     timestamp>string
 ] unit-test
+
+[
+    T{ timestamp f
+        2008
+        5
+        26
+        0
+        37
+        42.12345
+        T{ duration f 0 0 0 -5 0 0 }
+    }
+] [ "2008-05-26T00:37:42.12345-05:00" rfc3339>timestamp ] unit-test
diff --git a/extra/calendar/format/format.factor b/extra/calendar/format/format.factor
index 91a034f8bd..ff1811e9d5 100755
--- a/extra/calendar/format/format.factor
+++ b/extra/calendar/format/format.factor
@@ -1,4 +1,4 @@
-USING: math math.order math.parser kernel sequences io
+USING: math math.order math.parser math.functions kernel sequences io
 accessors arrays io.streams.string splitting
 combinators accessors debugger
 calendar calendar.format.macros ;
@@ -151,11 +151,15 @@ M: timestamp year. ( timestamp -- )
 : read-hms ( -- h m s )
     read-00 ":" expect read-00 ":" expect read-00 ;
 
+: read-rfc3339-seconds ( s -- s' ch )
+    "+-Z" read-until >r
+    [ string>number ] [ length 10 swap ^ ] bi / + r> ;
+
 : (rfc3339>timestamp) ( -- timestamp )
     read-ymd
     "Tt" expect
     read-hms
-    read1 { { CHAR: . [ read-000 1000 / + read1 ] } [ ] } case
+    read1 { { CHAR: . [ read-rfc3339-seconds ] } [ ] } case
     read-rfc3339-gmt-offset
     <timestamp> ;
 
diff --git a/extra/rss/rss.factor b/extra/rss/rss.factor
index 6e616e51a9..364c24b91f 100644
--- a/extra/rss/rss.factor
+++ b/extra/rss/rss.factor
@@ -18,51 +18,67 @@ TUPLE: entry title link description pub-date ;
 
 C: <entry> entry
 
+: try-parsing-timestamp ( string -- timestamp )
+    [ rfc822>timestamp ] [ drop rfc3339>timestamp ] recover ;
+
 : rss1.0-entry ( tag -- entry )
-    [ "title" tag-named children>string ] keep   
-    [ "link" tag-named children>string ] keep
-    [ "description" tag-named children>string ] keep
-    f "date" "http://purl.org/dc/elements/1.1/" <name>
-    tag-named dup [ children>string rfc822>timestamp ] when
-    <entry> ;
+    {
+        [ "title" tag-named children>string ]
+        [ "link" tag-named children>string ]
+        [ "description" tag-named children>string ]
+        [
+            f "date" "http://purl.org/dc/elements/1.1/" <name>
+            tag-named dup [ children>string try-parsing-timestamp ] when
+        ]
+    } cleave <entry> ;
 
 : rss1.0 ( xml -- feed )
     [
         "channel" tag-named
-        [ "title" tag-named children>string ] keep
-        "link" tag-named children>string
-    ] keep
-    "item" tags-named [ rss1.0-entry ] map <feed> ;
+        [ "title" tag-named children>string ]
+        [ "link" tag-named children>string ] bi
+    ] [ "item" tags-named [ rss1.0-entry ] map ] bi
+    <feed> ;
 
 : rss2.0-entry ( tag -- entry )
-    [ "title" tag-named children>string ] keep
-    [ "link" tag-named ] keep
-    [ "guid" tag-named dupd ? children>string ] keep
-    [ "description" tag-named children>string ] keep
-    "pubDate" tag-named children>string rfc822>timestamp <entry> ;
+    {
+        [ "title" tag-named children>string ]
+        [ { "link" "guid" } any-tag-named children>string ]
+        [ "description" tag-named children>string ]
+        [
+            { "date" "pubDate" } any-tag-named
+            children>string try-parsing-timestamp
+        ]
+    } cleave <entry> ;
 
 : rss2.0 ( xml -- feed )
     "channel" tag-named 
-    [ "title" tag-named children>string ] keep
-    [ "link" tag-named children>string ] keep
-    "item" tags-named [ rss2.0-entry ] map <feed> ;
+    [ "title" tag-named children>string ]
+    [ "link" tag-named children>string ]
+    [ "item" tags-named [ rss2.0-entry ] map ]
+    tri <feed> ;
 
 : atom1.0-entry ( tag -- entry )
-    [ "title" tag-named children>string ] keep
-    [ "link" tag-named "href" swap at ] keep
-    [
-        { "content" "summary" } any-tag-named
-        dup tag-children [ string? not ] contains?
-        [ tag-children [ write-chunk ] with-string-writer ]
-        [ children>string ] if
-    ] keep
-    { "published" "updated" "issued" "modified" } any-tag-named
-    children>string rfc3339>timestamp <entry> ;
+    {
+        [ "title" tag-named children>string ]
+        [ "link" tag-named "href" swap at ]
+        [
+            { "content" "summary" } any-tag-named
+            dup tag-children [ string? not ] contains?
+            [ tag-children [ write-chunk ] with-string-writer ]
+            [ children>string ] if
+        ]
+        [
+            { "published" "updated" "issued" "modified" } 
+            any-tag-named children>string try-parsing-timestamp
+        ]
+    } cleave <entry> ;
 
 : atom1.0 ( xml -- feed )
-    [ "title" tag-named children>string ] keep
-    [ "link" tag-named "href" swap at ] keep
-    "entry" tags-named [ atom1.0-entry ] map <feed> ;
+    [ "title" tag-named children>string ]
+    [ "link" tag-named "href" swap at ]
+    [ "entry" tags-named [ atom1.0-entry ] map ]
+    tri <feed> ;
 
 : xml>feed ( xml -- feed )
     dup name-tag {

From dc6af2f7bb7a1e65dcda9edd88a2019058df7b50 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 00:48:37 -0500
Subject: [PATCH 29/66] Fix USING

---
 extra/ui/tools/interactor/interactor.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extra/ui/tools/interactor/interactor.factor b/extra/ui/tools/interactor/interactor.factor
index c28e8aec7c..400169908b 100755
--- a/extra/ui/tools/interactor/interactor.factor
+++ b/extra/ui/tools/interactor/interactor.factor
@@ -6,7 +6,7 @@ models namespaces parser prettyprint quotations sequences
 strings threads listener classes.tuple ui.commands ui.gadgets
 ui.gadgets.editors ui.gadgets.presentations ui.gadgets.worlds
 ui.gestures definitions calendar concurrency.flags
-concurrency.mailboxes ui.tools.workspace accessors ;
+concurrency.mailboxes ui.tools.workspace accessors sets ;
 IN: ui.tools.interactor
 
 ! If waiting is t, we're waiting for user input, and invoking

From be0d85180ff4fb40dba659b7f898d9b6f14c3d4c Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 02:54:53 -0500
Subject: [PATCH 30/66] Debugging validation

---
 extra/html/components/components.factor | 13 ++++++
 extra/html/templates/chloe/chloe.factor | 14 ++++++
 extra/webapps/pastebin/new-paste.xml    |  4 +-
 extra/webapps/pastebin/paste.xml        |  4 +-
 extra/webapps/pastebin/pastebin.factor  | 61 +++++++++++++------------
 extra/webapps/pastebin/pastebin.xml     |  6 +--
 extra/webapps/planet/planet.factor      | 13 ++++--
 7 files changed, 76 insertions(+), 39 deletions(-)

diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
index e6df343161..382636d952 100644
--- a/extra/html/components/components.factor
+++ b/extra/html/components/components.factor
@@ -44,6 +44,12 @@ SYMBOL: values
 : with-each-tuple ( seq quot -- )
     '[ from-tuple @ ] with-each-index ; inline
 
+: with-assoc-values ( assoc quot -- )
+    '[ blank-values , from-assoc @ ] with-scope ; inline
+
+: with-tuple-values ( assoc quot -- )
+    '[ blank-values , from-tuple @ ] with-scope ; inline
+
 : nest-values ( name quot -- )
     swap [
         [
@@ -51,6 +57,13 @@ SYMBOL: values
         ] with-scope
     ] dip set-value ; inline
 
+: nest-tuple ( name quot -- )
+    swap [
+        [
+            H{ } clone [ <mirror> values set call ] keep
+        ] with-scope
+    ] dip set-value ; inline
+
 : object>string ( object -- string )
     {
         { [ dup real? ] [ number>string ] }
diff --git a/extra/html/templates/chloe/chloe.factor b/extra/html/templates/chloe/chloe.factor
index 4430e69336..6790a9f666 100644
--- a/extra/html/templates/chloe/chloe.factor
+++ b/extra/html/templates/chloe/chloe.factor
@@ -217,6 +217,18 @@ STRING: button-tag-markup
 : each-assoc-tag ( tag -- )
     [ with-each-assoc ] (each-tag) ;
 
+: (bind-tag) ( tag quot -- )
+    [
+        [ "name" required-attr value ] keep
+        '[ , process-tag-children ]
+    ] dip call ; inline
+
+: bind-tuple-tag ( tag -- )
+    [ with-tuple-values ] (bind-tag) ;
+
+: bind-assoc-tag ( tag -- )
+    [ with-assoc-values ] (bind-tag) ;
+
 : error-message-tag ( tag -- )
     children>string render-error ;
 
@@ -280,6 +292,8 @@ STRING: button-tag-markup
         { "each" [ each-tag ] }
         { "each-assoc" [ each-assoc-tag ] }
         { "each-tuple" [ each-tuple-tag ] }
+        { "bind-assoc" [ bind-assoc-tag ] }
+        { "bind-tuple" [ bind-tuple-tag ] }
         { "comment" [ drop ] }
         { "call-next-template" [ drop call-next-template ] }
 
diff --git a/extra/webapps/pastebin/new-paste.xml b/extra/webapps/pastebin/new-paste.xml
index 1abd4d494b..6abae4895b 100644
--- a/extra/webapps/pastebin/new-paste.xml
+++ b/extra/webapps/pastebin/new-paste.xml
@@ -10,8 +10,8 @@
 			<tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
 			<tr><th class="field-label">Author: </th><td><t:field t:name="author" /></td></tr>
 			<tr><th class="field-label">Mode: </th><td><t:choice t:name="mode" t:choices="modes" /></td></tr>
-			<tr><th class="field-label big-field-label">Description: </th><td><t:textarea t:name="contents" /></td></tr>
-			<tr><th class="field-label">Captcha: </th><td><t:captcha t:name="captcha" /></td></tr>
+			<tr><th class="field-label big-field-label">Body: </th><td><t:textarea t:name="contents" t:rows="20" t:cols="60" /></td></tr>
+			<tr><th class="field-label">Captcha: </th><td><t:field t:name="captcha" /></td></tr>
 			<tr>
 			<td></td>
 			<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
diff --git a/extra/webapps/pastebin/paste.xml b/extra/webapps/pastebin/paste.xml
index 1f65ff6765..57c2fdb7c2 100644
--- a/extra/webapps/pastebin/paste.xml
+++ b/extra/webapps/pastebin/paste.xml
@@ -44,8 +44,8 @@
 				<tr><th class="field-label">Summary: </th><td><t:field t:name="summary" /></td></tr>
 				<tr><th class="field-label">Author: </th><td><t:field t:name="author" /></td></tr>
 				<tr><th class="field-label">Mode: </th><td><t:choice t:name="mode" t:choices="modes" /></td></tr>
-				<tr><th class="field-label big-field-label">Description:</th><td><t:textarea t:name="contents" /></td></tr>
-				<tr><th class="field-label">Captcha: </th><td><t:captcha t:name="captcha" /></td></tr>
+				<tr><th class="field-label big-field-label">Body:</th><td><t:textarea t:name="contents" t:rows="20" t:cols="60" /></td></tr>
+				<tr><th class="field-label">Captcha: </th><td><t:field t:name="captcha" /></td></tr>
 				<tr>
 				<td></td>
 				<td>Leave the captcha blank. Spam-bots will fill it indiscriminantly, so their attempts to register will be blocked.</td>
diff --git a/extra/webapps/pastebin/pastebin.factor b/extra/webapps/pastebin/pastebin.factor
index 0772181b00..9852bf47cb 100644
--- a/extra/webapps/pastebin/pastebin.factor
+++ b/extra/webapps/pastebin/pastebin.factor
@@ -1,7 +1,7 @@
 ! Copyright (C) 2007, 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
 USING: namespaces assocs sorting sequences kernel accessors
-hashtables sequences.lib db.types db.tuples db
+hashtables sequences.lib db.types db.tuples db combinators
 calendar calendar.format math.parser rss xml.writer
 xmode.catalog validators html.components html.templates.chloe
 http.server
@@ -121,7 +121,9 @@ M: annotation entity-link
             validate-integer-id
             "id" value paste from-tuple
 
+            "id" value
             "new-annotation" [
+                "id" set-value
                 mode-names "modes" set-value
                 "factor" "mode" set-value
             ] nest-values
@@ -145,6 +147,19 @@ M: annotation entity-link
         [ validate-integer-id ] >>init
         [ "id" value paste annotations>> paste-feed ] >>feed ;
 
+: validate-paste ( -- )
+    {
+        { "summary" [ v-one-line ] }
+        { "author" [ v-one-line ] }
+        { "mode" [ v-mode ] }
+        { "contents" [ v-required ] }
+        { "captcha" [ v-captcha ] }
+    } validate-params ;
+
+: deposit-paste-slots ( tuple -- )
+    now >>date
+    { "summary" "author" "mode" "contents" } deposit-slots ;
+
 : <new-paste-action> ( -- action )
     <page-action>
         [
@@ -155,19 +170,13 @@ M: annotation entity-link
         "new-paste" pastebin-template >>template
 
         [
-            {
-                { "summary" [ v-one-line ] }
-                { "author" [ v-one-line ] }
-                { "mode" [ v-mode ] }
-                { "contents" [ v-required ] }
-                { "captcha" [ v-captcha ] }
-            } validate-params
+            validate-paste
 
             f <paste>
-                now >>date
-                dup { "summary" "author" "mode" "contents" } deposit-slots
+            [ deposit-paste-slots ]
             [ insert-tuple ]
-            [ id>> "$pastebin/paste" <id-redirect> ] bi
+            [ id>> "$pastebin/paste" <id-redirect> ]
+            tri
         ] >>submit ;
 
 : <delete-paste-action> ( -- action )
@@ -185,26 +194,22 @@ M: annotation entity-link
 ! ! !
 
 : <new-annotation-action> ( -- action )
-    <action>
-        [
-            {
-                { "summary" [ v-one-line ] }
-                { "author" [ v-one-line ] }
-                { "mode" [ v-mode ] }
-                { "contents" [ v-required ] }
-                { "captcha" [ v-captcha ] }
-            } validate-params
-        ] >>validate
+    <page-action>
+        [ validate-paste ] >>validate
+
+        [ "id" param "$pastebin/paste" <id-redirect> ] >>display
 
         [
             f f <annotation>
-                now >>date
-                dup { "summary" "author" "mode" "contents" } deposit-slots
-            [ insert-tuple ]
-            [
-                ! Add anchor here
-                "id" value "$pastebin/paste" <id-redirect>
-            ] bi
+            {
+                [ deposit-paste-slots ]
+                [ { "id" } deposit-slots ]
+                [ insert-tuple ]
+                [
+                    ! Add anchor here
+                    id>> "$pastebin/paste" <id-redirect>
+                ]
+            } cleave
         ] >>submit ;
 
 : <delete-annotation-action> ( -- action )
diff --git a/extra/webapps/pastebin/pastebin.xml b/extra/webapps/pastebin/pastebin.xml
index f0abd97c63..9ec2cb7976 100644
--- a/extra/webapps/pastebin/pastebin.xml
+++ b/extra/webapps/pastebin/pastebin.xml
@@ -13,9 +13,9 @@
 
 		<t:each-tuple t:values="pastes">
 			<tr>
-				<td><t:a t:href="$pastebin/view-paste" t:query="id"><t:field t:name="summary" /></t:a></td>
-				<td><t:field t:name="author" /></td>
-				<td><t:field t:name="date" /></td>
+				<td><t:a t:href="$pastebin/paste" t:query="id"><t:label t:name="summary" /></t:a></td>
+				<td><t:label t:name="author" /></td>
+				<td><t:label t:name="date" /></td>
 			</tr>
 		</t:each-tuple>
 	</table>
diff --git a/extra/webapps/planet/planet.factor b/extra/webapps/planet/planet.factor
index e3b5b17a32..414a59f3b2 100755
--- a/extra/webapps/planet/planet.factor
+++ b/extra/webapps/planet/planet.factor
@@ -132,6 +132,9 @@ posting "POSTINGS"
 : <id-redirect> ( id next -- response )
     swap "id" associate <standard-redirect> ;
 
+: deposit-blog-slots ( blog -- )
+    { "name" "www-url" "feed-url" } deposit-slots ;
+
 : <new-blog-action> ( -- action )
     <page-action>
         "new-blog" planet-template >>template
@@ -140,9 +143,10 @@ posting "POSTINGS"
 
         [
             f <blog>
-                dup { "name" "www-url" "feed-url" } deposit-slots
+            [ deposit-blog-slots ]
             [ insert-tuple ]
-            [ id>> "$planet-factor/admin/edit-blog" <id-redirect> ] bi
+            [ id>> "$planet-factor/admin/edit-blog" <id-redirect> ]
+            tri
         ] >>submit ;
     
 : <edit-blog-action> ( -- action )
@@ -161,9 +165,10 @@ posting "POSTINGS"
 
         [
             f <blog>
-                dup { "id" "name" "www-url" "feed-url" } deposit-slots
+            [ deposit-blog-slots ]
             [ update-tuple ]
-            [ id>> "$planet-factor/admin" <id-redirect> ] bi
+            [ id>> "$planet-factor/admin" <id-redirect> ]
+            tri
         ] >>submit ;
 
 TUPLE: planet-factor-admin < dispatcher ;

From 79d1570b35588de0e895a561b25a1e99fad1e75e Mon Sep 17 00:00:00 2001
From: James Cash <james.nvc@gmail.com>
Date: Mon, 26 May 2008 04:35:18 -0400
Subject: [PATCH 31/66] Refactoring, cleaning up code

---
 extra/lisp/lisp-tests.factor |  2 +-
 extra/lisp/lisp.factor       | 91 ++++++++++++++++++------------------
 2 files changed, 47 insertions(+), 46 deletions(-)

diff --git a/extra/lisp/lisp-tests.factor b/extra/lisp/lisp-tests.factor
index f1db203a78..0312080907 100644
--- a/extra/lisp/lisp-tests.factor
+++ b/extra/lisp/lisp-tests.factor
@@ -7,7 +7,7 @@ IN: lisp.test
 [
     init-env
     
-    "#f" [ f ] lisp-define 
+    "#f" [ f ] lisp-define
     "#t" [ t ] lisp-define
     
     "+" "math" "+" define-primitve
diff --git a/extra/lisp/lisp.factor b/extra/lisp/lisp.factor
index 0f5e4b4d2e..82a331f2ca 100644
--- a/extra/lisp/lisp.factor
+++ b/extra/lisp/lisp.factor
@@ -2,7 +2,8 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel peg sequences arrays strings combinators.lib
 namespaces combinators math bake locals locals.private accessors
-vectors syntax lisp.parser assocs parser sequences.lib words quotations ;
+vectors syntax lisp.parser assocs parser sequences.lib words quotations
+fry ;
 IN: lisp
 
 DEFER: convert-form
@@ -12,52 +13,52 @@ DEFER: lookup-var
 ! Functions to convert s-exps to quotations
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 : convert-body ( s-exp -- quot )
-  [ convert-form ] map [ ] [ compose ] reduce ; inline
+    [ ] [ convert-form compose ] reduce ; inline
   
 : convert-if ( s-exp -- quot )
-  rest [ convert-form ] map reverse first3  [ % , , if ] bake ;
-  
+    rest first3 [ convert-form ] tri@ '[ @ , , if ] ;
+    
 : convert-begin ( s-exp -- quot )  
-  rest [ convert-form ] map >quotation [ , [ funcall ] each ] bake ;
-  
+    rest [ convert-form ] [ ] map-as '[ , [ funcall ] each ] ;
+    
 : convert-cond ( s-exp -- quot )  
-  rest [ body>> >array [ convert-form ] map first2 swap `{ [ % funcall ] , } bake ]
-  map >array [ , cond ] bake ;
-  
+    rest [ body>> first2 [ convert-form ] bi@ [ '[ @ funcall ] ] dip 2array ]
+    { } map-as '[ , cond ]  ;
+    
 : convert-general-form ( s-exp -- quot )
-  unclip convert-form swap convert-body [ , % funcall ] bake ;
+    unclip convert-form swap convert-body swap '[ , @ funcall ] ;
 
 ! words for convert-lambda  
 <PRIVATE  
 : localize-body ( assoc body -- assoc newbody )  
-  [ dup lisp-symbol? [ over dupd [ name>> ] dip at swap or ]
+    [ dup lisp-symbol? [ over dupd [ name>> ] dip at swap or ]
                      [ dup s-exp? [ body>> localize-body <s-exp> ] when ] if
                    ] map ;
-  
+    
 : localize-lambda ( body vars -- newbody newvars )
-  make-locals dup push-locals swap
-  [ swap localize-body <s-exp> convert-form swap pop-locals ] dip swap ;
+    make-locals dup push-locals swap
+    [ swap localize-body <s-exp> convert-form swap pop-locals ] dip swap ;
                    
 : split-lambda ( s-exp -- body vars )                   
-  first3 -rot nip [ body>> ] bi@ [ name>> ] map ; inline
-  
+    first3 -rot nip [ body>> ] bi@ [ name>> ] map ; inline
+    
 : rest-lambda ( body vars -- quot )  
-  "&rest" swap [ remove ] [ index ] 2bi
-  [ localize-lambda <lambda> ] dip
-  [ , cut swap [ % , ] bake , compose ] bake ;
-  
+    "&rest" swap [ index ] [ remove ] 2bi
+    localize-lambda <lambda>
+    '[ , cut '[ @ , ] , compose ] ;
+    
 : normal-lambda ( body vars -- quot )
-  localize-lambda <lambda> [ , compose ] bake ;
+    localize-lambda <lambda> '[ , compose ] ;
 PRIVATE>
-  
+    
 : convert-lambda ( s-exp -- quot )  
-  split-lambda dup "&rest"  swap member? [ rest-lambda ] [ normal-lambda ] if ;
-  
+    split-lambda "&rest" over member? [ rest-lambda ] [ normal-lambda ] if ;
+    
 : convert-quoted ( s-exp -- quot )  
-  second [ , ] bake ;
-  
+    second 1quotation ;
+    
 : convert-list-form ( s-exp -- quot )  
-  dup first dup lisp-symbol?
+    dup first dup lisp-symbol?
     [ name>>
       { { "lambda" [ convert-lambda ] }
         { "quote" [ convert-quoted ] }
@@ -67,35 +68,35 @@ PRIVATE>
        [ drop convert-general-form ]
       } case ]
     [ drop convert-general-form ] if ;
-  
+    
 : convert-form ( lisp-form -- quot )
-  { { [ dup s-exp? ] [ body>> convert-list-form ] }
-    { [ dup lisp-symbol? ] [ [ , lookup-var ] bake ] }
-    [ [ , ] bake ]
-  } cond ;
-                
+    { { [ dup s-exp? ] [ body>> convert-list-form ] }
+    { [ dup lisp-symbol? ] [ '[ , lookup-var ] ] }
+    [ 1quotation ]
+    } cond ;
+    
 : lisp-string>factor ( str -- quot )
-  lisp-expr parse-result-ast convert-form lambda-rewrite call ;
-  
+    lisp-expr parse-result-ast convert-form lambda-rewrite call ;
+    
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
 SYMBOL: lisp-env
 ERROR: no-such-var var ;
 
 : init-env ( -- )
-  H{ } clone lisp-env set ;
+    H{ } clone lisp-env set ;
 
 : lisp-define ( name quot -- )
-  swap lisp-env get set-at ;
-  
+    swap lisp-env get set-at ;
+    
 : lisp-get ( name -- word )
-  dup lisp-env get at [ ] [ no-such-var throw ] ?if ;
-  
+    dup lisp-env get at [ ] [ no-such-var throw ] ?if ;
+    
 : lookup-var ( lisp-symbol -- quot )
-  name>> lisp-get ;
-  
+    name>> lisp-get ;
+    
 : funcall ( quot sym -- * )
-  dup lisp-symbol?  [ lookup-var ] when call ; inline
-  
+    dup lisp-symbol?  [ lookup-var ] when call ; inline
+    
 : define-primitve ( name vocab word -- )  
-  swap lookup [ [ , ] compose call ] bake lisp-define ;
\ No newline at end of file
+    swap lookup 1quotation '[ , compose call ] lisp-define ;
\ No newline at end of file

From a77bbfc28e8a34c804970e416a025bf3bbaed80d Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 05:44:33 -0500
Subject: [PATCH 32/66] Fix unit test failure

---
 core/parser/parser-tests.factor | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/core/parser/parser-tests.factor b/core/parser/parser-tests.factor
index 6f31b0ad7c..e8199d3520 100755
--- a/core/parser/parser-tests.factor
+++ b/core/parser/parser-tests.factor
@@ -463,7 +463,25 @@ must-fail-with
 
 [ [ ] ] [
     2 [
-        "IN: classes.tests DEFER: twice-fails FORGET: twice-fails MIXIN: twice-fails"
+        "IN: parser.tests DEFER: twice-fails FORGET: twice-fails MIXIN: twice-fails"
         <string-reader> "twice-fails-test" parse-stream
     ] times
 ] unit-test
+
+[ [ ] ] [
+    "IN: parser.tests : staging-problem-test-1 1 ; : staging-problem-test-2 staging-problem-test-1 ;"
+    <string-reader> "staging-problem-test" parse-stream
+] unit-test
+
+[ t ] [ "staging-problem-test-1" "parser.tests" lookup >boolean ] unit-test
+
+[ t ] [ "staging-problem-test-2" "parser.tests" lookup >boolean ] unit-test
+
+[ [ ] ] [
+    "IN: parser.tests << : staging-problem-test-1 1 ; >> : staging-problem-test-2 staging-problem-test-1 ;"
+    <string-reader> "staging-problem-test" parse-stream
+] unit-test
+
+[ t ] [ "staging-problem-test-1" "parser.tests" lookup >boolean ] unit-test
+
+[ t ] [ "staging-problem-test-2" "parser.tests" lookup >boolean ] unit-test

From 8af320a2c00133718b1e6a23684e382c5cc1442b Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 17:15:54 -0500
Subject: [PATCH 33/66] Improve math.functions

---
 extra/math/functions/functions-tests.factor |  6 ++
 extra/math/functions/functions.factor       | 66 +++++++++++++--------
 extra/math/libm/libm.factor                 | 20 +++++++
 3 files changed, 67 insertions(+), 25 deletions(-)

diff --git a/extra/math/functions/functions-tests.factor b/extra/math/functions/functions-tests.factor
index c9215d8de7..51879fc6c6 100755
--- a/extra/math/functions/functions-tests.factor
+++ b/extra/math/functions/functions-tests.factor
@@ -39,6 +39,12 @@ IN: math.functions.tests
 [ 0.0 ] [ 0 sin ] unit-test
 [ 0.0 ] [ 0 asin ] unit-test
 
+[ t ] [ 10 atan real? ] unit-test
+[ f ] [ 10 atanh real? ] unit-test
+
+[ t ] [ 10 asin sin 10 1.e-10 ~ ] unit-test
+[ t ] [ -1 sqrt neg dup acos cos 1.e-10 ~ ] unit-test
+
 [ 100 ] [ 100 100 gcd nip ] unit-test
 [ 100 ] [ 1000 100 gcd nip ] unit-test
 [ 100 ] [ 100 1000 gcd nip ] unit-test
diff --git a/extra/math/functions/functions.factor b/extra/math/functions/functions.factor
index bce93fbb11..bb43e4a721 100755
--- a/extra/math/functions/functions.factor
+++ b/extra/math/functions/functions.factor
@@ -125,74 +125,90 @@ M: real absq sq ;
 M: number (^)
     swap >polar 3dup ^theta >r ^mag r> polar> ;
 
+: [-1,1]? ( x -- ? )
+    dup complex? [ drop f ] [ abs 1 <= ] if ; inline
+
+: >=1? ( x -- ? )
+    dup complex? [ drop f ] [ 1 >= ] if ; inline
+
 : exp ( x -- y ) >rect swap fexp swap polar> ; inline
 
 : log ( x -- y ) >polar swap flog swap rect> ; inline
 
 : cos ( x -- y )
-    >float-rect 2dup
-    fcosh swap fcos * -rot
-    fsinh swap fsin neg * rect> ; foldable
+    dup complex? [
+        >float-rect 2dup
+        fcosh swap fcos * -rot
+        fsinh swap fsin neg * rect>
+    ] [ fcos ] if ; foldable
 
 : sec ( x -- y ) cos recip ; inline
 
 : cosh ( x -- y )
-    >float-rect 2dup
-    fcos swap fcosh * -rot
-    fsin swap fsinh * rect> ; foldable
+    dup complex? [
+        >float-rect 2dup
+        fcos swap fcosh * -rot
+        fsin swap fsinh * rect>
+    ] [ fcosh ] if ; foldable
 
 : sech ( x -- y ) cosh recip ; inline
 
 : sin ( x -- y )
-    >float-rect 2dup
-    fcosh swap fsin * -rot
-    fsinh swap fcos * rect> ; foldable
+    dup complex? [
+        >float-rect 2dup
+        fcosh swap fsin * -rot
+        fsinh swap fcos * rect>
+    ] [ fsin ] if ; foldable
 
 : cosec ( x -- y ) sin recip ; inline
 
 : sinh ( x -- y )
-    >float-rect 2dup
-    fcos swap fsinh * -rot
-    fsin swap fcosh * rect> ; foldable
+    dup complex? [
+        >float-rect 2dup
+        fcos swap fsinh * -rot
+        fsin swap fcosh * rect>
+    ] [ fsinh ] if ; foldable
 
 : cosech ( x -- y ) sinh recip ; inline
 
-: tan ( x -- y ) dup sin swap cos / ; inline
+: tan ( x -- y )
+    dup complex? [ dup sin swap cos / ] [ ftan ] if ; inline
 
-: tanh ( x -- y ) dup sinh swap cosh / ; inline
+: tanh ( x -- y )
+    dup complex? [ dup sinh swap cosh / ] [ ftanh ] if ; inline
 
-: cot ( x -- y ) dup cos swap sin / ; inline
+: cot ( x -- y ) tan recip ; inline
 
-: coth ( x -- y ) dup cosh swap sinh / ; inline
+: coth ( x -- y ) tanh recip ; inline
 
-: acosh ( x -- y ) dup sq 1- sqrt + log ; inline
+: acosh ( x -- y )
+    dup >=1? [ facosh ] [ dup sq 1- sqrt + log ] if ; inline
 
 : asech ( x -- y ) recip acosh ; inline
 
-: asinh ( x -- y ) dup sq 1+ sqrt + log ; inline
+: asinh ( x -- y )
+    dup complex? [ dup sq 1+ sqrt + log ] [ fasinh ] if ; inline
 
 : acosech ( x -- y ) recip asinh ; inline
 
-: atanh ( x -- y ) dup 1+ swap 1- neg / log 2 / ; inline
+: atanh ( x -- y )
+    dup [-1,1]? [ fatanh ] [ dup 1+ swap 1- neg / log 2 / ] if ; inline
 
 : acoth ( x -- y ) recip atanh ; inline
 
-: [-1,1]? ( x -- ? )
-    dup complex? [ drop f ] [ abs 1 <= ] if ; inline
-
 : i* ( x -- y ) >rect neg swap rect> ;
 
 : -i* ( x -- y ) >rect swap neg rect> ;
 
 : asin ( x -- y )
-    dup [-1,1]? [ >float fasin ] [ i* asinh -i* ] if ; inline
+    dup [-1,1]? [ fasin ] [ i* asinh -i* ] if ; inline
 
 : acos ( x -- y )
-    dup [-1,1]? [ >float facos ] [ asin pi 2 / swap - ] if ;
+    dup [-1,1]? [ facos ] [ asin pi 2 / swap - ] if ;
     inline
 
 : atan ( x -- y )
-    dup [-1,1]? [ >float fatan ] [ i* atanh i* ] if ; inline
+    dup complex? [ i* atanh i* ] [ fatan ] if ; inline
 
 : asec ( x -- y ) recip acos ; inline
 
diff --git a/extra/math/libm/libm.factor b/extra/math/libm/libm.factor
index 0cc402e6e5..f70c8d2a77 100644
--- a/extra/math/libm/libm.factor
+++ b/extra/math/libm/libm.factor
@@ -15,6 +15,18 @@ IN: math.libm
     "double" "libm" "atan" { "double" } alien-invoke ;
     foldable
 
+: facosh ( x -- y )
+    "double" "libm" "acosh" { "double" } alien-invoke ;
+    foldable
+
+: fasinh ( x -- y )
+    "double" "libm" "asinh" { "double" } alien-invoke ;
+    foldable
+
+: fatanh ( x -- y )
+    "double" "libm" "atanh" { "double" } alien-invoke ;
+    foldable
+
 : fatan2 ( x y -- z )
     "double" "libm" "atan2" { "double" "double" } alien-invoke ;
     foldable
@@ -27,6 +39,10 @@ IN: math.libm
     "double" "libm" "sin" { "double" } alien-invoke ;
     foldable
 
+: ftan ( x -- y )
+    "double" "libm" "tan" { "double" } alien-invoke ;
+    foldable
+
 : fcosh ( x -- y )
     "double" "libm" "cosh" { "double" } alien-invoke ;
     foldable
@@ -35,6 +51,10 @@ IN: math.libm
     "double" "libm" "sinh" { "double" } alien-invoke ;
     foldable
 
+: ftanh ( x -- y )
+    "double" "libm" "tanh" { "double" } alien-invoke ;
+    foldable
+
 : fexp ( x -- y )
     "double" "libm" "exp" { "double" } alien-invoke ;
     foldable

From c3aa938869ae9f6e0b10b55ebc88c0a84ba2a249 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Mon, 26 May 2008 17:21:51 -0500
Subject: [PATCH 34/66] Another unit test

---
 extra/math/functions/functions-tests.factor | 1 +
 1 file changed, 1 insertion(+)

diff --git a/extra/math/functions/functions-tests.factor b/extra/math/functions/functions-tests.factor
index 51879fc6c6..6176c12d21 100755
--- a/extra/math/functions/functions-tests.factor
+++ b/extra/math/functions/functions-tests.factor
@@ -44,6 +44,7 @@ IN: math.functions.tests
 
 [ t ] [ 10 asin sin 10 1.e-10 ~ ] unit-test
 [ t ] [ -1 sqrt neg dup acos cos 1.e-10 ~ ] unit-test
+[ t ] [ 10 asinh sinh 10 1.e-10 ~ ] unit-test
 
 [ 100 ] [ 100 100 gcd nip ] unit-test
 [ 100 ] [ 1000 100 gcd nip ] unit-test

From 8f69fd5aa89d087c01a4c5561be8d4623053d7ed Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 00:01:04 -0500
Subject: [PATCH 35/66] Fix simple links

---
 extra/farkup/farkup-tests.factor | 2 +-
 extra/farkup/farkup.factor       | 8 ++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/extra/farkup/farkup-tests.factor b/extra/farkup/farkup-tests.factor
index 043502cd24..4abd655d62 100755
--- a/extra/farkup/farkup-tests.factor
+++ b/extra/farkup/farkup-tests.factor
@@ -67,7 +67,7 @@ IN: farkup.tests
 
 [ "<p><img src=\"lol.jpg\"/></p>" ] [ "[[image:lol.jpg]]" convert-farkup ] unit-test
 [ "<p><img src=\"lol.jpg\" alt=\"teh lol\"/></p>" ] [ "[[image:lol.jpg|teh lol]]" convert-farkup ] unit-test
-[ "<p><a href=\"lol.com\"></a></p>" ] [ "[[lol.com]]" convert-farkup ] unit-test
+[ "<p><a href=\"lol.com\">lol.com</a></p>" ] [ "[[lol.com]]" convert-farkup ] unit-test
 [ "<p><a href=\"lol.com\">haha</a></p>" ] [ "[[lol.com|haha]]" convert-farkup ] unit-test
 
 [ ] [ "[{}]" convert-farkup drop ] unit-test
diff --git a/extra/farkup/farkup.factor b/extra/farkup/farkup.factor
index fad237635f..5dcfa7528e 100755
--- a/extra/farkup/farkup.factor
+++ b/extra/farkup/farkup.factor
@@ -6,6 +6,8 @@ splitting io.streams.string peg.parsers
 sequences.deep unicode.categories ;
 IN: farkup
 
+SYMBOL: relative-link-prefix
+
 <PRIVATE
 
 : delimiters ( -- string )
@@ -68,7 +70,9 @@ MEMO: eq ( -- parser )
     CHAR: : over member? [
         dup { "http://" "https://" "ftp://" } [ head? ] with contains?
         [ drop "/" ] unless
-    ] when ;
+    ] [
+        relative-link-prefix get prepend
+    ] if ;
 
 : escape-link ( href text -- href-esc text-esc )
     >r check-url escape-quoted-string r> escape-string ;
@@ -100,7 +104,7 @@ MEMO: simple-link ( -- parser )
         "[[" token hide ,
         [ "|]" member? not ] satisfy repeat1 ,
         "]]" token hide ,
-    ] seq* [ first f make-link ] action ;
+    ] seq* [ first dup make-link ] action ;
 
 MEMO: labelled-link ( -- parser )
     [

From 91d7adcbf1274971e16f53f94aa6915b70cb71b7 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 00:01:27 -0500
Subject: [PATCH 36/66] Comparison component

---
 extra/html/components/components.factor | 9 ++++++++-
 extra/html/templates/chloe/chloe.factor | 1 +
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/extra/html/components/components.factor b/extra/html/components/components.factor
index 382636d952..efac730af6 100644
--- a/extra/html/components/components.factor
+++ b/extra/html/components/components.factor
@@ -4,7 +4,8 @@ USING: accessors kernel namespaces io math.parser assocs classes
 classes.tuple words arrays sequences sequences.lib splitting
 mirrors hashtables combinators continuations math strings
 fry locals calendar calendar.format xml.entities validators
-html.elements html.streams xmode.code2html farkup inspector ;
+html.elements html.streams xmode.code2html farkup inspector
+lcs.diff2html ;
 IN: html.components
 
 SYMBOL: values
@@ -211,6 +212,12 @@ SINGLETON: inspector
 M: inspector render*
     2drop [ describe ] with-html-stream ;
 
+! Diff component
+SINGLETON: comparison
+
+M: comparison render*
+    2drop htmlize-diff ;
+
 ! HTML component
 SINGLETON: html
 
diff --git a/extra/html/templates/chloe/chloe.factor b/extra/html/templates/chloe/chloe.factor
index 6790a9f666..092f79bb36 100644
--- a/extra/html/templates/chloe/chloe.factor
+++ b/extra/html/templates/chloe/chloe.factor
@@ -272,6 +272,7 @@ STRING: button-tag-markup
         { "code" [ code tuple-component-tag ] }
         { "farkup" [ farkup singleton-component-tag ] }
         { "inspector" [ inspector singleton-component-tag ] }
+        { "comparison" [ comparison singleton-component-tag ] }
         { "html" [ html singleton-component-tag ] }
 
         ! Forms

From e7438f4ab6f08456994bb932e10110f0cf5ecd84 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 00:01:57 -0500
Subject: [PATCH 37/66] Add support for rest urls

---
 extra/http/server/actions/actions.factor | 34 +++++++++++++++---------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git a/extra/http/server/actions/actions.factor b/extra/http/server/actions/actions.factor
index bcd2cbd585..eb5b8bfe68 100755
--- a/extra/http/server/actions/actions.factor
+++ b/extra/http/server/actions/actions.factor
@@ -2,11 +2,13 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: accessors sequences kernel assocs combinators http.server
 validators http hashtables namespaces fry continuations locals
-boxes xml.entities html.elements html.components io arrays ;
+boxes xml.entities html.elements html.components io arrays math ;
 IN: http.server.actions
 
 SYMBOL: params
 
+SYMBOL: rest-param
+
 : render-validation-messages ( -- )
     validation-messages get
     dup empty? [ drop ] [
@@ -15,7 +17,7 @@ SYMBOL: params
         </ul>
     ] if ;
 
-TUPLE: action init display validate submit ;
+TUPLE: action rest-param init display validate submit ;
 
 : new-action ( class -- action )
     new
@@ -43,19 +45,27 @@ TUPLE: action init display validate submit ;
     [ validate>> call ]
     [ submit>> call ] bi ;
 
+: handle-rest-param ( arg -- )
+    dup length 1 > action get rest-param>> not or
+    [ <404> exit-with ] [
+        action get rest-param>> associate rest-param set
+    ] if ;
+
 M: action call-responder* ( path action -- response )
     dup action set
     '[
-        , empty? [
-            init-validation
-            ,
-            request get [ request-params params set ] [ method>> ] bi
-            {
-                { "GET" [ handle-get ] }
-                { "HEAD" [ handle-get ] }
-                { "POST" [ handle-post ] }
-            } case
-        ] [ <404> ] if
+        , dup empty? [ drop ] [ handle-rest-param ] if
+
+        init-validation
+        ,
+        request get
+        [ request-params rest-param get assoc-union params set ]
+        [ method>> ] bi
+        {
+            { "GET" [ handle-get ] }
+            { "HEAD" [ handle-get ] }
+            { "POST" [ handle-post ] }
+        } case
     ] with-exit-continuation ;
 
 : param ( name -- value )

From 5f4ffa998fed0594d3529efdb339da86a3f88985 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 00:02:16 -0500
Subject: [PATCH 38/66] Wiki

---
 extra/lcs/diff2html/diff2html.factor          |  44 +++++
 .../factor-website/factor-website.factor      |   9 +-
 extra/webapps/factor-website/page.css         |   7 +-
 extra/webapps/wiki/articles.xml               |  15 ++
 extra/webapps/wiki/diff.xml                   |  35 ++++
 extra/webapps/wiki/edit.xml                   |  20 ++
 extra/webapps/wiki/revisions.xml              |  48 +++++
 extra/webapps/wiki/view.xml                   |  19 ++
 extra/webapps/wiki/wiki-common.xml            |  28 +++
 extra/webapps/wiki/wiki.css                   |  25 +++
 extra/webapps/wiki/wiki.factor                | 175 ++++++++++++++++++
 11 files changed, 421 insertions(+), 4 deletions(-)
 create mode 100644 extra/lcs/diff2html/diff2html.factor
 create mode 100644 extra/webapps/wiki/articles.xml
 create mode 100644 extra/webapps/wiki/diff.xml
 create mode 100644 extra/webapps/wiki/edit.xml
 create mode 100644 extra/webapps/wiki/revisions.xml
 create mode 100644 extra/webapps/wiki/view.xml
 create mode 100644 extra/webapps/wiki/wiki-common.xml
 create mode 100644 extra/webapps/wiki/wiki.css
 create mode 100644 extra/webapps/wiki/wiki.factor

diff --git a/extra/lcs/diff2html/diff2html.factor b/extra/lcs/diff2html/diff2html.factor
new file mode 100644
index 0000000000..a8f649e2c9
--- /dev/null
+++ b/extra/lcs/diff2html/diff2html.factor
@@ -0,0 +1,44 @@
+! Copyright (C) 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
+USING: lcs html.elements kernel qualified ;
+FROM: accessors => item>> ;
+FROM: io => write ;
+FROM: sequences => each empty? ;
+FROM: xml.entities => escape-string ;
+IN: lcs.diff2html
+
+GENERIC: diff-line ( obj -- )
+
+: write-item ( item -- )
+    item>> dup empty? [ drop "&nbsp;" ] [ escape-string ] if write ;
+
+M: retain diff-line
+    <tr>
+        dup [
+            <td "retain" =class td>
+                write-item
+            </td>
+        ] bi@
+    </tr> ;
+
+M: insert diff-line
+    <tr>
+        <td> </td>
+        <td "insert" =class td>
+            write-item
+        </td>
+    </tr> ;
+
+M: delete diff-line
+    <tr>
+        <td "delete" =class td>
+            write-item
+        </td>
+        <td> </td>
+    </tr> ;
+
+: htmlize-diff ( diff -- )
+    <table "comparison" =class table>
+        <tr> <th> "Old" write </th> <th> "New" write </th> </tr>
+        [ diff-line ] each
+    </table> ;
diff --git a/extra/webapps/factor-website/factor-website.factor b/extra/webapps/factor-website/factor-website.factor
index 1fb5d4c1a6..9ad4a05492 100644
--- a/extra/webapps/factor-website/factor-website.factor
+++ b/extra/webapps/factor-website/factor-website.factor
@@ -11,10 +11,11 @@ http.server.auth.login
 http.server.auth.providers.db
 http.server.boilerplate
 html.templates.chloe
-webapps.user-admin
 webapps.pastebin
 webapps.planet
-webapps.todo ;
+webapps.todo
+webapps.wiki
+webapps.user-admin ;
 IN: webapps.factor-website
 
 : test-db "resource:test.db" sqlite-db ;
@@ -34,6 +35,9 @@ IN: webapps.factor-website
         init-postings-table
 
         init-todo-table
+
+        init-articles-table
+        init-revisions-table
     ] with-db ;
 
 : <factor-website> ( -- responder )
@@ -41,6 +45,7 @@ IN: webapps.factor-website
         <todo-list> "todo" add-responder
         <pastebin> "pastebin" add-responder
         <planet-factor> "planet" add-responder
+        <wiki> "wiki" add-responder
         <user-admin> "user-admin" add-responder
     <login>
         users-in-db >>users
diff --git a/extra/webapps/factor-website/page.css b/extra/webapps/factor-website/page.css
index 606d574618..49e26883ad 100644
--- a/extra/webapps/factor-website/page.css
+++ b/extra/webapps/factor-website/page.css
@@ -42,12 +42,15 @@ a:hover, .link:hover {
 }
 
 .description {
-	border: 1px dashed #ccc;
-	background-color: #f5f5f5;
 	padding: 5px;
 	color: #000;
 }
 
+.description pre {
+	border: 1px dashed #ccc;
+	background-color: #f5f5f5;
+}
+
 .description p:first-child {
 	margin-top: 0px;
 }
diff --git a/extra/webapps/wiki/articles.xml b/extra/webapps/wiki/articles.xml
new file mode 100644
index 0000000000..a552c2618f
--- /dev/null
+++ b/extra/webapps/wiki/articles.xml
@@ -0,0 +1,15 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>All Articles</t:title>
+
+	<ul>
+		<t:each-tuple t:values="articles">
+			<li>
+				<t:a t:href="view" t:query="title"><t:label t:name="title"/></t:a>
+			</li>
+		</t:each-tuple>
+	</ul>
+
+</t:chloe>
diff --git a/extra/webapps/wiki/diff.xml b/extra/webapps/wiki/diff.xml
new file mode 100644
index 0000000000..378466f0bb
--- /dev/null
+++ b/extra/webapps/wiki/diff.xml
@@ -0,0 +1,35 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:bind-tuple t:name="old">
+		<t:title>Diff: <t:label t:name="title" /></t:title>
+	</t:bind-tuple>
+
+	<table>
+		<tr>
+			<th class="field-label">Old revision:</th>
+			<t:bind-tuple t:name="old">
+				<td>Created on <t:label t:name="date" /> by <t:label t:name="author" />.</td>
+			</t:bind-tuple>
+		</tr>
+		<tr>
+			<th class="field-label">New revision:</th>
+			<t:bind-tuple t:name="old">
+				<td>Created on <t:label t:name="date" /> by <t:label t:name="author" />.</td>
+			</t:bind-tuple>
+		</tr>
+	</table>
+
+	<t:comparison t:name="diff" />
+
+	<t:bind-tuple t:name="old">
+		<div class="navbar">
+			<t:a t:href="$wiki/view" t:query="title">Latest</t:a>
+			| <t:a t:href="$wiki/revisions" t:query="title">Revisions</t:a>
+			| <t:a t:href="$wiki/edit" t:query="title">Edit</t:a>
+			| <t:button t:action="$wiki/delete" t:for="title" class="link-button link">Delete</t:button>
+		</div>
+	</t:bind-tuple>
+
+</t:chloe>
diff --git a/extra/webapps/wiki/edit.xml b/extra/webapps/wiki/edit.xml
new file mode 100644
index 0000000000..85c8490c5d
--- /dev/null
+++ b/extra/webapps/wiki/edit.xml
@@ -0,0 +1,20 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>Edit: <t:label t:name="title" /></t:title>
+
+	<t:form t:action="$wiki/edit" t:for="title">
+
+		<p>
+			<t:textarea t:name="content" t:rows="30" t:cols="80" />
+		</p>
+
+		<p>
+			<input type="submit" value="Save" />
+		</p>
+
+	</t:form>
+
+	<t:button t:action="$wiki/delete" t:for="title" class="link-button link">Delete</t:button>
+</t:chloe>
diff --git a/extra/webapps/wiki/revisions.xml b/extra/webapps/wiki/revisions.xml
new file mode 100644
index 0000000000..fe74191773
--- /dev/null
+++ b/extra/webapps/wiki/revisions.xml
@@ -0,0 +1,48 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>Revisions of <t:label t:name="title" /></t:title>
+
+	<ul>
+		<t:each-tuple t:values="revisions">
+			<li>
+				<t:a t:href="revision" t:query="id">
+					<t:label t:name="date" /> by <t:label t:name="author" />
+				</t:a>
+			</li>
+		</t:each-tuple>
+	</ul>
+
+	<h2>View Differences</h2>
+
+	<form action="diff" method="get">
+		<table>
+			<tr>
+				<th class="field-label">Old revision:</th>
+				
+				<td>
+					<select name="old-id">
+						<t:each-tuple t:values="revisions">
+							<option> <t:label t:name="id" /> </option>
+						</t:each-tuple>
+					</select>
+				</td>
+			</tr>
+			<tr>
+				<th class="field-label">New revision:</th>
+				
+				<td>
+					<select name="new-id">
+						<t:each-tuple t:values="revisions">
+							<option> <t:label t:name="id" /> </option>
+						</t:each-tuple>
+					</select>
+				</td>
+			</tr>
+		</table>
+
+		<input type="submit" value="View" />
+	</form>
+
+</t:chloe>
diff --git a/extra/webapps/wiki/view.xml b/extra/webapps/wiki/view.xml
new file mode 100644
index 0000000000..c3536f374d
--- /dev/null
+++ b/extra/webapps/wiki/view.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title><t:label t:name="title" /></t:title>
+
+	<div class="description">
+		<t:farkup t:name="content" />
+	</div>
+
+	<div class="navbar">
+		<t:a t:href="$wiki/view" t:query="title">Latest</t:a>
+		| <t:a t:href="$wiki/revisions" t:query="title">Revisions</t:a>
+		| <t:a t:href="$wiki/edit" t:query="title">Edit</t:a>
+		| <t:button t:action="$wiki/delete" t:for="title" class="link-button link">Delete</t:button>
+		| This revision created on <t:label t:name="date" /> by <t:label t:name="author" />.
+	</div>
+
+</t:chloe>
diff --git a/extra/webapps/wiki/wiki-common.xml b/extra/webapps/wiki/wiki-common.xml
new file mode 100644
index 0000000000..d241f910ca
--- /dev/null
+++ b/extra/webapps/wiki/wiki-common.xml
@@ -0,0 +1,28 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:style t:include="resource:extra/webapps/wiki/wiki.css" />
+
+	<div class="navbar">
+
+		<t:a t:href="$wiki">Front Page</t:a>
+		| <t:a t:href="$wiki/articles">All Articles</t:a>
+
+		<t:if t:code="http.server.sessions:uid">
+
+			<t:if t:code="http.server.auth.login:allow-edit-profile?">
+				| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
+			</t:if>
+
+			| <t:button t:action="$login/logout" t:flow="begin" class="link-button link">Logout</t:button>
+
+		</t:if>
+
+	</div>
+
+	<h1><t:write-title /></h1>
+
+        <t:call-next-template />
+
+</t:chloe>
diff --git a/extra/webapps/wiki/wiki.css b/extra/webapps/wiki/wiki.css
new file mode 100644
index 0000000000..e737cdd898
--- /dev/null
+++ b/extra/webapps/wiki/wiki.css
@@ -0,0 +1,25 @@
+.comparison table, {
+    border-color: #666;
+    border-style: solid;
+}
+
+.comparison th {
+    border-width: 1px;
+    border-color: #666;
+    border-style: solid;
+}
+
+.comparison table {
+    border-width: 1px;
+    border-spacing: 0;
+    border-collapse: collapse;
+}
+
+
+.insert {
+    background-color: #9f9;
+}
+
+.delete {
+    background-color: #f99;
+}
diff --git a/extra/webapps/wiki/wiki.factor b/extra/webapps/wiki/wiki.factor
new file mode 100644
index 0000000000..2f281866c5
--- /dev/null
+++ b/extra/webapps/wiki/wiki.factor
@@ -0,0 +1,175 @@
+! Copyright (C) 2008 Slava Pestov
+! See http://factorcode.org/license.txt for BSD license.
+USING: accessors kernel hashtables calendar
+namespaces splitting sequences sorting math.order
+html.components
+html.templates.chloe
+http.server
+http.server.actions
+http.server.auth
+http.server.auth.login
+http.server.boilerplate
+validators
+db.types db.tuples lcs farkup ;
+IN: webapps.wiki
+
+TUPLE: article title revision ;
+
+article "ARTICLES" {
+    { "title" "TITLE" { VARCHAR 256 } +not-null+ +user-assigned-id+ }
+    ! { "AUTHOR" INTEGER +not-null+ } ! uid
+    ! { "PROTECTED" BOOLEAN +not-null+ }
+    { "revision" "REVISION" INTEGER +not-null+ } ! revision id
+} define-persistent
+
+: <article> ( title -- article ) article new swap >>title ;
+
+: init-articles-table article ensure-table ;
+
+TUPLE: revision id title author date content ;
+
+revision "REVISIONS" {
+    { "id" "ID" INTEGER +db-assigned-id+ }
+    { "title" "TITLE" { VARCHAR 256 } +not-null+ } ! article id
+    { "author" "AUTHOR" { VARCHAR 256 } +not-null+ } ! uid
+    { "date" "DATE" TIMESTAMP +not-null+ }
+    { "content" "CONTENT" TEXT +not-null+ }
+} define-persistent
+
+: <revision> ( id -- revision )
+    revision new swap >>id ;
+
+: init-revisions-table revision ensure-table ;
+
+: wiki-template ( name -- template )
+    "resource:extra/webapps/wiki/" swap ".xml" 3append <chloe> ;
+
+: <title-redirect> ( title next -- response )
+    swap "title" associate <standard-redirect> ;
+
+: validate-title ( -- )
+    { { "title" [ v-one-line ] } } validate-params ;
+
+: <main-article-action> ( -- action )
+    <action>
+        [ "Front Page" "$wiki/view" <title-redirect> ] >>display ;
+
+: <view-article-action> ( -- action )
+    <action>
+        "title" >>rest-param
+
+        [
+            validate-title
+            "view?title=" relative-link-prefix set
+        ] >>init
+
+        [
+            "title" value dup <article> select-tuple [
+                revision>> <revision> select-tuple from-tuple
+                "view" wiki-template <html-content>
+            ] [
+                "$wiki/edit" <title-redirect>
+            ] ?if
+        ] >>display ;
+
+: <view-revision-action> ( -- action )
+    <page-action>
+        [
+            { { "id" [ v-integer ] } } validate-params
+            "id" value <revision>
+            select-tuple from-tuple
+        ] >>init
+
+        "view" wiki-template >>template ;
+
+: add-revision ( revision -- )
+    [ insert-tuple ]
+    [
+        dup title>> <article> select-tuple [
+            swap id>> >>revision update-tuple
+        ] [
+            [ title>> ] [ id>> ] bi article boa insert-tuple
+        ] if*
+    ] bi ;
+
+: <edit-article-action> ( -- action )
+    <page-action>
+        [
+            validate-title
+            "title" value <article> select-tuple [
+                revision>> <revision> select-tuple from-tuple
+            ] when*
+        ] >>init
+
+        "edit" wiki-template >>template
+        
+        [
+            validate-title
+            { { "content" [ v-required ] } } validate-params
+
+            f <revision>
+                "title" value >>title
+                now >>date
+                logged-in-user get username>> >>author
+                "content" value >>content
+            [ add-revision ]
+            [ title>> "$wiki/view" <title-redirect> ] bi
+        ] >>submit ;
+
+: <list-revisions-action> ( -- action )
+    <page-action>
+        [
+            validate-title
+            f <revision> "title" value >>title select-tuples
+            [ [ date>> ] compare invert-comparison ] sort
+            "revisions" set-value
+        ] >>init
+
+        "revisions" wiki-template >>template ;
+
+: <delete-action> ( -- action )
+    <action>
+        [ validate-title ] >>validate
+
+        [
+            "title" value <article> delete-tuples
+            f <revision> "title" value >>title delete-tuples
+            "" f <standard-redirect>
+        ] >>submit ;
+
+: <diff-action> ( -- action )
+    <page-action>
+        [
+            {
+                { "old-id" [ v-integer ] }
+                { "new-id" [ v-integer ] }
+            } validate-params
+
+            "old-id" "new-id"
+            [ value <revision> select-tuple ] bi@
+            [ [ "old" set-value ] [ "new" set-value ] bi* ]
+            [ [ content>> string-lines ] bi@ diff "diff" set-value ]
+            2bi
+        ] >>init
+
+        "diff" wiki-template >>template ;
+
+: <list-articles-action> ( -- action )
+    <page-action>
+        [ f <article> select-tuples "articles" set-value ] >>init
+        "articles" wiki-template >>template ;
+
+TUPLE: wiki < dispatcher ;
+
+: <wiki> ( -- dispatcher )
+    wiki new-dispatcher
+        <main-article-action> "" add-responder
+        <view-article-action> "view" add-responder
+        <view-revision-action> "revision" add-responder
+        <edit-article-action> { } <protected> "edit" add-responder
+        <list-revisions-action> "revisions" add-responder
+        <delete-action> "delete" add-responder
+        <diff-action> "diff" add-responder
+        <list-articles-action> "articles" add-responder
+    <boilerplate>
+        "wiki-common" wiki-template >>template ;

From 8278ac5b28ac80438bc8e7be2be385d30ed727bd Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 01:18:38 -0500
Subject: [PATCH 39/66] Various fixes

---
 extra/http/server/auth/login/edit-profile.xml |  2 +-
 extra/http/server/auth/login/register.xml     |  2 +-
 extra/validators/validators-tests.factor      | 15 +++++++++++
 extra/validators/validators.factor            | 25 ++++++++++++++++---
 extra/webapps/user-admin/user-admin.factor    |  1 -
 extra/webapps/wiki/wiki.factor                |  4 +--
 extra/xmode/code2html/code2html.factor        |  2 +-
 7 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/extra/http/server/auth/login/edit-profile.xml b/extra/http/server/auth/login/edit-profile.xml
index 855dfa8469..6beaf5de6d 100644
--- a/extra/http/server/auth/login/edit-profile.xml
+++ b/extra/http/server/auth/login/edit-profile.xml
@@ -10,7 +10,7 @@
 	
 	<tr>
 		<th class="field-label">User name:</th>
-		<td><t:field t:name="username" /></td>
+		<td><t:label t:name="username" /></td>
 	</tr>
 	
 	<tr>
diff --git a/extra/http/server/auth/login/register.xml b/extra/http/server/auth/login/register.xml
index 4804410dde..9815f21945 100644
--- a/extra/http/server/auth/login/register.xml
+++ b/extra/http/server/auth/login/register.xml
@@ -63,7 +63,7 @@
 		<p>
 
 			<input type="submit" value="Register" />
-			<t:validation-message />
+			<t:validation-messages />
 
 		</p>
 
diff --git a/extra/validators/validators-tests.factor b/extra/validators/validators-tests.factor
index a981f782d3..7d4325cbb6 100644
--- a/extra/validators/validators-tests.factor
+++ b/extra/validators/validators-tests.factor
@@ -47,6 +47,21 @@ namespaces assocs ;
 [ "http:/www.factorcode.org" v-url ]
 [ "invalid URL" = ] must-fail-with
 
+[ 4561261212345467 ] [ "4561261212345467" v-credit-card ] unit-test
+
+[ 4561261212345467 ] [ "4561-2612-1234-5467" v-credit-card ] unit-test
+
+[ 0 ] [ "0000000000000000" v-credit-card ] unit-test
+
+[ "000000000" v-credit-card ] must-fail
+
+[ "0000000000000000000000000" v-credit-card ] must-fail
+
+[ "4561_2612_1234_5467" v-credit-card ] must-fail
+
+[ "4561-2621-1234-5467" v-credit-card ] must-fail
+
+
 [ 14 V{ } ] [
     [
         "14" "age" [ v-number 13 v-min-value 100 v-max-value ] validate
diff --git a/extra/validators/validators.factor b/extra/validators/validators.factor
index 2dcc2c04f9..aeb2dc2f80 100644
--- a/extra/validators/validators.factor
+++ b/extra/validators/validators.factor
@@ -1,9 +1,9 @@
 ! Copyright (C) 2006, 2008 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
-USING: kernel continuations sequences math namespaces sets
-math.parser assocs regexp fry unicode.categories sequences
-arrays hashtables words combinators mirrors classes quotations
-xmode.catalog ;
+USING: kernel continuations sequences sequences.lib math
+namespaces sets math.parser math.ranges assocs regexp fry
+unicode.categories arrays hashtables words combinators mirrors
+classes quotations xmode.catalog ;
 IN: validators
 
 : v-default ( str def -- str )
@@ -91,6 +91,23 @@ IN: validators
         "not a valid syntax mode" throw 
     ] unless ;
 
+: luhn? ( n -- ? )
+    string>digits <reversed>
+    [ odd? [ 2 * 10 /mod + ] when ] map-index
+    sum 10 mod 0 = ;
+
+: v-credit-card ( str -- n )
+    "- " diff
+    dup CHAR: 0 CHAR: 9 [a,b] diff empty? [
+        13 v-min-length
+        16 v-max-length
+        dup luhn? [ string>number ] [
+            "card number check failed" throw
+        ] if
+    ] [
+        "invalid credit card number format" throw
+    ] if ;
+
 SYMBOL: validation-messages
 SYMBOL: named-validation-messages
 
diff --git a/extra/webapps/user-admin/user-admin.factor b/extra/webapps/user-admin/user-admin.factor
index 728d5215f0..cdaf3f5ea9 100644
--- a/extra/webapps/user-admin/user-admin.factor
+++ b/extra/webapps/user-admin/user-admin.factor
@@ -12,7 +12,6 @@ http.server.auth.login
 http.server.auth
 http.server.sessions
 http.server.actions
-http.server.crud
 http.server ;
 IN: webapps.user-admin
 
diff --git a/extra/webapps/wiki/wiki.factor b/extra/webapps/wiki/wiki.factor
index 2f281866c5..d0e1aed7ce 100644
--- a/extra/webapps/wiki/wiki.factor
+++ b/extra/webapps/wiki/wiki.factor
@@ -166,10 +166,10 @@ TUPLE: wiki < dispatcher ;
         <main-article-action> "" add-responder
         <view-article-action> "view" add-responder
         <view-revision-action> "revision" add-responder
-        <edit-article-action> { } <protected> "edit" add-responder
         <list-revisions-action> "revisions" add-responder
-        <delete-action> "delete" add-responder
         <diff-action> "diff" add-responder
         <list-articles-action> "articles" add-responder
+        <edit-article-action> { } <protected> "edit" add-responder
+        <delete-action> { } <protected> "delete" add-responder
     <boilerplate>
         "wiki-common" wiki-template >>template ;
diff --git a/extra/xmode/code2html/code2html.factor b/extra/xmode/code2html/code2html.factor
index a9384ad861..6eccddc94a 100755
--- a/extra/xmode/code2html/code2html.factor
+++ b/extra/xmode/code2html/code2html.factor
@@ -1,4 +1,4 @@
-USING: xmode.tokens xmode.marker xmode.catalog kernel html
+USING: xmode.tokens xmode.marker xmode.catalog kernel
 html.elements io io.files sequences words io.encodings.utf8
 namespaces xml.entities ;
 IN: xmode.code2html

From 6edcd94b62a5d1e419a4c73cea9aeecdf33515db Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 02:28:48 -0500
Subject: [PATCH 40/66] Fixes

---
 extra/http/server/crud/crud.factor | 66 ------------------------------
 1 file changed, 66 deletions(-)
 delete mode 100755 extra/http/server/crud/crud.factor

diff --git a/extra/http/server/crud/crud.factor b/extra/http/server/crud/crud.factor
deleted file mode 100755
index 5fb7c15019..0000000000
--- a/extra/http/server/crud/crud.factor
+++ /dev/null
@@ -1,66 +0,0 @@
-! Copyright (C) 2008 Slava Pestov.
-! See http://factorcode.org/license.txt for BSD license.
-USING: kernel namespaces db.tuples math.parser
-accessors fry locals hashtables validators
-http.server
-http.server.actions
-http.server.components
-http.server.forms ;
-IN: http.server.crud
-
-:: <view-action> ( form ctor -- action )
-    <action>
-        { { "id" [ v-number ] } } >>get-params
-
-        [ "id" get ctor call select-tuple from-tuple ] >>init
-
-        [ form view-form ] >>display ;
-
-: <id-redirect> ( id next -- response )
-    swap "id" associate <standard-redirect> ;
-
-:: <edit-action> ( form ctor next -- action )
-    <action>
-        { { "id" [ [ v-number ] v-optional ] } } >>get-params
-
-        [
-            "id" get ctor call
-
-            "id" get
-            [ select-tuple from-tuple ]
-            [ from-tuple form set-defaults ]
-            if
-        ] >>init
-
-        [ form edit-form ] >>display
-
-        [
-            f ctor call from-tuple
-
-            form validate-form
-
-            values-tuple
-            "id" value [ update-tuple ] [ insert-tuple ] if
-
-            "id" value next <id-redirect>
-        ] >>submit ;
-
-:: <delete-action> ( ctor next -- action )
-    <action>
-        { { "id" [ v-number ] } } >>post-params
-
-        [
-            "id" get ctor call delete-tuples
-
-            next f <standard-redirect>
-        ] >>submit ;
-
-:: <list-action> ( form ctor -- action )
-    <action>
-        [
-            blank-values
-
-            f ctor call select-tuples "list" set-value
-
-            form view-form
-        ] >>display ;

From fa3ab3a8b83d5aef562850c5eded83d439795190 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 02:42:13 -0500
Subject: [PATCH 41/66] More fixes

---
 extra/http/server/auth/login/login.factor |  8 +++----
 extra/http/server/static/static.factor    | 26 +++++++++++------------
 2 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/extra/http/server/auth/login/login.factor b/extra/http/server/auth/login/login.factor
index e8c9bf8608..fd4fbab8e8 100755
--- a/extra/http/server/auth/login/login.factor
+++ b/extra/http/server/auth/login/login.factor
@@ -64,8 +64,8 @@ M: user-saver dispose
     3append <chloe> ;
 
 ! ! ! Login
-: successful-login ( user -- )
-    username>> set-uid ;
+: successful-login ( user -- response )
+    username>> set-uid "$login" end-flow ;
 
 : login-failed ( -- * )
     "invalid username or password" validation-error
@@ -84,9 +84,7 @@ M: user-saver dispose
             "password" value
             "username" value check-login
             [ successful-login ] [ login-failed ] if*
-        ] >>validate
-
-        [ "$login" end-flow ] >>submit ;
+        ] >>submit ;
 
 ! ! ! New user registration
 
diff --git a/extra/http/server/static/static.factor b/extra/http/server/static/static.factor
index 8c0e255e21..0e799fd3ad 100755
--- a/extra/http/server/static/static.factor
+++ b/extra/http/server/static/static.factor
@@ -1,10 +1,10 @@
 ! Copyright (C) 2004, 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: calendar html io io.files kernel math math.order
+USING: calendar io io.files kernel math math.order
 math.parser http http.server namespaces parser sequences strings
 assocs hashtables debugger http.mime sorting html.elements
 html.templates.fhtml logging calendar.format accessors
-io.encodings.binary fry ;
+io.encodings.binary fry xml.entities ;
 IN: http.server.static
 
 ! special maps mime types to quots with effect ( path -- )
@@ -58,20 +58,18 @@ TUPLE: file-responder root hook special allow-listings ;
 
 : file. ( name dirp -- )
     [ "/" append ] when
-    dup <a =href a> write </a> ;
+    dup <a =href a> escape-string write </a> ;
 
 : directory. ( path -- )
-    [
-        dup file-name [
-            [ <h1> file-name write </h1> ]
-            [
-                <ul>
-                    directory sort-keys
-                    [ <li> file. </li> ] assoc-each
-                </ul>
-            ] bi
-        ] simple-page
-    ] with-html-stream ;
+    dup file-name [
+        [ <h1> file-name escape-string write </h1> ]
+        [
+            <ul>
+                directory sort-keys
+                [ <li> file. </li> ] assoc-each
+            </ul>
+        ] bi
+    ] simple-page ;
 
 : list-directory ( directory -- response )
     file-responder get allow-listings>> [

From 83e9a717f71a5113e3ddb98d9ac19760d65609c0 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Tue, 27 May 2008 02:42:21 -0500
Subject: [PATCH 42/66] More wiki features

---
 extra/webapps/wiki/changes.xml     | 19 +++++++++++++++++++
 extra/webapps/wiki/diff.xml        |  4 ++--
 extra/webapps/wiki/revisions.xml   |  6 +++---
 extra/webapps/wiki/user-edits.xml  | 17 +++++++++++++++++
 extra/webapps/wiki/view.xml        |  2 +-
 extra/webapps/wiki/wiki-common.xml |  1 +
 extra/webapps/wiki/wiki.factor     | 29 ++++++++++++++++++++++++++++-
 7 files changed, 71 insertions(+), 7 deletions(-)
 create mode 100644 extra/webapps/wiki/changes.xml
 create mode 100644 extra/webapps/wiki/user-edits.xml

diff --git a/extra/webapps/wiki/changes.xml b/extra/webapps/wiki/changes.xml
new file mode 100644
index 0000000000..5efa0c045a
--- /dev/null
+++ b/extra/webapps/wiki/changes.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>Recent Changes</t:title>
+
+	<ul>
+		<t:each-tuple t:values="changes">
+			<li>
+				<t:a t:href="title" t:query="title"><t:label t:name="title" /></t:a>
+				on
+				<t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a>
+				by
+				<t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>
+			</li>
+		</t:each-tuple>
+	</ul>
+
+</t:chloe>
diff --git a/extra/webapps/wiki/diff.xml b/extra/webapps/wiki/diff.xml
index 378466f0bb..0fb0d6bae6 100644
--- a/extra/webapps/wiki/diff.xml
+++ b/extra/webapps/wiki/diff.xml
@@ -10,13 +10,13 @@
 		<tr>
 			<th class="field-label">Old revision:</th>
 			<t:bind-tuple t:name="old">
-				<td>Created on <t:label t:name="date" /> by <t:label t:name="author" />.</td>
+				<td>Created on <t:label t:name="date" /> by <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>.</td>
 			</t:bind-tuple>
 		</tr>
 		<tr>
 			<th class="field-label">New revision:</th>
 			<t:bind-tuple t:name="old">
-				<td>Created on <t:label t:name="date" /> by <t:label t:name="author" />.</td>
+				<td>Created on <t:label t:name="date" /> by <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>.</td>
 			</t:bind-tuple>
 		</tr>
 	</table>
diff --git a/extra/webapps/wiki/revisions.xml b/extra/webapps/wiki/revisions.xml
index fe74191773..4b7bdadf50 100644
--- a/extra/webapps/wiki/revisions.xml
+++ b/extra/webapps/wiki/revisions.xml
@@ -7,9 +7,9 @@
 	<ul>
 		<t:each-tuple t:values="revisions">
 			<li>
-				<t:a t:href="revision" t:query="id">
-					<t:label t:name="date" /> by <t:label t:name="author" />
-				</t:a>
+				<t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a>
+				by
+				<t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>
 			</li>
 		</t:each-tuple>
 	</ul>
diff --git a/extra/webapps/wiki/user-edits.xml b/extra/webapps/wiki/user-edits.xml
new file mode 100644
index 0000000000..cf19a38370
--- /dev/null
+++ b/extra/webapps/wiki/user-edits.xml
@@ -0,0 +1,17 @@
+<?xml version='1.0' ?>
+
+<t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
+
+	<t:title>Edits by <t:label t:name="author" /></t:title>
+
+	<ul>
+		<t:each-tuple t:values="user-edits">
+			<li>
+				<t:a t:href="view" t:query="title"><t:label t:name="title" /></t:a>
+				on
+				<t:a t:href="revision" t:query="id"><t:label t:name="date" /></t:a>
+			</li>
+		</t:each-tuple>
+	</ul>
+
+</t:chloe>
diff --git a/extra/webapps/wiki/view.xml b/extra/webapps/wiki/view.xml
index c3536f374d..56c8b37a1d 100644
--- a/extra/webapps/wiki/view.xml
+++ b/extra/webapps/wiki/view.xml
@@ -13,7 +13,7 @@
 		| <t:a t:href="$wiki/revisions" t:query="title">Revisions</t:a>
 		| <t:a t:href="$wiki/edit" t:query="title">Edit</t:a>
 		| <t:button t:action="$wiki/delete" t:for="title" class="link-button link">Delete</t:button>
-		| This revision created on <t:label t:name="date" /> by <t:label t:name="author" />.
+		| This revision created on <t:label t:name="date" /> by <t:a t:href="user-edits" t:query="author"><t:label t:name="author" /></t:a>.
 	</div>
 
 </t:chloe>
diff --git a/extra/webapps/wiki/wiki-common.xml b/extra/webapps/wiki/wiki-common.xml
index d241f910ca..23e61e55fe 100644
--- a/extra/webapps/wiki/wiki-common.xml
+++ b/extra/webapps/wiki/wiki-common.xml
@@ -8,6 +8,7 @@
 
 		<t:a t:href="$wiki">Front Page</t:a>
 		| <t:a t:href="$wiki/articles">All Articles</t:a>
+		| <t:a t:href="$wiki/changes">Recent Changes</t:a>
 
 		<t:if t:code="http.server.sessions:uid">
 
diff --git a/extra/webapps/wiki/wiki.factor b/extra/webapps/wiki/wiki.factor
index d0e1aed7ce..344a3d40bd 100644
--- a/extra/webapps/wiki/wiki.factor
+++ b/extra/webapps/wiki/wiki.factor
@@ -127,6 +127,16 @@ revision "REVISIONS" {
 
         "revisions" wiki-template >>template ;
 
+: <list-changes-action> ( -- action )
+    <page-action>
+        [
+            f <revision> select-tuples
+            [ [ date>> ] compare invert-comparison ] sort
+            "changes" set-value
+        ] >>init
+
+        "changes" wiki-template >>template ;
+
 : <delete-action> ( -- action )
     <action>
         [ validate-title ] >>validate
@@ -156,9 +166,24 @@ revision "REVISIONS" {
 
 : <list-articles-action> ( -- action )
     <page-action>
-        [ f <article> select-tuples "articles" set-value ] >>init
+        [
+            f <article> select-tuples
+            [ [ title>> ] compare ] sort
+            "articles" set-value
+        ] >>init
+
         "articles" wiki-template >>template ;
 
+: <user-edits-action> ( -- action )
+    <page-action>
+        [
+            { { "author" [ v-username ] } } validate-params
+            f <revision> "author" value >>author
+            select-tuples "user-edits" set-value
+        ] >>init
+
+        "user-edits" wiki-template >>template ;
+
 TUPLE: wiki < dispatcher ;
 
 : <wiki> ( -- dispatcher )
@@ -167,8 +192,10 @@ TUPLE: wiki < dispatcher ;
         <view-article-action> "view" add-responder
         <view-revision-action> "revision" add-responder
         <list-revisions-action> "revisions" add-responder
+        <user-edits-action> "user-edits" add-responder
         <diff-action> "diff" add-responder
         <list-articles-action> "articles" add-responder
+        <list-changes-action> "changes" add-responder
         <edit-article-action> { } <protected> "edit" add-responder
         <delete-action> { } <protected> "delete" add-responder
     <boilerplate>

From 64a3233fad10519b8818a9b01a0e43ddcc18caf3 Mon Sep 17 00:00:00 2001
From: Bruno Deferrari <utizoc@gmail.com>
Date: Tue, 27 May 2008 22:10:14 -0300
Subject: [PATCH 43/66] Reworked extra/irc (now in extra/irc/client)

---
 extra/irc/{ => client}/authors.txt |   0
 extra/irc/client/client.factor     | 275 +++++++++++++++++++++++++++
 extra/irc/{ => client}/summary.txt |   0
 extra/irc/irc.factor               | 286 -----------------------------
 4 files changed, 275 insertions(+), 286 deletions(-)
 rename extra/irc/{ => client}/authors.txt (100%)
 create mode 100644 extra/irc/client/client.factor
 rename extra/irc/{ => client}/summary.txt (100%)
 delete mode 100755 extra/irc/irc.factor

diff --git a/extra/irc/authors.txt b/extra/irc/client/authors.txt
similarity index 100%
rename from extra/irc/authors.txt
rename to extra/irc/client/authors.txt
diff --git a/extra/irc/client/client.factor b/extra/irc/client/client.factor
new file mode 100644
index 0000000000..19dca48e1d
--- /dev/null
+++ b/extra/irc/client/client.factor
@@ -0,0 +1,275 @@
+! Copyright (C) 2007 Doug Coleman, Slava Pestov.
+! See http://factorcode.org/license.txt for BSD license.
+USING: arrays combinators concurrency.mailboxes concurrency.futures io
+       io.encodings.8-bit io.sockets kernel namespaces sequences
+       sequences.lib splitting threads calendar classes.tuple
+       ascii assocs accessors destructors ;
+IN: irc.client
+
+! ======================================
+! Setup and running objects
+! ======================================
+
+SYMBOL: current-irc-client
+
+: irc-port 6667 ; ! Default irc port
+
+! "setup" objects
+TUPLE: irc-profile server port nickname password ;
+C: <irc-profile> irc-profile
+
+TUPLE: irc-channel-profile name password ;
+: <irc-channel-profile> ( -- irc-channel-profile ) irc-channel-profile new ;
+
+! "live" objects
+TUPLE: nick name channels log ;
+C: <nick> nick
+
+TUPLE: irc-client profile nick stream in-messages out-messages join-messages
+       listeners is-running ;
+: <irc-client> ( profile -- irc-client )
+    f V{ } clone V{ } clone <nick>
+    f <mailbox> <mailbox> <mailbox> H{ } clone f irc-client boa ;
+
+TUPLE: irc-listener in-messages out-messages ;
+: <irc-listener> ( -- irc-listener )
+    <mailbox> <mailbox> irc-listener boa ;
+
+! ======================================
+! Message objects
+! ======================================
+
+SINGLETON: irc-end ! Message used when the client isn't running anymore
+
+TUPLE: irc-message line prefix command parameters trailing timestamp ;
+TUPLE: logged-in < irc-message name ;
+TUPLE: ping < irc-message ;
+TUPLE: join < irc-message ;
+TUPLE: part < irc-message name channel ;
+TUPLE: quit < irc-message ;
+TUPLE: privmsg < irc-message name ;
+TUPLE: kick < irc-message channel who ;
+TUPLE: roomlist < irc-message channel names ;
+TUPLE: nick-in-use < irc-message asterisk name ;
+TUPLE: notice < irc-message type ;
+TUPLE: mode < irc-message name channel mode ;
+TUPLE: unhandled < irc-message ;
+
+<PRIVATE
+
+! ======================================
+! Shortcuts
+! ======================================
+
+: irc-client> ( -- irc-client ) current-irc-client get ;
+: irc-stream> ( -- stream ) irc-client> stream>> ;
+: irc-write ( s -- ) irc-stream> stream-write ;
+: irc-print ( s -- ) irc-stream> [ stream-print ] keep stream-flush ;
+
+! ======================================
+! IRC client messages
+! ======================================
+
+: /NICK ( nick -- )
+    "NICK " irc-write irc-print ;
+
+: /LOGIN ( nick -- )
+    dup /NICK
+    "USER " irc-write irc-write
+    " hostname servername :irc.factor" irc-print ;
+
+: /CONNECT ( server port -- stream )
+    <inet> latin1 <client> drop ;
+
+: /JOIN ( channel password -- )
+    "JOIN " irc-write
+    [ " :" swap 3append ] when* irc-print ;
+
+: /PART ( channel text -- )
+    [ "PART " irc-write irc-write ] dip
+    " :" irc-write irc-print ;
+
+: /KICK ( channel who -- )
+    [ "KICK " irc-write irc-write ] dip
+    " " irc-write irc-print ;
+
+: /PRIVMSG ( nick line -- )
+    [ "PRIVMSG " irc-write irc-write ] dip
+    " :" irc-write irc-print ;
+
+: /ACTION ( nick line -- )
+    [ 1 , "ACTION " % % 1 , ] "" make /PRIVMSG ;
+
+: /QUIT ( text -- )
+    "QUIT :" irc-write irc-print ;
+
+: /PONG ( text -- )
+    "PONG " irc-write irc-print ;
+
+! ======================================
+! Server message handling
+! ======================================
+
+USE: prettyprint
+
+GENERIC: handle-incoming-irc ( irc-message -- )
+
+M: irc-message handle-incoming-irc ( irc-message -- )
+    . ;
+
+M: logged-in handle-incoming-irc ( logged-in -- )
+    name>> irc-client> nick>> (>>name) ;
+
+M: ping handle-incoming-irc ( ping -- )
+    trailing>> /PONG ;
+
+M: nick-in-use handle-incoming-irc ( nick-in-use -- )
+    name>> "_" append /NICK ;
+
+M: privmsg handle-incoming-irc ( privmsg -- )
+    dup name>> irc-client> listeners>> at
+    [ in-messages>> mailbox-put ] [ drop ] if* ;
+
+M: join handle-incoming-irc ( join -- )
+    irc-client> join-messages>> mailbox-put ;
+
+! ======================================
+! Client message handling
+! ======================================
+
+GENERIC: handle-outgoing-irc ( obj -- )
+
+M: privmsg handle-outgoing-irc ( privmsg -- )
+   [ name>> ] [ trailing>> ] bi /PRIVMSG ;
+
+! ======================================
+! Message parsing
+! ======================================
+
+: split-at-first ( seq separators -- before after )
+    dupd [ member? ] curry find
+        [ cut 1 tail ]
+        [ swap ]
+    if ;
+
+: remove-heading-: ( seq -- seq ) dup ":" head? [ 1 tail ] when ;
+
+: parse-name ( string -- string )
+    remove-heading-: "!" split-at-first drop ;
+
+: split-prefix ( string -- string/f string )
+    dup ":" head?
+        [ remove-heading-: " " split1 ]
+        [ f swap ]
+    if ;
+
+: split-trailing ( string -- string string/f )
+    ":" split1 ;
+
+: string>irc-message ( string -- object )
+    dup split-prefix split-trailing
+    [ [ blank? ] trim " " split unclip swap ] dip
+    now irc-message boa ;
+
+: parse-irc-line ( string -- message )
+    string>irc-message
+    dup command>> {
+        { "PING" [ \ ping ] }
+        { "NOTICE" [ \ notice ] }
+        { "001" [ \ logged-in ] }
+        { "433" [ \ nick-in-use ] }
+        { "JOIN" [ \ join ] }
+        { "PART" [ \ part ] }
+        { "PRIVMSG" [ \ privmsg ] }
+        { "QUIT" [ \ quit ] }
+        { "MODE" [ \ mode ] }
+        { "KICK" [ \ kick ] }
+        [ drop \ unhandled ]
+    } case
+    [ [ tuple-slots ] [ parameters>> ] bi append ] dip prefix >tuple ;
+
+! ======================================
+! Reader/Writer
+! ======================================
+
+: stream-readln-or-close ( stream -- str/f )
+    dup stream-readln [ nip ] [ dispose f ] if* ;
+
+: handle-reader-message ( irc-message -- )
+    irc-client> in-messages>> mailbox-put ;
+
+: handle-stream-close ( -- )
+    irc-client> f >>is-running in-messages>> irc-end swap mailbox-put ;
+
+: reader-loop ( -- )
+    irc-client> stream>> stream-readln-or-close [
+        parse-irc-line handle-reader-message
+    ] [
+        handle-stream-close
+    ] if* ;
+
+: writer-loop ( -- )
+    irc-client> out-messages>> mailbox-get handle-outgoing-irc ;
+
+! ======================================
+! Processing loops
+! ======================================
+
+: in-multiplexer-loop ( -- )
+    irc-client> in-messages>> mailbox-get handle-incoming-irc ;
+
+! FIXME: Hack, this should be handled better
+GENERIC: add-name ( name obj -- obj )
+M: object add-name nip ;
+M: privmsg add-name swap >>name ;
+    
+: listener-loop ( name -- ) ! FIXME: take different values from the stack?
+    dup irc-client> listeners>> at [
+        out-messages>> mailbox-get add-name
+        irc-client> out-messages>>
+        mailbox-put
+    ] [ drop ] if* ;
+
+: spawn-irc-loop ( quot name -- )
+    [ [ irc-client> is-running>> ] compose ] dip
+    spawn-server drop ;
+
+: spawn-irc ( -- )
+    [ reader-loop ] "irc-reader-loop" spawn-irc-loop
+    [ writer-loop ] "irc-writer-loop" spawn-irc-loop
+    [ in-multiplexer-loop ] "in-multiplexer-loop" spawn-irc-loop ;
+
+! ======================================
+! Listener join request handling
+! ======================================
+
+: make-registered-listener ( join -- listener )
+    <irc-listener> swap trailing>>
+    dup [ listener-loop ] curry "listener" spawn-irc-loop
+    [ irc-client> listeners>> set-at ] curry keep ;
+
+: make-join-future ( name -- future )
+    [ [ swap trailing>> = ] curry ! compare name with channel name
+      irc-client> join-messages>> 60 seconds rot mailbox-get-timeout?
+      make-registered-listener ]
+    curry future ;
+
+PRIVATE>
+
+: (connect-irc) ( irc-client -- )
+    [ profile>> [ server>> ] keep port>> /CONNECT ] keep
+    swap >>stream
+    t >>is-running drop ;
+
+: connect-irc ( irc-client -- )
+    dup current-irc-client [
+        [ (connect-irc) ] [ profile>> nickname>> /LOGIN ] bi
+        spawn-irc
+    ] with-variable ;
+
+: listen-to ( irc-client name -- future )
+    swap current-irc-client [ [ f /JOIN ] keep make-join-future ] with-variable ;
+
+! shorcut for privmsgs, etc
+: sender>> ( obj -- string )
+    prefix>> parse-name ;
diff --git a/extra/irc/summary.txt b/extra/irc/client/summary.txt
similarity index 100%
rename from extra/irc/summary.txt
rename to extra/irc/client/summary.txt
diff --git a/extra/irc/irc.factor b/extra/irc/irc.factor
deleted file mode 100755
index 9a278fb67f..0000000000
--- a/extra/irc/irc.factor
+++ /dev/null
@@ -1,286 +0,0 @@
-! Copyright (C) 2007 Doug Coleman, Slava Pestov.
-! See http://factorcode.org/license.txt for BSD license.
-USING: arrays calendar combinators channels concurrency.messaging fry io
-       io.encodings.8-bit io.sockets kernel math namespaces sequences
-       sequences.lib splitting strings threads
-       continuations destructors classes.tuple ascii accessors ;
-IN: irc
-
-! utils
-: split-at-first ( seq separators -- before after )
-    dupd '[ , member? ] find
-        [ cut rest ]
-        [ swap ]
-    if ;
-
-: spawn-server-linked ( quot name -- thread )
-    >r '[ , [ ] [ ] while ] r>
-    spawn-linked ;
-! ---
-
-! Default irc port
-: irc-port 6667 ;
-
-! Message used when the client isn't running anymore
-SINGLETON: irc-end
-
-! "setup" objects
-TUPLE: irc-profile server port nickname password default-channels  ;
-C: <irc-profile> irc-profile
-
-TUPLE: irc-channel-profile name password auto-rejoin ;
-C: <irc-channel-profile> irc-channel-profile
-
-! "live" objects
-TUPLE: nick name channels log ;
-C: <nick> nick
-
-TUPLE: irc-client profile nick stream stream-channel controller-channel
-       listeners is-running ;
-: <irc-client> ( profile -- irc-client )
-    f V{ } clone V{ } clone <nick>
-    f <channel> <channel> V{ } clone f irc-client boa ;
-
-USE: prettyprint
-TUPLE: irc-listener channel ;
-! FIXME: spawn-server-linked con manejo de excepciones, mandar un mensaje final (ya se maneja esto al recibir mensajes del channel? )
-! tener la opción de dejar de correr un client??
-: <irc-listener> ( quot -- irc-listener )
-    <channel> irc-listener boa swap
-    [
-        [ channel>> '[ , from ] ]
-        [ '[ , curry f spawn drop ] ]
-        bi* compose "irc-listener" spawn-server-linked drop
-    ] [ drop ] 2bi ;
-
-! TUPLE: irc-channel name topic members log attributes ;
-! C: <irc-channel> irc-channel
-
-! the delegate of all irc messages
-TUPLE: irc-message line prefix command parameters trailing timestamp ;
-C: <irc-message> irc-message
-
-! "irc message" objects
-TUPLE: logged-in < irc-message name ;
-C: <logged-in> logged-in
-
-TUPLE: ping < irc-message ;
-C: <ping> ping
-
-TUPLE: join_ < irc-message ;
-C: <join> join_
-
-TUPLE: part < irc-message name channel ;
-C: <part> part
-
-TUPLE: quit ;
-C: <quit> quit
-
-TUPLE: privmsg < irc-message name ;
-C: <privmsg> privmsg
-
-TUPLE: kick < irc-message channel who ;
-C: <kick> kick
-
-TUPLE: roomlist < irc-message channel names ;
-C: <roomlist> roomlist
-
-TUPLE: nick-in-use < irc-message name ;
-C: <nick-in-use> nick-in-use
-
-TUPLE: notice < irc-message type ;
-C: <notice> notice
-
-TUPLE: mode < irc-message name channel mode ;
-C: <mode> mode
-
-TUPLE: unhandled < irc-message ;
-C: <unhandled> unhandled
-
-SYMBOL: irc-client
-: irc-client> ( -- irc-client ) irc-client get ;
-: irc-stream> ( -- stream ) irc-client> stream>> ;
-
-: remove-heading-: ( seq -- seq ) dup ":" head? [ rest ] when ;
-
-: parse-name ( string -- string )
-    remove-heading-: "!" split-at-first drop ;
-
-: sender>> ( obj -- string )
-    prefix>> parse-name ;
-
-: split-prefix ( string -- string/f string )
-    dup ":" head?
-        [ remove-heading-: " " split1 ]
-        [ f swap ]
-    if ;
-
-: split-trailing ( string -- string string/f )
-    ":" split1 ;
-
-: string>irc-message ( string -- object )
-    dup split-prefix split-trailing
-    [ [ blank? ] trim " " split unclip swap ] dip
-    now <irc-message> ;
-
-: me? ( name -- ? )
-    irc-client> nick>> name>> = ;
-
-: irc-write ( s -- )
-    irc-stream> stream-write ;
-
-: irc-print ( s -- )
-    irc-stream> [ stream-print ] keep stream-flush ;
-
-! Irc commands    
-
-: NICK ( nick -- )
-    "NICK " irc-write irc-print ;
-
-: LOGIN ( nick -- )
-    dup NICK
-    "USER " irc-write irc-write
-    " hostname servername :irc.factor" irc-print ;
-
-: CONNECT ( server port -- stream )
-    <inet> latin1 <client> drop ;
-
-: JOIN ( channel password -- )
-    "JOIN " irc-write
-    [ " :" swap 3append ] when* irc-print ;
-
-: PART ( channel text -- )
-    [ "PART " irc-write irc-write ] dip
-    " :" irc-write irc-print ;
-
-: KICK ( channel who -- )
-    [ "KICK " irc-write irc-write ] dip
-    " " irc-write irc-print ;
-    
-: PRIVMSG ( nick line -- )
-    [ "PRIVMSG " irc-write irc-write ] dip
-    " :" irc-write irc-print ;
-
-: SAY ( nick line -- )
-    PRIVMSG ;
-
-: ACTION ( nick line -- )
-    [ 1 , "ACTION " % % 1 , ] "" make PRIVMSG ;
-
-: QUIT ( text -- )
-    "QUIT :" irc-write irc-print ;
-
-: join-channel ( channel-profile -- )
-    [ name>> ] keep password>> JOIN ;
-
-: irc-connect ( irc-client -- )
-    [ profile>> [ server>> ] keep port>> CONNECT ] keep
-    swap >>stream t >>is-running drop ;
-    
-GENERIC: handle-irc ( obj -- )
-
-M: object handle-irc ( obj -- )
-    drop ;
-
-M: logged-in handle-irc ( obj -- )
-    name>>
-    irc-client> [ nick>> swap >>name drop ] keep 
-    profile>> default-channels>> [ join-channel ] each ;
-
-M: ping handle-irc ( obj -- )
-    "PONG " irc-write
-    trailing>> irc-print ;
-
-M: nick-in-use handle-irc ( obj -- )
-    name>> "_" append NICK ;
-
-: parse-irc-line ( string -- message )
-    string>irc-message
-    dup command>> {
-        { "PING" [ \ ping ] }
-        { "NOTICE" [ \ notice ] }
-        { "001" [ \ logged-in ] }
-        { "433" [ \ nick-in-use ] }
-        { "JOIN" [ \ join_ ] }
-        { "PART" [ \ part ] }
-        { "PRIVMSG" [ \ privmsg ] }
-        { "QUIT" [ \ quit ] }
-        { "MODE" [ \ mode ] }
-        { "KICK" [ \ kick ] }
-        [ drop \ unhandled ]
-    } case
-    [ [ tuple-slots ] [ parameters>> ] bi append ] dip prefix >tuple ;
-
-! Reader
-: handle-reader-message ( irc-client irc-message -- )
-    dup handle-irc swap stream-channel>> to ;
-
-: reader-loop ( irc-client -- )
-    dup stream>> stream-readln [
-        dup print parse-irc-line handle-reader-message
-    ] [
-        f >>is-running
-        dup stream>> dispose
-        irc-end over controller-channel>> to
-        stream-channel>> irc-end swap to
-    ] if* ;
-
-! Controller commands
-GENERIC: handle-command ( obj -- )
-
-M: object handle-command ( obj -- )
-    . ;
-
-TUPLE: send-message to text ;
-C: <send-message> send-message
-M: send-message handle-command ( obj -- )
-    dup to>> swap text>> SAY ;
-
-TUPLE: send-action to text ;
-C: <send-action> send-action
-M: send-action handle-command ( obj -- )
-    dup to>> swap text>> ACTION ;
-
-TUPLE: send-quit text ;
-C: <send-quit> send-quit
-M: send-quit handle-command ( obj -- )
-    text>> QUIT ;
-
-: irc-listen ( irc-client quot -- )
-    [ listeners>> ] [ <irc-listener> ] bi* swap push ;
-
-! Controller loop
-: controller-loop ( irc-client -- )
-    controller-channel>> from handle-command ;
-
-! Multiplexer
-: multiplex-message ( irc-client message -- )
-    swap listeners>> [ channel>> ] map
-    [ '[ , , to ] "message" spawn drop ] each-with ;
-
-: multiplexer-loop ( irc-client -- )
-    dup stream-channel>> from multiplex-message ;
-
-! process looping and starting
-: (spawn-irc-loop) ( irc-client quot name -- )
-    [ over >r curry r> '[ @ , is-running>> ] ] dip
-    spawn-server-linked drop ;
-
-: spawn-irc-loop ( irc-client quot name -- )
-    '[ , , , [ (spawn-irc-loop) receive ] [ print ] recover ]
-    f spawn drop ;
-
-: spawn-irc ( irc-client -- )
-    [ [ reader-loop ] "reader-loop" spawn-irc-loop ]
-    [ [ controller-loop ] "controller-loop" spawn-irc-loop ]
-    [ [ multiplexer-loop ] "multiplexer-loop" spawn-irc-loop ]
-    tri ;
-    
-: do-irc ( irc-client -- )
-    irc-client [
-        irc-client>
-        [ irc-connect ]
-        [ profile>> nickname>> LOGIN ]
-        [ spawn-irc ]
-        tri
-    ] with-variable ;
\ No newline at end of file

From f47ee3ef181b6fe56cc41050e17f02e50b1bdd9d Mon Sep 17 00:00:00 2001
From: Doug Coleman <doug.coleman@gmail.com>
Date: Wed, 28 May 2008 16:18:05 -0500
Subject: [PATCH 44/66] fix lists, tables

---
 extra/farkup/farkup-tests.factor | 11 +++++++++++
 extra/farkup/farkup.factor       |  8 ++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/extra/farkup/farkup-tests.factor b/extra/farkup/farkup-tests.factor
index 9a3862d097..91cc5ec360 100755
--- a/extra/farkup/farkup-tests.factor
+++ b/extra/farkup/farkup-tests.factor
@@ -71,3 +71,14 @@ IN: farkup.tests
 [ "<p><a href=\"lol.com\">haha</a></p>" ] [ "[[lol.com|haha]]" convert-farkup ] unit-test
 
 [ ] [ "[{}]" convert-farkup drop ] unit-test
+
+[
+    "<p>Feature comparison:\n<table><tr><td>a</td><td>Factor</td><td>Java</td><td>Lisp</td></tr><tr><td>Coolness</td><td>Yes</td><td>No</td><td>No</td></tr><tr><td>Badass</td><td>Yes</td><td>No</td><td>No</td></tr><tr><td>Enterprise</td><td>Yes</td><td>Yes</td><td>No</td></tr><tr><td>Kosher</td><td>Yes</td><td>No</td><td>Yes</td></tr></table></p>"
+] [ "Feature comparison:\n|a|Factor|Java|Lisp|\n|Coolness|Yes|No|No|\n|Badass|Yes|No|No|\n|Enterprise|Yes|Yes|No|\n|Kosher|Yes|No|Yes|\n" convert-farkup ] unit-test
+
+[
+    "<p>Feature comparison:\n\n<table><tr><td>a</td><td>Factor</td><td>Java</td><td>Lisp</td></tr><tr><td>Coolness</td><td>Yes</td><td>No</td><td>No</td></tr><tr><td>Badass</td><td>Yes</td><td>No</td><td>No</td></tr><tr><td>Enterprise</td><td>Yes</td><td>Yes</td><td>No</td></tr><tr><td>Kosher</td><td>Yes</td><td>No</td><td>Yes</td></tr></table></p>"
+] [ "Feature comparison:\n\n|a|Factor|Java|Lisp|\n|Coolness|Yes|No|No|\n|Badass|Yes|No|No|\n|Enterprise|Yes|Yes|No|\n|Kosher|Yes|No|Yes|\n" convert-farkup ] unit-test
+
+[ "<p>a-b</p>" ] [ "a-b" convert-farkup ] unit-test
+[ "<ul><li>a-b</li></ul>" ] [ "-a-b" convert-farkup ] unit-test
diff --git a/extra/farkup/farkup.factor b/extra/farkup/farkup.factor
index 98f0d0245f..47fe36b8ec 100755
--- a/extra/farkup/farkup.factor
+++ b/extra/farkup/farkup.factor
@@ -113,12 +113,14 @@ MEMO: labelled-link ( -- parser )
         "]]" token hide ,
     ] seq* [ first2 make-link ] action ;
 
-MEMO: link ( -- parser ) [ image-link , simple-link , labelled-link , ] choice* ;
+MEMO: link ( -- parser )
+    [ image-link , simple-link , labelled-link , ] choice* ;
 
 DEFER: line
 MEMO: list-item ( -- parser )
     [
-        "-" token hide , line ,
+        "-" token hide , ! text ,
+        [ "\r\n" member? not ] satisfy repeat1 [ >string escape-string ] action ,
     ] seq* [ "li" surround-with-foo ] action ;
 
 MEMO: list ( -- parser )
@@ -149,6 +151,8 @@ MEMO: code ( -- parser )
 
 MEMO: line ( -- parser )
     [
+        nl table 2seq ,
+        nl list 2seq ,
         text , strong , emphasis , link ,
         superscript , subscript , inline-code ,
         escaped-char , delimiter , eq ,

From 8f06c94ee83d204d316c56eb37f44767dce8cdb0 Mon Sep 17 00:00:00 2001
From: Doug Coleman <doug.coleman@gmail.com>
Date: Wed, 28 May 2008 17:02:58 -0500
Subject: [PATCH 45/66] add inheritance support for db.tuples

---
 extra/db/tuples/tuples-tests.factor | 19 +++++++++++++++++++
 extra/db/tuples/tuples.factor       |  2 +-
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/extra/db/tuples/tuples-tests.factor b/extra/db/tuples/tuples-tests.factor
index 4da82d92d6..b7c6fce933 100755
--- a/extra/db/tuples/tuples-tests.factor
+++ b/extra/db/tuples/tuples-tests.factor
@@ -414,6 +414,25 @@ TUPLE: does-not-persist ;
     [ class \ not-persistent = ] must-fail-with
 ] test-postgresql
 
+
+TUPLE: suparclass a ;
+
+suparclass f {
+    { "id" "ID" +db-assigned-id+ }
+    { "a" "A" INTEGER }
+} define-persistent
+
+TUPLE: subbclass < suparclass b ;
+
+subbclass "SUBCLASS" {
+    { "b" "B" TEXT }
+} define-persistent
+
+: test-db-inheritance ( -- )
+    [ ] [ subbclass ensure-table ] unit-test ;
+
+[ test-db-inheritance ] test-sqlite
+
 ! Don't comment these out. These words must infer
 \ bind-tuple must-infer
 \ insert-tuple must-infer
diff --git a/extra/db/tuples/tuples.factor b/extra/db/tuples/tuples.factor
index c940d121bb..0ffbd5bd47 100755
--- a/extra/db/tuples/tuples.factor
+++ b/extra/db/tuples/tuples.factor
@@ -19,7 +19,7 @@ ERROR: not-persistent ;
     "db-table" word-prop [ not-persistent ] unless* ;
 
 : db-columns ( class -- obj )
-    "db-columns" word-prop ;
+    superclasses [ "db-columns" word-prop ] map concat ;
 
 : db-relations ( class -- obj )
     "db-relations" word-prop ;

From b7a6e117ec2997501b756e455509cb5915b869bb Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Wed, 28 May 2008 17:04:59 -0500
Subject: [PATCH 46/66] Add no-follow option

---
 extra/farkup/farkup.factor | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/extra/farkup/farkup.factor b/extra/farkup/farkup.factor
index 5dcfa7528e..d58b54af37 100755
--- a/extra/farkup/farkup.factor
+++ b/extra/farkup/farkup.factor
@@ -7,6 +7,7 @@ sequences.deep unicode.categories ;
 IN: farkup
 
 SYMBOL: relative-link-prefix
+SYMBOL: link-no-follow?
 
 <PRIVATE
 
@@ -79,7 +80,12 @@ MEMO: eq ( -- parser )
 
 : make-link ( href text -- seq )
     escape-link
-    [ "<a href=\"" , >r , r> "\">" , [ , ] when* "</a>" , ] { } make ;
+    [
+        "<a" ,
+        " href=\"" , >r , r>
+        link-no-follow? get [ " nofollow=\"true\"" , ] when
+        "\">" , , "</a>" ,
+    ] { } make ;
 
 : make-image-link ( href alt -- seq )
     escape-link

From 0f040470d7e62c95d36475baa0001d734840dbf9 Mon Sep 17 00:00:00 2001
From: Doug Coleman <doug.coleman@gmail.com>
Date: Wed, 28 May 2008 17:51:02 -0500
Subject: [PATCH 47/66] add histogram word to assocs.lib

---
 extra/assocs/lib/lib.factor | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/extra/assocs/lib/lib.factor b/extra/assocs/lib/lib.factor
index 7c274edb2e..c3e487a9fc 100755
--- a/extra/assocs/lib/lib.factor
+++ b/extra/assocs/lib/lib.factor
@@ -1,5 +1,5 @@
 USING: arrays assocs kernel vectors sequences namespaces
-random math.parser ;
+random math.parser math fry ;
 IN: assocs.lib
 
 : ref-at ( table key -- value ) swap at ;
@@ -40,3 +40,8 @@ IN: assocs.lib
 
 : set-at-unique ( value assoc -- key )
     dup generate-key [ swap set-at ] keep ;
+
+: histogram ( assoc quot -- assoc' )
+    H{ } clone [
+        swap [ change-at ] 2curry assoc-each
+    ] keep ;

From bc1e021afd5242a39414e0aea1478065c691ff67 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Wed, 28 May 2008 18:17:58 -0500
Subject: [PATCH 48/66] Bug fixes

---
 extra/http/server/callbacks/callbacks.factor     |  2 +-
 extra/locals/locals-tests.factor                 |  2 +-
 extra/locals/locals.factor                       | 12 ++++++++++++
 extra/macros/macros-tests.factor                 | 12 +++++++++++-
 extra/macros/macros.factor                       |  3 +++
 extra/memoize/memoize-tests.factor               | 13 ++++++++++++-
 extra/memoize/memoize.factor                     |  6 ++++++
 extra/multi-methods/multi-methods.factor         |  2 +-
 extra/tangle/html/html.factor                    |  2 +-
 extra/xmode/code2html/responder/responder.factor | 10 +++++-----
 10 files changed, 53 insertions(+), 11 deletions(-)

diff --git a/extra/http/server/callbacks/callbacks.factor b/extra/http/server/callbacks/callbacks.factor
index 40ba540ac6..3b819e067b 100755
--- a/extra/http/server/callbacks/callbacks.factor
+++ b/extra/http/server/callbacks/callbacks.factor
@@ -1,7 +1,7 @@
 ! Copyright (C) 2004 Chris Double.
 ! Copyright (C) 2006, 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: html http http.server io kernel math namespaces
+USING: http http.server io kernel math namespaces
 continuations calendar sequences assocs hashtables
 accessors arrays alarms quotations combinators fry assocs.lib ;
 IN: http.server.callbacks
diff --git a/extra/locals/locals-tests.factor b/extra/locals/locals-tests.factor
index 87bc49f366..4e670cdac0 100755
--- a/extra/locals/locals-tests.factor
+++ b/extra/locals/locals-tests.factor
@@ -257,7 +257,7 @@ M:: fixnum next-method-test ( a -- b ) a call-next-method 1 + ;
 
 :: a-word-with-locals ( a b -- ) ;
 
-: new-definition "IN: locals.tests\nUSING: math ;\n: a-word-with-locals ( -- x ) 2 3 + ;\n" ;
+: new-definition "USING: math ;\nIN: locals.tests\n: a-word-with-locals ( -- x ) 2 3 + ;\n" ;
 
 [ ] [ new-definition eval ] unit-test
 
diff --git a/extra/locals/locals.factor b/extra/locals/locals.factor
index af4f1a77b6..e74d0b6078 100755
--- a/extra/locals/locals.factor
+++ b/extra/locals/locals.factor
@@ -364,6 +364,9 @@ M: lambda-word definer drop \ :: \ ; ;
 M: lambda-word definition
     "lambda" word-prop body>> ;
 
+M: lambda-word reset-word
+    [ f "lambda" set-word-prop ] [ call-next-method ] bi ;
+
 INTERSECTION: lambda-macro macro lambda-word ;
 
 M: lambda-macro definer drop \ MACRO:: \ ; ;
@@ -371,6 +374,9 @@ M: lambda-macro definer drop \ MACRO:: \ ; ;
 M: lambda-macro definition
     "lambda" word-prop body>> ;
 
+M: lambda-macro reset-word
+    [ f "lambda" set-word-prop ] [ call-next-method ] bi ;
+
 INTERSECTION: lambda-method method-body lambda-word ;
 
 M: lambda-method definer drop \ M:: \ ; ;
@@ -378,6 +384,9 @@ M: lambda-method definer drop \ M:: \ ; ;
 M: lambda-method definition
     "lambda" word-prop body>> ;
 
+M: lambda-method reset-word
+    [ f "lambda" set-word-prop ] [ call-next-method ] bi ;
+
 INTERSECTION: lambda-memoized memoized lambda-word ;
 
 M: lambda-memoized definer drop \ MEMO:: \ ; ;
@@ -385,6 +394,9 @@ M: lambda-memoized definer drop \ MEMO:: \ ; ;
 M: lambda-memoized definition
     "lambda" word-prop body>> ;
 
+M: lambda-memoized reset-word
+    [ f "lambda" set-word-prop ] [ call-next-method ] bi ;
+
 : method-stack-effect ( method -- effect )
     dup "lambda" word-prop vars>>
     swap "method-generic" word-prop stack-effect
diff --git a/extra/macros/macros-tests.factor b/extra/macros/macros-tests.factor
index 59a53afb70..d5011b0ecb 100644
--- a/extra/macros/macros-tests.factor
+++ b/extra/macros/macros-tests.factor
@@ -1,4 +1,14 @@
 IN: macros.tests
 USING: tools.test macros math kernel arrays
-vectors ;
+vectors io.streams.string prettyprint parser ;
 
+MACRO: see-test ( a b -- c ) + ;
+
+[ "USING: macros math ;\nIN: macros.tests\nMACRO: see-test ( a b -- c ) + ;\n" ]
+[ [ \ see-test see ] with-string-writer ]
+unit-test
+
+[ t ] [
+    "USING: math ;\nIN: macros.tests\n: see-test ( a b -- c ) - ;\n" dup eval
+    [ \ see-test see ] with-string-writer =
+] unit-test
diff --git a/extra/macros/macros.factor b/extra/macros/macros.factor
index b242f91d3b..88bfd01fbe 100755
--- a/extra/macros/macros.factor
+++ b/extra/macros/macros.factor
@@ -23,6 +23,9 @@ M: macro definer drop \ MACRO: \ ; ;
 
 M: macro definition "macro" word-prop ;
 
+M: macro reset-word
+    [ f "macro" set-word-prop ] [ call-next-method ] bi ;
+
 : macro-expand ( ... word -- quot ) "macro" word-prop call ;
 
 : n*quot ( n seq -- seq' ) <repetition> concat >quotation ;
diff --git a/extra/memoize/memoize-tests.factor b/extra/memoize/memoize-tests.factor
index 43428efbe0..c2592b38ca 100644
--- a/extra/memoize/memoize-tests.factor
+++ b/extra/memoize/memoize-tests.factor
@@ -1,6 +1,7 @@
 ! Copyright (C) 2007 Slava Pestov, Daniel Ehrenberg.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: math kernel memoize tools.test parser ;
+USING: math kernel memoize tools.test parser
+prettyprint io.streams.string sequences ;
 IN: memoize.tests
 
 MEMO: fib ( m -- n )
@@ -9,3 +10,13 @@ MEMO: fib ( m -- n )
 [ 89 ] [ 10 fib ] unit-test
 
 [ "USING: kernel math memoize ; IN: memoize.tests MEMO: x ( a b c d e -- f g h i j ) >r >r >r >r 1+ r> r> r> r> ;" eval ] must-fail
+
+MEMO: see-test ( a -- b ) reverse ;
+
+[ "USING: memoize sequences ;\nIN: memoize.tests\nMEMO: see-test ( a -- b ) reverse ;\n" ]
+[ [ \ see-test see ] with-string-writer ]
+unit-test
+
+[ ] [ "IN: memoize.tests : fib ;" eval ] unit-test
+
+[ "IN: memoize.tests\n: fib ( -- ) ;\n" ] [ [ \ fib see ] with-string-writer ] unit-test
diff --git a/extra/memoize/memoize.factor b/extra/memoize/memoize.factor
index 4136f9eaff..7da2ee0f0d 100755
--- a/extra/memoize/memoize.factor
+++ b/extra/memoize/memoize.factor
@@ -44,8 +44,14 @@ IN: memoize
 PREDICATE: memoized < word "memoize" word-prop ;
 
 M: memoized definer drop \ MEMO: \ ; ;
+
 M: memoized definition "memo-quot" word-prop ;
 
+M: memoized reset-word
+    [ { "memoize" "memo-quot" } reset-props ]
+    [ call-next-method ]
+    bi ;
+
 : memoize-quot ( quot effect -- memo-quot )
     gensym swap dupd "declared-effect" set-word-prop
     dup rot define-memoized 1quotation ;
diff --git a/extra/multi-methods/multi-methods.factor b/extra/multi-methods/multi-methods.factor
index b1073c116d..46ad6fc58e 100755
--- a/extra/multi-methods/multi-methods.factor
+++ b/extra/multi-methods/multi-methods.factor
@@ -4,7 +4,7 @@ USING: kernel math sequences vectors classes classes.algebra
 combinators arrays words assocs parser namespaces definitions
 prettyprint prettyprint.backend quotations arrays.lib
 debugger io compiler.units kernel.private effects accessors
-hashtables sorting shuffle math.order ;
+hashtables sorting shuffle math.order sets ;
 IN: multi-methods
 
 ! PART I: Converting hook specializers
diff --git a/extra/tangle/html/html.factor b/extra/tangle/html/html.factor
index fc604f4d46..2ec6b52609 100644
--- a/extra/tangle/html/html.factor
+++ b/extra/tangle/html/html.factor
@@ -1,6 +1,6 @@
 ! Copyright (C) 2008 Alex Chapman
 ! See http://factorcode.org/license.txt for BSD license.
-USING: accessors html html.elements io io.streams.string kernel namespaces semantic-db sequences strings tangle.path ;
+USING: accessors html.elements io io.streams.string kernel namespaces semantic-db sequences strings tangle.path ;
 IN: tangle.html
 
 TUPLE: element attributes ;
diff --git a/extra/xmode/code2html/responder/responder.factor b/extra/xmode/code2html/responder/responder.factor
index e059aeb7ff..2f56a5b819 100755
--- a/extra/xmode/code2html/responder/responder.factor
+++ b/extra/xmode/code2html/responder/responder.factor
@@ -1,16 +1,16 @@
 ! Copyright (C) 2007, 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: io.files io.encodings.utf8 namespaces http.server
-http.server.static http xmode.code2html kernel html sequences
+USING: io io.files io.encodings.utf8 namespaces http.server
+http.server.static http xmode.code2html kernel sequences
 accessors fry ;
 IN: xmode.code2html.responder
 
 : <sources> ( root -- responder )
     [
         drop
-         '[
-            , [ file-name ] keep utf8 [
-                [ htmlize-stream ] with-html-stream
+        dup '[
+            , utf8 [
+                , file-name input-stream get htmlize-stream
             ] with-file-reader
         ] <html-content>
     ] <file-responder> ;

From ec71ee094078ecf0dafdca0b6874dda9cb142568 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@173.160.255.10.in-addr.arpa>
Date: Wed, 28 May 2008 19:34:18 -0500
Subject: [PATCH 49/66] Bug fixes

---
 core/classes/classes-tests.factor     |  4 ++--
 core/classes/tuple/tuple-tests.factor | 11 ++++++++++-
 core/classes/tuple/tuple.factor       |  6 ------
 core/compiler/units/units.factor      | 16 +++++++++++++---
 core/generic/generic.factor           | 16 ++++++++++------
 core/parser/parser-tests.factor       |  4 ++--
 core/parser/parser.factor             |  7 +++----
 core/sequences/sequences-tests.factor |  6 ------
 core/sets/sets-tests.factor           |  6 ++++++
 core/syntax/syntax.factor             |  7 ++++---
 core/words/words.factor               |  4 +++-
 11 files changed, 53 insertions(+), 34 deletions(-)

diff --git a/core/classes/classes-tests.factor b/core/classes/classes-tests.factor
index 8d20da78b5..eb55b5fccd 100755
--- a/core/classes/classes-tests.factor
+++ b/core/classes/classes-tests.factor
@@ -166,6 +166,6 @@ GENERIC: method-forget-test
 TUPLE: method-forget-class ;
 M: method-forget-class method-forget-test ;
 
-[ f ] [ \ method-forget-test "methods" assoc-empty? ] unit-test
+[ f ] [ \ method-forget-test "methods" word-prop assoc-empty? ] unit-test
 [ ] [ [ \ method-forget-class forget ] with-compilation-unit ] unit-test
-[ t ] [ \ method-forget-test "methods" assoc-empty? ] unit-test
+[ t ] [ \ method-forget-test "methods" word-prop assoc-empty? ] unit-test
diff --git a/core/classes/tuple/tuple-tests.factor b/core/classes/tuple/tuple-tests.factor
index 0cf7ea3510..ab6c139f7b 100755
--- a/core/classes/tuple/tuple-tests.factor
+++ b/core/classes/tuple/tuple-tests.factor
@@ -4,7 +4,7 @@ namespaces quotations sequences.private classes continuations
 generic.standard effects classes.tuple classes.tuple.private
 arrays vectors strings compiler.units accessors classes.algebra
 calendar prettyprint io.streams.string splitting inspector
-columns math.order ;
+columns math.order classes.private ;
 IN: classes.tuple.tests
 
 TUPLE: rect x y w h ;
@@ -543,6 +543,7 @@ TUPLE: another-forget-accessors-test ;
 ! Missing error check
 [ "IN: classes.tuple.tests USE: words TUPLE: wrong-superclass < word ;" eval ] must-fail
 
+! Class forget messyness
 TUPLE: subclass-forget-test ;
 
 TUPLE: subclass-forget-test-1 < subclass-forget-test ;
@@ -551,6 +552,14 @@ TUPLE: subclass-forget-test-3 < subclass-forget-test-2 ;
 
 [ ] [ "IN: classes.tuple.tests FORGET: subclass-forget-test" eval ] unit-test
 
+[ H{ { subclass-forget-test-2 subclass-forget-test-2 } } ]
+[ subclass-forget-test-2 class-usages ]
+unit-test
+
+[ H{ { subclass-forget-test-3 subclass-forget-test-3 } } ]
+[ subclass-forget-test-3 class-usages ]
+unit-test
+
 [ f ] [ subclass-forget-test-1 tuple-class? ] unit-test
 [ f ] [ subclass-forget-test-2 tuple-class? ] unit-test
 [ subclass-forget-test-3 new ] must-fail
diff --git a/core/classes/tuple/tuple.factor b/core/classes/tuple/tuple.factor
index f4054c8468..4e6ce0d2bb 100755
--- a/core/classes/tuple/tuple.factor
+++ b/core/classes/tuple/tuple.factor
@@ -226,12 +226,6 @@ M: tuple-class reset-class
         } reset-props
     ] bi ;
 
-: reset-tuple-class ( class -- )
-    [ [ reset-class ] [ update-map- ] bi ] each-subclass ;
-
-M: tuple-class forget*
-    [ reset-tuple-class ] [ call-next-method ] bi ;
-
 M: tuple-class rank-class drop 0 ;
 
 M: tuple clone
diff --git a/core/compiler/units/units.factor b/core/compiler/units/units.factor
index 729cfcd179..c2e84429cf 100755
--- a/core/compiler/units/units.factor
+++ b/core/compiler/units/units.factor
@@ -83,7 +83,14 @@ SYMBOL: update-tuples-hook
     call-recompile-hook
     call-update-tuples-hook
     dup [ drop compiled-crossref? ] assoc-contains? modify-code-heap
-    updated-definitions notify-definition-observers ;
+     ;
+
+: with-nested-compilation-unit ( quot -- )
+    [
+        H{ } clone changed-definitions set
+        H{ } clone outdated-tuples set
+        [ finish-compilation-unit ] [ ] cleanup
+    ] with-scope ; inline
 
 : with-compilation-unit ( quot -- )
     [
@@ -92,8 +99,11 @@ SYMBOL: update-tuples-hook
         H{ } clone outdated-tuples set
         <definitions> new-definitions set
         <definitions> old-definitions set
-        [ finish-compilation-unit ]
-        [ ] cleanup
+        [
+            finish-compilation-unit
+            updated-definitions
+            notify-definition-observers
+        ] [ ] cleanup
     ] with-scope ; inline
 
 : compile-call ( quot -- )
diff --git a/core/generic/generic.factor b/core/generic/generic.factor
index e446689303..b9a556e316 100755
--- a/core/generic/generic.factor
+++ b/core/generic/generic.factor
@@ -147,12 +147,16 @@ M: method-body forget*
     [ implementors ] [ [ swap 2array ] curry ] bi map forget-all ;
 
 M: class forget* ( class -- )
-    {
-        [ forget-methods ]
-        [ update-map- ]
-        [ reset-class ]
-        [ call-next-method ]
-    } cleave ;
+    [
+        class-usages [
+            drop
+            [ forget-methods ]
+            [ update-map- ]
+            [ reset-class ]
+            tri
+        ] assoc-each
+    ]
+    [ call-next-method ] bi ;
 
 M: assoc update-methods ( assoc -- )
     implementors* [ make-generic ] each ;
diff --git a/core/parser/parser-tests.factor b/core/parser/parser-tests.factor
index e8199d3520..37eb5f148e 100755
--- a/core/parser/parser-tests.factor
+++ b/core/parser/parser-tests.factor
@@ -461,10 +461,10 @@ must-fail-with
     "methods" word-prop assoc-size
 ] unit-test
 
-[ [ ] ] [
+[ ] [
     2 [
         "IN: parser.tests DEFER: twice-fails FORGET: twice-fails MIXIN: twice-fails"
-        <string-reader> "twice-fails-test" parse-stream
+        <string-reader> "twice-fails-test" parse-stream drop
     ] times
 ] unit-test
 
diff --git a/core/parser/parser.factor b/core/parser/parser.factor
index f08ba8fbc2..3f46d1dd30 100755
--- a/core/parser/parser.factor
+++ b/core/parser/parser.factor
@@ -357,10 +357,9 @@ M: staging-violation summary
     "A parsing word cannot be used in the same file it is defined in." ;
 
 : execute-parsing ( word -- )
-    new-definitions get [
-        dupd first key? [ staging-violation ] when
-    ] when*
-    execute ;
+    [ changed-definitions get key? [ staging-violation ] when ]
+    [ execute ]
+    bi ;
 
 : parse-step ( accum end -- accum ? )
     scan-word {
diff --git a/core/sequences/sequences-tests.factor b/core/sequences/sequences-tests.factor
index 0511721c18..81384a40c4 100755
--- a/core/sequences/sequences-tests.factor
+++ b/core/sequences/sequences-tests.factor
@@ -215,12 +215,6 @@ unit-test
     3 V{ 1 2 3 4 5 6 } clone [ delete-nth ] keep
 ] unit-test
 
-[ V{ 1 2 3 } ]
-[ 3 V{ 1 2 } clone [ adjoin ] keep ] unit-test
-
-[ V{ 1 2 3 } ]
-[ 3 V{ 1 3 2 } clone [ adjoin ] keep ] unit-test
-
 ! erg's random tester found this one
 [ SBUF" 12341234" ] [
     9 <sbuf> dup "1234" swap push-all dup dup swap push-all
diff --git a/core/sets/sets-tests.factor b/core/sets/sets-tests.factor
index 86ee100da5..b6e6443afa 100644
--- a/core/sets/sets-tests.factor
+++ b/core/sets/sets-tests.factor
@@ -15,3 +15,9 @@ IN: sets.tests
 
 [ V{ } ] [ { } { } union ] unit-test
 [ V{ 1 2 3 4 } ] [ { 1 2 3 } { 2 3 4 } union ] unit-test
+
+[ V{ 1 2 3 } ]
+[ 3 V{ 1 2 } clone [ adjoin ] keep ] unit-test
+
+[ V{ 1 2 3 } ]
+[ 3 V{ 1 3 2 } clone [ adjoin ] keep ] unit-test
diff --git a/core/syntax/syntax.factor b/core/syntax/syntax.factor
index 2410185b18..7ed79f77f1 100755
--- a/core/syntax/syntax.factor
+++ b/core/syntax/syntax.factor
@@ -101,7 +101,7 @@ IN: bootstrap.syntax
 
     "DEFER:" [
         scan in get create
-        dup old-definitions get first delete-at
+        dup old-definitions get [ delete-at ] with each
         set-word
     ] define-syntax
 
@@ -189,8 +189,9 @@ IN: bootstrap.syntax
     "MAIN:" [ scan-word in get vocab set-vocab-main ] define-syntax
 
     "<<" [
-        [ \ >> parse-until >quotation ] with-compilation-unit
-        call
+        [
+            \ >> parse-until >quotation
+        ] with-nested-compilation-unit call
     ] define-syntax
 
     "call-next-method" [
diff --git a/core/words/words.factor b/core/words/words.factor
index 5812516912..5549f98010 100755
--- a/core/words/words.factor
+++ b/core/words/words.factor
@@ -175,7 +175,9 @@ PRIVATE>
 : define-symbol ( word -- )
     dup [ ] curry define-inline ;
 
-: reset-word ( word -- )
+GENERIC: reset-word ( word -- )
+
+M: word reset-word
     {
         "unannotated-def"
         "parsing" "inline" "foldable" "flushable"

From 8bff6eba523455165baf6c5c0a696dc0646b319e Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@173.160.255.10.in-addr.arpa>
Date: Wed, 28 May 2008 19:43:01 -0500
Subject: [PATCH 50/66] Fix silly DEFER: error

---
 core/parser/parser-tests.factor | 2 ++
 core/parser/parser.factor       | 2 +-
 core/syntax/syntax.factor       | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/core/parser/parser-tests.factor b/core/parser/parser-tests.factor
index 37eb5f148e..df6c9dadc5 100755
--- a/core/parser/parser-tests.factor
+++ b/core/parser/parser-tests.factor
@@ -485,3 +485,5 @@ must-fail-with
 [ t ] [ "staging-problem-test-1" "parser.tests" lookup >boolean ] unit-test
 
 [ t ] [ "staging-problem-test-2" "parser.tests" lookup >boolean ] unit-test
+
+[ "DEFER: blah" eval ] [ error>> no-current-vocab? ] must-fail-with
diff --git a/core/parser/parser.factor b/core/parser/parser.factor
index 3f46d1dd30..46e93753b5 100755
--- a/core/parser/parser.factor
+++ b/core/parser/parser.factor
@@ -236,7 +236,7 @@ PREDICATE: unexpected-eof < unexpected
 ERROR: no-current-vocab ;
 
 M: no-current-vocab summary ( obj -- )
-    drop "Current vocabulary is f, use IN:" ;
+    drop "Not in a vocabulary; IN: form required" ;
 
 : current-vocab ( -- str )
     in get [ no-current-vocab ] unless* ;
diff --git a/core/syntax/syntax.factor b/core/syntax/syntax.factor
index 7ed79f77f1..27c8609a99 100755
--- a/core/syntax/syntax.factor
+++ b/core/syntax/syntax.factor
@@ -100,7 +100,7 @@ IN: bootstrap.syntax
     ] define-syntax
 
     "DEFER:" [
-        scan in get create
+        scan current-vocab create
         dup old-definitions get [ delete-at ] with each
         set-word
     ] define-syntax

From 73b0e07277b5b6f3f1d3d78dfea280bee8cd0a8a Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Wed, 28 May 2008 21:44:02 -0500
Subject: [PATCH 51/66] combinators.lib: Add || variants

---
 extra/combinators/lib/lib.factor | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/extra/combinators/lib/lib.factor b/extra/combinators/lib/lib.factor
index 4c4a988935..2c7f2bbb03 100755
--- a/extra/combinators/lib/lib.factor
+++ b/extra/combinators/lib/lib.factor
@@ -77,8 +77,21 @@ MACRO: <--&& ( quots -- )
     [ [ 2dup ] prepend [ not ] append [ f ] ] t short-circuit
     [ 2nip ] append ;
 
+! or
+
 MACRO: || ( quots -- ? ) [ [ t ] ] f short-circuit ;
 
+MACRO: 0|| ( quots -- ? ) [ [ t ] ] f short-circuit ;
+
+MACRO: 1|| ( quots -- ? )
+  [ [ dup ] prepend [ t ] ] f short-circuit [ nip ] append ;
+
+MACRO: 2|| ( quots -- ? )
+  [ [ 2dup ] prepend [ t ] ] f short-circuit [ 2nip ] append ;
+
+MACRO: 3|| ( quots -- ? )
+  [ [ 2dup ] prepend [ t ] ] f short-circuit [ 2nip ] append ;
+
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 ! ifte
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

From ce4f8871bf5464495d400440d585bc85d713fd82 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Wed, 28 May 2008 23:08:54 -0500
Subject: [PATCH 52/66] dns: Add support for AAAA records

---
 extra/dns/dns.factor | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/extra/dns/dns.factor b/extra/dns/dns.factor
index 560db69bb2..f10bdea0bf 100644
--- a/extra/dns/dns.factor
+++ b/extra/dns/dns.factor
@@ -38,7 +38,7 @@ TUPLE: message
 ! TYPE
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-SYMBOLS: A NS MD MF CNAME SOA MB MG MR NULL WKS PTR HINFO MINFO MX TXT ;
+SYMBOLS: A NS MD MF CNAME SOA MB MG MR NULL WKS PTR HINFO MINFO MX TXT AAAA ;
 
 : type-table ( -- table )
   {
@@ -58,6 +58,7 @@ SYMBOLS: A NS MD MF CNAME SOA MB MG MR NULL WKS PTR HINFO MINFO MX TXT ;
     { MINFO 14 }
     { MX    15 }
     { TXT   16 }
+    { AAAA  28 }
   } ;
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -126,6 +127,8 @@ SYMBOLS: NO-ERROR FORMAT-ERROR SERVER-FAILURE NAME-ERROR NOT-IMPLEMENTED
 
 : ip->ba ( ip -- ba ) "." split [ string>number ] map >byte-array ;
 
+: ipv6->ba ( ip -- ba ) ":" split [ 16 base> ] map [ 2 >be ] map concat ;
+
 : label->ba ( label -- ba ) [ >byte-array ] [ length ] bi prefix ;
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -330,6 +333,13 @@ SYMBOLS: NO-ERROR FORMAT-ERROR SERVER-FAILURE NAME-ERROR NOT-IMPLEMENTED
 
 : get-ip ( ba i -- ip ) dup 4 + subseq >array [ number>string ] map "." join ;
 
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: get-ipv6 ( ba i -- ip )
+  dup 16 + subseq 2 group [ be> 16 >base ] map ":" join ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
 : get-rdata ( ba i type -- rdata )
     {
       { CNAME [ get-name ] }
@@ -338,6 +348,7 @@ SYMBOLS: NO-ERROR FORMAT-ERROR SERVER-FAILURE NAME-ERROR NOT-IMPLEMENTED
       { MX    [ get-mx   ] }
       { SOA   [ get-soa  ] }
       { A     [ get-ip   ] }
+      { AAAA  [ get-ipv6 ] }
     }
   case ;
 

From e14a9ec0fb35bc16a51cba6de45de4dbb71377ad Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Wed, 28 May 2008 23:09:19 -0500
Subject: [PATCH 53/66] dns.cache: cache-get* word

---
 extra/dns/cache/cache.factor | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/extra/dns/cache/cache.factor b/extra/dns/cache/cache.factor
index 75bbf9de9d..aeba35f29d 100644
--- a/extra/dns/cache/cache.factor
+++ b/extra/dns/cache/cache.factor
@@ -68,7 +68,7 @@ SYMBOL: NX
 
 : expired? ( entry -- ? ) time>> time->ttl 0 <= ;
 
-: cache-get ( query -- result )
+: cache-get* ( query -- rrs/NX/f )
   dup table-get               ! query result
     {
       { [ dup f = ]      [ 2drop f ]          } ! not in the cache
@@ -80,6 +80,15 @@ SYMBOL: NX
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
+ERROR: name-error name ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: cache-get ( query -- rrs/f )
+  dup cache-get* dup NX = [ drop name>> name-error ] [ nip ] if ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
 : rr->entry ( rr -- entry )
   [ ttl>> ttl->time ] [ rdata>> {1} >vector ] bi entry boa ;
 

From 5a2ff64c3f0768829920aaae1eced721e54557d6 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Wed, 28 May 2008 23:12:01 -0500
Subject: [PATCH 54/66] Add dns.recursive for recursive queries

---
 extra/dns/recursive/recursive.factor | 182 +++++++++++++++++++++++++++
 1 file changed, 182 insertions(+)
 create mode 100644 extra/dns/recursive/recursive.factor

diff --git a/extra/dns/recursive/recursive.factor b/extra/dns/recursive/recursive.factor
new file mode 100644
index 0000000000..6fe8ec96da
--- /dev/null
+++ b/extra/dns/recursive/recursive.factor
@@ -0,0 +1,182 @@
+
+USING: kernel continuations
+       combinators
+       sequences
+       random
+       unicode.case
+       accessors symbols
+       combinators.lib combinators.cleave
+       newfx
+       dns dns.cache ;
+
+IN: dns.recursive
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: root-dns-servers ( -- servers )
+  {
+    "192.5.5.241"
+    "192.112.36.4"
+    "128.63.2.53"
+    "192.36.148.17"
+    "192.58.128.30"
+    "193.0.14.129"
+    "199.7.83.42"
+    "202.12.27.33"
+    "198.41.0.4"
+  } ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: cache-message ( message -- message )
+  dup dup rcode>> NAME-ERROR =
+    [
+      [ question-section>> 1st ]
+      [ authority-section>> [ type>> SOA = ] filter random ttl>> ]
+      bi
+      cache-nx
+    ]
+    [
+        {
+          [ answer-section>>     cache-add-rrs ]
+          [ authority-section>>  cache-add-rrs ]
+          [ additional-section>> cache-add-rrs ]
+        }
+      cleave
+    ]
+  if ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: query->message ( query -- message ) <query-message> ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: {name-type-class} ( obj -- seq )
+  [ name>> >lower ] [ type>> ] [ class>> ] tri {3} ;
+
+: rr=query? ( rr query -- ? ) [ {name-type-class} ] bi@ = ;
+
+: rr-filter ( rrs query -- rrs ) [ rr=query? ] curry filter ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: message-query ( message -- query ) question-section>> 1st ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: answer-hits ( message -- rrs )
+  [ answer-section>> ] [ message-query ] bi rr-filter ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: name-hits ( message -- rrs )
+  [ answer-section>> ] [ message-query clone A >>type ] bi rr-filter ;
+
+: cname-hits ( message -- rrs )
+  [ answer-section>> ] [ message-query clone CNAME >>type ] bi rr-filter ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: authority-hits ( message -- rrs )
+  authority-section>> [ type>> NS = ] filter ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+SYMBOLS: ANSWERED NO-NAME-SERVERS UNCLASSIFIED ;
+
+: classify-message ( message -- symbol )
+    {
+      { [ dup rcode>> NAME-ERROR     = ] [ drop NAME-ERROR      ] }
+      { [ dup rcode>> SERVER-FAILURE = ] [ drop SERVER-FAILURE  ] }
+      { [ dup answer-hits empty? not   ] [ drop ANSWERED        ] }
+      { [ dup cname-hits  empty? not   ] [ drop CNAME           ] }
+      { [ dup authority-hits empty?    ] [ drop NO-NAME-SERVERS ] }
+      { [ t                            ] [ drop UNCLASSIFIED    ] }
+    }
+  cond ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+DEFER: name->ip
+
+! : name->ip/f ( name -- ip/f ) [ name->ip ] [ drop f ] recover ;
+
+! : extract-ns-ips ( message -- ips )
+!   authority-hits [ rdata>> name->ip/f ] map [ ] filter ;
+
+: extract-ns-ips ( message -- ips )
+  authority-hits [ rdata>> name->ip ] map [ ] filter ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: recursive-query ( query servers -- message )
+  dup random                                 ! query servers server
+  pick query->message 0 >>rd                 ! query servers server message
+  over ask-server                            ! query servers server message
+  cache-message                              ! query servers server message
+  dup classify-message                       ! query servers server message sym
+    {
+      { NAME-ERROR      [ -roll 3drop ] }
+      { ANSWERED        [ -roll 3drop ] }
+      { CNAME           [ -roll 3drop ] }
+      { NO-NAME-SERVERS [ -roll 3drop ] }
+      {
+        SERVER-FAILURE
+        [
+          -roll                              ! message query servers server
+          remove                             ! message query servers
+          dup empty?
+            [ 2drop ]
+            [ rot drop recursive-query ]
+          if
+        ]
+      }
+      [                                      ! query servers server message sym
+        drop nip nip                         ! query message
+        extract-ns-ips                       ! query ips
+        recursive-query
+      ]
+    }
+  case ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: canonical/cache ( name -- name )
+  dup CNAME IN query boa cache-get dup [ nip 1st rdata>> ] [ drop ] if ;
+
+: name->ip/cache ( name -- ip/f )
+  canonical/cache
+  A IN query boa cache-get dup [ random rdata>> ] [ ] if ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+:  name-hits? ( message -- message ? ) dup  name-hits empty? not ;
+: cname-hits? ( message -- message ? ) dup cname-hits empty? not ;
+
+: name->ip/server ( name -- ip-or-f )
+  A IN query boa root-dns-servers recursive-query ! message
+    {
+      { [ name-hits? ]  [ name-hits  random rdata>>          ] }
+      { [ cname-hits? ] [ cname-hits random rdata>> name->ip ] }
+      { [ t           ] [ drop f ] }
+    }
+  cond ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+! : name->ip ( name -- ip )
+!   { [ name->ip/cache ] [ name->ip/server ] [ name-error ] } 1|| ;
+
+: name->ip ( name -- ip )
+  dup name->ip/cache dup
+    [ nip ]
+    [
+      drop dup name->ip/server dup
+        [ nip ]
+        [ drop name-error ]
+      if
+    ]
+  if ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

From cf587c054dd35e9ce41480cf39c7567745be0df4 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 02:40:32 -0500
Subject: [PATCH 55/66] Tweak font rendering to avoid roundoff error

---
 extra/opengl/opengl.factor        |   4 +-
 extra/ui/freetype/freetype.factor | 126 +++++++++++++++++-------------
 2 files changed, 74 insertions(+), 56 deletions(-)

diff --git a/extra/opengl/opengl.factor b/extra/opengl/opengl.factor
index a6e76cdc9e..79470131f3 100755
--- a/extra/opengl/opengl.factor
+++ b/extra/opengl/opengl.factor
@@ -203,9 +203,7 @@ TUPLE: sprite loc dim dim2 dlist texture ;
     dup sprite-loc gl-translate
     GL_TEXTURE_2D over sprite-texture glBindTexture
     init-texture
-    GL_QUADS [ dup sprite-dim2 four-sides ] do-state
-    dup sprite-dim { 1 0 } v*
-    swap sprite-loc v- gl-translate
+    GL_QUADS [ sprite-dim2 four-sides ] do-state
     GL_TEXTURE_2D 0 glBindTexture ;
 
 : rect-vertices ( lower-left upper-right -- )
diff --git a/extra/ui/freetype/freetype.factor b/extra/ui/freetype/freetype.factor
index 1c83bc9713..be4f2ba8ae 100755
--- a/extra/ui/freetype/freetype.factor
+++ b/extra/ui/freetype/freetype.factor
@@ -3,7 +3,8 @@
 USING: alien alien.accessors alien.c-types arrays io kernel libc
 math math.vectors namespaces opengl opengl.gl prettyprint assocs
 sequences io.files io.styles continuations freetype
-ui.gadgets.worlds ui.render ui.backend byte-arrays ;
+ui.gadgets.worlds ui.render ui.backend byte-arrays accessors
+locals ;
 
 IN: ui.freetype
 
@@ -41,8 +42,8 @@ M: font hashcode* drop font hashcode* ;
     ] bind ;
 
 M: freetype-renderer free-fonts ( world -- )
-    dup world-handle select-gl-context
-    world-fonts [ nip second free-sprites ] assoc-each ;
+    [ handle>> select-gl-context ]
+    [ fonts>> [ nip second free-sprites ] assoc-each ] bi ;
 
 : ttf-name ( font style -- name )
     2array H{
@@ -67,7 +68,7 @@ M: freetype-renderer free-fonts ( world -- )
     #! We use FT_New_Memory_Face, not FT_New_Face, since
     #! FT_New_Face only takes an ASCII path name and causes
     #! problems on localized versions of Windows
-    freetype -rot 0 f <void*> [
+    [ freetype ] 2dip 0 f <void*> [
         FT_New_Memory_Face freetype-error
     ] keep *void* ;
 
@@ -85,29 +86,29 @@ SYMBOL: dpi
 : font-units>pixels ( n font -- n )
     face-size face-size-y-scale FT_MulFix ;
 
-: init-ascent ( font face -- )
-    dup face-y-max swap font-units>pixels swap set-font-ascent ;
+: init-ascent ( font face -- font )
+    dup face-y-max swap font-units>pixels >>ascent ; inline
 
-: init-descent ( font face -- )
-    dup face-y-min swap font-units>pixels swap set-font-descent ;
+: init-descent ( font face -- font )
+    dup face-y-min swap font-units>pixels >>descent ; inline
 
-: init-font ( font -- )
-    dup font-handle 2dup init-ascent dupd init-descent
-    dup font-ascent over font-descent - ft-ceil
-    swap set-font-height ;
+: init-font ( font -- font )
+    dup handle>> init-ascent
+    dup handle>> init-descent
+    dup [ ascent>> ] [ descent>> ] bi - ft-ceil >>height ; inline
+
+: set-char-size ( handle size -- )
+    0 swap 6 shift dpi get-global dup FT_Set_Char_Size freetype-error ;
 
 : <font> ( handle -- font )
-    H{ } clone
-    { set-font-handle set-font-widths } font construct
-    dup init-font ;
-
-: (open-font) ( font -- open-font )
-    first3 >r open-face dup 0 r> 6 shift
-    dpi get-global dpi get-global FT_Set_Char_Size
-    freetype-error <font> ;
+    font new
+        H{ } clone >>widths
+        over first2 open-face >>handle
+        dup handle>> rot third set-char-size
+        init-font ;
 
 M: freetype-renderer open-font ( font -- open-font )
-    freetype drop open-fonts get [ (open-font) ] cache ;
+    freetype drop open-fonts get [ <font> ] cache ;
 
 : load-glyph ( font char -- glyph )
     >r font-handle dup r> 0 FT_Load_Char
@@ -132,30 +133,36 @@ M: freetype-renderer string-height ( open-font string -- h )
     load-glyph dup
     FT_RENDER_MODE_NORMAL FT_Render_Glyph freetype-error ;
 
-: copy-pixel ( bit tex -- bit tex )
-    255 f pick set-alien-unsigned-1 1+
-    f pick alien-unsigned-1
-    f pick set-alien-unsigned-1 >r 1+ r> 1+ ;
+:: copy-pixel ( i j bitmap texture -- i j )
+    255 tex j set-alien-unsigned-1
+    i bitmap alien-unsigned-1 j 1 + texture set-alien-unsigned-1
+    i 1 + j 2 + ; inline
 
-: (copy-row) ( bit tex bitend texend -- bitend texend )
-    >r pick over >= [
-        2nip r>
-    ] [
-        >r copy-pixel r> r> (copy-row)
-    ] if ;
+: (copy-row) ( i j bitmap texture end -- )
+    i end < [
+        i j bitmap texture copy-pixel
+        i j bitmap texture end (copy-row)
+    ] when ; inline
 
-: copy-row ( bit tex width width2 -- bitend texend width width2 )
-    [ pick + >r pick + r> (copy-row) ] 2keep ;
+: copy-row ( i j bitmap texture width width2 -- i j )
+    i j bitmap texture i width + (copy-row)
+    i width +
+    j width2 + ; inline
 
-: copy-bitmap ( glyph texture -- )
-    over glyph-bitmap-rows >r
-    over glyph-bitmap-width dup next-power-of-2 2 *
-    >r >r >r glyph-bitmap-buffer alien-address r> r> r> r> 
-    [ copy-row ] times 2drop 2drop ;
+:: copy-bitmap ( glyph texture -- )
+    [let* | texture [ texture alien-address ]
+            bitmap [ glyph glyph-bitmap-buffer alien-address ]
+            rows [ glyph glyph-bitmap-rows ]
+            width [ glyph glyph-bitmap-width ]
+            width2 [ width next-power-of-2 2 * ] |
+        0 0
+        rows [ bitmap texture width width2 copy-row ] times
+        2drop
+    ] ;
 
 : bitmap>texture ( glyph sprite -- id )
     tuck sprite-size2 * 2 * [
-        alien-address [ copy-bitmap ] keep <alien> gray-texture
+        [ copy-bitmap ] keep gray-texture
     ] with-malloc ;
 
 : glyph-texture-loc ( glyph font -- loc )
@@ -163,34 +170,47 @@ M: freetype-renderer string-height ( open-font string -- h )
     font-ascent swap glyph-hori-bearing-y - ft-floor 2array ;
 
 : glyph-texture-size ( glyph -- dim )
-    dup glyph-bitmap-width next-power-of-2
-    swap glyph-bitmap-rows next-power-of-2 2array ;
+    [ glyph-bitmap-width next-power-of-2 ]
+    [ glyph-bitmap-rows next-power-of-2 ]
+    bi 2array ;
 
-: <char-sprite> ( font char -- sprite )
+: <char-sprite> ( open-font char -- sprite )
     over >r render-glyph dup r> glyph-texture-loc
     over glyph-size pick glyph-texture-size <sprite>
     [ bitmap>texture ] keep [ init-sprite ] keep ;
 
-: draw-char ( open-font char sprites -- )
-    [ dupd <char-sprite> ] cache nip
-    sprite-dlist glCallList ;
+:: char-sprite ( open-font sprites char -- sprite )
+    char sprites [ open-font swap <char-sprite> ] cache ;
 
-: (draw-string) ( open-font sprites string loc -- )
+: draw-char ( open-font sprites char loc -- )
+    GL_MODELVIEW [
+        0 0 glTranslated
+        char-sprite sprite-dlist glCallList
+    ] do-matrix ;
+
+: char-widths ( open-font string -- widths )
+    [ char-width ] with { } map-as ;
+
+: scan-sums ( seq -- seq' )
+    0 [ + ] accumulate nip ;
+
+:: (draw-string) ( open-font sprites string loc -- )
     GL_TEXTURE_2D [
-        [
-            [ >r 2dup r> swap draw-char ] each 2drop
+        loc [
+            string open-font string char-widths scan-sums [
+                [ open-font sprites ] 2dip draw-char
+            ] 2each
         ] with-translation
     ] do-enabled ;
 
-: font-sprites ( open-font world -- pair )
-    world-fonts [ open-font H{ } clone 2array ] cache ;
+: font-sprites ( font world -- open-font sprites )
+    world-fonts [ open-font H{ } clone 2array ] cache first2 ;
 
 M: freetype-renderer draw-string ( font string loc -- )
-    >r >r world get font-sprites first2 r> r> (draw-string) ;
+    >r >r world get font-sprites r> r> (draw-string) ;
 
 : run-char-widths ( open-font string -- widths )
-    [ char-width ] with { } map-as
-    dup 0 [ + ] accumulate nip swap 2 v/n v+ ;
+    char-widths [ scan-sums ] [ 2 v/n ] bi v+ ;
 
 M: freetype-renderer x>offset ( x open-font string -- n )
     dup >r run-char-widths [ <= ] with find drop

From d57c66690da5a85fc9a8b74235906b460f68622c Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 02:47:30 -0500
Subject: [PATCH 56/66] Fix errors reported by builder

---
 core/sets/sets-docs.factor                    | 2 +-
 extra/html/components/components-tests.factor | 2 +-
 extra/tangle/html/html-tests.factor           | 2 +-
 extra/trees/splay/splay-tests.factor          | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/core/sets/sets-docs.factor b/core/sets/sets-docs.factor
index 97fbc973f0..205d4d34bf 100644
--- a/core/sets/sets-docs.factor
+++ b/core/sets/sets-docs.factor
@@ -28,7 +28,7 @@ HELP: adjoin
 { $description "Removes all elements equal to " { $snippet "elt" } ", and adds " { $snippet "elt" } " at the end of the sequence." }
 { $examples
     { $example
-        "USING: namespaces prettyprint sequences ;"
+        "USING: namespaces prettyprint sets ;"
         "V{ \"beans\" \"salsa\" \"cheese\" } \"v\" set"
         "\"nachos\" \"v\" get adjoin"
         "\"salsa\" \"v\" get adjoin"
diff --git a/extra/html/components/components-tests.factor b/extra/html/components/components-tests.factor
index f2b0049a8e..1a0f849a8f 100644
--- a/extra/html/components/components-tests.factor
+++ b/extra/html/components/components-tests.factor
@@ -1,7 +1,7 @@
 IN: html.components.tests
 USING: tools.test kernel io.streams.string
 io.streams.null accessors inspector html.streams
-html.components ;
+html.components namespaces ;
 
 [ ] [ blank-values ] unit-test
 
diff --git a/extra/tangle/html/html-tests.factor b/extra/tangle/html/html-tests.factor
index 8e7d8c24e1..88ad748400 100644
--- a/extra/tangle/html/html-tests.factor
+++ b/extra/tangle/html/html-tests.factor
@@ -1,4 +1,4 @@
-USING: html kernel semantic-db tangle.html tools.test ;
+USING: kernel semantic-db tangle.html tools.test ;
 IN: tangle.html.tests
 
 [ "test" ] [ "test" >html ] unit-test
diff --git a/extra/trees/splay/splay-tests.factor b/extra/trees/splay/splay-tests.factor
index 29bc153030..e54e3cd538 100644
--- a/extra/trees/splay/splay-tests.factor
+++ b/extra/trees/splay/splay-tests.factor
@@ -1,7 +1,7 @@
 ! Copyright (c) 2005 Mackenzie Straight.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: kernel tools.test trees.splay math namespaces assocs
-sequences random ;
+sequences random sets ;
 IN: trees.splay.tests
 
 : randomize-numeric-splay-tree ( splay-tree -- )

From 41c845cf738aef558821b7d4d4b94cd973d86da0 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 02:51:16 -0500
Subject: [PATCH 57/66] Encoding issue?

---
 extra/unicode/collation/collation-tests.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extra/unicode/collation/collation-tests.factor b/extra/unicode/collation/collation-tests.factor
index c9d6cb808f..b4a54bb11d 100755
--- a/extra/unicode/collation/collation-tests.factor
+++ b/extra/unicode/collation/collation-tests.factor
@@ -20,7 +20,7 @@ IN: unicode.collation.tests
     [ execute ] 2with each ;
 
 [ f f f f ] [ "hello" "hi" test-equality ] unit-test
-[ t f f f ] [ "hello" "h�llo" test-equality ] unit-test
+[ t f f f ] [ "hello" "h\u0000e9llo" test-equality ] unit-test
 [ t t f f ] [ "hello" "HELLO" test-equality ] unit-test
 [ t t t f ] [ "hello" "h e l l o." test-equality ] unit-test
 [ t t t t ] [ "hello" "\0hello\0" test-equality ] unit-test

From 21fcc8a542a6a5574866684ab354fff7f32c0539 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 03:17:36 -0500
Subject: [PATCH 58/66] Oops

---
 extra/ui/freetype/freetype.factor | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/extra/ui/freetype/freetype.factor b/extra/ui/freetype/freetype.factor
index be4f2ba8ae..3512bbf670 100755
--- a/extra/ui/freetype/freetype.factor
+++ b/extra/ui/freetype/freetype.factor
@@ -134,24 +134,23 @@ M: freetype-renderer string-height ( open-font string -- h )
     FT_RENDER_MODE_NORMAL FT_Render_Glyph freetype-error ;
 
 :: copy-pixel ( i j bitmap texture -- i j )
-    255 tex j set-alien-unsigned-1
-    i bitmap alien-unsigned-1 j 1 + texture set-alien-unsigned-1
+    255 j texture set-char-nth
+    i bitmap char-nth j 1 + texture set-char-nth
     i 1 + j 2 + ; inline
 
-: (copy-row) ( i j bitmap texture end -- )
+:: (copy-row) ( i j bitmap texture end -- )
     i end < [
         i j bitmap texture copy-pixel
-        i j bitmap texture end (copy-row)
+            bitmap texture end (copy-row)
     ] when ; inline
 
-: copy-row ( i j bitmap texture width width2 -- i j )
+:: copy-row ( i j bitmap texture width width2 -- i j )
     i j bitmap texture i width + (copy-row)
     i width +
     j width2 + ; inline
 
 :: copy-bitmap ( glyph texture -- )
-    [let* | texture [ texture alien-address ]
-            bitmap [ glyph glyph-bitmap-buffer alien-address ]
+    [let* | bitmap [ glyph glyph-bitmap-buffer ]
             rows [ glyph glyph-bitmap-rows ]
             width [ glyph glyph-bitmap-width ]
             width2 [ width next-power-of-2 2 * ] |

From 05c3c82e3c0bce204686a30c8c68a0e6dafe5f65 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Thu, 29 May 2008 05:17:13 -0500
Subject: [PATCH 59/66] newfx: index

---
 extra/newfx/newfx.factor | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/extra/newfx/newfx.factor b/extra/newfx/newfx.factor
index abe0449d06..e017dc4b2b 100644
--- a/extra/newfx/newfx.factor
+++ b/extra/newfx/newfx.factor
@@ -170,6 +170,11 @@ METHOD: as-mutate { object object assoc }       set-at ;
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
+: index    ( seq obj -- i ) swap sequences:index ;
+: index-of ( obj seq -- i )      sequences:index ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
 : 1st 0 at ;
 : 2nd 1 at ;
 : 3rd 2 at ;

From 188fab8f003cac11a5d0df17469e688ba2907552 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Thu, 29 May 2008 05:17:30 -0500
Subject: [PATCH 60/66] dns: move some words to dns

---
 extra/dns/cache/cache.factor | 28 ++++++++++++++++++++++++++++
 extra/dns/dns.factor         |  7 ++++++-
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/extra/dns/cache/cache.factor b/extra/dns/cache/cache.factor
index aeba35f29d..4167c7b16e 100644
--- a/extra/dns/cache/cache.factor
+++ b/extra/dns/cache/cache.factor
@@ -119,3 +119,31 @@ ERROR: name-error name ;
 : cache-add-rr ( rr -- ) [ rr->query ] [ ] bi cache-add ;
 
 : cache-add-rrs ( rrs -- ) [ cache-add-rr ] each ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+! cache-name-error
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: message-soa ( message -- rr/soa )
+  authority-section>> [ type>> SOA = ] filter 1st ;
+
+: cache-name-error ( message -- message )
+  dup
+    [ message-query ] [ message-soa ttl>> ] bi
+  cache-nx ;
+
+: cache-message-records ( message -- message )
+  dup
+    {
+      [ answer-section>>     cache-add-rrs ]
+      [ authority-section>>  cache-add-rrs ]
+      [ additional-section>> cache-add-rrs ]
+    }
+  cleave ;
+
+: cache-message ( message -- message )
+  dup rcode>> NAME-ERROR = [ cache-name-error ] when
+  cache-message-records ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
diff --git a/extra/dns/dns.factor b/extra/dns/dns.factor
index f10bdea0bf..9404ccdad1 100644
--- a/extra/dns/dns.factor
+++ b/extra/dns/dns.factor
@@ -470,4 +470,9 @@ SYMBOLS: NO-ERROR FORMAT-ERROR SERVER-FAILURE NAME-ERROR NOT-IMPLEMENTED
 
 : ask ( message -- message ) dns-server ask-server ;
 
-: <query-message> ( query -- message ) <message> swap {1} >>question-section ;
\ No newline at end of file
+: query->message ( query -- message ) <message> swap {1} >>question-section ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: message-query ( message -- query ) question-section>> 1st ;
+

From a109d10b3df78961f596f9f1c68b199ffda473e0 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Thu, 29 May 2008 05:17:55 -0500
Subject: [PATCH 61/66] dns.recursive: Try out an optimized name->ip/server

---
 extra/dns/recursive/recursive.factor | 67 +++++++++++++++-------------
 1 file changed, 35 insertions(+), 32 deletions(-)

diff --git a/extra/dns/recursive/recursive.factor b/extra/dns/recursive/recursive.factor
index 6fe8ec96da..3a74667845 100644
--- a/extra/dns/recursive/recursive.factor
+++ b/extra/dns/recursive/recursive.factor
@@ -2,6 +2,7 @@
 USING: kernel continuations
        combinators
        sequences
+       math
        random
        unicode.case
        accessors symbols
@@ -28,30 +29,6 @@ IN: dns.recursive
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-: cache-message ( message -- message )
-  dup dup rcode>> NAME-ERROR =
-    [
-      [ question-section>> 1st ]
-      [ authority-section>> [ type>> SOA = ] filter random ttl>> ]
-      bi
-      cache-nx
-    ]
-    [
-        {
-          [ answer-section>>     cache-add-rrs ]
-          [ authority-section>>  cache-add-rrs ]
-          [ additional-section>> cache-add-rrs ]
-        }
-      cleave
-    ]
-  if ;
-
-! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-: query->message ( query -- message ) <query-message> ;
-
-! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
 : {name-type-class} ( obj -- seq )
   [ name>> >lower ] [ type>> ] [ class>> ] tri {3} ;
 
@@ -61,10 +38,6 @@ IN: dns.recursive
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-: message-query ( message -- query ) question-section>> 1st ;
-
-! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
 : answer-hits ( message -- rrs )
   [ answer-section>> ] [ message-query ] bi rr-filter ;
 
@@ -110,7 +83,7 @@ DEFER: name->ip
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
-: recursive-query ( query servers -- message )
+: (recursive-query) ( query servers -- message )
   dup random                                 ! query servers server
   pick query->message 0 >>rd                 ! query servers server message
   over ask-server                            ! query servers server message
@@ -128,20 +101,39 @@ DEFER: name->ip
           remove                             ! message query servers
           dup empty?
             [ 2drop ]
-            [ rot drop recursive-query ]
+            [ rot drop (recursive-query) ]
           if
         ]
       }
       [                                      ! query servers server message sym
         drop nip nip                         ! query message
         extract-ns-ips                       ! query ips
-        recursive-query
+        (recursive-query)
       ]
     }
   case ;
 
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
+: cdr-name ( name -- name ) dup CHAR: . index 1+ tail ;
+
+: cache-get-ns ( name -- seq/f ) NS IN query boa cache-get ;
+
+: name->servers ( name -- servers )
+    {
+      { [ dup "" = ]         [ drop root-dns-servers ] }
+      { [ dup cache-get-ns ] [ cache-get-ns [ rdata>> name->ip ] map ] }
+      { [ t ]                [ cdr-name name->servers ] }
+    }
+  cond ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+: recursive-query ( query -- message )
+  dup name>> name->servers (recursive-query) ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
 : canonical/cache ( name -- name )
   dup CNAME IN query boa cache-get dup [ nip 1st rdata>> ] [ drop ] if ;
 
@@ -154,8 +146,19 @@ DEFER: name->ip
 :  name-hits? ( message -- message ? ) dup  name-hits empty? not ;
 : cname-hits? ( message -- message ? ) dup cname-hits empty? not ;
 
+! : name->ip/server ( name -- ip-or-f )
+!   A IN query boa root-dns-servers recursive-query ! message
+!     {
+!       { [ name-hits? ]  [ name-hits  random rdata>>          ] }
+!       { [ cname-hits? ] [ cname-hits random rdata>> name->ip ] }
+!       { [ t           ] [ drop f ] }
+!     }
+!   cond ;
+
+! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
 : name->ip/server ( name -- ip-or-f )
-  A IN query boa root-dns-servers recursive-query ! message
+  A IN query boa recursive-query ! message
     {
       { [ name-hits? ]  [ name-hits  random rdata>>          ] }
       { [ cname-hits? ] [ cname-hits random rdata>> name->ip ] }

From 3bd5144f2030284c9e38e7f373880d765519d2f8 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Thu, 29 May 2008 10:11:12 -0500
Subject: [PATCH 62/66] dns.resolver: minor fix

---
 extra/dns/resolver/resolver.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extra/dns/resolver/resolver.factor b/extra/dns/resolver/resolver.factor
index c8a9f22d08..7e0f6b4190 100644
--- a/extra/dns/resolver/resolver.factor
+++ b/extra/dns/resolver/resolver.factor
@@ -62,7 +62,7 @@ IN: dns.resolver
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
 : canonical/server ( name -- name )
-  dup CNAME IN query boa <query-message> ask* answer-section>>
+  dup CNAME IN query boa query->message ask* answer-section>>
   [ type>> CNAME = ] filter dup empty? not
     [ nip 1st rdata>> ]
     [ drop ]

From a8cdb2226d7b78e12e02336f619fcef2f26440b3 Mon Sep 17 00:00:00 2001
From: Eduardo Cavazos <dharmatech@finkelstein.stackeffects.info>
Date: Thu, 29 May 2008 10:11:54 -0500
Subject: [PATCH 63/66] dns.resolver: another fix

---
 extra/dns/resolver/resolver.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extra/dns/resolver/resolver.factor b/extra/dns/resolver/resolver.factor
index 7e0f6b4190..38fe59dc41 100644
--- a/extra/dns/resolver/resolver.factor
+++ b/extra/dns/resolver/resolver.factor
@@ -70,7 +70,7 @@ IN: dns.resolver
 
 : name->ip/server ( name -- ip )
   canonical/server
-  dup A IN query boa <query-message> ask* answer-section>>
+  dup A IN query boa query->message ask* answer-section>>
   [ type>> A = ] filter dup empty? not
     [ nip random rdata>> ]
     [ 2drop f ]

From 5e9b59160845320c60f21e54d80e713fc5be30e0 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 17:32:59 -0500
Subject: [PATCH 64/66] Fix file-responder breakage

---
 extra/http/server/static/static.factor | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/extra/http/server/static/static.factor b/extra/http/server/static/static.factor
index 0e799fd3ad..8814004589 100755
--- a/extra/http/server/static/static.factor
+++ b/extra/http/server/static/static.factor
@@ -4,7 +4,7 @@ USING: calendar io io.files kernel math math.order
 math.parser http http.server namespaces parser sequences strings
 assocs hashtables debugger http.mime sorting html.elements
 html.templates.fhtml logging calendar.format accessors
-io.encodings.binary fry xml.entities ;
+io.encodings.binary fry xml.entities destructors ;
 IN: http.server.static
 
 ! special maps mime types to quots with effect ( path -- )
@@ -29,16 +29,14 @@ TUPLE: file-responder root hook special allow-listings ;
         swap >>root
         H{ } clone >>special ;
 
+: (serve-static) ( path mime-type -- response )
+    [ [ binary <file-reader> &dispose ] dip <content> ]
+    [ drop file-info [ size>> ] [ modified>> ] bi ] 2bi
+    [ "content-length" set-header ]
+    [ "last-modified" set-header ] bi* ;
+
 : <static> ( root -- responder )
-    [
-        <content>
-        swap [
-            file-info
-            [ size>> "content-length" set-header ]
-            [ modified>> "last-modified" set-header ] bi
-        ]
-        [ '[ , binary <file-reader> output-stream get stream-copy ] >>body ] bi
-    ] <file-responder> ;
+    [ (serve-static) ] <file-responder> ;
 
 : serve-static ( filename mime-type -- response )
     over modified-since?

From c525d0057d78cc3d23d146648e9293649790f851 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 17:33:05 -0500
Subject: [PATCH 65/66] Help lint fix

---
 extra/ui/freetype/freetype-docs.factor | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extra/ui/freetype/freetype-docs.factor b/extra/ui/freetype/freetype-docs.factor
index f463a7c0e7..855df9f564 100755
--- a/extra/ui/freetype/freetype-docs.factor
+++ b/extra/ui/freetype/freetype-docs.factor
@@ -38,7 +38,7 @@ HELP: render-glyph
 { $description "Renders a character and outputs a pointer to the bitmap." } ;
 
 HELP: <char-sprite>
-{ $values { "font" font } { "char" "a non-negative integer" } { "sprite" sprite } }
+{ $values { "open-font" font } { "char" "a non-negative integer" } { "sprite" sprite } }
 { $description "Renders a character to an OpenGL texture and records a display list which draws a quad with this texture. This word allocates native resources which must be freed by " { $link free-sprites } "." } ;
 
 HELP: (draw-string)

From 4ef0ff1ca15a5b7db3807b3725bc09c247d457c6 Mon Sep 17 00:00:00 2001
From: Slava Pestov <slava@slava-pestovs-macbook-pro.local>
Date: Thu, 29 May 2008 17:33:11 -0500
Subject: [PATCH 66/66] Remove unnecessary padding

---
 vm/code_gc.h | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/vm/code_gc.h b/vm/code_gc.h
index 658dc990ae..ecc9f697f5 100644
--- a/vm/code_gc.h
+++ b/vm/code_gc.h
@@ -17,9 +17,6 @@ typedef struct _F_BLOCK
 
 	/* Used during compaction */
 	struct _F_BLOCK *forwarding;
-
-	/* Alignment padding */
-	CELL padding[4];
 } F_BLOCK;
 
 typedef struct {