The slowest part of a @racket[render] is parsing and decoding the source file. Often, previewing a single source file necessarily means decoding others (for instance templates, or other source files that are linked into the main source file). But usually, only one source file is changing at a time. Therefore, Pollen stores copies of the exports of source files —namely, whatever is stored in @code[(format "'~a" world:main-pollen-export)] and @code[(format "'~a" world:meta-pollen-export)] —in the cache so they can be reused.
The slowest part of a @racket[render] is parsing and decoding the source file. Often, previewing a single source file necessarily means decoding others (for instance templates, or other source files that are linked into the main source file). But usually, only one source file is changing at a time. Therefore, Pollen stores copies of the exports of source files —namely, whatever is stored in @code[(format "~a" world:main-pollen-export)] and @code[(format "~a" world:meta-pollen-export)] —in the cache so they can be reused.
@defparam[current-cache hash hash?
#:value (make-cache)]{A parameter that refers to the current cache. It is initialized with @racket[make-cache].
The cache is a hash table that uses the complete path of a source file as its keys. The value associated with each of these keys is a subcache —another hash table with keys @racket['doc], @racket['metas] (for storing the exports of the source file) and @racket['mod-time] (for storing the modification time, provided by @racket[file-or-directory-modify-seconds]).}
@defproc[
@defproc[
(cached-require
(cached-require
[source-path pathish?]
[source-path pathish?]
[key (or/c 'doc 'metas 'mod-time)])
[key (or/c 'doc 'metas 'mod-time)])
(or/c txexpr? hash? integer?)]
(or/c txexpr? hash? integer?)]
Similar to @racket[(dynamic-require _source-path _key)], except that it tries to get the requested value out of @racket[current-cache]. If it's not there, or out of date, @racket[dynamic-require] is used to update the value.
Similar to @racket[(dynamic-require _source-path _key)], except that it first tries to retrieve the requested value out of @racket[current-cache]. If it's not there, or out of date, @racket[dynamic-require] is used to update the value.
If you want the speed benefit of the cache, you should @bold{always} use @racket[cached-require] to get data from Pollen source files. That doesn't mean you can't still use functions like @racket[require], @racket[local-require], and @racket[dynamic-require]. They'll just be slower.
The only keyssupported are @racket['doc], @racket['metas], and @racket['mod-time].
If you want the speed benefit of the cache, you should @bold{always} use @racket[cached-require] to get data from Pollen source files. That doesn't mean you can't still use functions like @racket[require], @racket[local-require], and @racket[dynamic-require]. They'll just be slower.
@defproc[
(current-cache)
(or/c #f hash?)]
A parameter that refers to the current cache. It starts with a value of @racket[#f]. It has to be initialized with @racket[make-cache].
The cache is a hash table that uses the complete path of a source file as its keys. The value associated with each of these keys is another hash table with keys @code[(format "'~a" world:main-pollen-export)], @code[(format "'~a" world:meta-pollen-export)] (for storing the exports of the source file) and @racket['mod-time] (for storing the modification time).
@defproc[
@defproc[
(make-cache)
(make-cache)
@ -43,5 +47,5 @@ Clears @racket[current-cache]. When only the nuclear option will do.
(cache-ref
(cache-ref
[source-path pathish?])
[source-path pathish?])
hash?]
hash?]
Returns the cached value associated with the key @racket[_source-path], which will itself be a hash table. See @racket[current-cache].
Returns the subcache associated with the key @racket[_source-path], which will itself be a hash table. See @racket[current-cache].
Recursively process a @racket[_tagged-xexpr], usually the one exported from a Pollen source file as @racket['doc]. This function doesn't do much on its own. Rather, it provides the hooks upon which harder-working functions can be hung. By default, the @racket[_tagged-xexpr] from a source file is tagged with @racket[root]. Recall from @secref{Pollen mechanics} that any tag can have a function attached to it. So the typical way to use @racket[decode] is to attach your decoding functions to it, and then define @racket[root] to invoke your @racket[decode] function. Then it will be automatically applied to every @racket['doc] during compile.
Recursively process a @racket[_tagged-xexpr], usually the one exported from a Pollen source file as @racket[doc].
For instance, here's how @racket[decode] is attached to @racket['root] in @italic{Butterick's Practical Typography}. There's not much to it —
This function doesn't do much on its own. Rather, it provides the hooks upon which harder-working functions can be hung.
Recall from @secref{Pollen mechanics} that any tag can have a function attached to it. By default, the @racket[_tagged-xexpr] from a source file is tagged with @racket[root]. So the typical way to use @racket[decode] is to attach your decoding functions to it, and then define @racket[root] to invoke your @racket[decode] function. Then it will be automatically applied to every @racket[doc] during compile.
For instance, here's how @racket[decode] is attached to @racket[root] in @italic{Butterick's Practical Typography}. There's not much to it —
@codeblock|{
@codeblock|{
(define (root . items)
(define (root . items)
@ -58,17 +62,21 @@ This illustrates another important point: even though @racket[decode] presents a
Right —nothing. That's because the default value for the decoding arguments is the identity function, @racket[(λ(x)x)]. So all the input gets passed through intact unless another action is specified.
Right —nothing. That's because the default value for the decoding arguments is the identity function, @racket[(λ(x)x)]. So all the input gets passed through intact unless another action is specified.
The @racket[_*-proc] arguments of @racket[decode] take procedures that are applied to specific categories of elements within @racket[_txexpr].
The @racket[_txexpr-tag-proc] argument is a procedure that handles X-expression tags.
The @racket[_txexpr-tag-proc] argument is a procedure that handles X-expression tags.
@examples[#:eval my-eval
@examples[#:eval my-eval
(define tx '(p "I'm from a strange" (strong "namespace")))
(define tx '(p "I'm from a strange" (strong "namespace")))
(code:comment @#,t{Tags are symbols, so a tag-proc should return a symbol})
The @racket[_txexpr-attrs-proc] argument is a procedure that handles lists of X-expression attributes. (The @racket[txexpr] module, included at no extra charge with Pollen, includes useful helper functions for dealing with attribute lists.)
The @racket[_txexpr-attrs-proc] argument is a procedure that handles lists of X-expression attributes. (The @racketmodname[txexpr] module, included at no extra charge with Pollen, includes useful helper functions for dealing with these attribute lists.)
@examples[#:eval my-eval
@examples[#:eval my-eval
(define tx '(p [[id "first"]] "If I only had a brain."))
(define tx '(p [[id "first"]] "If I only had a brain."))
(code:comment @#,t{Attrs is a list, so cons is OK for simple cases})
@ -86,18 +96,25 @@ Note that @racket[_txexpr-attrs-proc] will change the attributes of every tagged
The @racket[_txexpr-elements-proc] argument is a procedure that operates on the list of elements that represents the content of each tagged X-expression. Note that each element of an X-expression is subject to two passes through the decoder: once now, as a member of the list of elements, and also later, through its type-specific decoder (i.e., @racket[_string-proc], @racket[_symbol-proc], and so on).
The @racket[_txexpr-elements-proc] argument is a procedure that operates on the list of elements that represents the content of each tagged X-expression. Note that each element of an X-expression is subject to two passes through the decoder: once now, as a member of the list of elements, and also later, through its type-specific decoder (i.e., @racket[_string-proc], @racket[_symbol-proc], and so on).
So why do you need @racket[_txexpr-elements-proc]? Because some types of element decoding depend on context, thus it's necessary to handle the elements as a group. For instance, paragraph detection. The behavior is not merely a @racket[map] across each element:
So why do you need @racket[_txexpr-elements-proc]? Because some types of element decoding depend on context, thus it's necessary to handle the elements as a group. For instance, the doubling function above, though useless, requires handling the element list as a whole, because elements are being added.
A more useful example: paragraph detection. The behavior is not merely a @racket[map] across each element:
The @racket[_string-proc], @racket[_symbol-proc], @racket[_valid-char-proc], and @racket[_cdata-proc] arguments are procedures that operate on X-expressions that are strings, symbols, valid-chars, and CDATA, respectively. Deliberately, the output contracts for these procedures accept any kind of X-expression (meaning, the procedure can change the X-expression type).
The @racket[_string-proc], @racket[_symbol-proc], @racket[_valid-char-proc], and @racket[_cdata-proc] arguments are procedures that operate on X-expressions that are strings, symbols, valid-chars, and CDATA, respectively. Deliberately, the output contracts for these procedures accept any kind of X-expression (meaning, the procedure can change the X-expression type).
@examples[#:eval my-eval
@examples[#:eval my-eval
(code:comment @#,t{A div with string, entity, character, and cdata elements})
(code:comment @#,t{The rulify function is selectively applied to each})
(decode tx #:symbol-proc rulify)
(print (decode tx #:string-proc rulify))
(decode tx #:valid-char-proc rulify)
(print (decode tx #:symbol-proc rulify))
(decode tx #:cdata-proc rulify)
(print (decode tx #:valid-char-proc rulify))
(print (decode tx #:cdata-proc rulify))
]
]
@ -147,10 +171,16 @@ The @racket[_tags-to-exclude] argument is useful if you're decoding source that'
]
]
@section{Blocks}
@section{Blocks}
@defmodule[pollen/decode/block]
Because it's convenient, Pollen categorizes tagged X-expressions into two categories: @italic{block} and @italic{inline}. Why is it convenient? When using @racket[decode], you often want to treat the two categories differently. Not that you have to. But this is how you can.
Because it's convenient, Pollen categorizes tagged X-expressions into two categories: @italic{block} and @italic{inline}. Why is it convenient? When using @racket[decode], you often want to treat the two categories differently. Not that you have to. But this is how you can.
(paras '(body "This wants to be a paragraph." "\n\n" (bloq "But not this.")))
(paras '(body "I want to be a paragraph." "\n\n" (bloq "But not me.")))
(code:comment @#,t{Right: bloq is treated as a block})
]
]
If you find the idea of registering block tags unbearable, good news. The @racket[project-block-tags] include the standard HTML block tags by default. So if you just want to use things like @racket[div] and @racket[p] and @racket[h1–h6], you'll get the right behavior for free.
If you find the idea of registering block tags unbearable, good news. The @racket[project-block-tags] include the standard HTML block tags by default. So if you just want to use things like @racket[div] and @racket[p] and @racket[h1–h6], you'll get the right behavior for free.
(paras '(body "This wants to be a paragraph." "\n\n" (div "But not this.")))
(paras '(body "I want to be a paragraph." "\n\n" (div "But not me.")))
]
]
@ -188,15 +220,9 @@ If you find the idea of registering block tags unbearable, good news. The @racke
boolean?]
boolean?]
Predicate that tests whether @racket[_v] is a tagged X-expression, and if so, whether the tag is among the @racket[project-block-tags]. If not, it is treated as inline. To adjust how this test works, use @racket[register-block-tag].
Predicate that tests whether @racket[_v] is a tagged X-expression, and if so, whether the tag is among the @racket[project-block-tags]. If not, it is treated as inline. To adjust how this test works, use @racket[register-block-tag].
An assortment of typography & layout functions, designed to be used with @racket[decode]. These aren't hard to write. So if you like these, use them. If not, make your own.
An assortment of typography & layout functions, designed to be used with @racket[decode]. These aren't hard to write. So if you like these, use them. If not, make your own.
@ -205,7 +231,7 @@ An assortment of typography & layout functions, designed to be used with @racket
(whitespace?
(whitespace?
[v any/c])
[v any/c])
boolean?]
boolean?]
A predicate that returns @racket[#t] for any stringlike @racket[_v] that's entirely whitespace, but also the empty string, as well as lists and vectors that are made only of @racket[whitespace?] members. Following the regexp convention, @racket[whitespace?] does not return @racket[#t] for a nonbreaking space. If you prefer that behavior, use @racket[whitespace/nbsp?].
A predicate that returns @racket[#t] for any stringlike @racket[_v] that's entirely whitespace, but also the empty string, as well as lists and vectors that are made only of @racket[whitespace?] members. Following the @racket[regexp-match] convention, @racket[whitespace?] does not return @racket[#t] for a nonbreaking space. If you prefer that behavior, use @racket[whitespace/nbsp?].
@examples[#:eval my-eval
@examples[#:eval my-eval
@ -257,6 +283,7 @@ In @racket[_str], convert three hyphens to an em dash, and two hyphens to an en
(define tricky-string "I had a few --- OK, like 6--8 --- thin mints.")
(define tricky-string "I had a few --- OK, like 6--8 --- thin mints.")
(display tricky-string)
(display tricky-string)
(display (smart-dashes tricky-string))
(display (smart-dashes tricky-string))
(code:comment @#,t{Monospaced font not great for showing dashes, but you get the idea})
@ -19,15 +19,16 @@ Books and other long documents are usually organized in a structured way —at
(pagetree?
(pagetree?
[possible-pagetree any/c])
[possible-pagetree any/c])
boolean?]
boolean?]
Test whether @racket[_possible-pagetree] is a valid pagetree. It must be a @racket[txexpr?] where all elements are @racket[pagenode?] and unique within @racket[_possible-pagetree] (not counting the root node).
Test whether @racket[_possible-pagetree] is a valid pagetree. It must be a @racket[txexpr?] where all elements are @racket[pagenode?], and each is unique within @racket[_possible-pagetree] (not counting the root node).
@ -53,7 +54,9 @@ Test whether @racket[_possible-pagenode] is a valid pagenode. A pagenode can be
@margin-note{Pagenodes are symbols (rather than strings) so that pagetrees will be valid tagged X-expressions, which is a more convenient format for validation & processing.}
@margin-note{Pagenodes are symbols (rather than strings) so that pagetrees will be valid tagged X-expressions, which is a more convenient format for validation & processing.}
@examples[#:eval my-eval
@examples[#:eval my-eval
(code:comment @#,t{Three symbols, the third one annoying but valid})
(map pagenode? '(symbol index.html | silly |))
(map pagenode? '(symbol index.html | silly |))
(code:comment @#,t{A number, a string, a txexpr, and a whitespace symbol})
@ -195,7 +198,7 @@ Return the pagenode immediately after @racket[_p]. For @racket[next*], return al
[pagetree pagetree?])
[pagetree pagetree?])
list?
list?
]
]
Convert @racket[_pagetree] to a simple list, preserving order.
Convert @racket[_pagetree] to a simple list. Equivalent to a pre-order depth-first traversal of @racket[_pagetree].
@defproc[
@defproc[
(in-pagetree?
(in-pagetree?
@ -210,4 +213,4 @@ Report whether @racket[_pagenode] is in @racket[_pagetree].
[p pathish?])
[p pathish?])
pagenode?
pagenode?
]
]
Convert path @racket[_p] to a pagenode —meaning, make it relative to @racket[world:current-project-root], run it through @racket[->output-path], and convert it to a symbol. Does not tell you whether the resultant pagenode actually exists in the current pagetree (for that, use @racket[pagenode-in-pagetree?]).
Convert path @racket[_p] to a pagenode —meaning, make it relative to @racket[world:current-project-root], run it through @racket[->output-path], and convert it to a symbol. Does not tell you whether the resultant pagenode actually exists in the current pagetree (for that, use @racket[in-pagetree?]).
@ -24,8 +24,7 @@ A @racketmodname[pollen/markup] or @racketmodname[pollen/markdown] file is rende
Be aware that rendering with a template uses @racket[include-template] within @racket[eval]. For complex pages, it can be slow the first time. Caching is used to make subsequent requests faster.
Be aware that rendering with a template uses @racket[include-template] within @racket[eval]. For complex pages, it can be slow the first time. Caching is used to make subsequent requests faster.
For those panicked at the use of @racket[eval], please don't be. As the author of @racket[include-template] has already advised, ``If you insist on dynamicism'' — and yes, I do insist — ``there is always @racket[eval].''
For those panicked at the use of @racket[eval], please don't be. As the author of @racket[include-template] has already advised, ``If you insist on dynamicism'' — and yes, I do insist — ``@link["http://docs.racket-lang.org/web-server/faq.html#%28part._.How_do_.I_use_templates__dynamically__%29"]{there is always @racket[eval].}''
@secref["How do I use templates “dynamically\"?" #:doc '(lib "web-server/scribblings/faq.scrbl")]
@defproc[
@defproc[
(render-to-file
(render-to-file
@ -46,8 +45,7 @@ Like @racket[render-to-file], but the render only happens if one of these condit
@itemlist[#:style 'ordered
@itemlist[#:style 'ordered
@item{The @racket[_force-render?] flag —set with the @racket[#:force] keyword — is @racket[#t].}
@item{The @racket[_force-render?] flag —set with the @racket[#:force] keyword — is @racket[#t].}
@item{No file exists at @racket[_output-path].
@item{No file exists at @racket[_output-path]. (Thus, an easy way to force a render of a particular @racket[_output-path] is to delete it.)}
@margin-note{Corollary: an easy way to force a render of a particular @racket[_output-path] from the desktop is to delete it.}}
@item{Either @racket[_source-path] or @racket[_template-path] have changed since the last trip through @racket[render].}
@item{Either @racket[_source-path] or @racket[_template-path] have changed since the last trip through @racket[render].}
@ -68,18 +66,19 @@ Render multiple @racket[_source-paths] in one go. This can be faster than @racke
Using @racket[_pagetree], or a pagetree loaded from @racket[_pagetree-source], render the files included in that pagetree using @racket[render-batch].
Using @racket[_pagetree], or a pagetree loaded from @racket[_pagetree-source], render the pages in that pagetree using @racket[render-batch].
@defproc[
@defproc[
(get-template-for
(get-template-for
[source-path complete-path?])
[source-path complete-path?])
(or/c #f complete-path?)]
(or/c #f complete-path?)]
Find a template file for @racket[_source-path], with the following priority:
Find a template file for @racket[_source-path], with the following priority:
@itemlist[#:style 'ordered
If the metas for @racket[_source-path] have a key for @code[(format "'~a" world:template-meta-key)], then use the value of this key.
@item{If the metas for @racket[_source-path] have a key for @code[(format "~a" world:template-meta-key)], then use the value of this key.}
If this key doesn't exist, or if it points to a nonexistent file, look for a default template in the project directory with the name @code[(format "~a.[output extension].~a" world:default-template-prefix world:template-source-ext)]. Meaning, if @racket[_source-path] is @code[(format "intro.html.~a" world:markup-source-ext)], the output path would be @code["intro.html"], so the default template would be @code[(format "~a.html.~a" world:default-template-prefix world:template-source-ext)].
@item{If this key doesn't exist, or if it points to a nonexistent file, look for a default template in the project directory with the name @code[(format "~a.[output extension].~a" world:default-template-prefix world:template-source-ext)]. Meaning, if @racket[_source-path] is @code[(format "intro.html.~a" world:markup-source-ext)], the output path would be @code["intro.html"], so the default template would be @code[(format "~a.html.~a" world:default-template-prefix world:template-source-ext)].}
If this file doesn't exist, the fallback template is used.
@item{If this file doesn't exist, use the fallback template as a last resort.}]
This function is called when a template is needed, but a @racket[_template-path] argument is missing (for instance, in @racket[render] or @racket[render-to-file]).
This function is called when a template is needed, but a @racket[_template-path] argument is missing (for instance, in @racket[render] or @racket[render-to-file]).
@ -25,7 +25,7 @@ Convert @racket[_xexpr] to an HTML string. Similar to @racket[xexpr->string], bu
(->html tx)
(->html tx)
]
]
Be careful not to pass existing HTML strings into this function, because the @code{<} and @code{>} symbols will be escaped. Fine if that's what you want, but you probably don't.
Be careful not to pass existing HTML strings into this function, because the angle brackets will be escaped. Fine if that's what you want, but you probably don't.
@examples[#:eval my-eval
@examples[#:eval my-eval
(define tx '(p "You did" (em "what?")))
(define tx '(p "You did" (em "what?")))
@ -61,10 +61,16 @@ Find matches for @racket[_key] in @racket[_value-source], first by looking in it
Look up the value of @racket[_key] in @racket[_meta-source]. The @racket[_meta-source] argument can be either a set of metas (i.e., a @racket[hash]) or a @racket[pagenode?], from which metas are pulled. If no value exists for @racket[_key], you get @racket[#f].
Look up the value of @racket[_key] in @racket[_meta-source]. The @racket[_meta-source] argument can be either a set of metas (i.e., a @racket[hash]) or a @racket[pagenode?], from which metas are pulled. If no value exists for @racket[_key], you get @racket[#f].
(code:comment @#,t{Import doc & metas from 'ice-cream submodule})
(require 'ice-cream)
(select-from-metas 'template metas)
('target . select-from-metas . metas)
(select-from-metas 'nonexistent-key metas)
]
]
@ -77,11 +83,16 @@ Look up the value of @racket[_key] in @racket[_meta-source]. The @racket[_meta-s
Look up the value of @racket[_key] in @racket[_doc-source]. The @racket[_doc-source] argument can be either be a @code{doc} (i.e., a @racket[txexpr]) or a @racket[pagenode?], from which doc is pulled. If no value exists for @racket[_key], you get @racket[#f].
Look up the value of @racket[_key] in @racket[_doc-source]. The @racket[_doc-source] argument can be either be a @code{doc} (i.e., a @racket[txexpr]) or a @racket[pagenode?], from which doc is pulled. If no value exists for @racket[_key], you get @racket[#f].
@examples[#:eval my-eval
@examples[#:eval my-eval
(define my-doc '(body (question "Gelato?")
(module gelato pollen/markup
(answer "Nocciola") (answer "Pistachio")))
'(div (question "Flavor?")
(select-from-doc 'question my-doc)
(answer "Nocciola") (answer "Pistachio"))
('answer . select-from-doc . my-doc)
'(meta ((template "sub.xml.pt")))
(select-from-doc 'nonexistent-key my-doc)
'(meta ((target "print"))))
(code:comment @#,t{Import doc & metas from 'gelato submodule})
You'll probably never invoke this module directly. But it's implicitly imported into every Pollen markup file. And if you don't know what it does, you might end up surprised by some of the behavior you get.
@defform[(#%top . id)]{
In standard Racket, @racket[#%top] is the function of last resort, called when @racket[_id] is not bound to any value. As such, it typically reports a syntax error.
@examples[
(code:comment @#,t{Let's call em without defining it})
(em "Bonjour")
(code:comment @#,t{(em "Bonjour") is being converted to ((#%top . em) "Bonjour")})
(code:comment @#,t{So calling ((#%top . em) "Bonjour") will give the same result})
((#%top . em) "Bonjour")
]
In the Pollen markup environment, however, this behavior is annoying. Because when you're writing X-expressions, you don't necessarily want to define all your tags ahead of time.
So Pollen redefines @racket[#%top]. For convenience, Pollen's version of @racket[#%top] assumes that an undefined tag should just refer to an X-expression beginning with that tag.
@examples[
(code:comment @#,t{Again, let's call em without defining it, but using pollen/top})
(require pollen/top)
(em "Bonjour")
(code:comment @#,t{(em "Bonjour") is still being converted to ((#%top . em) "Bonjour")})
(code:comment @#,t{But now, ((#%top . em) "Bonjour") gives a different result})
((#%top . em) "Bonjour")
]
The good news is that this behavior means you use any tag you want in your markup without defining it in advance. You can still attach a function to the tag later, which will automatically supersede @racket[#%top].
@examples[
(define (em x) `(span ((style "font-size:100px")) ,x))
(em "Bonjour")
]
The bad news is that you'll never get an ``undefined identifier'' error. These undefined identifiers will happily sail through and be converted to tags.
@examples[
(require pollen/top)
(define (em . xs) `(span ((style "font-size:100px")) ,@xs))
(code:comment @#,t{There's a typo in my tag})
(erm "Bonjour")
]
This isn't a bug. It's just a natural consequence of how Pollen's @racket[#%top] works. It can, however, make debugging difficult sometimes. Let's suppose my markup depends on @racket[very-important-function], which I don't import correctly.
So the undefined-function bug goes unreported. Again, that's not a bug in Pollen —there's just no way for it to tell the difference between an identifier that's deliberately undefined and one that's inadvertently undefined. If you want to guarantee that you're invoking a defined identifier, use @racket[def/c].}
Pollen's version of @racket[#%top] has one other convenience —it will automatically interpret symbol + string pairs at the front of your expression as X-expression attributes, if the symbols are followed by a colon. If you leave out the colon, the symbols will be interpreted as part of the content of the tag.
@examples[
(require pollen/top)
(em 'id: "greeting" 'class: "large" "Bonjour")
(code:comment @#,t{Don't forget the colons})
(em 'id "greeting" 'class "large" "Bonjour")
(code:comment @#,t{Don't forget to provide a value for each attribute})
(em 'id: 'class: "large" "Bonjour")
]
@defform[(def/c id)]{Invoke @racket[_id] if it's a defined identifier, otherwise raise an error. This form reverses the behavior of @racket[#%top] (in other words, it restores default Racket behavior).
Recall this example from before. In standard Racket, you get an undefined-identifier error.