Generic (X)HTML generation framework

(Will later move into the scribble collection.)
This commit is contained in:
Eli Barzilay 2010-06-04 14:11:19 -04:00
parent 6197de9a09
commit 9a0b78d9ad
6 changed files with 838 additions and 0 deletions

6
collects/meta/web/README Normal file
View File

@ -0,0 +1,6 @@
* html: generic shared code for (X)HTML generation (tag output and
resource rendering and referring).
* common: specific code for the PLT web pages.
* other stuff: actual content.

View File

@ -0,0 +1,4 @@
#lang racket/base
(require "html/main.rkt")
(provide (all-from-out "html/main.rkt"))

View File

@ -0,0 +1,462 @@
#lang racket/base
;; (X)HTML elements etc.
(require "xml.rkt" scribble/text)
;; ----------------------------------------------------------------------------
;; Xhtml toplevel
;; creation of xhtml files requires some extra stuff
(define xhtml-prefix
(literal
(string-append
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n")))
(provide xhtml)
(define (xhtml . body)
(list xhtml-prefix
(apply html 'xmlns: "http://www.w3.org/1999/xhtml" body)
"\n"))
;; ----------------------------------------------------------------------------
;; Elements
;; For complete reference: http://www.w3.org/TR/html/dtds.html
;; (See also http://www.w3schools.com/tags/)
;; The dtds, in increasing size:
;; http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd
;; http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
;; http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd
;; These are all entities, taked from the DTDs. The ones marked with "[*]" are
;; defined later, since they need a different definition.
(define/provide-elements/not-empty
;; ========== Document Structure
html
;; ========== Document Head
head
;; The title element is not considered part of the flow of text.
;; It should be displayed, for example as the page header or
;; window title. Exactly one title is required per document.
title
;; base ; document base URI, can be empty [*]
;; meta ; generic metainformation, can be empty [*]
;; link ; relationship values, can be empty [*]
style ; style info, which may include CDATA sections
script ; script statements, which may include CDATA sections
noscript ; alternate content container for non script-based rendering
;; ========== Frames
frameset ; only one noframes element permitted per document
frame ; tiled window within frameset
iframe ; inline subwindow
noframes ; alternate content container for non frame-based rendering
;; ========== Document Body
body
div ; generic language/style container
;; ========== Paragraphs
p
;; ========== Headings
h1
h2
h3
h4
h5
h6
;; ========== Lists
ul ; Unordered list
ol ; Ordered (numbered) list
menu ; single column list (DEPRECATED)
dir ; multiple column list (DEPRECATED)
li ; list item
dl ; definition lists - dt for term, dd for its definition
dt
dd
;; ========== Address
address ; information on author
;; ========== Horizontal Rule
;; hr ; horizontal rule can be empty [*]
;; ========== Preformatted Text
pre
;; ========== Block-like Quotes
blockquote
;; ========== Text alignment
center ; center content
;; ========== Inserted/Deleted Text
ins
del
;; ========== The Anchor Element
a ; content is inline; except that anchors shouldn't be nested
;; ========== Inline Elements
span ; generic language/style container
bdo ; I18N BiDi over-ride
;; br ; forced line break, can be empty [*]
em ; emphasis
strong ; strong emphasis
dfn ; definitional
code ; program code
samp ; sample
kbd ; something user would type
var ; variable
cite ; citation
abbr ; abbreviation
acronym ; acronym
q ; inlined quote
sub ; subscript
sup ; superscript
tt ; fixed pitch font
i ; italic font
b ; bold font
big ; bigger font
small ; smaller font
u ; underline
s ; strike-through
strike ; strike-through
;; basefont ; base font size, can be empty [*]
font ; local change to font
;; ========== Object
object ; embeded objects
;; param ; parameters for objects, can also specify as attrs, can be empty [*]
applet ; Java applet
;; ========== Images
;; To avoid accessibility problems for people who aren't
;; able to see the image, you should provide a text
;; description using the alt and longdesc attributes.
;; In addition, avoid the use of server-side image maps.
;; img ; can be empty [*]
;; ========== Client-side image maps
;; map ; collides with scheme, but not really useful
;; area ; can be empty [*]
;; ========== Forms
form ; forms shouldn't be nested
label ; text that belongs to a form control
;; input ; form control, can be empty [*]
select ; option selector
optgroup ; option group
option ; selectable choice
textarea ; multi-line text field
fieldset ; group form fields
legend ; fieldset label (one per fieldset)
button ; push button
;; isindex ; single-line text input control (DEPRECATED), can be empty [*]
;; ========== Tables
table ; holds caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+)
caption ; caption text
thead ; header part, holds tr
tfoot ; footer part, holds tr
tbody ; body part, holds tr
colgroup ; column group, olds col
;; col ; column info, has only attributes, can be empty [*]
tr ; holds th or td
th ; header cell
td ; table cell
)
;; [*] empty elements, these are listed with an `EMPTY' content in
;; http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
(define/provide-elements/empty
base meta link hr br basefont param img area input isindex col)
;; [*] elements with a cdata/comment body
(provide script/inline)
(define (script/inline . args)
(let-values ([(attrs body) (attributes+body args)])
(make-element 'script attrs
`("\n"
,(set-prefix 0 (apply cdata #:line-prefix "//" body))
"\n"))))
(provide style/inline)
(define (style/inline . args)
(let-values ([(attrs body) (attributes+body args)])
(make-element 'style attrs
`("\n" ,(apply comment #:newlines? #t body) "\n"))))
;; ----------------------------------------------------------------------------
;; Entities
;; The three dtds that define the set of entities are at:
;; http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
;; http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
;; http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
(define/provide-entities
nbsp ndash mdash bull middot sdot lsquo rsquo sbquo ldquo rdquo bdquo
lang rang dagger Dagger plusmn deg)
#; ; the complete list
(define/provide-entities
;; 24.2 Character entity references for ISO 8859-1 characters
nbsp ;00A0 no-break space = non-breaking space
iexcl ;00A1 inverted exclamation mark
cent ;00A2 cent sign
pound ;00A3 pound sign
curren ;00A4 currency sign
yen ;00A5 yen sign = yuan sign
brvbar ;00A6 broken bar = broken vertical bar
sect ;00A7 section sign
uml ;00A8 diaeresis = spacing diaeresis
copy ;00A9 copyright sign
ordf ;00AA feminine ordinal indicator
laquo ;00AB left-pointing double angle quotation mark = left pointing guillemet
not ;00AC not sign
shy ;00AD soft hyphen = discretionary hyphen
reg ;00AE registered sign = registered trade mark sign
macr ;00AF macron = spacing macron = overline = APL overbar
deg ;00B0 degree sign
plusmn ;00B1 plus-minus sign = plus-or-minus sign
sup2 ;00B2 superscript two = superscript digit two = squared
sup3 ;00B3 superscript three = superscript digit three = cubed
acute ;00B4 acute accent = spacing acute
micro ;00B5 micro sign
para ;00B6 pilcrow sign = paragraph sign
middot ;00B7 middle dot = Georgian comma = Greek middle dot
cedil ;00B8 cedilla = spacing cedilla
sup1 ;00B9 superscript one = superscript digit one
ordm ;00BA masculine ordinal indicator
raquo ;00BB right-pointing double angle quotation mark = right pointing guillemet
frac14 ;00BC vulgar fraction one quarter = fraction one quarter
frac12 ;00BD vulgar fraction one half = fraction one half
frac34 ;00BE vulgar fraction three quarters = fraction three quarters
iquest ;00BF inverted question mark = turned question mark
Agrave ;00C0 latin capital letter A with grave = latin capital letter A grave
Aacute ;00C1 latin capital letter A with acute
Acirc ;00C2 latin capital letter A with circumflex
Atilde ;00C3 latin capital letter A with tilde
Auml ;00C4 latin capital letter A with diaeresis
Aring ;00C5 latin capital letter A with ring above = latin capital letter A ring
AElig ;00C6 latin capital letter AE = latin capital ligature AE
Ccedil ;00C7 latin capital letter C with cedilla
Egrave ;00C8 latin capital letter E with grave
Eacute ;00C9 latin capital letter E with acute
Ecirc ;00CA latin capital letter E with circumflex
Euml ;00CB latin capital letter E with diaeresis
Igrave ;00CC latin capital letter I with grave
Iacute ;00CD latin capital letter I with acute
Icirc ;00CE latin capital letter I with circumflex
Iuml ;00CF latin capital letter I with diaeresis
ETH ;00D0 latin capital letter ETH
Ntilde ;00D1 latin capital letter N with tilde
Ograve ;00D2 latin capital letter O with grave
Oacute ;00D3 latin capital letter O with acute
Ocirc ;00D4 latin capital letter O with circumflex
Otilde ;00D5 latin capital letter O with tilde
Ouml ;00D6 latin capital letter O with diaeresis
times ;00D7 multiplication sign
Oslash ;00D8 latin capital letter O with stroke = latin capital letter O slash
Ugrave ;00D9 latin capital letter U with grave
Uacute ;00DA latin capital letter U with acute
Ucirc ;00DB latin capital letter U with circumflex
Uuml ;00DC latin capital letter U with diaeresis
Yacute ;00DD latin capital letter Y with acute
THORN ;00DE latin capital letter THORN
szlig ;00DF latin small letter sharp s = ess-zed
agrave ;00E0 latin small letter a with grave = latin small letter a grave
aacute ;00E1 latin small letter a with acute
acirc ;00E2 latin small letter a with circumflex
atilde ;00E3 latin small letter a with tilde
auml ;00E4 latin small letter a with diaeresis
aring ;00E5 latin small letter a with ring above = latin small letter a ring
aelig ;00E6 latin small letter ae = latin small ligature ae
ccedil ;00E7 latin small letter c with cedilla
egrave ;00E8 latin small letter e with grave
eacute ;00E9 latin small letter e with acute
ecirc ;00EA latin small letter e with circumflex
euml ;00EB latin small letter e with diaeresis
igrave ;00EC latin small letter i with grave
iacute ;00ED latin small letter i with acute
icirc ;00EE latin small letter i with circumflex
iuml ;00EF latin small letter i with diaeresis
eth ;00F0 latin small letter eth
ntilde ;00F1 latin small letter n with tilde
ograve ;00F2 latin small letter o with grave
oacute ;00F3 latin small letter o with acute
ocirc ;00F4 latin small letter o with circumflex
otilde ;00F5 latin small letter o with tilde
ouml ;00F6 latin small letter o with diaeresis
divide ;00F7 division sign
oslash ;00F8 latin small letter o with stroke, = latin small letter o slash
ugrave ;00F9 latin small letter u with grave
uacute ;00FA latin small letter u with acute
ucirc ;00FB latin small letter u with circumflex
uuml ;00FC latin small letter u with diaeresis
yacute ;00FD latin small letter y with acute
thorn ;00FE latin small letter thorn
yuml ;00FF latin small letter y with diaeresis
;; 24.3 Character entity references for symbols, mathematical symbols, and
;; Greek letters
;; Latin Extended-B
fnof ;0192 latin small f with hook = function = florin
;; Greek
Alpha ;0391 greek capital letter alpha
Beta ;0392 greek capital letter beta
Gamma ;0393 greek capital letter gamma
Delta ;0394 greek capital letter delta
Epsilon ;0395 greek capital letter epsilon
Zeta ;0396 greek capital letter zeta
Eta ;0397 greek capital letter eta
Theta ;0398 greek capital letter theta
Iota ;0399 greek capital letter iota
Kappa ;039A greek capital letter kappa
Lambda ;039B greek capital letter lambda
Mu ;039C greek capital letter mu
Nu ;039D greek capital letter nu
Xi ;039E greek capital letter xi
Omicron ;039F greek capital letter omicron
Pi ;03A0 greek capital letter pi
Rho ;03A1 greek capital letter rho
Sigma ;03A3 greek capital letter sigma
Tau ;03A4 greek capital letter tau
Upsilon ;03A5 greek capital letter upsilon
Phi ;03A6 greek capital letter phi
Chi ;03A7 greek capital letter chi
Psi ;03A8 greek capital letter psi
Omega ;03A9 greek capital letter omega
alpha ;03B1 greek small letter alpha
beta ;03B2 greek small letter beta
gamma ;03B3 greek small letter gamma
delta ;03B4 greek small letter delta
epsilon ;03B5 greek small letter epsilon
zeta ;03B6 greek small letter zeta
eta ;03B7 greek small letter eta
theta ;03B8 greek small letter theta
iota ;03B9 greek small letter iota
kappa ;03BA greek small letter kappa
lambda ;03BB greek small letter lambda
mu ;03BC greek small letter mu
nu ;03BD greek small letter nu
xi ;03BE greek small letter xi
omicron ;03BF greek small letter omicron
pi ;03C0 greek small letter pi
rho ;03C1 greek small letter rho
sigmaf ;03C2 greek small letter final sigma
sigma ;03C3 greek small letter sigma
tau ;03C4 greek small letter tau
upsilon ;03C5 greek small letter upsilon
phi ;03C6 greek small letter phi
chi ;03C7 greek small letter chi
psi ;03C8 greek small letter psi
omega ;03C9 greek small letter omega
thetasym ;03D1 greek small letter theta symbol
upsih ;03D2 greek upsilon with hook symbol
piv ;03D6 greek pi symbol
;; *** General Punctuation
bull ;2022 bullet = black small circle
hellip ;2026 horizontal ellipsis = three dot leader
prime ;2032 prime = minutes = feet
Prime ;2033 double prime = seconds = inches
oline ;203E overline = spacing overscore
frasl ;2044 fraction slash
;; *** Letterlike Symbols
weierp ;2118 script capital P = power set = Weierstrass p
image ;2111 blackletter capital I = imaginary part
real ;211C blackletter capital R = real part symbol
trade ;2122 trade mark sign
alefsym ;2135 alef symbol = first transfinite cardinal
;; *** Arrows
larr ;2190 leftwards arrow
uarr ;2191 upwards arrow
rarr ;2192 rightwards arrow
darr ;2193 downwards arrow
harr ;2194 left right arrow
crarr ;21B5 downwards arrow with corner leftwards = carriage return
lArr ;21D0 leftwards double arrow
uArr ;21D1 upwards double arrow
rArr ;21D2 rightwards double arrow
dArr ;21D3 downwards double arrow
hArr ;21D4 left right double arrow
;; Mathematical Operators
forall ;2200 for all
part ;2202 partial differential
exist ;2203 there exists
empty ;2205 empty set = null set = diameter
nabla ;2207 nabla = backward difference
isin ;2208 element of
notin ;2209 not an element of
ni ;220B contains as member
prod ;220F n-ary product = product sign
sum ;2211 n-ary sumation
minus ;2212 minus sign
lowast ;2217 asterisk operator
radic ;221A square root = radical sign
prop ;221D proportional to
infin ;221E infinity
ang ;2220 angle
and ;2227 logical and = wedge
or ;2228 logical or = vee
cap ;2229 intersection = cap
cup ;222A union = cup
int ;222B integral
there4 ;2234 therefore
sim ;223C tilde operator = varies with = similar to
cong ;2245 approximately equal to
asymp ;2248 almost equal to = asymptotic to
ne ;2260 not equal to
equiv ;2261 identical to
le ;2264 less-than or equal to
ge ;2265 greater-than or equal to
sub ;2282 subset of
sup ;2283 superset of
nsub ;2284 not a subset of
sube ;2286 subset of or equal to
supe ;2287 superset of or equal to
oplus ;2295 circled plus = direct sum
otimes ;2297 circled times = vector product
perp ;22A5 up tack = orthogonal to = perpendicular
sdot ;22C5 dot operator
;; Miscellaneous Technical
lceil ;2308 left ceiling = apl upstile
rceil ;2309 right ceiling
lfloor ;230A left floor = apl downstile
rfloor ;230B right floor
lang ;2329 left-pointing angle bracket = bra
rang ;232A right-pointing angle bracket = ket
;; Geometric Shapes
loz ;25CA lozenge
;; Miscellaneous Symbols
spades ;2660 black spade suit
clubs ;2663 black club suit = shamrock
hearts ;2665 black heart suit = valentine
diams ;2666 black diamond suit
;; 24.4 Character entity references for markup-significant and
;; internationalization characters
;; C0 Controls and Basic Latin
quot ;0022 quotation mark = APL quote
amp ;0026 ampersand
lt ;003C less-than sign
gt ;003E greater-than sign
;; Latin Extended-A
OElig ;0152 latin capital ligature OE
oelig ;0153 latin small ligature oe
Scaron ;0160 latin capital letter S with caron
scaron ;0161 latin small letter s with caron
Yuml ;0178 latin capital letter Y with diaeresis
;; Spacing Modifier Letters
circ ;02C6 modifier letter circumflex accent
tilde ;02DC small tilde
;; General Punctuation
ensp ;2002 en space
emsp ;2003 em space
thinsp ;2009 thin space
zwnj ;200C zero width non-joiner
zwj ;200D zero width joiner
lrm ;200E left-to-right mark
rlm ;200F right-to-left mark
ndash ;2013 en dash
mdash ;2014 em dash
lsquo ;2018 left single quotation mark
rsquo ;2019 right single quotation mark
sbquo ;201A single low-9 quotation mark
ldquo ;201C left double quotation mark
rdquo ;201D right double quotation mark
bdquo ;201E double low-9 quotation mark
dagger ;2020 dagger
Dagger ;2021 double dagger
permil ;2030 per mille sign
lsaquo ;2039 single left-pointing angle quotation mark
rsaquo ;203A single right-pointing angle quotation mark
euro ;20AC euro sign
)

View File

@ -0,0 +1,21 @@
#lang racket/base
(provide (except-out (all-from-out racket/base) #%top)
(rename-out [top #%top])
;; to be used as a text language
(all-from-out scribble/text)
;; provide a `text' alias
(rename-out [begin/text text])
;; main functionality
(all-from-out "xml.rkt" "html.rkt" "resource.rkt"))
(require "xml.rkt" "html.rkt" "resource.rkt"
scribble/text (for-syntax racket/base))
(define-syntax (top stx)
(syntax-case stx ()
[(_ . x)
(let ([x* (syntax-e #'x)])
(if (and (symbol? x*) (regexp-match? #rx":$" (symbol->string x*)))
#''x
#'(#%top . x)))]))

View File

@ -0,0 +1,198 @@
#lang racket/base
;; Resources are referrable & renderable objects, (most are html pages)
;; (resource path renderer referrer) creates and returns a new "resource"
;; value. The arguments are:
;; - `path': the path of the output file, relative to the working directory,
;; indicating where the resource file should be put at, also corresponding to
;; the URL it will be found at. It must be a `/'-separated relative string,
;; no `..', `.', or `//', and it can end in `/' (which will turn to
;; "index.html").
;; - `renderer': a unary function that renders the resource, receiving the path
;; for the file to be created as an argument. This path will be different
;; than the `path' argument because this function is invoked in the target
;; directory.
;; - `referrer': a function accepting one or more arguments (and possibly
;; keywords) that produces a value to be used to refer to this resource
;; (using `a', `img', etc). The first value that will be passed to this
;; function will be the actual URL path, which depends on the currently
;; rendered page path -- the argument will be relative to it.
;; The resulting resource value is actually a rendering function that is
;; similar to the `referrer', except without the first URL argument -- when it
;; is called, it invokes the `referrer' function with the actual (relativized)
;; URL. Creating a resource registers the `renderer' to be executed when
;; rendering is initiated. Note that more resources can be created while
;; rendering; they will also be rendered in turn until no more resources are
;; created.
(require racket/list racket/string scribble/text "xml.rkt")
;; default file, urls to it will point to its directory instead, and a
;; /-suffixed path will render to this file
(define default-file "index.html")
;; the currently rendered directory, as a list
(define rendered-dirpath (make-parameter '()))
;; a mapping from path prefixes to urls (actually, any string) -- when two
;; paths are in the same prefix, links from one to the other are relative, but
;; if they're in different prefixes, the url will be used instead; the roots
;; are expected to be disjoint (= no "/foo" and "/foo/bar" roots)
(provide url-roots)
(define url-roots
;; takes in a (listof (list prefix-string url-string)), and produces an alist
;; with lists of strings for the keys; the prefix-strings are split on "/"s,
;; and the url-strings can be anything at all actually (they are put as-is
;; before the path with a "/" between them)
(make-parameter #f
(lambda (x)
(and (list? x) (pair? x)
(map (lambda (x)
(cons (regexp-match* #rx"[^/]+" (car x))
(regexp-replace #rx"/$" (cadr x) "")))
x)))))
;; a utility for relative paths, taking the above `default-file' and
;; `url-roots' into consideration.
(define (relativize file tgtdir curdir)
(define file* (if (equal? file default-file) "" file))
(define roots (url-roots))
(define (make-rooted path)
(ormap (lambda (root+url)
(let loop ([r (car root+url)] [p path])
(if (null? r)
`(,(cdr root+url) ,@p ,file*)
(and (pair? p) (loop (cdr r) (cdr p))))))
roots))
(define result
(let loop ([t tgtdir] [c curdir] [pfx '()])
(cond
;; find shared prefix
[(and (pair? t) (pair? c) (equal? (car t) (car c)))
(loop (cdr t) (cdr c) (cons (car t) pfx))]
;; done
[(or (not roots) ; if there are no roots
(make-rooted (reverse pfx))) ; or if they share a root
;; then make them relative
`(,@(map (lambda (_) "..") c) ,@t ,file*)]
;; different roots => use the one for the target
[(make-rooted t)]
;; otherwise throw an error
[else (error 'relativize "target url is not in any known root: ~a"
(string-join `(,@tgtdir ,file*) "/"))])))
(if (equal? '("") result) "." (string-join result "/")))
#| tests
(require tests/eli-tester)
(define R relativize)
(let ()
(test do (test (R "bleh.txt" '() '() ) => "bleh.txt"
(R "bleh.txt" '("x") '() ) => "x/bleh.txt"
(R "bleh.txt" '("x" "y") '() ) => "x/y/bleh.txt"
(R "bleh.txt" '() '("x") ) => "../bleh.txt"
(R "bleh.txt" '("x") '("x") ) => "bleh.txt"
(R "bleh.txt" '("x" "y") '("x") ) => "y/bleh.txt"
(R "bleh.txt" '() '("x" "y")) => "../../bleh.txt"
(R "bleh.txt" '("x") '("x" "y")) => "../bleh.txt"
(R "bleh.txt" '("x" "y") '("x" "y")) => "bleh.txt"
(R "bleh.txt" '("x" "y") '("y" "x")) => "../../x/y/bleh.txt"
(R "index.html" '() '() ) => "."
(R "index.html" '("x") '() ) => "x/"
(R "index.html" '("x" "y") '() ) => "x/y/"
(R "index.html" '() '("x") ) => "../"
(R "index.html" '("x") '("x") ) => "."
(R "index.html" '("x" "y") '("x") ) => "y/"
(R "index.html" '() '("x" "y")) => "../../"
(R "index.html" '("x") '("x" "y")) => "../"
(R "index.html" '("x" "y") '("x" "y")) => "."
(R "index.html" '("x" "y") '("y" "x")) => "../../x/y/")
do (parameterize ([url-roots '(["/x" "/X/"] ["/y" "/Y/"])])
(test (R "bleh.txt" '() '() ) =error> "not in any"
(R "bleh.txt" '("x") '() ) => "/X/bleh.txt"
(R "bleh.txt" '("x" "y") '() ) => "/X/y/bleh.txt"
(R "bleh.txt" '() '("x") ) =error> "not in any"
(R "bleh.txt" '("x") '("x") ) => "bleh.txt"
(R "bleh.txt" '("x" "y") '("x") ) => "y/bleh.txt"
(R "bleh.txt" '() '("x" "y")) =error> "not in any"
(R "bleh.txt" '("x") '("x" "y")) => "../bleh.txt"
(R "bleh.txt" '("x" "y") '("x" "y")) => "bleh.txt"
(R "bleh.txt" '("x" "y") '("y" "x")) => "/X/y/bleh.txt"
(R "index.html" '() '() ) =error> "not in any"
(R "index.html" '("x") '() ) => "/X/"
(R "index.html" '("x" "y") '() ) => "/X/y/"
(R "index.html" '() '("x") ) =error> "not in any"
(R "index.html" '("x") '("x") ) => "."
(R "index.html" '("x" "y") '("x") ) => "y/"
(R "index.html" '() '("x" "y")) =error> "not in any"
(R "index.html" '("x") '("x" "y")) => "../"
(R "index.html" '("x" "y") '("x" "y")) => "."
(R "index.html" '("x" "y") '("y" "x")) => "/X/y/"))))
|#
;; utility for keeping a list of renderer thunks
(define-values [add-renderer get/reset-renderers]
(let ([l '()] [s (make-semaphore 1)])
;; map paths to #t -- used to avoid overwriting files
(define t (make-hash))
(define-syntax-rule (S body) (call-with-semaphore s (lambda () body)))
(values (lambda (path renderer)
(S (if (hash-ref t path #f)
(error 'resource "path used for two resources: ~e" path)
(begin (hash-set! t path #t) (set! l (cons renderer l))))))
(lambda () (S (begin0 (reverse l) (set! l '())))))))
;; `#:exists' determines what happens when the render destination exists, it
;; can be one of: #f (do nothing), 'delete-file (delete if a file exists, error
;; if exists as a directory)
(provide resource)
(define (resource path renderer referrer #:exists [exists 'delete-file])
(define (bad reason) (error 'resource "bad path, ~a: ~e" reason path))
(unless (string? path) (bad "must be a string"))
(for ([x (in-list '([#rx"^/" "must be relative"]
[#rx"//" "must not have empty elements"]
[#rx"(?:^|/)[.][.]?(?:/|$)"
"must not contain `.' or `..'"]))])
(when (regexp-match? (car x) path) (bad (cadr x))))
(let ([path (regexp-replace #rx"(?<=^|/)$" path default-file)])
(define-values (dirpathlist filename)
(let-values ([(l r) (split-at-right (regexp-split #rx"/" path) 1)])
(values l (car r))))
(define (render)
(let loop ([ps dirpathlist])
(if (pair? ps)
(begin (unless (directory-exists? (car ps))
(if (or (file-exists? (car ps)) (link-exists? (car ps)))
(bad "exists as a file/link")
(make-directory (car ps))))
(parameterize ([current-directory (car ps)])
(loop (cdr ps))))
(begin (cond [(not exists)] ; do nothing
[(or (file-exists? filename) (link-exists? filename))
(delete-file filename)]
[(directory-exists? filename)
(bad "exists as directory")])
(parameterize ([rendered-dirpath dirpathlist])
(printf " ~a\n" path)
(renderer filename))))))
(define (url) (relativize filename dirpathlist (rendered-dirpath)))
(add-renderer path render)
(make-keyword-procedure
(lambda (kws kvs . args) (keyword-apply referrer kws kvs (url) args))
(lambda args (apply referrer (url) args)))))
;; a convenient utility to create renderers from some output function (like
;; `output-xml' or `display') and some content
(provide file-writer)
(define ((file-writer writer content) file)
(call-with-output-file file (lambda (o) (writer content o))))
;; runs all renderers, and any renderers that might have been added on the way
(provide render-all)
(define (render-all)
(printf "Rendering...\n")
(let loop ()
(let ([todo (get/reset-renderers)])
(unless (null? todo)
(for-each (lambda (r) (r)) todo)
(loop)))) ; if more were created
(printf "Rendering done.\n"))

View File

@ -0,0 +1,147 @@
#lang racket/base
;; XML-like objects and functions, with rendering
(require scribble/text)
;; ----------------------------------------------------------------------------
;; Represent attribute names as `foo:' symbols. They are made self-quoting in
;; the language. A different option would be to use the usual racket keyword
;; arguments, but that tends to have problems like disallowing repeated uses of
;; the same keyword, sorting the keywords alphabetically, and ambiguity when
;; some keywords are meant to do the usual thing (customize a function) instead
;; of representing an attribute. It's more convenient to just have a separate
;; mechanism for this, so racket keywords are still used in the same way, and
;; orthogonal to specifying attributes. Another possibility is to have a new
;; type, with `foo:' evaluating to instances -- but it's often convenient to
;; pass them around as quoted lists.
(define attribute->symbol
(let ([t (make-weak-hasheq)])
(lambda (x)
(and (symbol? x)
(hash-ref! t x
(lambda ()
(let ([m (regexp-match #rx"^(.*):$" (symbol->string x))])
(and m (string->symbol (cadr m))))))))))
(provide attribute?)
(define attribute? attribute->symbol)
(provide attributes+body)
(define (attributes+body xs)
(let loop ([xs xs] [as '()])
(let ([a (and (pair? xs) (attribute->symbol (car xs)))])
(cond [(not a) (values (reverse as) xs)]
[(null? (cdr xs)) (error 'attriubtes+body
"missing attribute value for `~s:'" a)]
[else (loop (cddr xs) (cons (cons a (cadr xs)) as))]))))
;; ----------------------------------------------------------------------------
;; An output that handles xml quoting, customizable
;; TODO: make this more conveniently customizable and extensible
(define (write-string/xml-quote str p [start 0] [end (string-length str)])
(let loop ([start start])
(when (< start end)
(let ([m (regexp-match-positions #rx"[&<>\"]" str start end p)])
(when m
(write-string (case (string-ref str (caar m))
[(#\&) "&amp;"]
[(#\<) "&lt;"]
[(#\>) "&gt;"]
[(#\") "&quot;"])
p)
(loop (cdar m)))))))
(provide xml-writer)
(define xml-writer (make-parameter write-string/xml-quote))
(provide output-xml)
(define (output-xml content [p (current-output-port)])
(output (disable-prefix (with-writer (xml-writer) content)) p))
;; ----------------------------------------------------------------------------
;; Structs for xml data: elements, literals, entities
(provide make-element)
(define-struct element (tag attrs body [cache #:auto #:mutable])
#:transparent #:omit-define-syntaxes #:auto-value #f
#:property prop:procedure
(lambda (e)
(unless (element-cache e) (set-element-cache! e (element->output e)))
(element-cache e)))
(provide element)
(define (element tag . args)
(let-values ([(attrs body) (attributes+body args)])
(make-element tag attrs body)))
;; similar to element, but will always have a closing tag instead of using the
;; short syntax (see also `element->output' below)
(provide element/not-empty)
(define (element/not-empty tag . args)
(let-values ([(attrs body) (attributes+body args)])
(make-element tag attrs (if (null? body) '(#f) body))))
;; convert an element to something output-able
(define (element->output e)
(let ([tag (element-tag e)]
[attrs (element-attrs e)]
[body (element-body e)])
;; null body means a lone tag, tags that should always have a closer will
;; have a '(#f) as their body (see below)
(list (with-writer #f "<" tag)
(map (lambda (attr)
(let ([name (car attr)] [val (cdr attr)])
(cond [(not val) #f]
;; #t means just mention the attribute
[(eq? #t val) (with-writer #f (list " " name))]
[else (list (with-writer #f (list " " name "=\""))
val
(with-writer #f "\""))])))
attrs)
(if (null? body)
(with-writer #f " />")
(list (with-writer #f ">")
body
(with-writer #f "</" tag ">"))))))
;; ----------------------------------------------------------------------------
;; Literals
;; literal "struct" for things that are not escaped
(provide literal)
(define (literal . contents) (with-writer #f contents))
;; entities are implemented as literals
(provide entity)
(define (entity x) (literal "&" (and (number? x) "#") x ";"))
;; comments and cdata
(provide comment)
(define (comment #:newlines? [newlines? #f] . body)
(let ([newline (and newlines? "\n")])
(literal "<!--" newline body newline "-->")))
(provide cdata)
(define (cdata #:newlines? [newlines? #t] #:line-prefix [pfx #f] . body)
(let ([newline (and newlines? "\n")])
(literal pfx "<![CDATA[" newline body newline pfx "]]>")))
;; ----------------------------------------------------------------------------
;; Template definition forms
(provide define/provide-elements/empty
define/provide-elements/not-empty
define/provide-entities)
(define-syntax-rule (define/provide-elements/empty tag ...)
(begin (provide tag ...)
(define (tag . args) (apply element 'tag args)) ...))
(define-syntax-rule (define/provide-elements/not-empty tag ...)
(begin (provide tag ...)
(define (tag . args) (apply element/not-empty 'tag args)) ...))
(define-syntax-rule (define/provide-entities ent ...)
(begin (provide ent ...)
(define ent ; use string-append to make it a little faster
(literal (string-append "&" (symbol->string 'ent) ";")))
...))