pull/9/head
Matthew Butterick 11 years ago
parent 04e7a63583
commit 1e523376c9

@ -30,9 +30,9 @@ clone copies rendered files to desktop
#|
[("render") `(begin
;; todo: take extensions off the comand line
(displayln "Render preproc & pagemap files ...")
(displayln "Render preproc & pagetree files ...")
(require "render.rkt" "file-tools.rkt" "world.rkt")
(apply render-batch (append-map project-files-with-ext (list world:preproc-source-ext world:pagemap-source-ext))))]
(apply render-batch (append-map project-files-with-ext (list world:preproc-source-ext world:pagetree-source-ext))))]
[("clone") (let ([target-path
(if (> (len args) 1)
(->path (get args 1))
@ -47,7 +47,7 @@ clone copies rendered files to desktop
markup-source?
preproc-source?
template-source?
pagemap-source?
pagetree-source?
pollen-script?
magic-directory?
racket-file?)))

@ -21,7 +21,7 @@
;; helper function for pagemap
;; helper function for pagetree
;; make paths absolute to test whether files exist,
;; then convert back to relative
(define+provide/contract (visible? path)
@ -87,7 +87,7 @@
(make-source-utility-functions preproc)
(make-source-utility-functions null)
(make-source-utility-functions pagemap)
(make-source-utility-functions pagetree)
(make-source-utility-functions markup)
(make-source-utility-functions template)
(make-source-utility-functions scribble)

@ -30,7 +30,7 @@
(let* ([file-ext-pattern (pregexp "\\w+$")]
[here-ext (string->symbol (car (regexp-match file-ext-pattern here-path)))])
(cond
[(equal? here-ext world:pagemap-source-ext) world:reader-mode-pagemap]
[(equal? here-ext world:pagetree-source-ext) world:reader-mode-pagetree]
[(equal? here-ext world:markup-source-ext) world:reader-mode-markup]
[(equal? here-ext world:markdown-source-ext) world:reader-mode-markdown]
[else world:reader-mode-preproc]))
@ -63,7 +63,7 @@
;; set up the 'doc export
(require pollen/decode)
(define doc (apply (cond
[(equal? parser-mode world:reader-mode-pagemap) (λ xs ((dynamic-require 'pollen/pagemap 'decode-pagemap) xs))]
[(equal? parser-mode world:reader-mode-pagetree) (λ xs ((dynamic-require 'pollen/pagetree 'decode-pagetree) xs))]
;; 'root is the hook for the decoder function.
;; If it's not a defined identifier, it just hits #%top and becomes `(root ,@body ...)
[(or (equal? parser-mode world:reader-mode-markup)

@ -1,121 +0,0 @@
#lang racket/base
(require racket/path)
(require "tools.rkt" "world.rkt" "decode.rkt" sugar txexpr "cache.rkt")
(define+provide current-pagemap (make-parameter #f))
(define+provide (node? x)
(->boolean (and (symbol? x) (try (not (whitespace/nbsp? (->string x)))
(except [exn:fail? (λ(e) #f)])))))
(define+provide (nodeish? x)
(try (node? (->symbol x))
(except [exn:fail? (λ(e) #f)])))
(define/contract+provide (->node x)
(nodeish? . -> . node?)
(->symbol x))
(define+provide/contract (decode-pagemap xs)
(txexpr-elements? . -> . any/c) ; because pagemap is being explicitly validated
(validate-pagemap
(decode (cons world:pagemap-root-node xs)
#:txexpr-elements-proc (λ(xs) (filter (compose1 not whitespace?) xs))
#:string-proc string->symbol))) ; because faster than ->node
(define+provide (validate-pagemap x)
(let ([nodes (pagemap->list x)])
(and
(andmap (λ(p) (or (node? p) (error (format "validate-pagemap: \"~a\" is not a valid node" p)))) nodes)
(try (members-unique?/error nodes)
(except [exn:fail? (λ(e) (error (format "validate-pagemap: ~a" (exn-message e))))]))
x)))
(define+provide (pagemap? x)
(try (->boolean (validate-pagemap x))
(except [exn:fail? (λ(e) #f)])))
;; Try loading from pagemap file, or failing that, synthesize pagemap.
(define+provide/contract (make-project-pagemap project-dir)
(pathish? . -> . pagemap?)
(define pagemap-source (build-path project-dir world:default-pagemap))
(cached-require pagemap-source world:main-pollen-export))
(define+provide/contract (parent-node p [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f node?))
(and pagemap p
(let ([node (->node p)])
(if (member node (map (λ(x) (if (list? x) (car x) x)) (cdr pagemap)))
(car pagemap)
(ormap (λ(x) (parent-node node x)) (filter list? pagemap))))))
(define+provide/contract (child-nodes p [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f (listof node?)))
(and pagemap p
(let ([node (->node p)])
(if (equal? node (car pagemap))
(map (λ(x) (if (list? x) (car x) x)) (cdr pagemap))
(ormap (λ(x) (child-nodes node x)) (filter list? pagemap))))))
(define+provide/contract (sibling-nodes p [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f (listof node?)))
(child-nodes (parent-node p pagemap) pagemap))
;; flatten tree to sequence
(define+provide/contract (pagemap->list pagemap)
(pagemap? . -> . (listof node?))
; use cdr to get rid of root tag at front
(cdr (flatten pagemap)))
(define (adjacent-nodes side p [pagemap (current-pagemap)])
; ((symbol? (or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f (listof node?)))
(and pagemap p
(let* ([node (->node p)]
[proc (if (equal? side 'left) takef takef-right)]
[result (proc (pagemap->list pagemap) (λ(x) (not (equal? node x))))])
(and (not (empty? result)) result))))
(define+provide/contract (previous-nodes node [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f (listof node?)))
(adjacent-nodes 'left node pagemap))
(define+provide/contract (next-nodes node [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f (listof node?)))
(adjacent-nodes 'right node pagemap))
(define+provide/contract (previous-node node [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f node?))
(let ([result (previous-nodes node pagemap)])
(and result (last result))))
(define+provide/contract (next-node node [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . (or/c #f node?))
(let ([result (next-nodes node pagemap)])
(and result (first result))))
(define/contract+provide (path->node path)
(coerce/path? . -> . coerce/symbol?)
(->output-path (find-relative-path (world:current-project-root) (->complete-path path))))
(define+provide/contract (node-in-pagemap? node [pagemap (current-pagemap)])
(((or/c #f nodeish?)) (pagemap?) . ->* . boolean?)
(->boolean (and node (member node (pagemap->list pagemap)))))

@ -0,0 +1,121 @@
#lang racket/base
(require racket/path)
(require "tools.rkt" "world.rkt" "decode.rkt" sugar txexpr "cache.rkt")
(define+provide current-pagetree (make-parameter #f))
(define+provide (pagenode? x)
(->boolean (and (symbol? x) (try (not (whitespace/nbsp? (->string x)))
(except [exn:fail? (λ(e) #f)])))))
(define+provide (pagenodeish? x)
(try (pagenode? (->symbol x))
(except [exn:fail? (λ(e) #f)])))
(define/contract+provide (->pagenode x)
(pagenodeish? . -> . pagenode?)
(->symbol x))
(define+provide/contract (decode-pagetree xs)
(txexpr-elements? . -> . any/c) ; because pagetree is being explicitly validated
(validate-pagetree
(decode (cons world:pagetree-root-node xs)
#:txexpr-elements-proc (λ(xs) (filter (compose1 not whitespace?) xs))
#:string-proc string->symbol))) ; because faster than ->pagenode
(define+provide (validate-pagetree x)
(let ([pagenodes (pagetree->list x)])
(and
(andmap (λ(p) (or (pagenode? p) (error (format "validate-pagetree: \"~a\" is not a valid pagenode" p)))) pagenodes)
(try (members-unique?/error pagenodes)
(except [exn:fail? (λ(e) (error (format "validate-pagetree: ~a" (exn-message e))))]))
x)))
(define+provide (pagetree? x)
(try (->boolean (validate-pagetree x))
(except [exn:fail? (λ(e) #f)])))
;; Try loading from pagetree file, or failing that, synthesize pagetree.
(define+provide/contract (make-project-pagetree project-dir)
(pathish? . -> . pagetree?)
(define pagetree-source (build-path project-dir world:default-pagetree))
(cached-require pagetree-source world:main-pollen-export))
(define+provide/contract (parent pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f pagenode?))
(and pt pnish
(let ([pagenode (->pagenode pnish)])
(if (member pagenode (map (λ(x) (if (list? x) (car x) x)) (cdr pt)))
(car pt)
(ormap (λ(x) (parent pagenode x)) (filter list? pt))))))
(define+provide/contract (children p [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f (listof pagenode?)))
(and pt p
(let ([pagenode (->pagenode p)])
(if (equal? pagenode (car pt))
(map (λ(x) (if (list? x) (car x) x)) (cdr pt))
(ormap (λ(x) (children pagenode x)) (filter list? pt))))))
(define+provide/contract (siblings pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f (listof pagenode?)))
(children (parent pnish pt) pt))
;; flatten tree to sequence
(define+provide/contract (pagetree->list pt)
(pagetree? . -> . (listof pagenode?))
; use cdr to get rid of root tag at front
(cdr (flatten pt)))
(define (adjacents side pnish [pt (current-pagetree)])
; ((symbol? (or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f (listof pagenode?)))
(and pt pnish
(let* ([pagenode (->pagenode pnish)]
[proc (if (equal? side 'left) takef takef-right)]
[result (proc (pagetree->list pt) (λ(x) (not (equal? pagenode x))))])
(and (not (empty? result)) result))))
(define+provide/contract (previous* pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f (listof pagenode?)))
(adjacents 'left pnish pt))
(define+provide/contract (next* pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f (listof pagenode?)))
(adjacents 'right pnish pt))
(define+provide/contract (previous pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f pagenode?))
(let ([result (previous* pnish pt)])
(and result (last result))))
(define+provide/contract (next pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . (or/c #f pagenode?))
(let ([result (next* pnish pt)])
(and result (first result))))
(define/contract+provide (path->pagenode path)
(coerce/path? . -> . coerce/symbol?)
(->output-path (find-relative-path (world:current-project-root) (->complete-path path))))
(define+provide/contract (in-pagetree? pnish [pt (current-pagetree)])
(((or/c #f pagenodeish?)) (pagetree?) . ->* . boolean?)
(->boolean (and pnish (member pnish (pagetree->list pt)))))

@ -1,4 +1,4 @@
#lang racket/base
(require pollen/lang/reader-base)
(make-reader-with-mode world:reader-mode-pagemap)
(make-reader-with-mode world:reader-mode-pagetree)

@ -1,6 +1,6 @@
#lang racket/base
(require racket/file racket/rerequire racket/path racket/match)
(require sugar "file.rkt" "cache.rkt" "world.rkt" "debug.rkt" "pagemap.rkt" "project-requires.rkt")
(require sugar "file.rkt" "cache.rkt" "world.rkt" "debug.rkt" "pagetree.rkt" "project-requires.rkt")
;; when you want to generate everything fresh,
@ -49,12 +49,12 @@
(for-each render-to-file-if-needed xs))
(define/contract+provide (render-pagemap pagemap-or-path)
((or/c pagemap? pathish?) . -> . void?)
(define pagemap (if (pagemap? pagemap-or-path)
pagemap-or-path
(cached-require pagemap-or-path world:main-pollen-export)))
(apply render-batch (pagemap->list pagemap)))
(define/contract+provide (render-pagetree pagetree-or-path)
((or/c pagetree? pathish?) . -> . void?)
(define pagetree (if (pagetree? pagetree-or-path)
pagetree-or-path
(cached-require pagetree-or-path world:main-pollen-export)))
(apply render-batch (pagetree->list pagetree)))
(define/contract+provide (render-for-dev-server so-pathish #:force [force #f])
@ -64,7 +64,7 @@
[(ormap (λ(test) (test so-path)) (list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source?))
(let-values ([(source-path output-path) (->source+output-paths so-path)])
(render-to-file-if-needed source-path output-path #:force force))]
[(pagemap-source? so-path) (render-pagemap so-path)]))
[(pagetree-source? so-path) (render-pagetree so-path)]))
(void))
@ -163,7 +163,7 @@
,(require-project-require-files source-path)
(let ([doc (cached-require ,source-path ',world:main-pollen-export)]
[metas (cached-require ,source-path ',world:meta-pollen-export)])
(local-require pollen/pagemap pollen/template pollen/top)
(local-require pollen/pagetree pollen/template pollen/top)
(define here (metas->here metas))
(include-template #:command-char ,world:template-field-delimiter ,(->string (find-relative-path source-dir template-path))))))
@ -224,7 +224,7 @@
pollen/decode
pollen/file
pollen/main
pollen/pagemap
pollen/pagetree
pollen/cache
sugar
txexpr
@ -244,7 +244,7 @@
(list? . -> . bytes?)
(parameterize ([current-namespace (make-base-namespace)]
[current-output-port (current-error-port)]
[current-pagemap (make-project-pagemap (world:current-project-root))])
[current-pagetree (make-project-pagetree (world:current-project-root))])
(for-each (λ(mod-name) (namespace-attach-module cache-ns mod-name))
`(web-server/templates
xml
@ -257,7 +257,7 @@
pollen/debug
pollen/decode
pollen/file
pollen/pagemap
pollen/pagetree
pollen/cache
sugar
txexpr

@ -7,5 +7,6 @@
@include-section["cache.scrbl"]
@include-section["decode.scrbl"]
@include-section["file.scrbl"]
@include-section["pagemap.scrbl"]
@include-section["pagetree.scrbl"]
@include-section["render.scrbl"]
@include-section["template.scrbl"]

@ -1,213 +0,0 @@
#lang scribble/manual
@(require scribble/eval pollen/cache pollen/world (for-label racket (except-in pollen #%module-begin) pollen/world pollen/pagemap txexpr pollen/decode pollen/file))
@(define my-eval (make-base-eval))
@(my-eval `(require pollen pollen/pagemap txexpr))
@title{Pagemaps}
@defmodule[pollen/pagemap]
A @italic{pagemap} is a hierarchical list of Pollen output files. A pagemap source file has the extension @code[(format ".~a" world:pagemap-source-ext)]. A pagemap provides a convenient way of separating the structure of the pages from the page sources, and navigating around this structure.
Pagemaps are made of @italic{nodes}. Usually these nodes will be names of output files in your project. (If you think it would've been more logical to call them ``pages,'' perhaps. When I think of a web page, I think of a file on a disk. Whereas nodes 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 pagemap figure it out.
@defproc[
(pagemap?
[possible-pagemap any/c])
boolean?]
Test whether @racket[_possible-pagemap] is a valid pagemap. It must be a @racket[txexpr?] where all elements are @racket[node?] and unique within @racket[_possible-pagemap] (not counting the root node).
@examples[#:eval my-eval
(pagemap? '(root index.html))
(pagemap? '(root index.html index.html))
(pagemap? '(root index.html "index.html"))
(define nested-pmap '(root 1.html 2.html (3.html 3a.html 3b.html)))
(pagemap? nested-pmap)
(pagemap? `(root index.html ,nested-pmap (subsection.html more.html)))
(pagemap? `(root index.html ,nested-pmap (subsection.html ,nested-pmap)))
]
@defproc[
(validate-pagemap
[possible-pagemap any/c])
pagemap?]
Like @racket[pagemap?], but raises a descriptive error if @racket[_possible-pagemap] is invalid, and otherwise returns @racket[_possible-pagemap] itself.
@examples[#:eval my-eval
(validate-pagemap '(root (mama.html son.html daughter.html) uncle.html))
(validate-pagemap `(root (,+ son.html daughter.html) uncle.html))
(validate-pagemap '(root (mama.html son.html son.html) mama.html))
]
@defproc[
(node?
[possible-node any/c])
boolean?]
Test whether @racket[_possible-node] is a valid node (short for ``pagemap node''). A node can be any @racket[symbol?] that is not @racket[whitespace/nbsp?] Every leaf of a pagemap is a node. In practice, your nodes will likely be names of output files.
@margin-note{Nodes are symbols (rather than strings) so that pagemaps will be valid tagged X-expressions, which is a more convenient format for validation & processing.}
@examples[#:eval my-eval
(map node? '(symbol index.html | silly |))
(map node? '(9.999 "index.html" (p "Hello") | |))
]
@defproc[
(nodeish?
[v any/c])
boolean?]
Return @racket[#t] if @racket[_v] can be converted with @racket[->node].
@examples[#:eval my-eval
(map nodeish? '(9.999 "index.html" | |))
]
@defproc[
(->node
[v nodeish?])
node?]
Convert @racket[_v] to a node.
@examples[#:eval my-eval
(map nodeish? '(symbol 9.999 "index.html" | silly |))
(map ->node '(symbol 9.999 "index.html" | silly |))
]
@section{Navigation}
@defparam[current-pagemap pagemap pagemap?
#:value #f]{
A parameter that defines the default pagemap used by pagemap navigation functions (e.g., @racket[parent-node], @racket[chidren], et al.) if another is not explicitly specified. Initialized to @racket[#f].}
@defproc[
(parent-node
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f node?)]
Find the parent-node node of @racket[_p] within @racket[_pagemap]. Return @racket[#f] if there isn't one.
@examples[#:eval my-eval
(current-pagemap '(root (mama.html son.html daughter.html) uncle.html))
(parent-node 'son.html)
(parent-node "mama.html")
(parent-node (parent-node 'son.html))
(parent-node (parent-node (parent-node 'son.html)))
]
@defproc[
(child-nodes
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f node?)]
Find the child nodes of @racket[_p] within @racket[_pagemap]. Return @racket[#f] if there aren't any.
@examples[#:eval my-eval
(current-pagemap '(root (mama.html son.html daughter.html) uncle.html))
(child-nodes 'mama.html)
(child-nodes 'uncle.html)
(child-nodes 'root)
(map child-nodes (child-nodes 'root))
]
@defproc[
(sibling-nodes
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f node?)]
Find the sibling nodes of @racket[_p] within @racket[_pagemap]. The list will include @racket[_p] itself. But the function will still return @racket[#f] if @racket[_pagemap] is @racket[#f].
@examples[#:eval my-eval
(current-pagemap '(root (mama.html son.html daughter.html) uncle.html))
(sibling-nodes 'son.html)
(sibling-nodes 'daughter.html)
(sibling-nodes 'mama.html)
]
@deftogether[(
@defproc[
(previous-node
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f node?)]
@defproc[
(previous-nodes
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f (listof node?))]
)]
Return the node immediately before @racket[_p]. For @racket[previous-nodes], return all the nodes before @racket[_p], in sequence. In both cases, return @racket[#f] if there aren't any nodes. The root node is ignored.
@examples[#:eval my-eval
(current-pagemap '(root (mama.html son.html daughter.html) uncle.html))
(previous-node 'daughter.html)
(previous-node 'son.html)
(previous-node (previous-node 'daughter.html))
(previous-node 'mama.html)
(previous-nodes 'daughter.html)
(previous-nodes 'uncle.html)
]
@deftogether[(
@defproc[
(next-node
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f node?)]
@defproc[
(next-nodes
[p (or/c #f nodeish?)]
[pagemap pagemap? (current-pagemap)])
(or/c #f (listof node?))]
)]
Return the node immediately after @racket[_p]. For @racket[next-nodes], return all the nodes after @racket[_p], in sequence. In both cases, return @racket[#f] if there aren't any nodes. The root node is ignored.
@examples[#:eval my-eval
(current-pagemap '(root (mama.html son.html daughter.html) uncle.html))
(next-node 'son.html)
(next-node 'daughter.html)
(next-node (next-node 'son.html))
(next-node 'uncle.html)
(next-nodes 'mama.html)
(next-nodes 'daughter.html)
]
@section{Utilities}
@defproc[
(pagemap->list
[pagemap pagemap?])
list?
]
Convert @racket[_pagemap] to a simple list, preserving order.
@defproc[
(node-in-pagemap?
[node node?]
[pagemap pagemap? (current-pagemap)])
boolean?
]
Report whether @racket[_node] is in @racket[_pagemap].
@defproc[
(path->node
[p pathish?])
node?
]
Convert path @racket[_p] to a node — 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 node actually exists in the current pagemap (for that, use @racket[node-in-pagemap?]).

@ -0,0 +1,213 @@
#lang scribble/manual
@(require scribble/eval pollen/cache pollen/world (for-label racket (except-in pollen #%module-begin) pollen/world pollen/pagetree txexpr pollen/decode pollen/file))
@(define my-eval (make-base-eval))
@(my-eval `(require pollen pollen/pagetree txexpr))
@title{Pagetrees}
@defmodule[pollen/pagetree]
A @italic{pagetree} is a hierarchical list of Pollen output files. A pagetree source file has the extension @code[(format ".~a" world:pagetree-source-ext)]. 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 @italic{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.
@defproc[
(pagetree?
[possible-pagetree any/c])
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).
@examples[#:eval my-eval
(pagetree? '(root index.html))
(pagetree? '(root index.html index.html))
(pagetree? '(root index.html "index.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)))
(pagetree? `(root index.html ,nested-ptree (subsection.html ,nested-ptree)))
]
@defproc[
(validate-pagetree
[possible-pagetree any/c])
pagetree?]
Like @racket[pagetree?], but raises a descriptive error if @racket[_possible-pagetree] is invalid, and otherwise returns @racket[_possible-pagetree] itself.
@examples[#:eval my-eval
(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))
]
@defproc[
(pagenode?
[possible-pagenode any/c])
boolean?]
Test whether @racket[_possible-pagenode] is a valid pagenode. A pagenode can be any @racket[symbol?] that is not @racket[whitespace/nbsp?] Every leaf of a pagetree is a pagenode. In practice, your pagenodes will likely be names of output files.
@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
(map pagenode? '(symbol index.html | silly |))
(map pagenode? '(9.999 "index.html" (p "Hello") | |))
]
@defproc[
(pagenodeish?
[v any/c])
boolean?]
Return @racket[#t] if @racket[_v] can be converted with @racket[->pagenode].
@examples[#:eval my-eval
(map pagenodeish? '(9.999 "index.html" | |))
]
@defproc[
(->pagenode
[v pagenodeish?])
pagenode?]
Convert @racket[_v] to a pagenode.
@examples[#:eval my-eval
(map pagenodeish? '(symbol 9.999 "index.html" | silly |))
(map ->pagenode '(symbol 9.999 "index.html" | silly |))
]
@section{Navigation}
@defparam[current-pagetree pagetree pagetree?
#:value #f]{
A parameter that defines the default pagetree used by pagetree navigation functions (e.g., @racket[parent-pagenode], @racket[chidren], et al.) if another is not explicitly specified. Initialized to @racket[#f].}
@defproc[
(parent
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f pagenode?)]
Find the parent pagenode of @racket[_p] within @racket[_pagetree]. Return @racket[#f] if there isn't one.
@examples[#:eval my-eval
(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)))
]
@defproc[
(children
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f pagenode?)]
Find the child pagenodes of @racket[_p] within @racket[_pagetree]. Return @racket[#f] if there aren't any.
@examples[#:eval my-eval
(current-pagetree '(root (mama.html son.html daughter.html) uncle.html))
(children 'mama.html)
(children 'uncle.html)
(children 'root)
(map children (children 'root))
]
@defproc[
(siblings
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f pagenode?)]
Find the sibling pagenodes of @racket[_p] within @racket[_pagetree]. The list will include @racket[_p] itself. But the function will still return @racket[#f] if @racket[_pagetree] is @racket[#f].
@examples[#:eval my-eval
(current-pagetree '(root (mama.html son.html daughter.html) uncle.html))
(siblings 'son.html)
(siblings 'daughter.html)
(siblings 'mama.html)
]
@deftogether[(
@defproc[
(previous
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f pagenode?)]
@defproc[
(previous*
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f (listof pagenode?))]
)]
Return the pagenode immediately before @racket[_p]. For @racket[previous*], return all the pagenodes before @racket[_p], in sequence. In both cases, return @racket[#f] if there aren't any pagenodes. The root pagenode is ignored.
@examples[#:eval my-eval
(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)
]
@deftogether[(
@defproc[
(next
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f pagenode?)]
@defproc[
(next*
[p (or/c #f pagenodeish?)]
[pagetree pagetree? (current-pagetree)])
(or/c #f (listof pagenode?))]
)]
Return the pagenode immediately after @racket[_p]. For @racket[next*], return all the pagenodes after @racket[_p], in sequence. In both cases, return @racket[#f] if there aren't any pagenodes. The root pagenode is ignored.
@examples[#:eval my-eval
(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)
]
@section{Utilities}
@defproc[
(pagetree->list
[pagetree pagetree?])
list?
]
Convert @racket[_pagetree] to a simple list, preserving order.
@defproc[
(in-pagetree?
[pagenode pagenode?]
[pagetree pagetree? (current-pagetree)])
boolean?
]
Report whether @racket[_pagenode] is in @racket[_pagetree].
@defproc[
(path->pagenode
[p pathish?])
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?]).

@ -6,7 +6,9 @@
@(my-eval `(require pollen))
@title{Pollen: the book is a program}
@title[#:style 'toc]{Pollen: the book is a program}
@author[(author+email "Matthew Butterick" "mb@mbtype.com")]
@ -22,6 +24,9 @@ That language is Racket. I chose Racket because while the idea for Pollen had be
Or, if you can find a better digital book-publishing tool, use that. Personally, I'm never going back to the way I used to work.
@local-table-of-contents[]
@section{Installation}
Install Racket, which includes DrRacket.

@ -66,9 +66,9 @@ Render multiple @racket[_source-paths] in one go. This can be faster than @racke
@defproc*[
(
[(render-pagemap [pagemap pagemap?]) void?]
[(render-pagemap [pagemap-source pathish?]) void?])]
Using @racket[_pagemap], or a pagemap loaded from @racket[_pagemap-source], render the files included in that pagemap using @racket[render-batch].
[(render-pagetree [pagetree pagetree?]) void?]
[(render-pagetree [pagetree-source pathish?]) void?])]
Using @racket[_pagetree], or a pagetree loaded from @racket[_pagetree-source], render the files included in that pagetree using @racket[render-batch].
@defproc[
(get-template-for

@ -1,6 +1,6 @@
#lang scribble/manual
@(require scribble/eval pollen/cache pollen/world (for-label racket (except-in pollen #%module-begin) pollen/template pollen/render xml))
@(require scribble/eval pollen/cache pollen/world (for-label racket (except-in pollen #%module-begin) pollen/template pollen/render xml pollen/pagetree))
@(define my-eval (make-base-eval))
@(my-eval `(require pollen pollen/template xml))
@ -22,3 +22,64 @@ Convert @racket[_xexpr] to an HTML string. Similar to @racket[xexpr->string], bu
(xexpr->string 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.
@examples[#:eval my-eval
(define tx '(p "You did" (em "what?")))
(->html tx)
(->html (->html tx))
]
@deftogether[(
@defproc[
(from
[query symbolish?]
[pagenode pagenodeish?])
(or/c #f txexpr-element?)]
@defproc[
(from*
[query symbolish?]
[pagenode pagenodeish?])
(or/c #f (listof txexpr-element?))]
)]
Find matches for @racket[_query] in @racket[_pagenode], first by looking in its @code{metas} (using @racket[from-metas]) and then by looking in its @code{doc} (using @racket[from-doc]). With @racket[from], you get the first result; with @racket[from*], you get them all. In both cases, you get @racket[#f] if there are no matches.
@defproc[
(from-metas
[query symbolish?]
[meta-source (or/c pagenodeish? hash?)])
(or/c #f txexpr-element?)]
Look up the value of @racket[_query] 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[_query], you get @racket[#f].
@examples[#:eval my-eval
(define my-metas (hash 'template "sub.xml.pt" 'target "print"))
(from-metas 'template my-metas)
('target . from-metas . my-metas)
(from-metas 'nonexistent-key my-metas)
]
@defproc[
(from-doc
[query symbolish?]
[doc-source (or/c pagenodeish? txexpr?)])
(or/c #f txexpr-element?)]
Look up the value of @racket[_query] 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[_query], you get @racket[#f].
@examples[#:eval my-eval
(define my-doc '(body (question "Gelato?")
(answer "Nocciola") (answer "Pistachio")))
(from-doc 'question my-doc)
('answer . from-doc . my-doc)
(from-doc 'nonexistent-key my-doc)
]

@ -5,7 +5,7 @@
(require web-server/http/request-structs)
(require web-server/http/response-structs)
(require 2htdp/image)
(require "world.rkt" "render.rkt" sugar txexpr "file.rkt" "debug.rkt" "pagemap.rkt" "cache.rkt")
(require "world.rkt" "render.rkt" sugar txexpr "file.rkt" "debug.rkt" "pagetree.rkt" "cache.rkt")
(module+ test (require rackunit))
@ -173,12 +173,12 @@
(cond ; in cell
[source (cons (format "in/~a" source) "in")]
[(or (pagemap-source? filename) (sourceish? filename)) (cons (format "in/~a" filename) "in")]
[(or (pagetree-source? filename) (sourceish? filename)) (cons (format "in/~a" filename) "in")]
[else empty-cell])
(cond ; out cell
[(directory-exists? (build-path dashboard-dir filename)) (cons #f #f)]
[(pagemap-source? filename) empty-cell]
[(pagetree-source? filename) empty-cell]
[else (cons (format "out/~a" filename) "out")]))))))
(define (ineligible-path? x) (or (not (visible? x)) (member x world:reserved-paths)))
@ -190,16 +190,16 @@
(define path-is-directory? (λ(f) (directory-exists? (build-path dashboard-dir f))))
(define subdirectories (filter path-is-directory? all-paths))
(define files (filter-not path-is-directory? all-paths))
(define pagemap-sources (filter pagemap-source? files))
(define other-files (filter-not pagemap-source? files))
(define pagetree-sources (filter pagetree-source? files))
(define other-files (filter-not pagetree-source? files))
(define (sort-names xs) (sort xs #:key ->string string<?))
;; put subdirs in list ahead of files (so they appear at the top)
(append (sort-names subdirectories) (sort-names pagemap-sources) (sort-names other-files)))
(append (sort-names subdirectories) (sort-names pagetree-sources) (sort-names other-files)))
(define project-paths
(filter-not ineligible-path?
(if (file-exists? dashboard-pmap)
(map ->path (pagemap->list (cached-require (->path dashboard-pmap) world:main-pollen-export)))
(map ->path (pagetree->list (cached-require (->path dashboard-pmap) world:main-pollen-export)))
(unique-sorted-output-paths (directory-list dashboard-dir)))))
(body-wrapper

@ -13,7 +13,7 @@
(define-values (pollen-servlet _)
(dispatch-rules
[((string-arg) ... (? pagemap-source?)) route-dashboard]
[((string-arg) ... (? pagetree-source?)) route-dashboard]
[((string-arg) ... "in" (string-arg)) route-in]
[((string-arg) ... "out" (string-arg)) route-out]
[((string-arg) ... "xexpr" (string-arg)) route-xexpr]

@ -1,7 +1,7 @@
#lang racket/base
(require (for-syntax racket/base))
(require racket/string xml xml/path sugar/define sugar/container sugar/coerce/contract)
(require "tools.rkt" txexpr "world.rkt" "cache.rkt" "pagemap.rkt")
(require "tools.rkt" txexpr "world.rkt" "cache.rkt" "pagetree.rkt")
(require sugar/coerce/value)
@ -9,73 +9,63 @@
(define/contract+provide (metas->here metas)
(hash? . -> . node?)
(path->node ('here-path . from-metas . metas)))
(hash? . -> . pagenode?)
(path->pagenode ('here-path . from-metas . metas)))
(define/contract (get-doc node-or-path)
((or/c node? pathish?) . -> . (or/c txexpr? string?))
(define/contract (get-doc pagenode-or-path)
((or/c pagenode? pathish?) . -> . (or/c txexpr? string?))
(define source-path (->source-path (cond
[(node? node-or-path) (node->path node-or-path)]
[else node-or-path])))
[(pagenode? pagenode-or-path) (pagenode->path pagenode-or-path)]
[else pagenode-or-path])))
(if source-path
(cached-require source-path world:main-pollen-export)
(error (format "get-doc: no source found for '~a' in directory ~a" node-or-path (current-directory)))))
(error (format "get-doc: no source found for '~a' in directory ~a" pagenode-or-path (current-directory)))))
(define/contract (get-metas node-or-path)
((or/c node? pathish?) . -> . hash?)
(define/contract (get-metas pagenode-or-path)
((or/c pagenode? pathish?) . -> . hash?)
(define source-path (->source-path (cond
[(node? node-or-path) (node->path node-or-path)]
[else node-or-path])))
[(pagenode? pagenode-or-path) (pagenode->path pagenode-or-path)]
[else pagenode-or-path])))
(if source-path
(cached-require source-path world:meta-pollen-export)
(error (format "get-metas: no source found for '~a' in directory ~a" node-or-path (current-directory)))))
(error (format "get-metas: no source found for '~a' in directory ~a" pagenode-or-path (current-directory)))))
(define (node->path node)
(build-path (world:current-project-root) (symbol->string node)))
(define (pagenode->path pagenode)
(build-path (world:current-project-root) (symbol->string pagenode)))
(define+provide/contract (from-node query node)
(define+provide/contract (from query pagenode)
(coerce/symbol? coerce/symbol? . -> . (or/c #f txexpr-element?))
(define result (from-node* query node))
(define result (from* query pagenode))
(if (null? result) #f (car result)))
(define+provide/contract (from-node* query node)
(coerce/symbol? coerce/symbol? . -> . (listof txexpr-element?))
(define meta-result (from-metas query node))
(append (if meta-result (list meta-result) null) (from-doc query node)))
(define+provide/contract (from* query pagenode)
(coerce/symbol? coerce/symbol? . -> . (or/c #f (listof txexpr-element?)))
(define meta-result (from-metas query pagenode))
(define doc-result (from-doc query pagenode))
(define combined-result (append (if meta-result (list meta-result) null)
(or doc-result null)))
(if (null? combined-result) #f combined-result))
(define/contract+provide (from-metas query node-or-metas)
(coerce/symbol? (or/c node? hash?) . -> . (or/c #f txexpr-element?))
(let ([metas (or (and (node? node-or-metas) (get-metas node-or-metas)) node-or-metas)])
(define/contract+provide (from-metas query meta-source)
(coerce/symbol? (or/c pagenode? hash?) . -> . (or/c #f txexpr-element?))
(let ([metas (or (and (pagenode? meta-source) (get-metas meta-source)) meta-source)])
(with-handlers ([exn:fail? (λ(e) #f)])
(hash-ref metas query))))
(define/contract+provide (from-doc query node-or-doc)
(coerce/symbol? (or/c node? txexpr?) . -> . (or/c #f txexpr-elements?))
(let ([doc (or (and (node? node-or-doc) (get-doc node-or-doc)) node-or-doc)])
(with-handlers ([exn:fail? (λ(e) null)])
(define/contract+provide (from-doc query doc-source)
(coerce/symbol? (or/c pagenode? txexpr?) . -> . (or/c #f txexpr-elements?))
(let ([doc (or (and (pagenode? doc-source) (get-doc doc-source)) doc-source)])
(with-handlers ([exn:fail? (λ(e) #f)])
(se-path*/list (list query) doc))))
;; turns input into xexpr-elements so they can be spliced into template
;; (as opposed to dropped in as a full txexpr)
;; by returning a list, pollen rules will automatically merge into main flow
;; todo: explain why
;; todo: do I need this?
(define+provide/contract (splice x)
((or/c txexpr? txexpr-elements? string?) . -> . txexpr-elements?)
(cond
[(txexpr? x) (get-elements x)]
[(txexpr-elements? x) x]
[(string? x) (->list x)]))
(define+provide/contract (->html x)
(xexpr? . -> . string?)
(xexpr->html x))

@ -71,9 +71,9 @@
(check-false (preproc-source? #f)))
(module+ test
(check-true (pagemap-source? (format "foo.~a" world:pagemap-source-ext)))
(check-false (pagemap-source? (format "~a.foo" world:pagemap-source-ext)))
(check-false (pagemap-source? #f)))
(check-true (pagetree-source? (format "foo.~a" world:pagetree-source-ext)))
(check-false (pagetree-source? (format "~a.foo" world:pagetree-source-ext)))
(check-false (pagetree-source? #f)))
(module+ test
(check-true (markup-source? "foo.pm"))
(check-false (markup-source? "foo.p"))

@ -1,69 +1,69 @@
#lang racket/base
(require rackunit)
(require "../pagemap.rkt" "../world.rkt")
(require "../pagetree.rkt" "../world.rkt")
(check-false (node? "foo-bar"))
(check-false (node? "Foo_Bar_0123"))
(check-true (node? 'foo-bar))
(check-false (node? "foo-bar.p"))
(check-false (node? "/Users/MB/foo-bar"))
(check-false (node? #f))
(check-false (node? ""))
(check-false (node? " "))
(check-false (pagenode? "foo-bar"))
(check-false (pagenode? "Foo_Bar_0123"))
(check-true (pagenode? 'foo-bar))
(check-false (pagenode? "foo-bar.p"))
(check-false (pagenode? "/Users/MB/foo-bar"))
(check-false (pagenode? #f))
(check-false (pagenode? ""))
(check-false (pagenode? " "))
(check-true (pagemap? '(foo)))
(check-true (pagemap? '(foo (hee))))
(check-true (pagemap? '(foo (hee (uncle foo)))))
(check-false (pagemap? '(foo (hee hee (uncle foo)))))
(check-true (pagetree? '(foo)))
(check-true (pagetree? '(foo (hee))))
(check-true (pagetree? '(foo (hee (uncle foo)))))
(check-false (pagetree? '(foo (hee hee (uncle foo)))))
(define test-pagemap-main `(pagemap-main foo bar (one (two three))))
(define test-pagemap (pagemap-root->pagemap test-pagemap-main))
(check-equal? (parent 'three test-pagemap) 'two)
(check-equal? (parent "three" test-pagemap) 'two)
(check-false (parent #f test-pagemap))
(check-false (parent 'nonexistent-name test-pagemap))
(define test-pagetree-main `(pagetree-main foo bar (one (two three))))
(define test-pagetree (pagetree-root->pagetree test-pagetree-main))
(check-equal? (parent 'three test-pagetree) 'two)
(check-equal? (parent "three" test-pagetree) 'two)
(check-false (parent #f test-pagetree))
(check-false (parent 'nonexistent-name test-pagetree))
(check-equal? (children 'one test-pagemap) '(two))
(check-equal? (children 'two test-pagemap) '(three))
(check-false (children 'three test-pagemap))
(check-false (children #f test-pagemap))
(check-false (children 'fooburger test-pagemap))
(check-equal? (children 'one test-pagetree) '(two))
(check-equal? (children 'two test-pagetree) '(three))
(check-false (children 'three test-pagetree))
(check-false (children #f test-pagetree))
(check-false (children 'fooburger test-pagetree))
(check-equal? (siblings 'one test-pagemap) '(foo bar one))
(check-equal? (siblings 'foo test-pagemap) '(foo bar one))
(check-equal? (siblings 'two test-pagemap) '(two))
(check-false (siblings #f test-pagemapap))
(check-false (siblings 'invalid-key test-pagemap))
(check-equal? (siblings 'one test-pagetree) '(foo bar one))
(check-equal? (siblings 'foo test-pagetree) '(foo bar one))
(check-equal? (siblings 'two test-pagetree) '(two))
(check-false (siblings #f test-pagetree))
(check-false (siblings 'invalid-key test-pagetree))
(check-equal? (previous* 'one test-pagemap) '(foo bar))
(check-equal? (previous* 'three test-pagemap) '(foo bar one two))
(check-false (previous* 'foo test-pagemap))
(check-equal? (previous* 'one test-pagetree) '(foo bar))
(check-equal? (previous* 'three test-pagetree) '(foo bar one two))
(check-false (previous* 'foo test-pagetree))
(check-equal? (previous 'one test-pagemap) 'bar)
(check-equal? (previous 'three test-pagemap) 'two)
(check-false (previous 'foo test-pagemap))
(check-equal? (previous 'one test-pagetree) 'bar)
(check-equal? (previous 'three test-pagetree) 'two)
(check-false (previous 'foo test-pagetree))
(check-equal? (next 'foo test-pagemap) 'bar)
(check-equal? (next 'one test-pagemap) 'two)
(check-false (next 'three test-pagemap))
(check-equal? (next 'foo test-pagetree) 'bar)
(check-equal? (next 'one test-pagetree) 'two)
(check-false (next 'three test-pagetree))
(check-equal? (pagemap->list test-pagemap) '(foo bar one two three))
(check-equal? (pagetree->list test-pagetree) '(foo bar one two three))
(let ([sample-main `(world:pollen-tree-root-name foo bar (one (two three)))])
(check-equal? (pagemap-root->pagemap sample-main)
(check-equal? (pagetree-root->pagetree sample-main)
`(world:pollen-tree-root-name foo bar (one (two three)))))
(define files '("foo.html" "bar.html" "bar.html.p" "zap.html" "zap.xml"))
(check-equal? (node->url/paths 'foo.html files) "foo.html")
(check-equal? (node->url/paths 'bar.html files) "bar.html")
(check-equal? (pagenode->url/paths 'foo.html files) "foo.html")
(check-equal? (pagenode->url/paths 'bar.html files) "bar.html")
;; (check-equal? (name->url 'zap files) 'error) ;; todo: how to test error?
(check-false (node->url/paths 'hee files))
(check-false (pagenode->url/paths 'hee files))
(set! test-pagemap-main `(,world:pagemap-root-node foo bar (one (two three))))
(check-equal? (pagemap-root->pagemap test-pagemap-main)
`(,world:pagemap-root-node foo bar (one (two three))))
(set! test-pagetree-main `(,world:pagetree-root-node foo bar (one (two three))))
(check-equal? (pagetree-root->pagetree test-pagetree-main)
`(,world:pagetree-root-node foo bar (one (two three))))

@ -8,7 +8,7 @@
(define markup-source-ext 'pm)
(define markdown-source-ext 'pmd)
(define null-source-ext 'p)
(define pagemap-source-ext 'pmap)
(define pagetree-source-ext 'ptree)
(define template-source-ext 'pt)
(define scribble-source-ext 'scrbl)
@ -17,12 +17,12 @@
(define reader-mode-preproc 'pre)
(define reader-mode-markup 'markup)
(define reader-mode-markdown 'markdown)
(define reader-mode-pagemap 'pmap)
(define reader-mode-pagetree 'ptree)
(define decodable-extensions (list markup-source-ext pagemap-source-ext))
(define decodable-extensions (list markup-source-ext pagetree-source-ext))
(define default-pagemap "index.pmap")
(define pagemap-root-node 'pagemap-root)
(define default-pagetree "index.ptree")
(define pagetree-root-node 'pagetree-root)
(define template-source-prefix "-")
(define expression-delimiter #\◊)

Loading…
Cancel
Save