@margin-note{Pythonistas might object to the @racket[(string? elem)] test in the last function as a missed chance for ``duck typing.'' You can do duck typing in Racket (see @racket[with-handlers]) but it's not idiomatic. IMO this is wise. Duck typing is a bad habit: it substitutes an explicit, readable test (@racket[string?]) for an implicit, indirect test (``I know if this isn't a @racket[string?], then a certain error will arise.'')}
@margin-note{Explicit type checking — e.g., @racket[(string? elem)] — is common in Racket. You can do ``duck typing'' (see @racket[with-handlers]) but it's not idiomatic. IMO this is wise — better to have an explicit, readable test (likse @racket[string?]) rather than an implicit, indirect one (``If the input isn't a @racket[string?], then a certain error will arise.'')}
Because of the expression-based structure of Racket, it's often possible to write functions in an absurdly nested style. For instance, the last function could be written like so:
This is a good way to lose your friends, and then your mind. You may not care to spell everything out the way I've been doing in this sample project. But readability is a virtuous habit.
@defproc[
@defproc[
@ -419,7 +433,7 @@ Our list of elements could contain sequences like @racket['("\n" "\n" "\n")], wh
procedure?]
procedure?]
Helper function that makes other tag functions that make lists.
Helper function that makes other tag functions that make lists.
In Racket you'll often see functions that make other functions. (In Racket these are also known as @seclink["Additional_Higher-Order_Functions"
In Racket you'll often see functions that make other functions. (These are sometimes called @seclink["Additional_Higher-Order_Functions"
#:doc '(lib "scribblings/reference/reference.scrbl")]{@italic{higher-order functions}}.) This is a good way to avoid making a bunch of functions that have small variations.
#:doc '(lib "scribblings/reference/reference.scrbl")]{@italic{higher-order functions}}.) This is a good way to avoid making a bunch of functions that have small variations.
One way to write this function is like so:
One way to write this function is like so:
@ -461,16 +475,14 @@ These can now be easily defined using the @racket[make-list-function] helper.
(btw
(btw
[tx-element txexpr?] ...)
[tx-element txexpr?] ...)
txexpr]
txexpr]
Make the "By the Way" list at the bottom of many pages,
Make the ``By the Way'' list that appears at the bottom of many pages, @link["http://typographyforlawyers.com/what-is-typography.html"]{like this one}.
e.g. http://typographyforlawyers.com/what-is-typography.html
Another example of using a tag function to handle fiddly HTML markup.
Another example of using a tag function to handle fiddly HTML markup.
The @racket[btw] tag expands to an HTML list, which we will then crack open and add a headline div.
The @racket[btw] tag expands to an HTML list. We will then crack this open and slip in a @racket[div] for the headline.
@ -486,23 +498,17 @@ The @racket[btw] tag expands to an HTML list, which we will then crack open and
[target string?])
[target string?])
txexpr?]
txexpr?]
)]
)]
Create a styled cross-reference link, with optional destination argument.
Create a styled cross-reference link, with optional destination argument, so it can be used two ways:
◊xref{target}
@terminal{
◊xref["url"]{target}
◊xref{target}
◊xref["url"]{target}}
For this tag function, we'll assume that @racket[_target] is a single text argument, because that's how it will be used. But to be safe, we'll raise an error if we get too many arguments.
For this tag function, we will assume that target is a single text argument,
What makes this function a little tricky is that @racket[_url] is optional, but if it appears, it appears first. That makes this a good job for @racket[case-lambda], which lets you define separate branches for your function depending on the number of arguments provided.
because that's how it will be used.
But to be safe, we'll raise an arity error if we get too many arguments.
;; What makes this function a little tricky is that the url argument is optional,
In the one-argument case, rather than duplicate the line of code in the two-argument case, we call the function again with a second argument.
;; but if it appears, it appears first.
;; This is a good job for @racket[case-lambda], which lets you define separate branches for your function
;; depending on the total number of arguments provided.
;; one argument: must be a target. Note the Rackety recursive technique here:
;; we'll create a second argument and then call @racket[xref] again.
;; two arguments: must be a url followed by a target.
;; more than two arguments: raise an arity error.
@chunk[<xref>
@chunk[<xref>
(define xref
(define xref
@ -519,35 +525,32 @@ But to be safe, we'll raise an arity error if we get too many arguments.
(target->url
(target->url
[target string?])
[target string?])
string?]
string?]
Convert the target text of an xref into a url.
Convert the target text of an @racket[xref] into a url.
This function depends on a personal commitment to name source files in a logical, predictable way, e.g., ``Why Does Typography Matter?'' becomes @tt{why-does-typography-matter.html}. This way, the name of the source file for a page can be derived from its title.
This function depends on my commitment to name my source files in a logical, predictable way,
If you needed to associate targets with URLs arbitrarily, you could store the targets and URLs in an association list or hashtable. But I prefer this approach, because it's easy to add new pages and cross-references, without the extra housekeeping step.
e.g., "Why Does Typography Matter?" becomes "why-does-typography-matter.html".
If you needed to associate targets with URLs arbitrarily, you could store the targets and URLs
in an association list or hashtable.
I do it this way so that it's easy to add new pages and xrefs, without the extra housekeeping step
The name of the source file for a page is determined by its title.
@chunk[<target->url>
@chunk[<target->url>
(define (target->url target)
(define (target->url target)
(define nonbreaking-space (~a #\u00A0))
(define nonbreaking-space (~a #\u00A0))
(let* ([xn target]
(let* ([x target]
[xn (string-trim xn "?")] ; delete a question mark at the end
[x (string-trim x "?")] ; delete a question mark at the end
[xn (string-downcase xn)] ; put string in all lowercase
[x (string-downcase x)] ; put string in all lowercase
[xn (regexp-replace* #rx"é" xn "e")] ; remove accented é
[x (regexp-replace* #rx"é" x "e")] ; remove accented é
[xn (if (regexp-match #rx"^foreword" xn) "foreword" xn)] ; special rule for foreword
[x (if (regexp-match #rx"^foreword" x) "foreword" x)] ; special rule for foreword
[xn (if (regexp-match #rx"^table of contents" xn) "toc" xn)] ; special rule for toc
[x (if (regexp-match #rx"^table of contents" x) "toc" x)] ; special rule for toc
[xn (string-replace xn nonbreaking-space "-")] ; replace nbsp with hyphen
[x (string-replace x nonbreaking-space "-")] ; replace nbsp with hyphen
[xn (string-replace xn " " "-")]) ; replace word space with hyphen
[x (string-replace x " " "-")]) ; replace word space with hyphen
(format "~a.html" xn)))]
(format "~a.html" x)))]
@defproc[
@defproc[
(xref-font
(xref-font
[font-name string?])
[font-name string?])
txexpr?]
txexpr?]
Special version of @racket[xref] for the fontrec directory.
Special version of @racket[xref] for the @filepath{fontrec}subdirectory.
@chunk[<xref-font>
@chunk[<xref-font>
(define (xref-font font-name)
(define (xref-font font-name)
@ -561,20 +564,19 @@ This could also be done with @racket[make-default-tag-function]. And as a rule o
macros for the times you can't avoid using them. Otherwise, use a function.
macros for the times you can't avoid using them. Otherwise, use a function.
We'll bend that rule here because this is a quick & easy example macro. What makes it suitable to be
We'll bend that rule here because this is a quick & easy example macro. What makes it suitable to be
handled as a macro is that we want to use the name of the identifier (for instance 'topic') as an
handled as a macro is that we want to use the name of the identifier (for instance @racket[topic]) as an
argument to the function. Ordinarily we can't do that, but with a macro, we can.
argument to the function. With a function, we can't do that. But with a macro, we can.
@racket[define-syntax-rule] is the easiest macro form: essentially you're writing a code template
with arguments that will be filled in when you invoke the macro.
; first, heading-name is used as an identifier
; then it's used as a symbol that is converted to a string.
@racket[define-syntax-rule] is the easiest macro form: essentially you're writing a code template with arguments that will be filled in when you invoke the macro. Notice how @racket[heading-name] appears in two roles: first as an identifier name, and then as a literal symbol.
(make-default-tag-function tag #:class (symbol->string 'heading-name))))]
(make-default-tag-function tag
#:class (symbol->string 'heading-name))))]
``Wait, why does @racket['heading-name] not produce the literal symbol @racket['heading-name]?'' The @racket['heading-name] syntax is just shorthand for @tt{(quote heading-name)}. Because this is a macro, the @racket[heading-name] inside this expression gets replaced with the value of the macro argument @racket[heading-name] before @racket[quote] is evaluated.
@deftogether[(
@deftogether[(
@defproc[
@defproc[
@ -597,6 +599,7 @@ with arguments that will be filled in when you invoke the macro.
(chapter
(chapter
[tx-element xexpr?] ...)
[tx-element xexpr?] ...)
txexpr?])]
txexpr?])]
Make a heading with a certain tag, using the @racket[define-heading] macro.
@chunk[<headings>
@chunk[<headings>
(define-heading topic 'h3)
(define-heading topic 'h3)
@ -606,16 +609,14 @@ with arguments that will be filled in when you invoke the macro.
Macro for defining a function that makes a heading by relying on data in the metas.
Macro for defining a function that makes a heading by pulling the page title out of the metas.
This macro relies on @racket[syntax-case] rather than @racket[define-syntax-rule]. It's a little more complicated, but also more flexible (and more idiomatic in Racket). @racket[define-syntax-rule] is actually a special-case version of @racket[syntax-case]. The best advice on learning macros is to start with @racket[syntax-case], because you can't live without it.
This macro relies on @racket[syntax-case] rather than @racket[define-syntax-rule].
@margin-note{Greg Hendershott's @link["http://www.greghendershott.com/fear-of-macros/pattern-matching.html"]{Fear of Macros} is a great place to start if you're new to Racket macros.}
It's a little more complicated, but also more flexible (and more idiomatic in Racket).
@racket[define-syntax-rule] is actually a special simplified version of @racket[syntax-case].
The best advice on learning macros is to start with @racket[syntax-case], because you can't live without it.
Good tutorial: http://www.greghendershott.com/fear-of-macros/pattern-matching.html
Otherwise this macro is similar to @racket[define-heading], except that we want to introduce a new identifier
Otherwise this macro is similar to @racket[define-heading], except that we want to introduce a new identifier
based on the name given to the macro. So if we pass @racket[topic] to the macro, it will define
with a different name, but based on the argument given to the macro. So if we pass @racket[topic] to the macro, it will define
an identifier called @racket[topic-from-metas]. You can't do that with @racket[define-syntax-rule].
an identifier called @racket[topic-from-metas]. You can't do that with @racket[define-syntax-rule].
@chunk[<define-heading-from-metas>
@chunk[<define-heading-from-metas>
@ -623,7 +624,8 @@ an identifier called @racket[topic-from-metas]. You can't do that with @racket[d
@ -649,7 +651,7 @@ an identifier called @racket[topic-from-metas]. You can't do that with @racket[d
[topic-xexpr xexpr?]
[topic-xexpr xexpr?]
[tx-element xexpr?] ...)
[tx-element xexpr?] ...)
txexpr?]
txexpr?]
Convert a topic + subhead into one HTML markup unit
Convert a topic + subhead into one HTML markup unit. Notice the use of @racket[no-hyphens-attr], which we defined in the @secref["Values"] section.
@chunk[<hanging-topic>
@chunk[<hanging-topic>
(define (hanging-topic topic-xexpr . tx-elements)
(define (hanging-topic topic-xexpr . tx-elements)
@ -660,59 +662,34 @@ Convert a topic + subhead into one HTML markup unit
(quick-table
(quick-table
[table-rows xexpr?] ...)
[table-rows xexpr?] ...)
txexpr?]
txexpr?]
Make an HTML table using simplified notation
Make an HTML table using simplified notation. In HTML, wrapping every paragraph in <p> tags is a terrible and dull task. But formatting tables is even worse. This function lets you make simple tables using @litchar{|} to signify columns, and line breaks to signify rows.
◊quick-table{heading left | heading center | heading right
upper left | upper center | upper right
lower left | lower center | lower right}
In HTML, wrapping every paragraph in <p> tags is a terrible and dull task.
@terminal{
But formatting tables is even worse.
◊quick-table{
heading left | heading center | heading right
This function lets you make simple tables using "|" to signify columns,
upper left | upper center | upper right
and line breaks to signify rows.
lower left | lower center | lower right}}
Let's uncork a few more whizzy Racket commands while we're at it.
This function assumes that each row has the same number of columns.
This function assumes that each row has the same number of columns. You could improve it to fill in blank cells in rows that need them.
You could improve it to fill in blank cells in rows that need them.
The idea is to break down the input into table headings and cells, and then work back up, wrapping each layer in the appropriate tags.
@chunk[<quick-table>
@chunk[<quick-table>
(define (quick-table . tx-elements)
(define (quick-table . tx-elements)
;; In Pollen, a multiline tx-elements block arrives as a list of lines and linebreak characters.
;; (A situation we already encountered in @racket[detect-list-items].)
(define rows-of-text-cells
(define rows-of-text-cells
(let ([text-rows (filter-not whitespace? tx-elements)]) ; throw out the linebreak characters
@ -722,27 +699,22 @@ You could improve it to fill in blank cells in rows that need them.
txexpr?]
txexpr?]
Create a thumbnail of a PDF that links to the PDF.
Create a thumbnail of a PDF that links to the PDF.
This function will only work properly if you have @racket[sips] on your system
This function will only work properly if you have @tt{sips} on your system (= command-line image-processing program, included with OS X).
(command-line image-processing program, included with OS X).
This shows how you can fold other kinds of project housekeeping into Pollen commands.
This shows how you can fold other kinds of project housekeeping into Pollen commands. Here, the function generates the thumbnail it needs when the page is compiled.
Here, the function generates the thumbnail it needs when the page is compiled.
One disadvantage of this approach is that the thumbnail will *always* be generated on recompile,
One disadvantage of this approach is that the thumbnail will always be generated on recompile, though you could put in some logic to avoid this (e.g., check the modification date of the PDF). In this case, @tt{sips} is fast enough that it's not bothersome.
though you could put in some logic to avoid this (e.g., check the modification date of the PDF).
In this case, @racket[sips] is fast enough that it's not bothersome.
Recall that every Pollen tag calls a function with the same name (if it exists, otherwise it just becomes a tag). This is also true of @racket[root].
Recall that every Pollen tag calls a function with the same name (if it exists, otherwise it just
@racket[root] has slightly special status inasmuch as it is the top tag of the X-expression, and thus the last tag function that will get called. Therefore, @racket[root] is a good place to put any processing that should happen once all the page content has been filled in.
becomes a tag). This is also true of @racket[root].
@racket[root] has slightly special status inasmuch as it is the top tag of the X-expression,
Often, you'll want to use a @racket[decode] or @racket[decode-elements] function, which can recursively perform different kinds of processing on different types of page elements.
and thus the last tag function that will get called. Therefore, @racket[root] is a good place to put any
processing that should happen once all the page content has been filled in.
Often, you'll want to use a @racket[decode] function, which can recursively perform different kinds of
In this case, we'll use @racket[decode-elements] twice. First, we'll use it just to detect paragraphs. We'll do this so that they're treated as @seclink["Block" #:doc '(lib "pollen/scribblings/pollen.scrbl")]{blocks} in the second phase, which does the rest of the processing.
processing on different types of page elements.
@chunk[<root>
@chunk[<root>
(define (root . elems)
(define (root . elems)
;; We will do the decoding in two steps.
(define elements-with-paragraphs
;; Detect paragraphs first so that they're treated as block-txexprs in next phase.
@ -818,14 +792,12 @@ processing on different types of page elements.
(hyphenate-block
(hyphenate-block
[block-tx txexpr?])
[block-tx txexpr?])
txexpr?]
txexpr?]
Helper function for root decoder
Helper function for @racket[root] decoder that handles hyphenation.
The basic @racket[hyphenate] function comes from the @racketmodname[hyphenate] module. We could attach @racket[hyphenate] to our @racket[root] decoder as a string processor rather than block processor. But we want to be able to handle our no-hyphens flag (aka @racket[no-hyphens-attr]), which is stored in the attributes of the X-expression. Therefore, we have to look at blocks, not just strings.
@chunk[<hyphenate-block>
@chunk[<hyphenate-block>
(define (hyphenate-block block-tx)
(define (hyphenate-block block-tx)
;; The basic @racket[hyphenate] function comes from the @racket[hyphenate] module.
;; We could attach @racket[hyphenate] to our decoder as a string processor rather than block processor.
;; But we want to be able to handle our "no-hyphens" flag (aka @racket[no-hyphens-attr]).
;; So we want to look at blocks, not strings.
(define (no-hyphens? tx)
(define (no-hyphens? tx)
(or (member (get-tag tx) '(th h1 h2 h3 h4 style script)) ; don't hyphenate these, no matter what
(or (member (get-tag tx) '(th h1 h2 h3 h4 style script)) ; don't hyphenate these, no matter what
(member no-hyphens-attr (get-attrs tx)))) ; also don't hyphenate blocks with @racket[no-hyphens-attr]
(member no-hyphens-attr (get-attrs tx)))) ; also don't hyphenate blocks with @racket[no-hyphens-attr]
@ -840,15 +812,10 @@ Helper function for root decoder
txexpr?]
txexpr?]
Perform tricky processing on quotation marks.
Perform tricky processing on quotation marks.
Because I'm a typography snob I like to push quotation marks into the margin a little bit
Because I'm a typography snob I like to push quotation marks into the margin a little bit when they appear at the left edge of a line (aka hanging quotes). This function just wraps left-hand quote marks in two little tags (@tt{push} and @tt{pull}) that I can then manipulate in CSS to get the effect.
when they appear at the left edge of a line (aka "hanging quotes").
This function just wraps left-hand quote marks in two little tags ("push" and "pull")
that I can then manipulate in CSS to get the effect.
@chunk[<make-quotes-hangable>
@chunk[<make-quotes-hangable>
(define (make-quotes-hangable str)
(define (make-quotes-hangable str)
;; using @racket[regexp-match*] with #:gap-select? makes it act like a funny kind of string splitter
@ -955,7 +920,7 @@ For use in our HTML templates. We could also define this function inside a templ
@section{Finally}
@section{Finally}
This last incantation is needed so @racketmodname[scribble/lp2] knows how to put together all the code chunks we've introduced in this file.
This last incantation is needed so this @racketmodname[scribble/lp2] document knows how to put together all the code chunks we've introduced in this file.