pull/9/head
Matthew Butterick 10 years ago
parent 97209d1019
commit 1c6311b067

@ -186,8 +186,8 @@
(list
(parameterize ([current-directory (world:current-project-root)])
(let ([source-metas (cached-require source-path 'metas)])
(and (world:template-meta-key . in? . source-metas)
(build-path source-dir (get source-metas world:template-meta-key))))) ; path based on metas
(and ((->symbol world:template-meta-key) . in? . source-metas)
(build-path source-dir (get source-metas (->string world:template-meta-key)))))) ; path based on metas
(build-path (world:current-project-root)
(add-ext (add-ext world:default-template-prefix (get-ext (->output-path source-path))) world:template-source-ext))))) ; path to default template
(build-path (world:current-server-extras-path) world:fallback-template)))) ; fallback template

@ -10,15 +10,15 @@
@defmodule[pollen/decode]
The @racket['doc] export of a Pollen markup file is a simple X-expression. @italic{Decoding} refers to any post-processing of this X-expression. The @racket[pollen/decode] module provides tools for creating decoders.
The @racket[doc] export of a Pollen markup file is a simple X-expression. @italic{Decoding} refers to any post-processing of this X-expression. The @racket[pollen/decode] module provides tools for creating decoders.
The decode step can happen separately from the compilation of the file. But you can also attach a decoder to the markup file's @racket['root] node, so the decoding happens automatically when the markup is compiled, and thus automatically incorporated into @racket['doc]. (Following this approach, you could also attach multiple decoders to different tags within @racket['doc].)
The decode step can happen separately from the compilation of the file. But you can also attach a decoder to the markup file's @racket[root] node, so the decoding happens automatically when the markup is compiled, and thus automatically incorporated into @racket[doc]. (Following this approach, you could also attach multiple decoders to different tags within @racket[doc].)
You can, of course, embed function calls within Pollen markup. But since markup is optimized for authors, decoding is useful for operations that can or should be moved out of the authoring layer.
One example is presentation and layout. For instance, @racket[detect-paragraphs] lets authors mark paragraphs in their source simply by using two carriage returns.
One example is presentation and layout. For instance, @racket[detect-paragraphs] is a decoder function that lets authors mark paragraphs in their source simply by using two carriage returns.
Another example is conversion of output into a particular data format. Most Pollen functions are optimized for HTML output, but it's simple to write decoders that target other formats.
Another example is conversion of output into a particular data format. Most Pollen functions are optimized for HTML output, but one could write a decoder that targets another format.

@ -10,3 +10,5 @@
@include-section["pagetree.scrbl"]
@include-section["render.scrbl"]
@include-section["template.scrbl"]
@include-section["top.scrbl"]
@include-section["world.scrbl"]

