Merge branch 'master' of factorcode.org:/git/factor

db4
Eduardo Cavazos 2008-05-28 23:12:30 -05:00
commit 6d15c7615f
209 changed files with 183637 additions and 3302 deletions

View File

@ -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

View File

@ -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 )

View File

@ -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" word-prop assoc-empty? ] unit-test
[ ] [ [ \ method-forget-class forget ] with-compilation-unit ] unit-test
[ t ] [ \ method-forget-test "methods" word-prop assoc-empty? ] unit-test

View File

@ -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

View File

@ -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

View File

@ -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 ;

View File

@ -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
@ -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 ;
@ -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 -- )

View File

@ -1,4 +1,4 @@
USING: help.markup help.syntax kernel quotations ;
USING: help.markup help.syntax kernel quotations dlists.private ;
IN: dlists
ARTICLE: "dlists" "Doubly-linked lists"
@ -51,38 +51,52 @@ HELP: dlist-empty?
HELP: push-front
{ $values { "obj" "an object" } { "dlist" dlist } }
{ $description "Push the object onto the front of the " { $link dlist } "." }
{ $notes "This operation is O(1)." }
{ $see-also push-back pop-front pop-front* pop-back pop-back* } ;
{ $notes "This operation is O(1)." } ;
HELP: push-front*
{ $values { "obj" "an object" } { "dlist" dlist } { "dlist-node" dlist-node } }
{ $description "Push the object onto the front of the " { $link dlist } " and return the newly created " { $snippet "dlist-node" } "." }
{ $notes "This operation is O(1)." } ;
HELP: push-back
{ $values { "obj" "an object" } { "dlist" dlist } }
{ $description "Push the object onto the back of the " { $link dlist } "." }
{ $notes "This operation is O(1)." }
{ $see-also push-front pop-front pop-front* pop-back pop-back* } ;
{ $notes "This operation is O(1)." } ;
HELP: push-back*
{ $values { "obj" "an object" } { "dlist" dlist } { "dlist-node" dlist-node } }
{ $description "Push the object onto the back of the " { $link dlist } " and return the newly created " { $snippet "dlist-node" } "." }
{ $notes "This operation is O(1)." } ;
HELP: peek-front
{ $values { "dlist" dlist } { "obj" "an object" } }
{ $description "Returns the object at the front of the " { $link dlist } "." } ;
HELP: pop-front
{ $values { "dlist" dlist } { "obj" "an object" } }
{ $description "Pop the object off the front of the " { $link dlist } " and return the object." }
{ $notes "This operation is O(1)." }
{ $see-also push-front push-back pop-front* pop-back pop-back* } ;
{ $notes "This operation is O(1)." } ;
HELP: pop-front*
{ $values { "dlist" dlist } }
{ $description "Pop the object off the front of the " { $link dlist } "." }
{ $notes "This operation is O(1)." }
{ $see-also push-front push-back pop-front pop-back pop-back* } ;
{ $notes "This operation is O(1)." } ;
HELP: peek-back
{ $values { "dlist" dlist } { "obj" "an object" } }
{ $description "Returns the object at the back of the " { $link dlist } "." } ;
HELP: pop-back
{ $values { "dlist" dlist } { "obj" "an object" } }
{ $description "Pop the object off the back of the " { $link dlist } " and return the object." }
{ $notes "This operation is O(1)." }
{ $see-also push-front push-back pop-front pop-front* pop-back* } ;
{ $notes "This operation is O(1)." } ;
HELP: pop-back*
{ $values { "dlist" dlist } }
{ $description "Pop the object off the back of the " { $link dlist } "." }
{ $notes "This operation is O(1)." }
{ $see-also push-front push-back pop-front pop-front* pop-back } ;
{ $notes "This operation is O(1)." } ;
{ push-front push-front* push-back push-back* peek-front pop-front pop-front* peek-back pop-back pop-back* } related-words
HELP: dlist-find
{ $values { "dlist" { $link dlist } } { "quot" quotation } { "obj/f" "an object or " { $link f } } { "?" "a boolean" } }

View File

