198 lines
4.9 KiB
Factor
198 lines
4.9 KiB
Factor
USING: accessors kernel sequences combinators kernel namespaces
|
|
classes.tuple assocs splitting words arrays
|
|
io io.files io.encodings.utf8 html.elements unicode.case
|
|
tuple-syntax xml xml.data xml.writer xml.utilities
|
|
http.server
|
|
http.server.auth
|
|
http.server.components
|
|
http.server.sessions
|
|
http.server.templating
|
|
http.server.boilerplate ;
|
|
IN: http.server.templating.chloe
|
|
|
|
! Chloe is Ed's favorite web designer
|
|
|
|
TUPLE: chloe path ;
|
|
|
|
C: <chloe> chloe
|
|
|
|
DEFER: process-template
|
|
|
|
: chloe-ns TUPLE{ name url: "http://factorcode.org/chloe/1.0" } ;
|
|
|
|
: chloe-tag? ( tag -- ? )
|
|
{
|
|
{ [ dup tag? not ] [ f ] }
|
|
{ [ dup chloe-ns names-match? not ] [ f ] }
|
|
[ t ]
|
|
} cond nip ;
|
|
|
|
SYMBOL: tags
|
|
|
|
: required-attr ( tag name -- value )
|
|
dup rot at*
|
|
[ nip ] [ drop " attribute is required" append throw ] if ;
|
|
|
|
: optional-attr ( tag name -- value )
|
|
swap at ;
|
|
|
|
: write-title-tag ( tag -- )
|
|
drop
|
|
"head" tags get member? "title" tags get member? not and
|
|
[ <title> write-title </title> ] [ write-title ] if ;
|
|
|
|
: style-tag ( tag -- )
|
|
dup "include" optional-attr dup [
|
|
swap children>string empty? [
|
|
"style tag cannot have both an include attribute and a body" throw
|
|
] unless
|
|
utf8 file-contents
|
|
] [
|
|
drop children>string
|
|
] if add-style ;
|
|
|
|
: write-style-tag ( tag -- )
|
|
drop <style> write-style </style> ;
|
|
|
|
: atom-tag ( tag -- )
|
|
[ "title" required-attr ]
|
|
[ "href" required-attr ]
|
|
bi set-atom-feed ;
|
|
|
|
: write-atom-tag ( tag -- )
|
|
drop
|
|
"head" tags get member? [
|
|
write-atom-feed
|
|
] [
|
|
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 ;
|
|
|
|
: a-start-tag ( tag -- )
|
|
<a
|
|
dup "value" optional-attr [ value f ] [
|
|
[ "href" required-attr ]
|
|
[ "query" optional-attr parse-query-attr ]
|
|
bi
|
|
] ?if link>string =href
|
|
a> ;
|
|
|
|
: process-tag-children ( tag -- )
|
|
[ process-template ] each ;
|
|
|
|
: a-tag ( tag -- )
|
|
[ a-start-tag ]
|
|
[ process-tag-children ]
|
|
[ drop </a> ]
|
|
tri ;
|
|
|
|
: form-start-tag ( tag -- )
|
|
<form
|
|
"POST" =method
|
|
[ "action" required-attr resolve-base-path =action ]
|
|
[ tag-attrs [ drop name-tag "action" = not ] assoc-subset print-attrs ] bi
|
|
form>
|
|
hidden-form-field ;
|
|
|
|
: form-tag ( tag -- )
|
|
[ form-start-tag ]
|
|
[ process-tag-children ]
|
|
[ drop </form> ]
|
|
tri ;
|
|
|
|
: attr>word ( value -- word/f )
|
|
dup ":" split1 swap lookup
|
|
[ ] [ "No such word: " swap append throw ] ?if ;
|
|
|
|
: attr>var ( value -- word/f )
|
|
attr>word dup symbol? [
|
|
"Must be a symbol: " swap append throw
|
|
] unless ;
|
|
|
|
: if-satisfied? ( tag -- ? )
|
|
{
|
|
[ "code" optional-attr [ attr>word execute ] [ t ] if* ]
|
|
[ "var" optional-attr [ attr>var get ] [ t ] if* ]
|
|
[ "svar" optional-attr [ attr>var sget ] [ t ] if* ]
|
|
[ "uvar" optional-attr [ attr>var uget ] [ t ] if* ]
|
|
} cleave 4array [ ] all? ;
|
|
|
|
: if-tag ( tag -- )
|
|
dup if-satisfied? [ process-tag-children ] [ drop ] if ;
|
|
|
|
: error-tag ( tag -- )
|
|
children>string render-error ;
|
|
|
|
: process-chloe-tag ( tag -- )
|
|
dup name-tag {
|
|
{ "chloe" [ [ process-template ] each ] }
|
|
{ "title" [ children>string set-title ] }
|
|
{ "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 ] }
|
|
{ "a" [ a-tag ] }
|
|
{ "form" [ form-tag ] }
|
|
{ "error" [ error-tag ] }
|
|
{ "if" [ if-tag ] }
|
|
{ "comment" [ drop ] }
|
|
{ "call-next-template" [ drop call-next-template ] }
|
|
[ "Unknown chloe tag: " swap append throw ]
|
|
} case ;
|
|
|
|
: process-tag ( tag -- )
|
|
{
|
|
[ name-tag >lower tags get push ]
|
|
[ write-start-tag ]
|
|
[ process-tag-children ]
|
|
[ write-end-tag ]
|
|
[ drop tags get pop* ]
|
|
} cleave ;
|
|
|
|
: process-template ( xml -- )
|
|
{
|
|
{ [ dup [ chloe-tag? ] is? ] [ process-chloe-tag ] }
|
|
{ [ dup [ tag? ] is? ] [ process-tag ] }
|
|
{ [ t ] [ write-item ] }
|
|
} cond ;
|
|
|
|
: process-chloe ( xml -- )
|
|
[
|
|
V{ } clone tags set
|
|
|
|
nested-template? get [
|
|
process-template
|
|
] [
|
|
{
|
|
[ xml-prolog write-prolog ]
|
|
[ xml-before write-chunk ]
|
|
[ process-template ]
|
|
[ xml-after write-chunk ]
|
|
} cleave
|
|
] if
|
|
] with-scope ;
|
|
|
|
M: chloe call-template*
|
|
path>> utf8 <file-reader> read-xml process-chloe ;
|
|
|
|
INSTANCE: chloe template
|