@ -1,169 +0,0 @@
#lang scribble/manual
@title{Pollen}
@author{Matthew Butterick}
Pollen is a publishing system that lets authors create beautiful and functional web-based books. Pollen brings together tools for writing, designing, programming, and testing. I used Pollen to make my book @link["http://practicaltypography.com"]{Butterick's Practical Typography}. You can use it to make your next web-based book — it's easy, powerful, and fun.
Skip to: Why Pollen?
Skip to: Why Racket?
Skip to: Quick tutorial
@section{Setting up Pollen}
Download Racket & install.
@code{raco pkg install pollen}
Open terminal and cd to your project directory.
Run @code{racket}.
At the prompt, type @code{(require pollen/setup)}.
Then type @code{(setup)}.
Quit Racket with ctrl+D.
At the terminal prompt, @code{./polcom start}.
@section{Quick start}
@subsection{You had me at Hello World}
Launch DrRacket. Open a new document.
Change the top line to #lang pollen. This will be the first line of any source file where you want to invoke the Pollen language.
Type @code{Hello World} underneath.
Run the file by clicking the Run button, or typing cmd-R.
The result window will show @code{Hello World}.
Congratulations, you've made your first Pollen document.
If you like, change the text and run the file again. The new text will appear in the result window.
Pollen is a programming language that operates in text mode by default. Meaning, all plain text in the source file is considered valid input, and gets passed through intact.
@subsection{The plot thickens}
Start a new Pollen document. Remember to change the top line.
Underneath, type @code{Hello ◊(+ 1 2) Worlds}. The character before the left parenthesis is called a lozenge. Type it by [doing such and such].
Ask yourself: what are you likely to get when you run the file?
OK, now run the file.
The result will be @code{Hello 3 Worlds}. Hopefully, that's what you expected.
Feel free to change the numbers inside the parenthesized expression and run the file again. The printed sum will change. You can also change the @code{+} sign to a @code{*} sign and make really big numbers. If you want to see your first stupid Pollen trick, type @code{Hello ◊(/ 38 57) of a World} and watch what happens.
Erase everything but the top line.
Type this: @code{
◊(define name "Roxy")
Hello ◊name}.
What do you suppose you'll get this time?
Run the file. You'll see @code{Hello Roxy}.
The lozenge character (◊) tells Pollen to interpret what follows as code rather than plain text. This character is therefore the gateway to all the programming functions available in Pollen. In the first case, it denoted a math expression. In the second case, it denoted the definition of a variable, and then the variable itself.
@subsection{Making an HTML page with Pollen}
By default, Pollen operates in preprocessor mode. That means it evaluates all the expressions in your document, renders each as text, and then outputs the whole document as a text file.
In this tutorial, you're going to make an HTML file. But you can use Pollen as a preprocessor for any kind of text file.
@margin-note{That means Pollen can act as a preprocessor for CSS, JavaScript, XML — and even source files for other programming languages.}
@subsection{Making an HTML page with Pollen in decoder mode}
@subsection{Using the Pollen dashboard}
@section{Why I made Pollen}
The nerds have already raced ahead to the quick tutorial. That's okay. Because software isn't just data structures and functions. It's ideas, and choices, and policies. It's design.
I created Pollen to overcome certain tool limitations that surfaced repeatedly in my work. If you agree with my characterization of those problems, then you'll probably like the solution that Pollen offers.
If not, you probably won't.
@subsection{The web-development problem}
I made my first web page in 1994, shortly after the web was invented. I opened my text editor (at the time, @link["http://www.barebones.com/products/bbedit/"]{BBEdit}) and pecked out @code{<html><body>Hello world</body></html>}, then loaded it in @link["http://en.wikipedia.org/wiki/Mosaic_(web_browser)"]{Mosaic}. So did a million others.
If you weren't around then, you didn't miss much. Everything about the web was horrible: the web browsers, the computers running the browsers, the dial-up connections feeding the browsers, and of course HTML itself. At that point, the desktop-software experience was already slick and refined. By comparison, using the web felt like banging rocks together.
That's no longer true. The web is now 20 years old. During that time, most parts of the web have improved dramatically — the connections are faster, the browsers are more sophisticated, the screens have more pixels.
But one part has not: the way we make web pages. Over the years, tools promising to simplify HTML development have come and mostly gone — from PageMill to Dreamweaver. Meanwhile, true web jocks have remained loyal to the original HTML power tool: the humble text editor.
In one way, this makes sense. Web pages are mostly made of text — HTML, CSS, JavaScript, and so on — and thus the simplest way to mainpulate them is with a text editor. While HTML and CSS are not programming languages, they lend themselves to semantic and logical structure that's most easily expressed by editing them as text. Text-based editing also 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 largely optimized to be readable by other software (namely, web browsers). HTML markup 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.
For these reasons, much of web development should lend itself to automation. But in practice, tools that enable this automation have been slow to arrive, and most come hobbled with unacceptable deficiencies.
@subsubsection{Why not a content management system, like WordPress?}
I used WordPress to make the original version of @link["http://typographyforlawyers.com/"]{Typography for Lawyers} (the precursor to Butterick's Practical Typography). Even WordPress founder Matt Mullenweg @link["http://ma.tt/2010/04/typography-for-lawyers/"]{thought} it was “a cool use of WordPress for a mini-book.” Thanks, Matt. At the time, WordPress was the best tool for the job.
But at the risk of having my @link["http://gravatar.com"]{Gravatar} revoked, I'll tell you I became disenchanted with WordPress because:
It's a resource hog.
Performance is questionable.
There's always a new security problem.
No source control.
PHP.
@subsubsection{Why not a CSS preprocessor, like Sass or LESS?}
A CSS preprocessor automates the generation of CSS data. These preprocessors do save time & effort, so using one is better than not using one. My objection is that they ask you to incur much of the overhead of learning a programming language but without delivering the benefits. Because unlike a general-purpose programming language, Sass and LESS can only manipulate CSS. Better to learn a programming language that can manipulate anything.
@subsubsection{Why not a static blog generator, like Jekyll or Pelican?}
@subsubsection{Why not a dynamic templating system, like Bottle?}
@subsection{The book-publishing problem}
@section{Why I used Racket}
@subsection{Writing & editing}
@subsection{Design & layout}
@subsection{Programming}
@section{License & credits}

@ -37,6 +37,9 @@ Install Pollen from the command line:
After that, you can update the package from the command line:
@verbatim{raco pkg update pollen}
@include-section["quick.scrbl"]
@include-section["why-pollen.scrbl"]
@section{Pollen source formats}

@ -0,0 +1,68 @@
#lang scribble/manual
@title{Quick start}
@section{You had me at Hello World}
Launch DrRacket. Open a new document.
Change the top line to #lang pollen. This will be the first line of any source file where you want to invoke the Pollen language.
Type @code{Hello World} underneath.
Run the file by clicking the Run button, or typing cmd-R.
The result window will show @code{Hello World}.
Congratulations, you've made your first Pollen document.
If you like, change the text and run the file again. The new text will appear in the result window.
Pollen is a programming language that operates in text mode by default. Meaning, all plain text in the source file is considered valid input, and gets passed through intact.
@section{The plot thickens}
Start a new Pollen document. Remember to change the top line.
Underneath, type @code{Hello ◊(+ 1 2) Worlds}. The character before the left parenthesis is called a lozenge. Type it by [doing such and such].
Ask yourself: what are you likely to get when you run the file?
OK, now run the file.
The result will be @code{Hello 3 Worlds}. Hopefully, that's what you expected.
Feel free to change the numbers inside the parenthesized expression and run the file again. The printed sum will change. You can also change the @code{+} sign to a @code{*} sign and make really big numbers. If you want to see your first stupid Pollen trick, type @code{Hello ◊(/ 38 57) of a World} and watch what happens.
Erase everything but the top line.
Type this: @code{
◊(define name "Roxy")
Hello ◊name}.
What do you suppose you'll get this time?
Run the file. You'll see @code{Hello Roxy}.
The lozenge character (◊) tells Pollen to interpret what follows as code rather than plain text. This character is therefore the gateway to all the programming functions available in Pollen. In the first case, it denoted a math expression. In the second case, it denoted the definition of a variable, and then the variable itself.
@section{Making an HTML page with Pollen}
By default, Pollen operates in preprocessor mode. That means it evaluates all the expressions in your document, renders each as text, and then outputs the whole document as a text file.
In this tutorial, you're going to make an HTML file. But you can use Pollen as a preprocessor for any kind of text file.
@margin-note{That means Pollen can act as a preprocessor for CSS, JavaScript, XML — and even source files for other programming languages.}
@subsection{Making an HTML page with Pollen in decoder mode}
@subsection{Using the Pollen dashboard}

@ -0,0 +1,55 @@
#lang scribble/manual
@title{Why I made Pollen}
The nerds have already raced ahead to the quick tutorial. That's okay. Because software isn't just data structures and functions. It's ideas, and choices, and policies. It's design.
I created Pollen to overcome certain tool limitations that surfaced repeatedly in my work. If you agree with my characterization of those problems, then you'll probably like the solution that Pollen offers.
If not, you probably won't.
@section{The web-development problem}
I made my first web page in 1994, shortly after the web was invented. I opened my text editor (at the time, @link["http://www.barebones.com/products/bbedit/"]{BBEdit}) and pecked out @code{<html><body>Hello world</body></html>}, then loaded it in @link["http://en.wikipedia.org/wiki/Mosaic_(web_browser)"]{Mosaic}. So did a million others.
If you weren't around then, you didn't miss much. Everything about the web was horrible: the web browsers, the computers running the browsers, the dial-up connections feeding the browsers, and of course HTML itself. At that point, the desktop-software experience was already slick and refined. By comparison, using the web felt like banging rocks together.
That's no longer true. The web is now 20 years old. During that time, most parts of the web have improved dramatically — the connections are faster, the browsers are more sophisticated, the screens have more pixels.
But one part has not: the way we make web pages. Over the years, tools promising to simplify HTML development have come and mostly gone — from PageMill to Dreamweaver. Meanwhile, true web jocks have remained loyal to the original HTML power tool: the humble text editor.
In one way, this makes sense. Web pages are mostly made of text — HTML, CSS, JavaScript, and so on — and thus the simplest way to mainpulate them is with a text editor. While HTML and CSS are not programming languages, they lend themselves to semantic and logical structure that's most easily expressed by editing them as text. Text-based editing also 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 largely optimized to be readable by other software (namely, web browsers). HTML markup 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.
For these reasons, much of web development should lend itself to automation. But in practice, tools that enable this automation have been slow to arrive, and most come hobbled with unacceptable deficiencies.
@subsection{Why not a content management system, like WordPress?}
I used WordPress to make the original version of @link["http://typographyforlawyers.com/"]{Typography for Lawyers} (the precursor to Butterick's Practical Typography). Even WordPress founder Matt Mullenweg @link["http://ma.tt/2010/04/typography-for-lawyers/"]{thought} it was “a cool use of WordPress for a mini-book.” Thanks, Matt. At the time, WordPress was the best tool for the job.
But at the risk of having my @link["http://gravatar.com"]{Gravatar} revoked, I'll tell you I became disenchanted with WordPress because:
It's a resource hog.
Performance is questionable.
There's always a new security problem.
No source control.
PHP.
@subsection{Why not a CSS preprocessor, like Sass or LESS?}
A CSS preprocessor automates the generation of CSS data. These preprocessors do save time & effort, so using one is better than not using one. My objection is that they ask you to incur much of the overhead of learning a programming language but without delivering the benefits. Because unlike a general-purpose programming language, Sass and LESS can only manipulate CSS. Better to learn a programming language that can manipulate anything.
@subsection{Why not a static blog generator, like Jekyll or Pelican?}
@subsection{Why not a dynamic templating system, like Bottle?}

@ -1,6 +1,6 @@
#lang scribble/manual
@(require scribble/eval pollen/cache pollen/world (for-label racket (except-in pollen #%module-begin) pollen/world))
@(require scribble/eval pollen/cache pollen/world (for-label racket (except-in pollen #%module-begin) pollen/world pollen/render))
@(define my-eval (make-base-eval))
@(my-eval `(require pollen pollen/world))
@ -9,4 +9,86 @@
@defmodule[pollen/world]
A set of global values and parameters that are used throughout the Pollen system. So if you don't like the defaults I've picked, you can change them.
A set of global values and parameters that are used throughout the Pollen system. If you don't like the defaults I've picked, change them.
All identifiers are exported with the prefix @racket[world:], and are so documented below.
@deftogether[(
@defthing[world:main-pollen-export symbol? #:value 'doc]
@defthing[world:meta-pollen-export symbol? #:value 'metas]
)]
The two exports from a compiled Pollen source file.
@(defthing world:pollen-require string? #:value "pollen-require.rkt")
File implicitly required into every Pollen source file from its directory.
@defparam[world:check-project-requires-in-render? check? boolean?
#:value #t]{
A parameter that determines whether the @racket[world:pollen-require] file is checked for changes on every pass through @racket[render]. (Can be faster to turn this off if you don't need it.) Initialized to @racket[#t].}
@defparam[world:current-module-root dir path?
#:value #f]{
A parameter that reports the path to the Pollen module. Initialized to @racket[#f], but set to a proper value when @racketmodname[pollen/server] runs.}
@defparam[world:current-server-extras-path dir path?
#:value #f]{
A parameter that reports the path to the directory of support files for the development server. Initialized to @racket[#f], but set to a proper value when @racketmodname[pollen/server] runs.}
@deftogether[(
@defthing[world:preproc-source-ext symbol? #:value 'pp]
@defthing[world:markup-source-ext symbol? #:value 'pm]
@defthing[world:markdown-source-ext symbol? #:value 'pmd]
@defthing[world:null-source-ext symbol? #:value 'p]
@defthing[world:pagetree-source-ext symbol? #:value 'ptree]
@defthing[world:template-source-ext symbol? #:value 'pt]
@defthing[world:scribble-source-ext symbol? #:value 'scrbl]
)]
File extensions for Pollen source files.
@defthing[world:decodable-extensions (listof symbol?) #:value (list world:markup-source-ext world:pagetree-source-ext)]
File extensions that are eligible for decoding.
@deftogether[(
@(defthing world:mode-auto symbol? #:value 'auto)
@(defthing world:mode-preproc symbol? #:value 'pre)
@(defthing world:mode-markup symbol? #:value 'markup)
@(defthing world:mode-markdown symbol? #:value 'markdown)
@(defthing world:mode-pagetree symbol? #:value 'ptree)
)]
Mode indicators for the Pollen reader and parser.
@defthing[world:default-pagetree string? #:value "index.ptree"]
Pagetree that Pollen dashboard loads by default in each directory.
@defthing[world:pagetree-root-node symbol? #:value 'pagetree-root]
Name of the root node in a decoded pagetree. It's ignored by the code, so its only role is to clue you in that you're looking at something that came out of the pagetree decoder.
@defthing[world:expression-delimiter char? #:value #\◊]
The magic character that delimits Pollen expressions.
@defthing[world:default-template-prefix string? #:value "main"]
Prefix of the default template.
@defthing[world:fallback-template string? #:value "fallback.html.pt"]
Name of the fallback template (i.e., the template used to render a Pollen markup file when no other template can be found).
@defthing[world:template-meta-key symbol? #:value 'template]
Meta key used to store a template name for that particular source file.
@deftogether[(
@(defthing world:newline string? #:value "\n")
@(defthing world:linebreak-separator string? #:value world:newline)
@(defthing world:paragraph-separator string? #:value "\n\n")
)]
Default separators used in decoding.
@(defthing world:dashboard-css string? #:value "poldash.css")
CSS file used for the dashboard.
@(defthing world:paths-excluded-from-dashboard (listof path?) #:value (map string->path (list "poldash.css" "compiled")))
Paths not shown in the Pollen dashboard.

@ -37,7 +37,7 @@
(define client (request-client-ip req))
(define localhost-client "::1")
(define url-string (url->string (request-uri req)))
(message "request:" (string-replace url-string world:dashboard-name " dashboard")
(message "request:" (string-replace url-string world:default-pagetree " dashboard")
(if (not (equal? client localhost-client)) (format "from ~a" client) "")))
;; pass string args to route, then
@ -70,9 +70,6 @@
(when wants-render (render-for-dev-server path))
(file->string path))
(module+ test
(check-equal? (slurp (build-path (current-directory) "tests/server-routes/bar.html") #:render #f) "<html><body><p>bar</p></body></html>"))
;; add a wrapper to txexpr that displays it as monospaced text
;; for "view source"ish functions
@ -152,8 +149,8 @@
text)))))
(define (make-parent-row)
(if parent-dir
(let* ([url-to-parent-dashboard (format "/~a" (find-relative-path (world:current-project-root) (build-path parent-dir world:dashboard-name)))]
[url-to-parent (string-replace url-to-parent-dashboard world:dashboard-name "")])
(let* ([url-to-parent-dashboard (format "/~a" (find-relative-path (world:current-project-root) (build-path parent-dir world:default-pagetree)))]
[url-to-parent (string-replace url-to-parent-dashboard world:default-pagetree "")])
`(tr (th ((colspan "3")) (a ((href ,url-to-parent-dashboard)) ,(format "up to ~a" url-to-parent)))))
`(tr (th ((colspan "3")(class "root")) "Pollen root"))))
@ -165,7 +162,7 @@
(append (list
(cond ; main cell
[(directory-exists? (build-path dashboard-dir filename)) ; links subdir to its dashboard
(cons (format "~a/~a" filename world:dashboard-name) (format "~a/" filename))]
(cons (format "~a/~a" filename world:default-pagetree) (format "~a/" filename))]
[(and source (equal? (get-ext source) "scrbl"))
(cons #f `(a ((href ,filename)) ,filename (span ((class "file-ext")) " (from " ,(path->string (find-relative-path dashboard-dir source)) ")")))]
[source (cons #f `(a ((href ,filename)) ,filename (span ((class "file-ext")) "." ,(get-ext source))))]
@ -181,7 +178,7 @@
[(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)))
(define (ineligible-path? x) (or (not (visible? x)) (member x world:paths-excluded-from-dashboard)))
(define (unique-sorted-output-paths xs)
(define output-paths (map ->output-path xs))

@ -24,7 +24,7 @@
(define server-name (format "http://localhost:~a" (world:current-server-port)))
(message (format "Project server is ~a" server-name) "(Ctrl-C to exit)")
(message (format "Project dashboard is ~a/~a" server-name world:dashboard-name))
(message (format "Project dashboard is ~a/~a" server-name world:default-pagetree))
(message "Ready to rock")

@ -12,7 +12,6 @@
(define template-source-ext 'pt)
(define scribble-source-ext 'scrbl)
(define mode-auto 'auto)
(define mode-preproc 'pre)
(define mode-markup 'markup)
@ -24,7 +23,6 @@
(define default-pagetree "index.ptree")
(define pagetree-root-node 'pagetree-root)
(define template-source-prefix "-")
(define expression-delimiter #\◊)
(define template-field-delimiter expression-delimiter)
@ -37,27 +35,18 @@
(define pollen-require "pollen-require.rkt")
(define missing-file-boilerplace "#lang pollen\n\n")
(define newline "\n")
(define linebreak-separator newline)
(define paragraph-separator "\n\n")
(define output-subdir 'public)
(define racket-path "/usr/bin/racket")
(define command-file "polcom")
(define reserved-paths
(map string->path (list command-file "poldash.css" "compiled")))
(define paths-excluded-from-dashboard
(map string->path (list "poldash.css" "compiled")))
(define current-project-root (make-parameter (current-directory)))
(define current-server-port (make-parameter 8088))
(define dashboard-name "index.ptree")
(define dashboard-css "poldash.css")
(define current-module-root (make-parameter #f))

Loading…
Cancel
Save