scribble/html: first cut at documentation

original commit: f943d37d7d30507c8b76fd358d568f361bc0f2ec
This commit is contained in:
Matthew Flatt 2014-02-05 21:53:55 -07:00
parent 702b3e6702
commit f7db2c37c2
4 changed files with 531 additions and 42 deletions

View File

@ -0,0 +1,485 @@
#lang scribble/doc
@(require scribble/manual
scribble/core
scribble/eval
(only-meta-in 0 "utils.rkt")
(for-label (except-in racket/base #%top #%module-begin)
racket/contract/base
racket/string
scribble/html))
@(define html-eval (make-base-eval))
@interaction-eval[#:eval html-eval (require scribble/html)]
@interaction-eval[#:eval html-eval (require racket/string)]
@title[#:tag "html" #:style 'toc]{HTML Generation}
@defmodulelang[scribble/html]{The @racketmodname[scribble/html]
language provides a way to generate HTML that is different from
@racketmodname[scribble/base]. The @racketmodname[scribble/base]
approach involves describing a document that can be rendered to HTML,
Latex, or other formats. The @racketmodname[scribble/html] approach,
in contrast, treats the document content as HTML format plus escapes.}
Specifically, @racketmodname[scribble/html] is like
@racketmodname[scribble/text], but with the following changes:
@itemize[
@item{The @racketmodname[scribble/html/html],
@racketmodname[scribble/html/xml], and
@racketmodname[scribble/html/resource] are re-exported,
in addition to @racketmodname[scribble/text].}
@item{Free identifiers that end with @litchar{:} are implicitly
quoted as symbols.}
]
When @racketmodname[scribble/html] is used via @racket[require]
instead of @hash-lang[], then it does not change the printing of
values, and it does not include the bindings of @racket[racket/base].
The @racketmodname[scribble/html/resource],
@racketmodname[scribble/html/xml], and
@racketmodname[scribble/html/html] libraries provide forms for
generating HTML as strings to be output in the same way as
@racketmodname[scribble/text].
@local-table-of-contents[]
@; ----------------------------------------
@section[#:tag "html-html"]{Generating HTML Strings}
@defmodule[scribble/html/html]{The @racketmodname[scribble/html/html]
provides functions for HTML representations that render to string form
via @racket[output-xml].}
@defproc[(doctype [s (or/c string 'html 'xhtml)]) procedure?]{
Produces a value that @tech{XML-renders} as a DOCTYPE declaration.
@examples[#:eval html-eval
(output-xml (doctype "?"))
(output-xml (doctype 'html))
(regexp-split #rx"\n|((?<=\") (?=\"))"
(xml->string (doctype 'xhtml)))]}
@defproc[(xhtml [content any/c] ...) procedure?]{
Produces a value that @tech{XML-renders} as the given content wrapped
as XHTML.
@examples[#:eval html-eval
(regexp-split #rx"\n|((?<=\") (?=\"))"
(xml->string (xhtml "Hello")))]}
@(define-syntax-rule (def-tags tag ...)
@deftogether[(
@defproc[(tag [v any/c] (... ...)) procedure?] ...
)]{
Like @racket[element/not-empty], but with the symbolic form of the function
name added as the first argument.
@examples[#:eval html-eval
(output-xml (title "The Book"))]})
@(def-tags
html
head
title
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
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
body
div ; generic language/style container
p
h1
h2
h3
h4
h5
h6
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 ; information on author
pre
blockquote
center ; center content
ins
del
a ; content is inline; except that anchors shouldn't be nested
span ; generic language/style container
bdo ; I18N BiDi over-ride
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
font ; local change to font
object ; embeded objects
applet ; Java applet
form ; forms shouldn't be nested
label ; text that belongs to a form control
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
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
tr ; holds th or td
th ; header cell
td)
@(define-syntax-rule (def-tags/empty tag ...)
@deftogether[(
@defproc[(tag [v any/c] (... ...)) procedure?] ...
)]{
Like @racket[element], but with the symbolic form of the function
name added as the first argument.
@examples[#:eval html-eval
(output-xml (hr))]})
@(def-tags/empty
base meta link hr br basefont param img area input isindex col)
@(define-syntax-rule (def-entities ent ...)
@deftogether[(
@defthing[ent procedure?] ...
)]{
The result of @racket[(entity '_id)] for each @racket[_id].
@examples[#:eval html-eval
(output-xml nbsp)]})
@(def-entities
nbsp ndash mdash bull middot sdot lsquo rsquo sbquo ldquo rdquo bdquo
lang rang dagger Dagger plusmn deg)
@defproc[(script/inline [v any/c] ...) procedure?]{
Procedures a value that renders as an inline script.
@examples[#:eval html-eval
(output-xml (script/inline type: "text/javascript" "var x = 5;"))]}
@defproc[(style/inline [v any/c] ...) procedure?]{
Procedures a value that renders as an inline style sheet.
@examples[#:eval html-eval
(output-xml (style/inline type: "text/css"
".racket { font-size: xx-large; }"))]}
@; ----------------------------------------
@section[#:tag "html-xml"]{Generating XML Strings}
@defmodule[scribble/html/xml]{The @racketmodname[scribble/html/xml]
provides functions for XML representations that @deftech{XML-render} to string form
via @racket[output-xml] or @racket[xml->string].}
@defproc[(output-xml [content any/c] [port output-port? (current-output-port)])
void?]{
Renders @racket[content] in the same way as @racket[output], but using
the value of @racket[xml-writer] as the @tech{current writer} so that
special characters are escaped as needed.}
@defproc[(xml->string [content any/c]) string?]{
Renders @racket[content] to a string via @racket[output-xml].}
@defparam[xml-writer writer ((string? output-port? . -> . void))]{
A parameter for a function that is used with @racket[with-writer] by
@racket[output-xml]. The default value is a function that escapes
@litchar{&}, @litchar{<}, @litchar{>}, and @litchar{"} to entity form.}
@defproc[(make-element [tag symbol?]
[attrs (listof (cons/c symbol? any/c))]
[content any/c])
procedure?]{
Produces a value that @tech{XML-renders} as XML for the
given tag, attributes, and content.
When an attribute in @racket[attrs] is mapped to @racket[#f], then it
is skipped. When an attribute is mapped to @racket[#t], then it is
rendered as present, but without a value.
@examples[#:eval html-eval
(output-xml (make-element 'b '() '("Try" #\space "Racket")))
(output-xml (make-element 'a '((href . "http://racket-lang.org")) "Racket"))
(output-xml (make-element 'div '((class . "big") (overlay . #t)) "example"))
]}
@defproc[(element [tag symbol?] [attrs-and-content any/c] ...)
procedure?]{
Like @racket[make-element], but the list of @racket[attrs-and-content]
is parsed via @racket[attributes+body] to separate the attributes and
content.
@examples[#:eval html-eval
(output-xml (element 'b "Try" #\space "Racket"))
(output-xml (element 'a 'href: "http://racket-lang.org" "Racket"))
(output-xml (element 'div 'class: "big" 'overlay: #t "example"))
(require scribble/html)
(output-xml (element 'div class: "big" overlay: #t "example"))
]}
@defproc[(element/not-empty [tag symbol?] [attrs-and-content any/c] ...)
procedure?]{
Like @racket[element], but the result always renders with an separate
closing tag.
@examples[#:eval html-eval
(output-xml (element 'span))
(output-xml (element/not-empty 'span))
]}
@defproc[(attribute? [v any/c]) (or/c #f symbol?)]{
Returns a symbol without if @racket[v] is a symbol that ends with
@litchar{:}, @racket[#f] otherwise. When a symbol is returned, it is
the same as @racket[v], but without the trailing @litchar{:}.
@examples[#:eval html-eval
(attribute? 'a:)
(attribute? 'a)
(require scribble/html)
(attribute? a:)
]}
@defproc[(attributes+body [lst list?]) (values (listof (cons/c symbol? any/c))
list?)]{
Parses @racket[lst] into an association list mapping attributes to
list elements plus a list of remaining elements. The first
even-positioned (counting from 0) non-@racket[attribute?] element of
@racket[lst] is the start of the ``remaining elements'' list, while
each preceding even-positioned attribute is mapped in the association
list to the immediately following element of @racket[lst]. In the
association list, the trailing @litchar{:} is stripped for each
attribute.}
@defproc[(split-attributes+body [lst list?]) (values list? list?)]{
Like @racket[attributes+body], but produces a flat list (of
alternating attributes and value) instead of an association list as
the first result.}
@defproc[(literal [content any/c] ...) procedure?]{
Produces a value that @tech{XML-renders} without escapes
for special characters.
@examples[#:eval html-eval
(output-xml (literal "a->b"))
(output-xml "a->b")]}
@defproc[(entity [v (or/c exact-integer? symbol?)]) procedure?]{
Produces a value that @tech{XML-renders} as a numeric or
symbolic entity.
@examples[#:eval html-eval
(output-xml (entity 'gt))]}
@defproc[(comment [content any/c] ... [#:newlines? newlines? any/c #f])
procedure?]{
Produces a value that @tech{XML-renders} as a comment with
literal content. If @racket[newlines?] is true, then newlines are
inserted before and after the content.
@examples[#:eval html-eval
(output-xml (comment "testing" 1 2 3))]}
@defproc[(cdata [content any/c] ...
[#:newlines? newlines? any/c #t]
[#:line-pfx line-pfx any/c #f])
procedure?]{
Produces a value that @tech{XML-renders} as CDATA with
literal content. If @racket[newlines?] is true, then newlines are
inserted before and after the content. The @racket[line-pfx] value is
rendered before the CDATA opening and closing markers.
@examples[#:eval html-eval
(output-xml (cdata "testing" 1 2 3))]}
@defform[(define/provide-elements/empty tag-id ...)]{
Defines and exports @racket[tag-id] as a function that is like
@racket[element], but with @racket['tag-id] added as the first argument.}
@defform[(define/provide-elements/not-empty tag-id ...)]{
Defines and exports @racket[tag-id] as a function that is like
@racket[element/not-empty], but with @racket['_tag-id] added as the
first argument.}
@defform[(define/provide-entities entity-id ...)]{
Defines and exports @racket[entity-id] as the
result of @racket[(entity '_entity-id)].}
@; ----------------------------------------
@section[#:tag "html-resources"]{HTML Resources}
@defmodule[scribble/html/resource]
@defproc[(resource [path string?]
[renderer (path-string? . -> . any)]
[#:exists exists (or/c 'delete-file #f) 'delete-file])
(and/c resource?
(->* () (any/c) -> string?))]{
Creates and returns a new @deftech{resource} value. Creating a
resource registers @racket[renderer] to be called when rendering is
initiated by @racket[render-all], while calling the result resource as
a function generates a URL for the resource.
For example, a typical use of @racket[resource] is to register the
generation of a CSS file, where the value produced by
@racket[resource] itself renders as the URL for the generated CSS
file. Another possible use of @racket[resource] is to generate an HTML
file, where the @racket[resource] result renders as the URL of the
generated HTML page.
The @racket[path] argument specifies the path of the output file,
relative to the working directory, indicating where the resource file
should be placed. Though @racket[url-roots], @racket[path] also
determines the ultimate URL. The @racket[path] string must be a
@litchar{/}-separated relative path with no @litchar{..}, @litchar{.},
or @litchar{//}. The @racket[path] string can end in @litchar{/}, in
which case @racket["index.html"] is effectively added to the string.
The @racket[renderer] argument renders the resource, receiving the
path for the file to be created. The path provided to
@racket[renderer] will be different from @racket[path], because the
function is invoked in the target directory.
The resulting resource value is a function that returns the URL for
the resource. The function accepts an optional boolean; if a true
value is provided, the result is an absolute URL, instead of relative.
Note that the function can be used as a value for @racket[output],
which uses the resource value as a thunk (that renders as the relative
URL for the resource). The default relative resulting URL is, of
course, a value that depends on the currently rendered resource that
uses this value.
When @racket[renderer] is called by @racket[render-all], more
resources can be created while rendering; the newly created resources
will also be rendered, in turn, until no more new resources are
created.
If @racket[exists] is @racket['delete-file] and the target file exists
when @racket[renderer] is to be called, then the file is deleted
before @racket[renderer] is called.}
@defparam[url-roots roots (or/c #f
(listof (or/c (list path-string? string?)
(list path-string? string? 'abs))))]{
A parameter that determines how resource paths are converted to URLs
for reference. A @racket[#f] value is equivalent to an empty list.
The parameter value is a mapping from path prefixes to URLs (actually,
any string). When two paths have the same prefix, links from one to
the other are relative (unless absolute links are requested); if they
have different prefixes, the URL will be used instead. The roots of
all paths are expected to be disjoint (e.g., no @racket["/foo"] and
@racket["/foo/bar"] roots).
If an item in the parameter's list includes @racket['abs], then an
absolute URL is produced for all references to files with the
corresponding prefix.}
@defproc[(resource? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] is a procedure (that takes 0 or 1
arguments) produced by @racket[resource].}
@defproc[(render-all) void?]{
Generates all resources registered via @racket[resource].}
@defproc[(file-writer [content-writer (any/c output-port? . -> . any)]
[content any/c])
(path-string? . -> . any)]{
Produces a function that is useful as a @racket[_writer] argument to
@racket[resource]. Given a path, the produced function writes
@racket[content] to the path by passing @racket[content] and an output
port for the file to @racket[content-writer].}
@; ------------------------------------------------------------
@close-eval[html-eval]

View File

@ -27,6 +27,7 @@ starting with the @filepath{scribble.scrbl} file.
@include-section["plt.scrbl"]
@include-section["lp.scrbl"]
@include-section["text.scrbl"]
@include-section["html.scrbl"]
@include-section["internals.scrbl"]
@include-section["running.scrbl"]

View File

@ -1277,7 +1277,10 @@ Outputs values to @racket[port] as follows for each kind of @racket[v]:
@item{procedure of 0 arguments: outputs the result of @racket[(v)]}
]}
]
Any other kind of @racket[v] triggers an exception.}
@defproc[(block [v any/c] ...) any/c]{

View File

@ -100,55 +100,55 @@
[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"
(module+ test
(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")) =error> "not in any"
(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" '() '() ) =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 "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")) =error> "not in any"
(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/" abs])])
(test (R "foo.txt" '("x" "1") '("x" "2")) => "../1/foo.txt"
(R "foo.txt" '("y" "1") '("y" "2")) => "/1/foo.txt"))))
|#
(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/"))
do (parameterize ([url-roots '(["/x" "/X/"] ["/y" "/Y/" abs])])
(test (R "foo.txt" '("x" "1") '("x" "2")) => "../1/foo.txt"
(R "foo.txt" '("y" "1") '("y" "2")) => "/1/foo.txt")))))
;; utility for keeping a list of renderer thunks
(define-values [add-renderer get/reset-renderers]