@ -1,6 +1,6 @@
USING: dlists dlists.private kernel tools.test random assocs
sets sequences namespaces sorting debugger io prettyprint
math ;
math accessors classes ;
IN: dlists.tests
[ t ] [ <dlist> dlist-empty? ] unit-test
@ -65,20 +65,17 @@ IN: dlists.tests
: assert-same-elements
[ prune natural-sort ] bi@ assert= ;
: dlist-push-all [ push-front ] curry each ;
: dlist-delete-all [ dlist-delete drop ] curry each ;
: dlist>array [ [ , ] dlist-slurp ] { } make ;
[ ] [
5 [ drop 30 random >fixnum ] map prune
6 [ drop 30 random >fixnum ] map prune 2dup nl . . nl
[
6 [ drop 30 random >fixnum ] map prune [
<dlist>
[ dlist-push-all ] keep
[ dlist-delete-all ] keep
dlist>array
[ push-all-front ]
[ dlist-delete-all ]
[ dlist>array ] tri
] 2keep swap diff assert-same-elements
] unit-test
@ -95,3 +92,13 @@ IN: dlists.tests
[ 1 ] [ "d" get dlist-length ] unit-test
[ 1 ] [ "d" get dlist>array length ] unit-test
[ t ] [ <dlist> 4 over push-back 5 over push-back [ obj>> 4 = ] dlist-find-node drop class dlist-node = ] unit-test
[ t ] [ <dlist> 4 over push-back 5 over push-back [ obj>> 5 = ] dlist-find-node drop class dlist-node = ] unit-test
[ t ] [ <dlist> 4 over push-back 5 over push-back* [ = ] curry dlist-find-node drop class dlist-node = ] unit-test
[ ] [ <dlist> 4 over push-back 5 over push-back [ drop ] dlist-each ] unit-test
[ <dlist> peek-front ] must-fail
[ <dlist> peek-back ] must-fail
[ <dlist> pop-front ] [ empty-dlist? ] must-fail-with
[ <dlist> pop-back ] [ empty-dlist? ] must-fail-with

View File

@ -1,7 +1,7 @@
! Copyright (C) 2007, 2008 Mackenzie Straight, Doug Coleman,
! Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: combinators kernel math sequences accessors ;
USING: combinators kernel math sequences accessors inspector ;
IN: dlists
TUPLE: dlist front back length ;
@ -47,7 +47,7 @@ C: <dlist-node> dlist-node
: (dlist-find-node) ( dlist-node quot -- node/f ? )
over [
[ >r obj>> r> call ] 2keep rot
[ call ] 2keep rot
[ drop t ] [ >r next>> r> (dlist-find-node) ] if
] [ 2drop f f ] if ; inline
@ -55,7 +55,7 @@ C: <dlist-node> dlist-node
>r front>> r> (dlist-find-node) ; inline
: dlist-each-node ( dlist quot -- )
[ t ] compose dlist-find-node 2drop ; inline
[ f ] compose dlist-find-node 2drop ; inline
PRIVATE>
@ -84,11 +84,17 @@ PRIVATE>
: push-all-back ( seq dlist -- )
[ push-back ] curry each ;
ERROR: empty-dlist ;
M: empty-dlist summary ( dlist -- )
drop "Emtpy dlist" ;
: peek-front ( dlist -- obj )
front>> obj>> ;
front>> [ empty-dlist ] unless* obj>> ;
: pop-front ( dlist -- obj )
dup front>> [
dup front>> [ empty-dlist ] unless*
[
dup next>>
f rot (>>next)
f over set-prev-when
@ -96,13 +102,15 @@ PRIVATE>
] 2keep obj>>
swap [ normalize-back ] keep dec-length ;
: pop-front* ( dlist -- ) pop-front drop ;
: pop-front* ( dlist -- )
pop-front drop ;
: peek-back ( dlist -- obj )
back>> obj>> ;
back>> [ empty-dlist ] unless* obj>> ;
: pop-back ( dlist -- obj )
dup back>> [
dup back>> [ empty-dlist ] unless*
[
dup prev>>
f rot (>>prev)
f over set-next-when
@ -110,9 +118,11 @@ PRIVATE>
] 2keep obj>>
swap [ normalize-front ] keep dec-length ;
: pop-back* ( dlist -- ) pop-back drop ;
: pop-back* ( dlist -- )
pop-back drop ;
: dlist-find ( dlist quot -- obj/f ? )
[ obj>> ] prepose
dlist-find-node [ obj>> t ] [ drop f f ] if ; inline
: dlist-contains? ( dlist quot -- ? )
@ -141,6 +151,7 @@ PRIVATE>
] if ; inline
: delete-node-if ( dlist quot -- obj/f )
[ obj>> ] prepose
delete-node-if* drop ; inline
: dlist-delete ( obj dlist -- obj/f )

View File

@ -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 ;

View File

@ -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 ;

View File

@ -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

View File

@ -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

View File

@ -460,3 +460,28 @@ must-fail-with
"change-combination" "parser.tests" lookup
"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 drop
] 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

View File

@ -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 {

View File

@ -342,3 +342,5 @@ INTERSECTION: intersection-see-test sequence number ;
[ ] [ \ compose see ] unit-test
[ ] [ \ curry see ] unit-test
[ "POSTPONE: [" ] [ \ [ unparse ] unit-test

View File

@ -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 } }

View File

@ -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 [ push-new ] keep ] unit-test
[ V{ 1 2 3 } ]
[ 3 V{ 1 3 2 } clone [ push-new ] keep ] unit-test
! erg's random tester found this one
[ SBUF" 12341234" ] [
9 <sbuf> dup "1234" swap push-all dup dup swap push-all

View File

@ -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
@ -680,7 +678,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 )

