Compare commits

..

1 Commits

Author SHA1 Message Date
Matthew Butterick c9c69d1063 use 1.1 3 years ago

@ -30,7 +30,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@master uses: actions/checkout@master
- uses: Bogdanp/setup-racket@v0.11 - uses: Bogdanp/setup-racket@v1.1
with: with:
distribution: 'full' distribution: 'full'
version: ${{ matrix.racket-version }} version: ${{ matrix.racket-version }}

@ -1,134 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
mb@mbtype.com (= Matthew Butterick).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

@ -6,11 +6,11 @@ I welcome pull requests. But accepting a PR obligates me to maintain that code f
* PRs for simple documentation fixes (e.g., spelling and grammar corrections) are always welcome. For more substantial changes, I dont necessarily prefer PRs to issues or feature requests. A good description of the problem with a working example is better than a half-baked PR. I can often fix it in less time than it would take to review the PR. (= Principle of Efficiency) * PRs for simple documentation fixes (e.g., spelling and grammar corrections) are always welcome. For more substantial changes, I dont necessarily prefer PRs to issues or feature requests. A good description of the problem with a working example is better than a half-baked PR. I can often fix it in less time than it would take to review the PR. (= Principle of Efficiency)
* If you want feedback on a potential PR, I recommend posting to the [Pollen forum](https://forums.matthewbutterick.com/c/typesetting/) rather than here. Because more people will see it. (= Principle of Exposure) * If you want feedback on a potential PR, I recommend posting to the [Pollen mailing list](http://groups.google.com/forum/#!forum/pollenpub) rather than here. Because more people will see it. (= Principle of Exposure)
* Small PRs are easier to accept than large ones. Large PRs should have a benefit worthy of their complexity. PRs that want to amend Pollens public interface receive the highest scrutiny. (= Principle of Proportionality) * Small PRs are easier to accept than large ones. Large PRs should have a benefit worthy of their complexity. PRs that want to amend Pollens public interface receive the highest scrutiny. (= Principle of Proportionality)
* I consider every PR, but I cant promise detailed code reviews or comments. Helpful Racketeers can be found on the [Pollen forum](https://forums.matthewbutterick.com/c/pollen/), the [Racket mailing list](https://lists.racket-lang.org/), and the Racket [Slack channel](https://racket.slack.com/). (= Principle of Specialization) * I consider every PR, but I cant promise detailed code reviews or comments. Helpful Racketeers can be found on the [Pollen mailing list](http://groups.google.com/forum/#!forum/pollenpub), the [Racket mailing list](https://lists.racket-lang.org/), and the Racket [Slack channel](https://racket.slack.com/). (= Principle of Specialization)
* PRs should be necessary, in the sense that the proposed change can only be accomplished by patching this repo. (Corollary: features that can live in a separate [package](https://pkgs.racket-lang.org/) probably should.) (= Principle of Necessity) * PRs should be necessary, in the sense that the proposed change can only be accomplished by patching this repo. (Corollary: features that can live in a separate [package](https://pkgs.racket-lang.org/) probably should.) (= Principle of Necessity)

@ -1,4 +1,4 @@
## Pollen: the book is a program [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CODE_OF_CONDUCT.md) ## Pollen: the book is a program [![Build Status](https://github.com/mbutterick/pollen/workflows/CI/badge.svg)](https://github.com/mbutterick/pollen/actions)
A book-publishing system written in [Racket](http://racket-lang.org). This is the software I use to publish & maintain my web-based books [Beautiful Racket](http://beautifulracket.com), [Practical Typography](http://practicaltypography.com), and [Typography for Lawyers](http://typographyforlawyers.com). A book-publishing system written in [Racket](http://racket-lang.org). This is the software I use to publish & maintain my web-based books [Beautiful Racket](http://beautifulracket.com), [Practical Typography](http://practicaltypography.com), and [Typography for Lawyers](http://typographyforlawyers.com).
@ -23,12 +23,8 @@ And update like so:
raco pkg update --update-deps pollen raco pkg update --update-deps pollen
Official forum: https://forums.matthewbutterick.com/c/typesetting/ Official discussion area: https://github.com/mbutterick/pollen-users
## License ## License
MIT MIT
## Project status
Actively developed, though the pace has slowed now that Pollen is arguably feature complete and stable. I use it almost every day so it's not going anywhere. But I have no plans to substantially enlarge or extend it.

@ -1,7 +1,7 @@
#lang info #lang info
(define collection 'multi) (define collection 'multi)
(define version "3.2") (define version "3.1")
(define deps '(["base" #:version "6.3"] (define deps '(["base" #:version "6.3"]
["txexpr" #:version "0.2"] ["txexpr" #:version "0.2"]
["sugar" #:version "0.2"] ["sugar" #:version "0.2"]

@ -4,6 +4,6 @@
(define raco-commands '(("pollen" (submod pollen/private/command raco) "issue Pollen command" #f))) (define raco-commands '(("pollen" (submod pollen/private/command raco) "issue Pollen command" #f)))
(define compile-omit-paths '("test" "tools" "server-extras" "scribblings/third-tutorial-files")) (define compile-omit-paths '("test" "tools" "server-extras" "scribblings/third-tutorial-files"))
;; it's redundant to test "pollen.scrbl" because it incorporates the other scribble sources by reference ;; it's redundant to test "pollen.scrbl" because it incorporates the other scribble sources by reference
(define test-omit-paths '("test/data" "tools" "server-extras" "scribblings/third-tutorial-files" "test/test-project-port.rkt")) (define test-omit-paths '("test/data" "tools" "server-extras" "scribblings/third-tutorial-files"))
;; don't put #"p" in this list because it's not a #lang ;; don't put #"p" in this list because it's not a #lang
(define module-suffixes '(#"pp" #"pm" #"pmd" #"ptree")) (define module-suffixes '(#"pp" #"pm" #"pmd" #"ptree"))

@ -48,9 +48,9 @@
(define pt-root-tag (setup:pagetree-root-node)) (define pt-root-tag (setup:pagetree-root-node))
(define (splice-nested-pagetree xs) (define (splice-nested-pagetree xs)
(apply append (for/list ([x (in-list xs)]) (apply append (for/list ([x (in-list xs)])
(if (and (txexpr? x) (eq? (get-tag x) pt-root-tag)) (if (and (txexpr? x) (eq? (get-tag x) pt-root-tag))
(get-elements x) (get-elements x)
(list x))))) (list x)))))
(validate-pagetree (validate-pagetree
(decode (cons pt-root-tag xs) (decode (cons pt-root-tag xs)
#:txexpr-elements-proc (compose1 splice-nested-pagetree (λ (xs) (filter-not whitespace? xs))) #:txexpr-elements-proc (compose1 splice-nested-pagetree (λ (xs) (filter-not whitespace? xs)))
@ -63,7 +63,7 @@
(define pagenodes (pagetree-strict->list x)) (define pagenodes (pagetree-strict->list x))
(for ([p (in-list pagenodes)] (for ([p (in-list pagenodes)]
#:unless (pagenode? p)) #:unless (pagenode? p))
(raise-argument-error 'validate-pagetree "valid pagenodes" p)) (raise-argument-error 'validate-pagetree "valid pagenodes" p))
(with-handlers ([exn:fail? (λ (e) (error 'validate-pagetree "~a" (exn-message e)))]) (with-handlers ([exn:fail? (λ (e) (error 'validate-pagetree "~a" (exn-message e)))])
(members-unique?/error pagenodes)) (members-unique?/error pagenodes))
x))) x)))
@ -137,7 +137,7 @@
(if (memq pagenode (map topmost-node current-children)) (if (memq pagenode (map topmost-node current-children))
current-parent current-parent
(for/or ([st (in-list (filter list? current-children))]) (for/or ([st (in-list (filter list? current-children))])
(loop pagenode st)))))) (loop pagenode st))))))
(if (eq? result (first pt)) (if (eq? result (first pt))
(and allow-root? result) (and allow-root? result)
result)) result))
@ -159,7 +159,7 @@
(match pagenode (match pagenode
[(== (first pt) eq?) (map topmost-node (rest pt))] [(== (first pt) eq?) (map topmost-node (rest pt))]
[_ (for/or ([subtree (in-list (filter pair? pt))]) [_ (for/or ([subtree (in-list (filter pair? pt))])
(loop pagenode subtree))])))) (loop pagenode subtree))]))))
(module-test-external (module-test-external
@ -190,7 +190,7 @@
(((or/c #f pagenodeish?)) ((or/c pagetree? pathish?)) . ->* . (or/c #f pagenodes?)) (((or/c #f pagenodeish?)) ((or/c pagetree? pathish?)) . ->* . (or/c #f pagenodes?))
(match (for/list ([sib (in-list (or (siblings pnish pt-or-path) empty))] (match (for/list ([sib (in-list (or (siblings pnish pt-or-path) empty))]
#:unless (eq? sib (->pagenode pnish))) #:unless (eq? sib (->pagenode pnish)))
sib) sib)
[(? pair? sibs) sibs] [(? pair? sibs) sibs]
[_ #false])) [_ #false]))
@ -220,15 +220,11 @@
(define+provide/contract (pagetree->paths pt-or-path) (define+provide/contract (pagetree->paths pt-or-path)
((or/c pagetree? pathish?) . -> . (listof complete-path?)) ((or/c pagetree? pathish?) . -> . (listof complete-path?))
(define-values (dir-for-resolving-paths pt) (parameterize ([current-directory (current-project-root)])
(match pt-or-path (map ->complete-path (pagetree->list (match pt-or-path
[(? pagetree?) (values (current-project-root) pt-or-path)] [(? pagetree? pt) pt]
[_ (define dir (match (dirname (->path pt-or-path)) [_ (cached-doc pt-or-path)])))))
['relative (current-project-root)]
[dir dir]))
(values dir (cached-doc pt-or-path))]))
(parameterize ([current-directory dir-for-resolving-paths])
(map ->complete-path (pagetree->list pt))))
(module-test-external (module-test-external
(define test-pagetree `(pagetree-main foo bar (one (two three)))) (define test-pagetree `(pagetree-main foo bar (one (two three))))

@ -36,7 +36,7 @@
;; user-designated files to track ;; user-designated files to track
(map ->string (setup:cache-watchlist source-path))))) (map ->string (setup:cache-watchlist source-path)))))
(define env-rec (for/list ([env-name (in-list (cons default-env-name (sort (setup:envvar-watchlist source-path) bytes<?)))]) (define env-rec (for/list ([env-name (in-list (cons default-env-name (sort (setup:envvar-watchlist source-path) bytes<?)))])
(cons env-name (match (getenv (->string env-name)) (cons env-name (match (getenv (string-downcase (->string env-name)))
[#false #false] [#false #false]
[str (string-downcase (->string str))])))) [str (string-downcase (->string str))]))))
(define poly-flag (and (has-inner-poly-ext? source-path) (current-poly-target))) (define poly-flag (and (has-inner-poly-ext? source-path) (current-poly-target)))

@ -1,10 +0,0 @@
#lang info
;; 210309
;; for unknown reason "mode-indentation.rkt"
;; started causing CI failures since 210215
;; consistently on 6.7, 6.8, 6.9, 7.7CS, 7.8CS, 7.9CS
;; I assume it has something to do with the fact that
;; it imports `framework` and `racket/gui`,
;; OTOH why does it fail in these?
(define test-omit-paths '("mode-indentation.rkt"))

@ -53,7 +53,7 @@
(let loop () (let loop ()
(define path (place-channel-put/get ch (list 'want-job))) (define path (place-channel-put/get ch (list 'want-job)))
(place-channel-put ch (list 'job-finished path (place-channel-put ch (list 'job-finished path
(with-handlers ([exn:fail? (λ (e) (cons #false (exn-message e)))]) (with-handlers ([exn:fail? (λ (e) #f)])
(path->hash path)))) (path->hash path))))
(loop)))) (loop))))
(handle-evt wp (λ (val) (list* wpidx wp val))))) (handle-evt wp (λ (val) (list* wpidx wp val)))))
@ -69,12 +69,12 @@
(message (format "caching @ job ~a: ~a" (~r (add1 wpidx) #:min-width (string-length (~r job-count)) #:pad-string " ") (find-relative-path starting-dir path))) (message (format "caching @ job ~a: ~a" (~r (add1 wpidx) #:min-width (string-length (~r job-count)) #:pad-string " ") (find-relative-path starting-dir path)))
(loop rest (cons wpidx actives))])] (loop rest (cons wpidx actives))])]
[(list wpidx wp 'job-finished path result) [(list wpidx wp 'job-finished path result)
(match result (if result
[(cons #false exn-msg) (message (format "caching failed on job ~a: ~a\n because ~a" (add1 wpidx) (find-relative-path starting-dir path) exn-msg))] (cache-ref! (paths->key 'source path) (λ () result))
[_ (cache-ref! (paths->key 'source path) (λ () result))]) (message (format "caching failed on job ~a: ~a" (add1 wpidx) (find-relative-path starting-dir path))))
(loop paths (remq wpidx actives))])))] (loop paths (remq wpidx actives))])))]
[else (for ([path (in-list uncached-paths)]) [else (for ([path (in-list uncached-paths)])
(message (format "caching: ~a" (find-relative-path starting-dir path))) (message (format "caching: ~a" (find-relative-path starting-dir path)))
(match (with-handlers ([exn:fail? (λ (e) (cons #false (exn-message e)))]) (path->hash path)) (match (with-handlers ([exn:fail? (λ (e) #f)]) (path->hash path))
[(cons #false exn-msg) (message (format "caching failed: ~a\n because ~a" (find-relative-path starting-dir path) exn-msg))] [#false (message (format "caching failed: ~a" (find-relative-path starting-dir path)))]
[result (cache-ref! (paths->key 'source path) (λ () result))]))])) [result (cache-ref! (paths->key 'source path) (λ () result))]))]))

@ -124,7 +124,6 @@
[(eq? mode default-mode-markdown) pollen-markdown-source-ext] [(eq? mode default-mode-markdown) pollen-markdown-source-ext]
[(eq? mode default-mode-markup) pollen-markup-source-ext] [(eq? mode default-mode-markup) pollen-markup-source-ext]
[(eq? mode default-mode-pagetree) pollen-pagetree-source-ext]))] [(eq? mode default-mode-pagetree) pollen-pagetree-source-ext]))]
[(module-language) 'pollen]
[else default]))) [else default])))
(define-syntax-rule (reader-module-begin mode . _) (define-syntax-rule (reader-module-begin mode . _)

@ -1 +1 @@
1710316839 1613430102

@ -2,7 +2,6 @@
(require racket/file (require racket/file
racket/path racket/path
racket/match racket/match
racket/string
racket/format racket/format
racket/place racket/place
racket/list racket/list
@ -76,33 +75,23 @@
;; initialize the workers ;; initialize the workers
(define worker-evts (define worker-evts
(for/list ([wpidx (in-range worker-count)]) (for/list ([wpidx (in-range worker-count)])
(define wp (define wp
(place ch (place ch
(let loop () (let loop ()
(match-define (list project-root source-path output-path poly-target) (match-define (list source-path output-path poly-target)
(place-channel-put/get ch (list 'wants-job))) (place-channel-put/get ch (list 'wants-job)))
;; we manually propagate our parameter values for (parameterize ([current-poly-target poly-target])
;; current-project-root and current-poly-target (place-channel-put/get ch (list 'wants-lock output-path))
;; because parameter values are not automatically shared ;; trap any exceptions and pass them back as crashed jobs.
;; between parallel threads. ;; otherwise, a crashed rendering place can't recover, and the parallel job will be stuck.
(parameterize ([current-project-root project-root] (with-handlers ([exn:fail? (λ (e) (place-channel-put ch (list 'crashed-job source-path output-path #f)))])
[current-poly-target poly-target]) (match-define-values (_ _ ms _)
(place-channel-put/get ch (list 'wants-lock output-path)) ;; we don't use `render-to-file-if-needed` because we've already checked the render cache
;; trap any exceptions and pass them back as crashed jobs. ;; if we reached this point, we know we need a render
;; otherwise, a crashed rendering place can't recover, and the parallel job will be stuck. (time-apply render-to-file (list source-path #f output-path)))
(place-channel-put ch (place-channel-put ch (list 'finished-job source-path output-path ms))))
(cons (loop))))
;; when rendering fails, first argument is the exception message (handle-evt wp (λ (val) (list* wpidx wp val)))))
(with-handlers ([exn:fail? (λ (e) (exn-message e))])
(match-define-values (_ _ ms _)
;; we don't use `render-to-file-if-needed` because we've already checked the render cache
;; if we reached this point, we know we need a render
(time-apply render-to-file (list source-path #f output-path)))
;; when rendering succeeds, first argument is rendering time in ms
ms)
(list source-path output-path))))
(loop))))
(handle-evt wp (λ (val) (list* wpidx wp val)))))
(define poly-target (current-poly-target)) (define poly-target (current-poly-target))
@ -140,39 +129,35 @@
;; crashed jobs are completed jobs that weren't finished ;; crashed jobs are completed jobs that weren't finished
(for/list ([jr (in-list completed-job-results)] (for/list ([jr (in-list completed-job-results)]
#:unless ($jobresult-finished-successfully jr)) #:unless ($jobresult-finished-successfully jr))
($jobresult-job jr))] ($jobresult-job jr))]
[else [else
(match (apply sync worker-evts) (match (apply sync worker-evts)
[(list wpidx wp 'wants-job) [(list wpidx wp 'wants-job)
(match jobs (match jobs
[(? null?) (loop null locks blocks completed-job-results completed-job-count)] [(? null?) (loop null locks blocks completed-job-results completed-job-count)]
[(cons ($job source-path output-path) rest) [(cons ($job source-path output-path) rest)
(place-channel-put wp (list (current-project-root) source-path output-path poly-target)) (place-channel-put wp (list source-path output-path poly-target))
(loop rest locks blocks completed-job-results completed-job-count)])] (loop rest locks blocks completed-job-results completed-job-count)])]
[(list wpidx wp status-arg source-path output-path) [(list wpidx wp (and (or 'finished-job 'crashed-job) tag) source-path output-path ms)
;; if the render was successful, the status arg is a number representing milliseconds spent rendering. (match tag
;; if not, the status argument is the exception message. ['finished-job
(define job-finished? (exact-nonnegative-integer? status-arg))
(match status-arg
[ms #:when job-finished?
(message
(format "rendered @ job ~a /~a ~a"
(~r (add1 wpidx) #:min-width (string-length (~r worker-count)) #:pad-string " ")
(find-relative-path (current-project-root) output-path)
(if (< ms 1000) (format "(~a ms)" ms) (format "(~a s)" (/ ms 1000.0)))))]
[(? string? exn-msg)
(message (message
(format "render crash @ job ~a /~a (retry pending)\n because ~a" (format "rendered @ job ~a /~a ~a"
(add1 wpidx) (~r (add1 wpidx) #:min-width (string-length (~r worker-count)) #:pad-string " ")
(find-relative-path (current-project-root) output-path) (find-relative-path (current-project-root) output-path)
exn-msg))] (if (< ms 1000) (format "(~a ms)" ms) (format "(~a s)" (/ ms 1000.0)))))]
[_ (raise-result-error 'render "exact-nonnegative-integer or string" status-arg)]) [_
(message
(format "render crash @ job ~a /~a (retry pending)"
(add1 wpidx)
(find-relative-path (current-project-root) output-path)))])
(loop jobs (loop jobs
(match (findf (λ (lock) (equal? ($lock-worker lock) wp)) locks) (match (findf (λ (lock) (equal? ($lock-worker lock) wp)) locks)
[#false locks] [#false locks]
[lock (remove lock locks)]) [lock (remove lock locks)])
blocks blocks
(let ([jr ($jobresult ($job source-path output-path) job-finished?)]) (let* ([job-finished? (eq? tag 'finished-job)]
[jr ($jobresult ($job source-path output-path) job-finished?)])
(cons jr completed-job-results)) (cons jr completed-job-results))
(add1 completed-job-count))] (add1 completed-job-count))]
[(list wpidx wp 'wants-lock output-path) [(list wpidx wp 'wants-lock output-path)
@ -181,12 +166,8 @@
(define current-null-output? (make-parameter #f)) (define current-null-output? (make-parameter #f))
(define+provide/contract (render-batch #:parallel [wants-parallel-render? #false] (define+provide/contract (render-batch #:parallel [wants-parallel-render? #false]
#:special [special-output #false] #:special [special-output #false] . paths-in)
#:output-paths [output-paths-in #false] . paths-in) ((#:parallel any/c) (#:special (or/c boolean? symbol?)) #:rest (listof pathish?) . ->* . void?)
(() (#:parallel any/c
#:special (or/c boolean? symbol?)
#:output-paths (or/c #false (listof pathish?)))
#:rest (listof pathish?) . ->* . void?)
;; Why not just (for-each render ...)? ;; Why not just (for-each render ...)?
;; Because certain files will pass through multiple times (e.g., templates) ;; Because certain files will pass through multiple times (e.g., templates)
;; And with render, they would be rendered repeatedly. ;; And with render, they would be rendered repeatedly.
@ -206,34 +187,27 @@
;; but the path arguments might also include pagetrees, ;; but the path arguments might also include pagetrees,
;; which expand to multiple files. ;; which expand to multiple files.
;; so this keeps everything correlated correctly. ;; so this keeps everything correlated correctly.
(cond (let loop ([paths paths-in] [sps null] [ops null])
[(and output-paths-in (= (length paths-in) (length output-paths-in))) (match paths
;; explicit list of paths: create jobs directly [(? null?)
(for/list ([path (in-list paths-in)] ;; it's possible that we have multiple output names for one poly file
[output-path (in-list output-paths-in)]) ;; so after we expand, we only remove duplicates where both the source and dest in the pair
($job path output-path))] ;; are the same
[else (let* ([pairs (remove-duplicates (map cons sps ops))]
(let loop ([paths paths-in] [sps null] [ops null]) [pairs (sort pairs path<? #:key car)]
(match paths [pairs (sort pairs path<? #:key cdr)])
[(? null?) (for/list ([pr (in-list pairs)])
;; it's possible that we have multiple output names for one poly file ($job (car pr) (cdr pr))))]
;; so after we expand, we only remove duplicates where both the source and dest in the pair [(cons path rest)
;; are the same (match (->complete-path path)
(let* ([pairs (remove-duplicates (map cons sps ops))] [(? pagetree-source? pt)
[pairs (sort pairs path<? #:key car)] (loop (append (pagetree->paths pt) rest) sps ops)]
[pairs (sort pairs path<? #:key cdr)]) [(app ->source-path sp) #:when (and sp (file-exists? sp))
(for/list ([pr (in-list pairs)]) (define op (match path
($job (car pr) (cdr pr))))] [(== (->output-path path)) path]
[(cons path rest) [_ (->output-path sp)]))
(match (->complete-path path) (loop rest (cons sp sps) (cons op ops))]
[(? pagetree-source? pt) [_ (loop rest sps ops)])])))
(loop (append (pagetree->paths pt) rest) sps ops)]
[(app ->source-path sp) #:when (and sp (file-exists? sp))
(define op (match path
[(== (->output-path path)) path]
[_ (->output-path sp)]))
(loop rest (cons sp sps) (cons op ops))]
[_ (loop rest sps ops)])]))]))
(cond (cond
[(null? all-jobs) (message "[no paths to render]")] [(null? all-jobs) (message "[no paths to render]")]
[(eq? special-output 'dry-run) (for-each message (map $job-source all-jobs))] [(eq? special-output 'dry-run) (for-each message (map $job-source all-jobs))]
@ -259,20 +233,6 @@
(define ram-cache (make-hash)) (define ram-cache (make-hash))
(define (get-external-render-proc v)
(match v
[(list (? module-path? mod) (? symbol? render-proc-id))
(with-handlers ([exn:fail:filesystem:missing-module?
(λ (e) (raise
(exn:fail:contract (string-replace (exn-message e) "standard-module-name-resolver" "external-renderer")
(exn-continuation-marks e))))]
[exn:fail:contract? ;; raised if dynamic-require can't find render-proc-id
(λ (e) (raise
(exn:fail:contract (string-replace (exn-message e) "dynamic-require" "external-renderer")
(exn-continuation-marks e))))])
(dynamic-require mod render-proc-id))]
[_ (raise-argument-error 'external-renderer "value in the form '(module-path proc-id)" v)]))
;; note that output and template order is reversed from typical ;; note that output and template order is reversed from typical
(define (render-to-file-base caller (define (render-to-file-base caller
force? force?
@ -299,11 +259,7 @@
[(not render-cache-activated?) 'render-cache-deactivated] [(not render-cache-activated?) 'render-cache-deactivated]
[else #false])) [else #false]))
(when render-needed? (when render-needed?
(define render-thunk (or maybe-render-thunk (define render-thunk (or maybe-render-thunk (λ () (render source-path template-path output-path)))) ; returns either string or bytes
(λ () ((or (let ([val (setup:external-renderer)])
(and val (get-external-render-proc val)))
render)
source-path template-path output-path)))) ; returns either string or bytes
(define render-result (define render-result
(cond (cond
[render-cache-activated? [render-cache-activated?
@ -449,18 +405,17 @@
(define (file-exists-or-has-source? path) ; path could be #f (define (file-exists-or-has-source? path) ; path could be #f
(and path (for/first ([proc (in-list (list values ->preproc-source-path ->null-source-path))] (and path (for/first ([proc (in-list (list values ->preproc-source-path ->null-source-path))]
#:when (file-exists? (proc path))) #:when (file-exists? (proc path)))
path))) path)))
(define (get-template-from-metas source-path output-path-ext) (define (get-template-from-metas source-path output-path-ext)
(with-handlers ([exn:fail:contract? (λ (e) #f)]) ; in case source-path doesn't work with cached-require (with-handlers ([exn:fail:contract? (λ (e) #f)]) ; in case source-path doesn't work with cached-require
(parameterize ([current-directory (current-project-root)]) (parameterize ([current-directory (current-project-root)])
(define source-metas (cached-metas source-path)) (define source-metas (cached-metas source-path))
(define template-name (match (select-from-metas pollen-template-meta-key source-metas) ; #f or atom or list (define template-name-or-names ; #f or atom or list
[(? list? names) (select-from-metas pollen-template-meta-key source-metas))
(for/first ([name (in-list names)] (define template-name (if (list? template-name-or-names)
#:when (equal? (get-ext name) (->string output-path-ext))) (findf (λ (tn) (eq? (get-ext tn) output-path-ext)) template-name-or-names)
name)] template-name-or-names))
[other other]))
(and template-name (simplify-path (cleanse-path (build-path (dirname source-path) template-name))))))) (and template-name (simplify-path (cleanse-path (build-path (dirname source-path) template-name)))))))
(define (get-default-template source-path output-path-ext) (define (get-default-template source-path output-path-ext)
@ -482,6 +437,7 @@
[maybe-output-path] [maybe-output-path]
[(->output-path source-path)] [(->output-path source-path)]
[else #false])) [else #false]))
(define key (template-cache-key source-path output-path))
(define (cache-thunk) (define (cache-thunk)
(match source-path (match source-path
[(or (? markup-source?) (? markdown-source?)) [(or (? markup-source?) (? markdown-source?))
@ -493,20 +449,17 @@
(for/or ([proc (list get-template-from-metas (for/or ([proc (list get-template-from-metas
get-default-template get-default-template
get-fallback-template)]) get-fallback-template)])
(file-exists-or-has-source? (proc source-path output-path-ext)))] (file-exists-or-has-source? (proc source-path output-path-ext)))]
[_ #false])) [_ #false]))
(cond (if (current-session-interactive?)
[(or (current-session-interactive?) (not (setup:render-cache-active source-path))) ;; don't cache templates in interactive session, for fresher reloads
;; don't cache templates in interactive session, for fresher reloads ;; this makes it possible to add template and have it show up in next render
;; this makes it possible to add template and have it show up in next render (cache-thunk)
(cache-thunk)] ;; otherwise, within a rendering session, this will prevent repeat players like "template.html.p"
;; otherwise, within a rendering session, this will prevent repeat players like "template.html.p" ;; from hitting the file cache repeatedly
;; from hitting the file cache repeatedly (hash-ref! ram-cache key (λ () (cache-ref! key cache-thunk)))))
[else
(define key (template-cache-key source-path output-path)) (module-test-external
(hash-ref! ram-cache key (λ () (cache-ref! key cache-thunk)))]))
#;(module-test-external
(require pollen/setup sugar/file sugar/coerce) (require pollen/setup sugar/file sugar/coerce)
(define fallback.html (build-path (current-server-extras-path) (define fallback.html (build-path (current-server-extras-path)
(add-ext pollen-fallback-template-prefix 'html))) (add-ext pollen-fallback-template-prefix 'html)))

@ -15,7 +15,7 @@ This is the core design principle of Pollen. Consistent with this principle, Pol
@item{@bold{A Pollen project consists of source files + static files.} A @italic{source file} is a file that can be compiled to produce certain output. A @italic{static file} is usable as it stands (e.g., an SVG file or webfont). Generally, the textual content of your book will live in source files, and other elements will be static files.} @item{@bold{A Pollen project consists of source files + static files.} A @italic{source file} is a file that can be compiled to produce certain output. A @italic{static file} is usable as it stands (e.g., an SVG file or webfont). Generally, the textual content of your book will live in source files, and other elements will be static files.}
@item{@bold{Source control is a good idea.} Because Pollen projects are software projects, they can be easily managed with systems for source control and collaboration. If you're a writer at heart, don't fear these systems — the learning curve is repaid by revision & edit tracking that's much easier than it is with Word or PDF files.} @item{@bold{Source control is a good idea.} Because Pollen projects are software projects, they can be easily managed with systems for source control and collaboration, like @link["http://github.com"]{GitHub}. If you're a writer at heart, don't fear these systems — the learning curve is repaid by revision & edit tracking that's much easier than it is with Word or PDF files.}
] ]

@ -43,7 +43,7 @@ Be warned that this will make your rendering much slower. But you will be guaran
@section{Scope of dependency tracking} @section{Scope of dependency tracking}
The compile cache tracks the modification date of the source file, the current setting of @secref["The_POLLEN_environment_variable"], and the modification dates of the template and @filepath{pollen.rkt} (if they exist). For @tt{poly} source files, it also tracks the @racket[current-poly-target]. It also tracks files you've listed in the optional setup value @racket[setup:cache-watchlist] and environment variables listed in the optional setup value @racket[setup:envvar-watchlist]. The compile cache tracks the modification date of the source file, the current setting of @secref["The_POLLEN_environment_variable"], and the modification dates of the template and @filepath{pollen.rkt} (if they exist). For @tt{poly} source files, it also tracks the @racket[current-poly-target]. It also tracks any files you've listed in the optional setup value @racket[setup:cache-watchlist].
It does not, however, track every possible dependency. So in a complex project, it's possible to create deep dependencies that aren't noticed by the cache. In particular, Pollen does not track pagetree files as dependencies of other source files. Thus, if you change a pagetree, you'll ordinarily need to use @exec{raco pollen reset} to clear the caches. It does not, however, track every possible dependency. So in a complex project, it's possible to create deep dependencies that aren't noticed by the cache. In particular, Pollen does not track pagetree files as dependencies of other source files. Thus, if you change a pagetree, you'll ordinarily need to use @exec{raco pollen reset} to clear the caches.

@ -748,14 +748,12 @@ And the metas:
@codeblock{ @codeblock{
#lang racket/base #lang racket/base
(require "path/to/your-pollen-source") ; doc and metas and everything else (require "pollen-source.rkt") ; doc and metas and everything else
(require (submod "path/to/your-pollen-source" metas)) ; just metas (require (submod "pollen-source.rkt" metas)) ; just metas
} }
The @id{metas} submodule gives you access to the @id{metas} hashtable @italic{without} compiling the rest of the file. So if you need to harvest metas from a set of source files — for instance, page titles (for a table of contents) or categories — using @racket[require] with the submodule will be faster. The @id{metas} submodule gives you access to the @id{metas} hashtable @italic{without} compiling the rest of the file. So if you need to harvest metas from a set of source files — for instance, page titles (for a table of contents) or categories — using @racket[require] with the submodule will be faster.
@bold{Pro tip #3}: Within a tag function, you can access the metas of the source currently being evaluated with @racket[current-metas].
@subsubsection{Inserting a comment} @subsubsection{Inserting a comment}
Two options. Two options.

@ -31,7 +31,7 @@ You can retrieve a meta value — even in the same document where you define it
@section{Splicing} @section{Splicing}
@defform[(\@ arg ...)] @defform[(\@ arg ...)]
The splicing tag signals that a list should be merged into its containing expression. The splicing tag is @racket['\@]. Splicing tag: signals that a list should be merged into its containing expression. The splicing tag is @racket[setup:splicing-tag].
@examples[#:eval my-eval @examples[#:eval my-eval
(module splicer pollen/markup (module splicer pollen/markup
@ -40,24 +40,6 @@ The splicing tag signals that a list should be merged into its containing expres
doc doc
] ]
The splicing tag is useful when you want to return a list of X-expressions in a situation where you can only return one. For instance, @secref["Tag_functions"] can only return one X-expression. But if we wrap the list of X-expressions in a splicing tag, they behave like a single X-expression. Later, Pollen will merge the list elements into the surrounding expression (as shown above).
@examples[#:eval my-eval
(require pollen/tag)
(code:comment @#,t{wrong: function returns a list of X-expressions})
(define-tag-function (multi attrs elems)
'("foo" "bar"))
(code:comment @#,t{right: function returns a list of X-expressions})
(code:comment @#,t{as elements inside a splicing tag})
(define-tag-function (multi2 attrs elems)
'(\@ "foo" "bar"))
]
Though the splicing tag is cosmetically identical to the abbreviated notation of @litchar{@"@"} for @racket[unquote-splicing], and has a similar purpose, it's not the same thing. The splicing tag isn't a variable — it's just a symbol that Pollen treats specially when generating output.
@defform[(when/splice condition pollen-args)] @defform[(when/splice condition pollen-args)]
If @racket[_condition] is true, put the @racket[_pollen-args] into the document. Within a template file, usually invoked like so: If @racket[_condition] is true, put the @racket[_pollen-args] into the document. Within a template file, usually invoked like so:

@ -98,58 +98,5 @@ In general, I subscribe to the view that software should let you do what you wan
I've been using Pollen daily for several years (and will continue to do so, because my main work is writing). I've made Pollen available because a) I'm certain that others have had the same frustrations that I have, and b) feature suggestions and bug reports make it more useful for everyone. I've been using Pollen daily for several years (and will continue to do so, because my main work is writing). I've made Pollen available because a) I'm certain that others have had the same frustrations that I have, and b) feature suggestions and bug reports make it more useful for everyone.
I hope you enjoy using it. I hope you enjoy using it. If you get stuck on something not covered here, see @secref["Getting_more_help" #:doc '(lib "pollen/scribblings/pollen.scrbl")].
@section{Getting more help}
@subsection{Bugs and feature requests}
Can be submitted as @link["https://git.matthewbutterick.com/mbutterick/pollen/issues"]{issues} at the main Pollen source repository.
@subsection{Questions & discussion}
For general tips and how-to questions, use the @link["https://forums.matthewbutterick.com/c/typesetting/"]{Pollen discussion forum}. I'll also use that list to post major changes and new features. You need an account to post (free and easy to set up with an email address).
(BTW, the former ``pollenpub'' Google Group and the ``pollen-users'' GitHub repo are now deprecated.)
@subsection{Can I see the source for @italic{Practical Typography} or @italic{Typography for Lawyers}?}
Yes, a tutorial project based on the previous version of @link["http://typographyforlawyers.com/"]{@italic{Typography for Lawyers}} is available by installing the @link["https://docs.racket-lang.org/pollen-tfl/"]{pollen-tfl} package the same way you installed Pollen.
The current versions of @italic{Practical Typography} & @italic{Typography for Lawyers} are generated from a single set of Pollen source files, which is a complication that makes them less suitable for an introductory tutorial. Still, even though this tutorial project is based on an earlier version, the coding techniques are very close to what I still use. Learn with confidence.
@subsection{Utilities & libraries}
@link["https://github.com/malcolmstill/pollen-count"]{pollen-count}: enumeration and cross-referencing library by Malcolm Still
@link["https://github.com/lijunsong/pollen-mode"]{pollen-mode}: Emacs mode for Pollen by Junsong Li
@link["https://github.com/basus/pollen-mode"]{Pollen mode}: Emacs mode for Pollen by Shrutarshi Basu
@link["https://docs.racket-lang.org/pollen-component/"]{Pollen Component}: Component-based development for Pollen by Leandro Facchinetti
@link["https://docs.racket-lang.org/css-expr/"]{CSS-expressions}: S-expression-based CSS by Leandro Facchinetti
@link["https://github.com/lijunsong/pollen-rock"]{Pollen Rock}: rendering server and an in-browser editor for Pollen
@link["https://github.com/appliedsciencestudio/talks/tree/master/mxnet"]{Polllen as a front end for Reveal.js} by Dave Liepmann. Reveal.js is a library that allows you to create slide presentations in pure HTML/CSS that run in the browser.
@subsection{More projects & guides}
@link["https://digitalwords.net"]{Digital Words} by Júda Ronén [@link["https://gitlab.com/rwmpelstilzchen/digitalwords.net"]{source}]
@link["https://thelocalyarn.com/excursus/secretary"]{Secretary of Foreign Relations} by Joel Dueck [@link["https://github.com/otherjoel/try-pollen"]{source}]
@link["https://github.com/fasiha/pollen-guide"]{A Poor Guide to Pollen} by Ahmed Fasih
@link["https://youtu.be/20GGVNBykaw"]{The World's Most Dangerous Racket Programmer} and @link["https://youtu.be/IMz09jYOgoc"]{Like a Blind Squirrel in a Ferrari}: short talks about Pollen that I gave at RacketCons 2013 and 2014, respectively.
@link["http://mstill.io"]{mstill.io blog} by Malcolm Still [@link["https://github.com/malcolmstill/mstill.io"]{source}]

@ -10,4 +10,4 @@
This module is licensed under the MIT License. This module is licensed under the MIT License.
Source repository at @link["https://git.matthewbutterick.com/mbutterick/pollen/"]{https://git.matthewbutterick.com/mbutterick/pollen/}. Suggestions & corrections welcome. Source repository at @link["http://github.com/mbutterick/pollen"]{http://github.com/mbutterick/pollen}. Suggestions & corrections welcome.

@ -0,0 +1,50 @@
#lang scribble/manual
@title{Getting more help}
@section{Bugs and feature requests}
Can be submitted as @link["https://github.com/mbutterick/pollen/issues"]{GitHub issues} at the main Pollen source repository.
@section{Questions & discussion}
For general tips and how-to questions, use the @link["https://github.com/mbutterick/pollen-users/issues"]{pollen-users discussion group} (on GitHub). I'll also use that list to post major changes and new features.
You need a GitHub account to post. If you don't have one, don't panic — they're free and easy to set up with an email address. @link["https://github.com/mbutterick/pollen-users"]{Instructions here}.
(BTW, the former ``pollenpub'' Google Group is now deprecated.)
@section{Utilities & libraries}
@link["https://github.com/malcolmstill/pollen-count"]{pollen-count}: enumeration and cross-referencing library by Malcolm Still
@link["https://github.com/lijunsong/pollen-mode"]{pollen-mode}: Emacs mode for Pollen by Junsong Li
@link["https://github.com/basus/pollen-mode"]{Pollen mode}: Emacs mode for Pollen by Shrutarshi Basu
@link["https://docs.racket-lang.org/pollen-component/"]{Pollen Component}: Component-based development for Pollen by Leandro Facchinetti
@link["https://docs.racket-lang.org/css-expr/"]{CSS-expressions}: S-expression-based CSS by Leandro Facchinetti
@link["https://github.com/lijunsong/pollen-rock"]{Pollen Rock}: rendering server and an in-browser editor for Pollen
@link["https://github.com/appliedsciencestudio/talks/tree/master/mxnet"]{Polllen as a front end for Reveal.js} by Dave Liepmann. Reveal.js is a library that allows you to create slide presentations in pure HTML/CSS that run in the browser.
@section{Can I see the source for Practical Typography or Typography for Lawyers?}
Yes, the source for @link["http://typographyforlawyers.com/"]{Typography for Lawyers} is available. In terms of content, TFL was originally the basis of Practical Typography. But in terms of code, this new TFL website is essentially a clone of Practical Typography, but rewritten to be clearer and more instructive, with extensive source comments. [@link["https://github.com/mbutterick/pollen-tfl"]{source}]
@section{More projects & guides}
@link["https://digitalwords.net"]{Digital Words} by Júda Ronén [@link["https://gitlab.com/rwmpelstilzchen/digitalwords.net"]{source}]
@link["https://thelocalyarn.com/excursus/secretary"]{Secretary of Foreign Relations} by Joel Dueck [@link["https://github.com/otherjoel/try-pollen"]{source}]
@link["https://github.com/fasiha/pollen-guide"]{A Poor Guide to Pollen} by Ahmed Fasih
@link["https://youtu.be/20GGVNBykaw"]{The World's Most Dangerous Racket Programmer} and @link["https://youtu.be/IMz09jYOgoc"]{Like a Blind Squirrel in a Ferrari}: short talks about Pollen that I gave at RacketCons 2013 and 2014, respectively.
@link["http://mstill.io"]{mstill.io blog} by Malcolm Still [@link["https://github.com/malcolmstill/mstill.io"]{source}]

@ -47,6 +47,7 @@ Or, if you can find a better digital-publishing tool, use that. But I'm never go
@include-section["programming-pollen.scrbl"] @include-section["programming-pollen.scrbl"]
@include-section["module-reference.scrbl"] @include-section["module-reference.scrbl"]
@include-section["unstable-module-reference.scrbl"] @include-section["unstable-module-reference.scrbl"]
@include-section["more-help.scrbl"]
@include-section["acknowledgments.scrbl"] @include-section["acknowledgments.scrbl"]
@include-section["license.scrbl"] @include-section["license.scrbl"]
@include-section["version-history.scrbl"] @include-section["version-history.scrbl"]

@ -102,9 +102,7 @@ Paths can also be specified as output rather than input paths, and the correspon
> raco pollen render foo.html > raco pollen render foo.html
> raco pollen render foo.html bar.html zam.css} > raco pollen render foo.html bar.html zam.css}
If a pagetree file is included in @racket[_source], all the files it lists will be rendered using the above rules. The optional @exec{--target} or @exec{-t} switch specifies the render target for multi-output source files. If the target is omitted, the renderer will use whatever target appears first in @racket[(setup:poly-targets)].
The optional @exec{--target} or @exec{-t} switch specifies the render target to use for multi-output source files. (Files of other types encountered in @racket[_source] will still be rendered as usual.) If the target is omitted, the renderer will use whatever target appears first in @racket[(setup:poly-targets)].
@terminal{ @terminal{
> raco pollen render -t pdf foo.poly.pm} > raco pollen render -t pdf foo.poly.pm}
@ -132,7 +130,9 @@ As a rule of thumb, parallel rendering works best if you do @exec{raco setup} fi
@italic{Warning}: In all cases, the newly rendered output file will overwrite any previous output file. @italic{Warning}: In all cases, the newly rendered output file will overwrite any previous output file.
@bold{Directory mode}: @racket[raco pollen render _directory] renders all preprocessor source files and then all pagetree files found in the specified directory. If none of these files are found, a pagetree will be generated for the directory (which will include all source files, but also everything else that exists there; see @secref["The_automatic_pagetree"]) and then rendered. If the @racket[_directory] argument is omitted, the command defaults to the current directory. @margin-note{As of mid-2020, Pollen's parallel-processing performance under the CS (= Chez Scheme) variant of Racket is worse than ordinary Racket. If you use Racket CS, you may get better results using @exec{-j 4} (which will limit the operation to four cores) than @exec{-p} (which will use all available cores).}
@bold{Directory mode}: @racket[raco pollen render _directory] renders all preprocessor source files and then all pagetree files found in the specified directory. If none of these files are found, a pagetree will be generated for the directory (which will include all source files) and then rendered. If the @racket[_directory] argument is omitted, the command defaults to the current directory.
In directory mode, this command can be invoked with two other optional arguments (in addition to the @exec{--target}, @exec{--parallel}, and @exec{--jobs} switches mentioned above): In directory mode, this command can be invoked with two other optional arguments (in addition to the @exec{--target}, @exec{--parallel}, and @exec{--jobs} switches mentioned above):

@ -1,6 +1,6 @@
#lang scribble/manual #lang scribble/manual
@(require "mb-tools.rkt") @(require "mb-tools.rkt")
@(require scribble/eval pollen/setup racket/string (for-label (except-in racket #%top) racket/runtime-path syntax/modresolve (except-in pollen #%module-begin #%top) pollen/render pollen/setup pollen/top)) @(require scribble/eval pollen/setup racket/string (for-label (except-in racket #%top) racket/runtime-path syntax/modresolve (except-in pollen #%module-begin #%top) pollen/setup pollen/top))
@(define my-eval (make-base-eval)) @(define my-eval (make-base-eval))
@(my-eval `(require pollen pollen/setup)) @(my-eval `(require pollen pollen/setup))
@ -153,16 +153,11 @@ Both the names and the values of environment variables are case-insensitive, so
@history[#:added "1.5"]} @history[#:added "1.5"]}
@defoverridable[allow-unbound-ids? boolean?]{Predicate that controls whether Pollen converts unbound identifiers into default tags by altering the behavior of @racket[#%top] in @racketmodname[pollen/top]. @defoverridable[allow-unbound-ids? boolean?]{Predicate that controls whether Pollen converts unbound identifiers into default tags by altering the behavior of @racket[#%top] in @racketmodname[pollen/top].
@history[#:added "2.0"]} @history[#:added "2.0"]}
@defoverridable[external-renderer (or/c (list/c module-path? symbol?) #f)]{A module path and identifier (suitable for use with @racket[dynamic-require]) that provide a function for Pollen to call instead of @racket[render] when rendering files needed by the @seclink["Using_the_project_server"]{project server} or when running @secref["raco_pollen_render"]. The function must accept the same arguments as @racket[render-to-file] and should return the final output as a @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{string} or @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{byte string}. Pollen will always write this return value out to the output file for you.
Setting this value gives you full control over (and responsibility for) how Pollen converts the compiled @racketidfont{doc} and @racketidfont{metas} from source files into their final output. Your renderer should be able to handle any of Pollens @seclink["Source_formats"]{source formats} or @seclink["Utility_formats"]{utility formats}. The operation of Pollens @racket[render] function is not affected by setting this value, so your renderer can use it as a fallback.
@history[#:added "3.2"]}
@section{Parameters} @section{Parameters}
I mean @italic{parameters} in the Racket sense, i.e. values that can be fed to @racket[parameterize]. I mean @italic{parameters} in the Racket sense, i.e. values that can be fed to @racket[parameterize].

@ -16,7 +16,7 @@ That's no longer true. The web is now more than 20 years old. During that time,
But one part hasn't improved much: the way we make web pages. Over the years, tools promising to simplify web development have come and mostly gone — from @link["http://www.macobserver.com/reviews/pagemill2.shtml"]{PageMill} to @link["http://www.adobe.com/products/dreamweaver.html"]{Dreamweaver} to @link["http://www.squarespace.com"]{Squarespace}. Meanwhile, serious web jocks have remained loyal to the original HTML power tool: the humble text editor. But one part hasn't improved much: the way we make web pages. Over the years, tools promising to simplify web development have come and mostly gone — from @link["http://www.macobserver.com/reviews/pagemill2.shtml"]{PageMill} to @link["http://www.adobe.com/products/dreamweaver.html"]{Dreamweaver} to @link["http://www.squarespace.com"]{Squarespace}. Meanwhile, serious web jocks have remained loyal to the original HTML power tool: the humble text editor.
In one way, this makes sense. Web pages are made mostly of text-based data — HTML, CSS, JavaScript, and so on — and the simplest way to manipulate this data is with a text editor. While HTML and CSS are not programming languages — you can't even compute 1 + 1 — they lend themselves to semantic and logical structure that's most easily expressed by editing them as text. Furthermore, text-based editing makes debugging and performance improvements easier. In one way, this makes sense. Web pages are made mostly of text-based data — HTML, CSS, JavaScript, and so on — and the simplest way to mainpulate this data is with a text editor. While HTML and CSS are not programming languages — you can't even compute 1 + 1 — they lend themselves to semantic and logical structure that's most easily expressed by editing them as text. Furthermore, text-based editing makes debugging and performance improvements easier.
But text-based editing is also limited. Though the underlying description of a web page is notionally human-readable, it's optimized to be readable by other software — namely, web browsers. HTML in particular is verbose and easily mistyped. And isn't it fatally dull to manage all the boilerplate, like surrounding every paragraph with @code{<p>...</p>}? Yes, it is. But text-based editing is also limited. Though the underlying description of a web page is notionally human-readable, it's optimized to be readable by other software — namely, web browsers. HTML in particular is verbose and easily mistyped. And isn't it fatally dull to manage all the boilerplate, like surrounding every paragraph with @code{<p>...</p>}? Yes, it is.

@ -21,7 +21,7 @@ Pygments is a Python library (though you don't need to know any Python to use it
@subsection[#:tag "pygments-with-pollen"]{Using Pygments with Pollen} @subsection[#:tag "pygments-with-pollen"]{Using Pygments with Pollen}
I used @link["http://pygments.org/"]{Pygments} for syntax highlighting in @link["https://beautifulracket.com/"]{@italic{Beautiful Racket}}. Links to the source are available at the bottom of the article. I used @link["http://pygments.org/"]{Pygments} for syntax highlighting in @link["http://unitscale.com/mb/technique/dual-typed-untyped-library.html"]{this article made with Pollen}. Links to the source are available at the bottom of the article.
@itemlist[#:style 'ordered @itemlist[#:style 'ordered

@ -405,9 +405,6 @@ Beyond that, all we need to do make sure our template has the three key ingredie
In your project directory, create a new file called @filepath{template.html.p}: In your project directory, create a new file called @filepath{template.html.p}:
@margin-note{If you're using DrRacket on Mac OS to save this file, it may insist on adding a @filepath{rkt} extension to the filename. If so, you can either correct the filename after you save the file, or instead use a different text editor to create @filepath{template.html.p}.}
@fileblock["template.html.p" @fileblock["template.html.p"
@codeblock[#:keep-lang-line? #f]{ @codeblock[#:keep-lang-line? #f]{
#lang pollen #lang pollen

@ -12,7 +12,7 @@ Inconsistent with this system, Pollen's version also appends a build number, whi
@section{Source code} @section{Source code}
Pollen's source code is @link["https://git.matthewbutterick.com/mbutterick/pollen/"]{available from this Git repo}. The @tt{MASTER} branch of the repo will always contain the most recent stable version. Pollen's source code is @link["http://github.com/mbutterick/pollen"]{available from this Git repo}. The @tt{MASTER} branch of the repo will always contain the most recent stable version.
Racket's @link["http://pkg.racket-lang.org"]{package catalog} relies on this branch, so if you get your updates with @tt{raco pkg update pollen}, you'll get the most recent updates from this branch. Racket's @link["http://pkg.racket-lang.org"]{package catalog} relies on this branch, so if you get your updates with @tt{raco pkg update pollen}, you'll get the most recent updates from this branch.
@ -24,10 +24,6 @@ Beyond keeping the commit history available, I make no promise to maintain the p
@section{Changelog} @section{Changelog}
@subsection{Version 3.2}
Added @racket[setup:external-renderer].
@subsection{Version 3.1} @subsection{Version 3.1}
Downgraded the following @racket[pollen/setup] values from configurable to fixed: @racket[here-path-key], @racket[extension-escape-char]. Downgraded the following @racket[pollen/setup] values from configurable to fixed: @racket[here-path-key], @racket[extension-escape-char].

@ -19,8 +19,7 @@
(let-values ([(dir name dir?) (split-path path)]) (let-values ([(dir name dir?) (split-path path)])
dir)) dir))
(provide find-nearest-default-directory-require) (define (get-path-to-override maybe-dir)
(define (find-nearest-default-directory-require maybe-dir)
(define starting-dir (cond (define starting-dir (cond
[(not maybe-dir) (current-directory)] [(not maybe-dir) (current-directory)]
[(directory-exists? maybe-dir) maybe-dir] [(directory-exists? maybe-dir) maybe-dir]
@ -51,13 +50,15 @@
;; exn:fail:contract? is raised if setup submodule doesn't exist ;; exn:fail:contract? is raised if setup submodule doesn't exist
;; in which case we use the default value. ;; in which case we use the default value.
;; but if something else is amiss, we want to let it bubble up ;; but if something else is amiss, we want to let it bubble up
(define setup-module-path (find-nearest-default-directory-require dir)) (define setup-module-path (get-path-to-override dir))
(with-handlers ([exn:fail:contract? (λ (exn) DEFAULT-NAME)] (with-handlers ([exn:fail:contract? (λ (exn) DEFAULT-NAME)]
;; certain errors in pollen.rkt will arrive here ;; a syntax error in pollen.rkt will arrive here
;; they do not indicate a defective setup module, so pass them through ;; exn:fail:read? for a syntactic failure (e.g., missing paren)
[exn:fail:read? raise] ; syntactic failure (e.g., missing paren) ;; exn:fail:syntax? for a semantic failure, (e.g., unbound identifier)
[exn:fail:syntax? raise] ; semantic failure (e.g., unbound identifier) ;; it does not indicate a defective setup module,
[exn:fail:filesystem? raise] ; filesystem failure (e.g., too many open files) ;; so pass it through
[exn:fail:read? raise]
[exn:fail:syntax? raise]
[exn? (λ (exn) (raise-user-error 'pollen/setup [exn? (λ (exn) (raise-user-error 'pollen/setup
(format "defective `setup` submodule in ~v\n~a" (path->string setup-module-path) (exn-message exn))))]) (format "defective `setup` submodule in ~v\n~a" (path->string setup-module-path) (exn-message exn))))])
(dynamic-require `(submod ,setup-module-path WORLD-SUBMOD) (dynamic-require `(submod ,setup-module-path WORLD-SUBMOD)
@ -134,5 +135,3 @@
(define-settable index-pages '("index.html")) (define-settable index-pages '("index.html"))
(define-settable allow-unbound-ids? #true) (define-settable allow-unbound-ids? #true)
(define-settable external-renderer #false)

Loading…
Cancel
Save