From a93405f5a52c04224164e2f34b24aed8231a7ec7 Mon Sep 17 00:00:00 2001
From: Matthew Butterick A pagetree is a hierarchical list of Pollen output files. A pagetree source file has the extension .ptree. A pagetree provides a convenient way of separating the structure of the pages from the page sources, and navigating around this structure. Pagetrees are made of pagenodes. Usually these pagenodes will be names of output files in your project. (If you think it would’ve been more logical to just call them “pages,” perhaps. When I think of a web page, I think of a file on a disk. Whereas pagenodes may — and often do — refer to files that don’t yet exist.) Books and other long documents are usually organized in a structured way — at minimum they have a sequence of pages, but more often they have sections with subsequences within. Individual Pollen source files don’t know anything about how they’re connected to other files. In theory, you could maintain this information within each source file. This would be a poor use of human energy. Let the pagetree figure it out. Examples: #t #f #f #t #t #f procedure Examples: '(root (mama.html son.html daughter.html) uncle.html) #f validate-pagetree: members-unique? failed because items aren’t unique: (son.html mama.html) 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: '(#t #t #t) '(#f #f #f #f) Example: '(#t #t #f) Examples: '(#t #t #t #t) '(symbol |9.999| index.html | silly |) parameter procedure (parent p [pagetree]) → (or/c #f pagenode?) Examples: 'mama.html 'root 'root #f procedure (children p [pagetree]) → (or/c #f pagenode?) Examples: '(son.html daughter.html) #f '(mama.html uncle.html) '((son.html daughter.html) #f) procedure (siblings p [pagetree]) → (or/c #f pagenode?) Examples: '(son.html daughter.html) '(son.html daughter.html) '(mama.html uncle.html) procedure (previous p [pagetree]) → (or/c #f pagenode?) procedure Examples: 'son.html 'mama.html 'mama.html #f '(mama.html son.html) '(mama.html son.html daughter.html) procedure (next p [pagetree]) → (or/c #f pagenode?) procedure Examples: 'daughter.html 'uncle.html 'uncle.html #f '(son.html daughter.html uncle.html) '(uncle.html) procedure (pagetree->list pagetree) → list? procedure (in-pagetree? pagenode [pagetree]) → boolean? procedure Books and other long documents are usually organized in a structured way — at minimum they have a sequence of pages, but more often they have sections with subsequences within. Individual pages in a Pollen project don’t know anything about how they’re connected to other pages. In theory, you could maintain this information within the source files. But this would be a poor use of human energy. Instead, use a pagetree. A pagetree is a simple abstraction for defining & working with sequences of pagenodes. Typically these pagenodes will be the names of output files in your project. “So it’s a list of web-page filenames?” Sort of. When I think of a web page, I think of an actual file on a disk. Keeping with Pollen’s orientation toward dynamic rendering, pagenodes may — and often do — refer to files that don’t yet exist. Moreover, by referring to output names rather than source names, you retain the flexibility to change the kind of source associated with a particular pagenode (e.g., from preprocessor source to Pollen markup). Pagetrees can be flat or hierarchical. A flat pagetree is just a list of pagenodes. A hierarchical pagetree can also contain recursively nested lists of pagenodes. But you needn’t pay attention to this distinction, as the pagetree functions don’t care which kind you use. Neither do I. Pagetrees surface throughout the Pollen system. They’re primarily used for navigation — for instance, calculating “previous,” “next,” or “up” links for a given page. A special pagetree, index.ptree, is used by the project server to order the files in a dashboard. Pagetrees can also be used to define batches of files for certain operations, for instance raco pollen render. You might find other uses for them too. A pagetree source file either starts with #lang pollen and uses the .ptree extension, or starts with #lang pollen/ptree and then can have any file extension. Unlike other Pollen source files, since the pagetree source is not rendered into an output format, the rest of the filename is up to you. Here’s a flat pagetree. Each line is considered a single pagenode (blank lines are ignored). Notice that no Pollen command syntax nor quoting is needed within the pagetree source: And here’s the output in DrRacket: '(pagetree-root index.html introduction.html main_argument.html conclusion.html) Keeping with usual Pollen policy, this is an X-expression. The pagetree-root is just an arbitrary tag that contains the pagetree. Upgrading to a hierarchical pagetree is simple. The same basic rule applies — one pagenode per line. But this time, you add Pollen command syntax: a lozenge ◊ in front of a pagenode marks it as the top of a nested list, and the sub-pagenodes of that list go between { curly braces }, like so: The output of our hierarchical pagetree: '(pagetree-root toc.html (first-chapter.html foreword.html introduction.html) (second-chapter.html (main-argument.html facts.html analysis.html) conclusion.html) bibliography.html) One advantage of using a source file is that when you run it in DrRacket, it will automatically be checked using validate-pagetree, which insures that every element in the pagetree meets pagenode?, and that all the pagenodes are unique. This pagetree has a duplicate pagenode, so it won’t run: Instead, you’ll get an error: validate-pagetree: members-unique? failed because item isn’t unique: (index.html) Experienced programmers may want to know that because a pagetree is just an X-expression, you can synthesize a pagetree using any Pollen or Racket tools for making X-expressions. For example, here’s some Racket code that generates the same pagetree as the flat.ptree source file above: Note that you need to take more care when building a pagetree by hand. Pagenodes are symbols, not strings, thus the use of string->symbol is mandatory. One benefit of using a pagetree source file is that it takes care of this housekeeping for you. Typically you’ll call the pagetree-navigation functions from inside templates, using the special variable here as the starting point. For more on this technique, see pagetree navigation. When you’re using the project server to view the files in a directory, the server will first look for a file called index.ptree. If it finds this pagetree file, it will use it to build the dashboard. If not, then it will synthesize a pagetree using a directory listing. For more on this technique, see Using the dashboard. The raco pollen render command is used to regenerate an output file from its source. If you pass a pagetree to raco pollen render, it will automatically render each file listed in the pagetree. For instance, many projects have auxiliary pages that don’t really belong in the main navigational flow. You can collect these pages in a separate pagetree: Thus, when you’re using pagetree-navigation functions within a template, you can use your main pagetree, and restrict the navigation to the main editorial content. But when you render the project, you can pass both pagetrees to raco pollen render. For more on this technique, see raco pollen render. Examples: #t #f #f #t #t #f procedure (validate-pagetree possible-pagetree) → pagetree? Examples: '(root (mama.html son.html daughter.html) uncle.html) #f validate-pagetree: members-unique? failed because items aren’t unique: (son.html mama.html) 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: '(#t #t #t) '(#f #f #f #f) procedure (pagenodeish? v) → boolean? Example: '(#t #t #f) procedure (->pagenode v) → pagenode? Examples: '(#t #t #t #t) '(symbol |9.999| index.html | silly |) parameter procedure Examples: 'mama.html 'root 'root #f procedure Examples: '(son.html daughter.html) #f '(mama.html uncle.html) '((son.html daughter.html) #f) procedure Examples: '(son.html daughter.html) '(son.html daughter.html) '(mama.html uncle.html) procedure procedure Examples: 'son.html 'mama.html 'mama.html #f '(mama.html son.html) '(mama.html son.html daughter.html) procedure procedure Examples: 'daughter.html 'uncle.html 'uncle.html #f '(son.html daughter.html uncle.html) '(uncle.html) procedure (pagetree->list pagetree) → list? procedure (in-pagetree? pagenode [pagetree]) → boolean? procedure (path->pagenode p) → pagenode? Pollen is a publishing system that helps authors create beautiful and functional web-based books. Pollen includes tools for writing, designing, programming, testing, and publishing. I used Pollen to create my book Butterick’s Practical Typography. Sure, go take a look. Is it better than the last digital book you encountered? Yes it is. Would you like your book to look like that? If so, keep reading. First, that digital books should be the best books we’ve ever had. So far, they’re not even close. Second, that because digital books are software, an author shouldn’t think of a book as merely data. The book is a program. Third, that the way we make digital books better than their predecessors is by exploiting this programmability. That’s what Pollen is for. Not that you need to be a programmer to use Pollen. On the contrary, the Pollen language is markup-based, so you can write & edit text naturally. But when you want to automate repetitive tasks, add cross-references, or pull in data from other sources, you can access a full programming language from within the text. That language is Racket. I chose Racket because while the idea for Pollen had been with me for several years, it simply wasn’t possible to build it with other languages. So if it’s unfamiliar to you, don’t panic. It was unfamiliar to me. Once you see what you can do with Pollen & Racket, you may be persuaded. I was. Or, if you can find a better digital-publishing tool, use that. But I’m never going back to the way I used to work. First, that digital books should be the best books we’ve ever had. So far, they’re not even close. Second, that because digital books are software, an author shouldn’t think of a book as merely data. The book is a program. Third, that the way we make digital books better than their predecessors is by exploiting this programmability.11.4 Pagetree
(require pollen/pagetree) package: pollen > (pagetree? '(root index.html)) > (pagetree? '(root duplicate.html duplicate.html)) > (pagetree? '(root index.html "string.html")) > (define nested-ptree '(root 1.html 2.html (3.html 3a.html 3b.html))) > (pagetree? nested-ptree) > (pagetree? `(root index.html ,nested-ptree (subsection.html more.html))) ; Nesting a subtree twice creates duplication > (pagetree? `(root index.html ,nested-ptree (subsection.html ,nested-ptree))) possible-pagetree : any/c > (validate-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (validate-pagetree `(root (,+ son.html daughter.html) uncle.html)) > (validate-pagetree '(root (mama.html son.html son.html) mama.html)) ; Three symbols, the third one annoying but valid > (map pagenode? '(symbol index.html | silly |)) ; A number, a string, a txexpr, and a whitespace symbol > (map pagenode? '(9.999 "index.html" (p "Hello") | |)) > (map pagenodeish? '(9.999 "index.html" | |)) > (map pagenodeish? '(symbol 9.999 "index.html" | silly |)) > (map ->pagenode '(symbol 9.999 "index.html" | silly |)) 11.4.1 Navigation
(current-pagetree pagetree) → void? pagetree : pagetree? p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (parent 'son.html) > (parent "mama.html") > (parent (parent 'son.html)) > (parent (parent (parent 'son.html))) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (children 'mama.html) > (children 'uncle.html) > (children 'root) > (map children (children 'root)) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (siblings 'son.html) > (siblings 'daughter.html) > (siblings 'mama.html) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (previous 'daughter.html) > (previous 'son.html) > (previous (previous 'daughter.html)) > (previous 'mama.html) > (previous* 'daughter.html) > (previous* 'uncle.html) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (next 'son.html) > (next 'daughter.html) > (next (next 'son.html)) > (next 'uncle.html) > (next* 'mama.html) > (next* 'daughter.html) 11.4.2 Utilities
pagetree : pagetree? pagenode : pagenode? pagetree : pagetree? = (current-pagetree) p : pathish? 11.4 Pagetree
(require pollen/pagetree) package: pollen 11.4.1 Making pagetrees with a source file
#lang pollen index.html introduction.html main_argument.html conclusion.html #lang pollen toc.html ◊first-chapter.html{ foreword.html introduction.html} ◊second-chapter.html{ ◊main-argument.html{ facts.html analysis.html} conclusion.html} bibliography.html #lang pollen index.html introduction.html main_argument.html conclusion.html index.html 11.4.2 Making pagetrees by hand
11.4.3 Using pagetrees for navigation
11.4.4 Using index.ptree in the dashboard
11.4.5 Using pagetrees with raco pollen render
#lang pollen 404-error.html terms-of-service.html webmaster.html [... and so on] 11.4.6 Functions
11.4.6.1 Predicates & validation
> (pagetree? '(root index.html)) > (pagetree? '(root duplicate.html duplicate.html)) > (pagetree? '(root index.html "string.html")) > (define nested-ptree '(root 1.html 2.html (3.html 3a.html 3b.html))) > (pagetree? nested-ptree) > (pagetree? `(root index.html ,nested-ptree (subsection.html more.html))) ; Nesting a subtree twice creates duplication > (pagetree? `(root index.html ,nested-ptree (subsection.html ,nested-ptree))) possible-pagetree : any/c > (validate-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (validate-pagetree `(root (,+ son.html daughter.html) uncle.html)) > (validate-pagetree '(root (mama.html son.html son.html) mama.html)) ; Three symbols, the third one annoying but valid > (map pagenode? '(symbol index.html | silly |)) ; A number, a string, a txexpr, and a whitespace symbol > (map pagenode? '(9.999 "index.html" (p "Hello") | |)) v : any/c > (map pagenodeish? '(9.999 "index.html" | |)) v : pagenodeish? > (map pagenodeish? '(symbol 9.999 "index.html" | silly |)) > (map ->pagenode '(symbol 9.999 "index.html" | silly |)) 11.4.6.2 Navigation
(current-pagetree pagetree) → void? pagetree : pagetree? p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (parent 'son.html) > (parent "mama.html") > (parent (parent 'son.html)) > (parent (parent (parent 'son.html))) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (children 'mama.html) > (children 'uncle.html) > (children 'root) > (map children (children 'root)) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (siblings 'son.html) > (siblings 'daughter.html) > (siblings 'mama.html) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (previous 'daughter.html) > (previous 'son.html) > (previous (previous 'daughter.html)) > (previous 'mama.html) > (previous* 'daughter.html) > (previous* 'uncle.html) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) p : (or/c #f pagenodeish?) pagetree : pagetree? = (current-pagetree) > (current-pagetree '(root (mama.html son.html daughter.html) uncle.html)) > (next 'son.html) > (next 'daughter.html) > (next (next 'son.html)) > (next 'uncle.html) > (next* 'mama.html) > (next* 'daughter.html) 11.4.6.3 Utilities
pagetree : pagetree? pagenode : pagenode? pagetree : pagetree? = (current-pagetree) p : pathish? Index
Index
Pollen: the book is a program Pollen: the book is a program
#lang pollen package: pollen
That’s what Pollen is for.
Not that you need to be a programmer to use Pollen. On the contrary, the Pollen language is markup-based, so you can write & edit text naturally. But when you want to automate repetitive tasks, add cross-references, or pull in data from other sources, you can access a full programming language from within the text.
That language is Racket. I chose Racket because while the idea for Pollen had been with me for several years, it simply wasn’t possible to build it with other languages. So if it’s unfamiliar to you, don’t panic. It was unfamiliar to me. Once you see what you can do with Pollen & Racket, you may be persuaded. I was.
Or, if you can find a better digital-publishing tool, use that. But I’m never going back to the way I used to work.