View File

@ -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." }

View File

@ -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

View File

@ -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? ;

View File

@ -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" } }

View File

@ -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> ;

View File

@ -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" [

View File

@ -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"

View File

@ -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 ;

View File

@ -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

View File

@ -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 ;

View File

@ -116,11 +116,11 @@ IN: cairo.samples
cr cairo_fill ;
: utf8 ( -- )
cr "Serif" CAIRO_FONT_SLANT_NORMAL CAIRO_FONT_WEIGHT_NORMAL
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 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

View File

@ -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

View File

@ -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> ;

View File

@ -27,3 +27,15 @@ circular strings ;
! This no longer fails
! [ "test" <circular> 5 swap nth ] must-fail
! [ "foo" <circular> CHAR: b 3 rot set-nth ] must-fail
[ { } ] [ 3 <growing-circular> >array ] unit-test
[ { 1 2 } ] [
3 <growing-circular>
[ 1 swap push-growing-circular ] keep
[ 2 swap push-growing-circular ] keep >array
] unit-test
[ { 3 4 5 } ] [
3 <growing-circular> dup { 1 2 3 4 5 } [
swap push-growing-circular
] with each >array
] unit-test

View File

@ -19,10 +19,6 @@ M: circular length seq>> length ;
M: circular virtual@ circular-wrap seq>> ;
M: circular nth virtual@ nth ;
M: circular set-nth virtual@ set-nth ;
M: circular virtual-seq seq>> ;
: change-circular-start ( n circular -- )
@ -36,3 +32,20 @@ M: circular virtual-seq seq>> ;
0 <string> <circular> ;
INSTANCE: circular virtual-sequence
TUPLE: growing-circular < circular length ;
M: growing-circular length length>> ;
: full? ( circular -- ? )
[ length ] [ seq>> length ] bi = ;
: set-peek ( elt seq -- )
[ length 1- ] keep set-nth ;
: push-growing-circular ( elt circular -- )
dup full? [ push-circular ]
[ [ 1+ ] change-length set-peek ] if ;
: <growing-circular> ( capacity -- growing-circular )
{ } new-sequence 0 0 growing-circular boa ;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ;

View File

@ -36,17 +36,17 @@ IN: farkup.tests
[ "<p>|a</p>" ]
[ "|a" convert-farkup ] unit-test
[ "<p>|a|</p>" ]
[ "<table><tr><td>a</td></tr></table>" ]
[ "|a|" convert-farkup ] unit-test
[ "<table><tr><td>a</td><td>b</td></tr></table>" ]
[ "a|b" convert-farkup ] unit-test
[ "|a|b|" convert-farkup ] unit-test
[ "<table><tr><td>a</td><td>b</td></tr></table>\n<table><tr><td>c</td><td>d</td></tr></table>" ]
[ "a|b\nc|d" convert-farkup ] unit-test
[ "<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>" ]
[ "|a|b|\n|c|d|" convert-farkup ] unit-test
[ "<table><tr><td>a</td><td>b</td></tr></table>\n<table><tr><td>c</td><td>d</td></tr></table>\n" ]
[ "a|b\nc|d\n" convert-farkup ] unit-test
[ "<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>" ]
[ "|a|b|\n|c|d|\n" convert-farkup ] unit-test
[ "<p><strong>foo</strong>\n</p><h1>aheading</h1>\n<p>adfasd</p>" ]
[ "*foo*\n=aheading=\nadfasd" convert-farkup ] unit-test
@ -62,12 +62,23 @@ 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
[ "<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
[
"<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

View File

@ -2,10 +2,13 @@
! 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
SYMBOL: relative-link-prefix
SYMBOL: link-no-follow?
<PRIVATE
: delimiters ( -- string )
@ -59,25 +62,30 @@ 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' )
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 ;
: 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
@ -102,7 +110,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 )
[
@ -113,12 +121,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 )
@ -129,12 +139,14 @@ MEMO: table-column ( -- parser )
text [ "td" surround-with-foo ] action ;
MEMO: table-row ( -- parser )
[
table-column "|" token hide list-of-many ,
] seq* [ "tr" surround-with-foo ] action ;
"|" token hide
table-column "|" token hide list-of
"|" token hide nl hide optional 4seq
[ "tr" surround-with-foo ] action ;
MEMO: table ( -- parser )
table-row repeat1 [ "table" surround-with-foo ] action ;
table-row repeat1
[ "table" surround-with-foo ] action ;
MEMO: code ( -- parser )
[
@ -147,6 +159,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 ,

View File

@ -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

View File

@ -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 ;

View File

@ -0,0 +1,180 @@
IN: html.components.tests
USING: tools.test kernel io.streams.string
io.streams.null accessors inspector html.streams
html.components ;
[ ] [ 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 >>cols render
] with-null-writer
] unit-test
[ ] [ blank-values ] unit-test
[ ] [ "new york" "city1" set-value ] unit-test
[ ] [ { "new york" "los angeles" "chicago" } "cities" set-value ] unit-test
[ ] [
[
"city1"
<choice>
"cities" >>choices
render
] with-null-writer
] unit-test
[ ] [ { "los angeles" "new york" } "city2" set-value ] unit-test
[ ] [
[
"city2"
<choice>
"cities" >>choices
t >>multiple
render
] with-null-writer
] unit-test
[ ] [
[
"city2"
<choice>
"cities" >>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
[ ] [ "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
[ ] [ blank-values ] unit-test
[ ] [
"factor" [
"concatenative" "model" set-value
] nest-values
] unit-test
[ H{ { "factor" H{ { "model" "concatenative" } } } } ] [ values get ] unit-test

View File

@ -0,0 +1,224 @@
! 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 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
lcs.diff2html ;
IN: html.components
SYMBOL: values
: value values get at ;
: set-value values get set-at ;
: blank-values H{ } clone values set ;
: prepare-value ( name object -- value name object )
[ [ value ] keep ] dip ; inline
: from-assoc ( assoc -- ) values get swap update ;
: 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
: 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 [
[
H{ } clone [ values set call ] keep
] 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 ] }
{ [ dup timestamp? ] [ timestamp>string ] }
{ [ dup string? ] [ ] }
{ [ dup word? ] [ word-name ] }
{ [ dup not ] [ drop "" ] }
} cond ;
GENERIC: render* ( value name render -- )
: render ( name renderer -- )
over named-validation-messages get at [
[ value>> ] [ message>> ] bi
[ -rot render* ] dip
render-error
] [
prepare-value 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
[ object>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 cols ;
: <textarea> ( -- renderer )
textarea new ;
M: textarea render*
<textarea
[ rows>> [ object>string =rows ] when* ]
[ cols>> [ object>string =cols ] when* ] bi
=name
textarea>
object>string escape-string write
</textarea> ;
! Choice
TUPLE: choice size multiple choices ;
: <choice> ( -- choice )
choice new ;
: render-option ( text selected? -- )
<option [ "true" =selected ] when option>
object>string escape-string write
</option> ;
: render-options ( options selected -- )
'[ dup , member? render-option ] each ;
M: choice render*
<select
swap =name
dup size>> [ object>string =size ] when*
dup multiple>> [ "true" =multiple ] when
select>
[ choices>> value ] [ 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> ;
! 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 ;
! Diff component
SINGLETON: comparison
M: comparison render*
2drop htmlize-diff ;
! HTML component
SINGLETON: html
M: html render* 2drop write ;

View File

@ -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

View File

@ -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> ;

View File

@ -1,267 +0,0 @@
! Copyright (C) 2004, 2006 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 ;
IN: html
GENERIC: browser-link-href ( presented -- href )
M: object browser-link-href drop f ;
TUPLE: html-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? ;
: not-a-div ( stream -- stream )
dup test-last-div? drop ; inline
: a-div ( stream -- straem )
t over set-html-stream-last-div? ; inline
: <html-stream> ( stream -- stream )
html-stream construct-delegate ;
<PRIVATE
TUPLE: html-sub-stream style stream ;
: (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
: 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 ;
: object-link-tag ( style quot -- )
presented pick at [
browser-link-href [
<a =href a> call </a>
] [ call ] if*
] [ call ] if* ; inline
: hex-color, ( triplet -- )
3 head-slice
[ 255 * >fixnum >hex 2 CHAR: 0 pad-left % ] each ;
: fg-css, ( color -- )
"color: #" % hex-color, "; " % ;
: bg-css, ( color -- )
"background-color: #" % hex-color, "; " % ;
: style-css, ( flag -- )
dup
{ italic bold-italic } member?
"font-style: " % "italic" "normal" ? % "; " %
{ bold bold-italic } member?
"font-weight: " % "bold" "normal" ? % "; " % ;
: size-css, ( size -- )
"font-size: " % # "pt; " % ;
: font-css, ( font -- )
"font-family: " % % "; " % ;
: apply-style ( style key quot -- style gadget )
>r over at r> when* ; inline
: make-css ( style quot -- str )
"" make nip ; inline
: span-css-style ( style -- str )
[
foreground [ fg-css, ] apply-style
background [ bg-css, ] apply-style
font [ font-css, ] apply-style
font-style [ style-css, ] apply-style
font-size [ size-css, ] apply-style
] make-css ;
: span-tag ( style quot -- )
over span-css-style dup empty? [
drop call
] [
<span =style span> call </span>
] if ; inline
: format-html-span ( string style stream -- )
[
[ [ drop delegate-write ] span-tag ] object-link-tag
] with-output-stream* ;
TUPLE: html-span-stream ;
M: html-span-stream dispose
end-sub-stream not-a-div format-html-span ;
: border-css, ( border -- )
"border: 1px solid #" % hex-color, "; " % ;
: padding-css, ( padding -- ) "padding: " % # "px; " % ;
: pre-css, ( margin -- )
[ "white-space: pre; font-family: monospace; " % ] unless ;
: div-css-style ( style -- str )
[
page-color [ bg-css, ] apply-style
border-color [ border-css, ] apply-style
border-width [ padding-css, ] apply-style
wrap-margin over at pre-css,
] make-css ;
: div-tag ( style quot -- )
swap div-css-style dup empty? [
drop call
] [
<div =style div> call </div>
] if ; inline
: format-html-div ( string style stream -- )
[
[ [ delegate-write ] div-tag ] object-link-tag
] with-output-stream* ;
TUPLE: html-block-stream ;
M: html-block-stream dispose ( quot style stream -- )
end-sub-stream a-div format-html-div ;
: border-spacing-css,
"padding: " % first2 max 2 /i # "px; " % ;
: table-style ( style -- str )
[
table-border [ border-css, ] apply-style
table-gap [ border-spacing-css, ] apply-style
] make-css ;
: table-attrs ( style -- )
table-style " border-collapse: collapse;" append =style ;
: do-escaping ( string style -- string )
html swap at [ escape-string ] unless ;
PRIVATE>
! Stream protocol
M: html-stream stream-write1 ( char stream -- )
>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 make-span-stream ( style stream -- stream' )
html-span-stream <html-sub-stream> ;
M: html-stream stream-format ( str style stream -- )
>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-write-table ( grid style stream -- )
a-div [
<table dup table-attrs table> swap [
<tr> [
<td "top" =valign swap table-style =style td>
>string write-html
</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 ;
! 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> ;

View File

@ -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 inspector ;
IN: html.streams.tests
: make-html-string
[ with-html-stream ] with-string-writer ; inline
@ -69,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

195
extra/html/streams/streams.factor Executable file
View File

@ -0,0 +1,195 @@
! 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 accessors ;
IN: html.streams
GENERIC: browser-link-href ( presented -- href )
M: object browser-link-href drop f ;
TUPLE: html-stream 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 )
f >>last-div ; inline
: a-div ( stream -- straem )
t >>last-div ; inline
: <html-stream> ( stream -- stream )
f html-stream boa ;
<PRIVATE
TUPLE: html-sub-stream < html-stream style parent ;
: new-html-sub-stream ( style stream class -- stream )
new
512 <sbuf> >>stream
swap >>parent
swap >>style ; inline
: end-sub-stream ( substream -- string style stream )
[ stream>> >string ] [ style>> ] [ parent>> ] tri ;
: object-link-tag ( style quot -- )
presented pick at [
browser-link-href [
<a =href a> call </a>
] [ call ] if*
] [ call ] if* ; inline
: hex-color, ( triplet -- )
3 head-slice
[ 255 * >fixnum >hex 2 CHAR: 0 pad-left % ] each ;
: fg-css, ( color -- )
"color: #" % hex-color, "; " % ;
: bg-css, ( color -- )
"background-color: #" % hex-color, "; " % ;
: style-css, ( flag -- )
dup
{ italic bold-italic } member?
"font-style: " % "italic" "normal" ? % "; " %
{ bold bold-italic } member?
"font-weight: " % "bold" "normal" ? % "; " % ;
: size-css, ( size -- )
"font-size: " % # "pt; " % ;
: font-css, ( font -- )
"font-family: " % % "; " % ;
: apply-style ( style key quot -- style gadget )
>r over at r> when* ; inline
: make-css ( style quot -- str )
"" make nip ; inline
: span-css-style ( style -- str )
[
foreground [ fg-css, ] apply-style
background [ bg-css, ] apply-style
font [ font-css, ] apply-style
font-style [ style-css, ] apply-style
font-size [ size-css, ] apply-style
] make-css ;
: span-tag ( style quot -- )
over span-css-style dup empty? [
drop call
] [
<span =style span> call </span>
] if ; inline
: format-html-span ( string style stream -- )
stream>> [
[ [ drop write ] span-tag ] object-link-tag
] with-output-stream* ;
TUPLE: html-span-stream < html-sub-stream ;
M: html-span-stream dispose
end-sub-stream not-a-div format-html-span ;
: border-css, ( border -- )
"border: 1px solid #" % hex-color, "; " % ;
: padding-css, ( padding -- ) "padding: " % # "px; " % ;
: pre-css, ( margin -- )
[ "white-space: pre; font-family: monospace; " % ] unless ;
: div-css-style ( style -- str )
[
page-color [ bg-css, ] apply-style
border-color [ border-css, ] apply-style
border-width [ padding-css, ] apply-style
wrap-margin over at pre-css,
] make-css ;
: div-tag ( style quot -- )
swap div-css-style dup empty? [
drop call
] [
<div =style div> call </div>
] if ; inline
: format-html-div ( string style stream -- )
stream>> [
[ [ write ] div-tag ] object-link-tag
] with-output-stream* ;
TUPLE: html-block-stream < html-sub-stream ;
M: html-block-stream dispose ( quot style stream -- )
end-sub-stream a-div format-html-div ;
: border-spacing-css,
"padding: " % first2 max 2 /i # "px; " % ;
: table-style ( style -- str )
[
table-border [ border-css, ] apply-style
table-gap [ border-spacing-css, ] apply-style
] make-css ;
: table-attrs ( style -- )
table-style " border-collapse: collapse;" append =style ;
: do-escaping ( string style -- string )
html swap at [ escape-string ] unless ;
PRIVATE>
! Stream protocol
M: html-stream stream-flush
stream>> stream-flush ;
M: html-stream stream-write1
>r 1string r> stream-write ;
M: html-stream stream-write
not-a-div >r escape-string r> stream>> stream-write ;
M: html-stream stream-format
>r html over at [ >r escape-string r> ] unless r>
format-html-span ;
M: html-stream stream-nl
dup last-div? [ drop ] [ [ <br/> ] with-output-stream* ] if ;
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>
stream>> >string write
</td>
] with each </tr>
] with each </table>
] with-output-stream* ;
M: html-stream dispose stream>> dispose ;
: with-html-stream ( quot -- )
output-stream get <html-stream> swap with-output-stream* ; inline

View File

@ -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; }

View File

@ -0,0 +1,161 @@
USING: html.templates html.templates.chloe
tools.test io.streams.string kernel sequences ascii boxes
namespaces xml html.components
splitting unicode.categories ;
IN: html.templates.chloe.tests
[ f ] [ f parse-query-attr ] unit-test
[ f ] [ "" parse-query-attr ] unit-test
[ H{ { "a" "b" } } ] [
blank-values
"b" "a" set-value
"a" parse-query-attr
] unit-test
[ H{ { "a" "b" } { "c" "d" } } ] [
blank-values
"b" "a" set-value
"d" "c" set-value
"a,c" parse-query-attr
] unit-test
: run-template
with-string-writer [ "\r\n\t" member? not ] filter
"?>" split1 nip ; inline
: test-template ( name -- template )
"resource:extra/html/templates/chloe/test/"
swap
".xml" 3append <chloe> ;
[ "Hello world" ] [
[
"test1" test-template call-template
] run-template
] unit-test
[ "Blah blah" "Hello world" ] [
[
<box> title set
[
"test2" test-template call-template
] run-template
title get box>
] with-scope
] unit-test
[ "<html><head><title>Hello world</title></head><body>Blah blah</body></html>" ] [
[
[
"test2" test-template call-template
] "test3" test-template with-boilerplate
] run-template
] unit-test
: test4-aux? t ;
[ "True" ] [
[
"test4" test-template call-template
] run-template
] unit-test
: test5-aux? f ;
[ "" ] [
[
"test5" test-template call-template
] run-template
] unit-test
SYMBOL: test6-aux?
[ "True" ] [
[
test6-aux? on
"test6" test-template call-template
] run-template
] unit-test
SYMBOL: test7-aux?
[ "" ] [
[
test7-aux? off
"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
[ ] [ { 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

View File

@ -3,17 +3,17 @@
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 math
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.components
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
@ -52,8 +52,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 +92,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 +124,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 +144,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 +168,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 ;
@ -208,30 +196,109 @@ 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) ;
: (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 ;
: 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>> dup "name" =
[ 2drop ] [ , set-at ] if
] 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 ] }
{ "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
{ "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 ] }
{ "even" [ even-tag ] }
{ "odd" [ odd-tag ] }
{ "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 ] }
[ "Unknown chloe tag: " swap append throw ]
[ "Unknown chloe tag: " prepend throw ]
} case ;
: process-tag ( tag -- )

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>> short. ":" print nl ]
[ error>> error. ]
bi ;
: call-template ( template -- )
[ call-template* ] [ \ template-error boa rethrow ] 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 ;

View File

@ -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 ;

View File

@ -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

View File

@ -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

View File

@ -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 ;

View File

@ -1,68 +1,94 @@
! 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 math ;
IN: http.server.actions
SYMBOL: params
SYMBOL: validation-message
SYMBOL: rest-param
: 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 rest-param 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 ;
: 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
'[
, [ 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
, 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 )
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 ;

View File

@ -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 ;

View File

@ -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>

View File

@ -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>

View File

@ -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 ;

View File

@ -10,12 +10,12 @@
<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>
@ -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>

View File

@ -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,122 @@ M: user-saver dispose
3append <chloe> ;
! ! ! Login
: <login-form>
"login" <form>
"login" login-template >>edit-template
"username" <username>
t >>required
add-field
"password" <password>
t >>required
add-field ;
: successful-login ( user -- response )
username>> set-uid
"$login" end-flow ;
username>> set-uid "$login" end-flow ;
: login-failed "invalid username or password" validation-failed-with ;
: login-failed ( -- * )
"invalid username or password" validation-error
validation-failed ;
:: <login-action> ( -- action )
[let | form [ <login-form> ] |
<action>
[ blank-values ] >>init
: <login-action> ( -- action )
<action>
[ "login" login-template <html-content> ] >>display
[ form edit-form ] >>display
[
{
{ "username" [ v-required ] }
{ "password" [ v-required ] }
} validate-params
[
blank-values
form validate-form
"password" value "username" value check-login
[ successful-login ] [ login-failed ] if*
] >>submit
] ;
"password" value
"username" value check-login
[ successful-login ] [ login-failed ] if*
] >>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 +222,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 )

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -8,62 +8,62 @@
<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>
<p>
<input type="submit" value="Register" />
<t:validation-message />
<t:validation-messages />
</p>

View File

@ -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 ;

View File

@ -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" = [

View File

@ -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
@ -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

View File

@ -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 ;

View File

@ -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

View File

@ -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 ;

View File

@ -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 ;

Some files were not shown because too many files have changed in this diff Show More