so much more

pull/9/head
Matthew Butterick 10 years ago
parent 898d652e40
commit c81aaef12a

@ -89,18 +89,20 @@
(make-source-utility-functions null)
(make-source-utility-functions pagetree)
(make-source-utility-functions markup)
(make-source-utility-functions markdown)
(make-source-utility-functions template)
(make-source-utility-functions scribble)
(define/contract+provide (->source-path path)
(coerce/path? . -> . (or/c #f path?))
(ormap (λ(proc) (proc path)) (list get-markup-source get-preproc-source get-null-source get-scribble-source)))
(ormap (λ(proc) (proc path)) (list get-markup-source get-markdown-source get-preproc-source get-null-source get-scribble-source)))
(define+provide/contract (->output-path x)
(coerce/path? . -> . coerce/path?)
(cond
[(or (markup-source? x) (preproc-source? x) (null-source? x)) (remove-ext x)]
[(or (markup-source? x) (preproc-source? x) (null-source? x) (markdown-source? x)) (remove-ext x)]
[(scribble-source? x) (add-ext (remove-ext x) 'html)]
[else x]))

@ -81,7 +81,6 @@
(define metas (make-hash (map meta-element->assoc meta-elements)))
(values doc-without-metas metas))
(define doc-with-metas
(let ([doc-raw (if (equal? parser-mode world:mode-markdown)
(apply (compose1 (dynamic-require 'markdown 'parse-markdown) string-append) doc-raw)
@ -89,10 +88,10 @@
`(placeholder-root
,@(cons (meta 'here-path: here-path)
;; cdr strips initial linebreak, but make sure doc-raw isn't blank
(if (and (list? doc-raw) (> (length doc-raw) 0)) (cdr doc-raw) doc-raw)))))
(if (and (list? doc-raw) (> (length doc-raw) 0) (equal? (car doc-raw) "\n")) (cdr doc-raw) doc-raw)))))
(define-values (doc-without-metas metas) (split-metas-to-hash doc-with-metas))
;; set up the 'doc export
(require pollen/decode)
(define doc (apply (cond

@ -64,7 +64,7 @@
((pathish?) (#:force boolean?) . ->* . void?)
(let ([so-path (->complete-path so-pathish)]) ; so-path = source or output path (could be either)
(cond
[(ormap (λ(test) (test so-path)) (list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source?))
[(ormap (λ(test) (test so-path)) (list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source? has/is-markdown-source?))
(let-values ([(source-path output-path) (->source+output-paths so-path)])
(render-to-file-if-needed source-path output-path #:force force))]
[(pagetree-source? so-path) (render-pagetree so-path)]))
@ -75,8 +75,8 @@
(complete-path? . -> . (values complete-path? complete-path?))
;; file-proc returns two values, but ormap only wants one
(define file-proc (ormap (λ(test file-proc) (and (test source-or-output-path) file-proc))
(list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source?)
(list ->null-source+output-paths ->preproc-source+output-paths ->markup-source+output-paths ->scribble-source+output-paths)))
(list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source? has/is-markdown-source?)
(list ->null-source+output-paths ->preproc-source+output-paths ->markup-source+output-paths ->scribble-source+output-paths ->markdown-source+output-paths)))
(file-proc source-or-output-path))
@ -119,8 +119,8 @@
(define render-proc
(cond
[(ormap (λ(test render-proc) (and (test source-path) render-proc))
(list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source?)
(list render-null-source render-preproc-source render-markup-source render-scribble-source))]
(list has/is-null-source? has/is-preproc-source? has/is-markup-source? has/is-scribble-source? has/is-markdown-source?)
(list render-null-source render-preproc-source render-markup-or-markdown-source render-scribble-source render-markup-or-markdown-source))]
[else (error (format "render: no rendering function found for ~a" source-path))]))
(message (format "render: ~a" (file-name-from-path source-path)))
@ -154,7 +154,7 @@
(render-through-eval `(begin (require pollen/cache)(cached-require ,source-path ',world:main-pollen-export))))))
(define/contract (render-markup-source source-path [maybe-template-path #f])
(define/contract (render-markup-or-markdown-source source-path [maybe-template-path #f])
((complete-path?) ((or/c #f complete-path?)) . ->* . bytes?)
(match-define-values (source-dir _ _) (split-path source-path))
(define template-path (or maybe-template-path (get-template-for source-path)))
@ -176,7 +176,7 @@
(define/contract (templated-source? path)
(complete-path? . -> . boolean?)
(and (markup-source? path)))
(or (markup-source? path) (markdown-source? path)))
(define/contract+provide (get-template-for source-path)

@ -6,4 +6,7 @@ One curious aspect of free software is that you can appropriate the benefits of
But the best tools do more than get the job done. They create an incentive to undertake jobs you wouldn't have attempted before. Racket encouraged me to become a better programmer so I could create Pollen. Likewise, I hope that Pollen encourages you to make things you couldn't before.
Thank you to Greg Hendershott for his Markdown parser.
MB

@ -2,7 +2,7 @@
@title{Quick start}
@(define (link-tt url) (link url (tt url)))
@section{Creating a source file}
@ -108,9 +108,9 @@ Project server is http://localhost:8080 (Ctrl-C to exit)
Project dashboard is http://localhost:8080/index.ptree
Ready to rock}
Open a web browser and point it at @tt{http://localhost:8080/index.ptree}. The top of the window will say @tt{Project root}. Below that will be a listing of the files in the directory.
Open a web browser and point it at @link-tt{http://localhost:8080/index.ptree}. The top of the window will say @tt{Project root}. Below that will be a listing of the files in the directory.
Among them will be @tt{hello.txt}, with a greyed-out @tt{.pp} extension. Click on it, and you'll be taken to @tt{http://localhost:8080/hello.txt}, where you'll see:
Among them will be @tt{hello.txt}, with a greyed-out @tt{.pp} extension. Click on it, and you'll be taken to @link-tt{http://localhost:8080/hello.txt}, where you'll see:
@verbatim{
Goodbye Stranger
@ -120,75 +120,184 @@ Take the Long Way Home
That's the boring part. Here's the good part. Leave the project server running. Open your source file again in DrRacket and edit it as follows:
@racketmod[pollen
@racketmod[#:file "hello.txt.pp" pollen
Mean Street
Panama
Hear About It Later]
Go back to your web browser and reload @tt{http://localhost:8080/hello.txt}. Now you'll see this:
Go back to your web browser and reload @link-tt{http://localhost:8080/hello.txt}. Now you'll see this:
@verbatim{
Mean Street
Panama
Hear About It Later}
The Pollen project server dyamically regenerates output files as you need them, including any time the underlying source file changes. If you like, try making some more changes to your @tt{hello.txt.pp} source file, and reloading the browser to see the updates.
Notice what happened — the Pollen project server dynamically regenerated the output file (@tt{hello.txt}) from the source file (@tt{hello.txt.pp}) after you edited the source. If you like, try making some more changes to @tt{hello.txt.pp}, and reloading the browser to see the updates in @tt{hello.txt}.
@section{Intermission}
That's it for input & output. Now let's circle back and see what Pollen source files can do, other than printing plain text.
That covers input & output. Now let's circle back and look at what else you can do with Pollen (beyond the epic achievement of displaying plain text in a web browser).
For the rest of this tutorial, I recommend keeping two windows on screen: a web-browser window pointed at your project server (the main URL is @link-tt{http://localhost:8080/index.ptree}) and the DrRacket editing window.
@section{Pollen as a preprocessor}
A @italic{preprocessor} is a tool for making systematic, automated changes to a source file before the main processing happens. A preprocessor can also be used to add programming logic to files that otherwise don't support it.
For instance, HTML. In DrRacket, create a new file called @tt{margin.html.pp} in your project directory:
@racketmod[#:file "margin.html.pp" pollen
<body style="margin: 5em; border:1px solid black">
5em is the inset.
</body>]
The ``@tt{.pp}'' file extension — which you saw before, with @tt{hello.txt.pp} — stands for ``Pollen preprocessor.'' You can use the Pollen preprocessor with any text-based file by inserting @tt{#lang pollen} as the first line, and adding the @tt{.pp} file extension.
But for now, go to your @link["http://localhost:8080/index.ptree"]{project dashboard} and click on @link["http://localhost:8080/margin.html"]{@tt{margin.html}}. You should see a black box containing the text ``5em is the inset.''
Let's suppose you want to change the inset to 30%. Without a preprocessor, you'd have to search & replace each value. But with a preprocessor, you can move the inset value into a variable, and update it from that one location. So first, introduce a variable called @tt{my-inset} by using the @racket[define] command:
@racketmod[#:file "margin.html.pp" pollen
◊define[my-inset]{30%}
<body style="margin: 10em; border:1px solid black">
10em is the inset.
</body>
]
The ◊ character is called a @italic{lozenge}. In Pollen, the lozenge is a special character that marks anything Pollen should interpret as a command (rather than plain text). The whole command @tt{◊define[my-inset]{30%}} means ``create a variable called @tt{my-inset} and give it the value @tt{30%}.''
Then put the variable into the HTML like so, this time using the ◊ character with the variable name in the two places the value appears:
@racketmod[#:file "margin.html.pp" pollen
◊define[my-inset]{30%}
<body style="margin: ◊my-inset; border:1px solid black">
◊my-inset is the inset.
</body>
]
Now reload @link["http://localhost:8080/margin.html"]{@tt{margin.html}}. You'll see that the size of the margin has changed (because of the change to the @tt{style} attribute) and so has the text of the HTML. If you like, try editing @tt{my-inset} with different values and reloading the page. You can also try using @racket[define] to create another variable (for instance, to change the color of the box border).
Still, this is the tiniest tip of the iceberg. The Pollen preprocessor gives you access to everything in the Racket programming language — including math functions, text manipulation, and so on.
@section{Markdown mode}
When used as a preprocessor, Pollen's rule is that what you write is what you get. But if you're targeting HTML, who wants to type out all those @tt{<tedious>tags</tedious>}? You can make Pollen do the heavy lifting by using it as a source decoder.
For the rest of this tutorial, I recommend keeping two windows on screen: a browser window with your project server, and the DrRacket editing window.
For instance, Markdown mode. Markdown is a simplified @link["https://daringfireball.net/projects/markdown/"]{notation system} for HTML. You can use Pollen's Markdown decoder by inserting @tt{#lang pollen} as the first line, and adding the @tt{.pmd} file extension.
@section{Preprocessor files}
Try it. In DrRacket, create a file with the following lines and save it as @tt{story.html.pmd}:
@racketmod[#:file "story.html.pmd" pollen
@section{Markup files}
Pollen + Markdown
-----------------
+ You **wanted** it — you #,(racketfont "_got_") it.
+ [search for Racket](https://google.com/search?q=racket)
]
As before, go to the @link["http://localhost:8080/index.ptree"]{dashboard} for the project server. This time, click the link for @link["http://localhost:8080/story.html"]{@tt{story.html}}. You'll see something like this:
@nested[#:style 'code-inset]{
@bold{@larger{Pollen + Markdown}}
@nested[#:style 'inset]{@itemlist[
@item{You @bold{wanted} it — you @italic{got} it.}
@item{@link["https://google.com/search?q=racket"]{search for Racket}}
]}}
As usual, you're welcome to edit @tt{story.html.pmd} and then refresh the web browser to see the changes.
In Markdown mode, you can still embed Pollen commands within the source as you did in preprocessor mode. Just keep in mind that your commands need to produce valid Markdown (as opposed to raw HTML). For instance, use @tt{define} to create a variable called @tt{metal}, and insert it into the Markdown:
@racketmod[#:file "story.html.pmd" pollen
◊define[metal]{Plutonium}
Pollen + ◊metal
--------
+ You **wanted** ◊metal — you #,(racketfont "_got_") it.
+ [search for ◊metal](https://google.com/search?q=◊metal)
]
@section{The plot thickens}
Refresh @tt{story.html} in the browser:
Start a new Pollen document. Remember to change the top line.
@nested[#:style 'code-inset]{
@bold{@larger{Pollen + Plutonium}}
Underneath, type @code{Hello ◊(+ 1 2) Worlds}. The character before the left parenthesis is called a lozenge. Type it by [doing such and such].
@nested[#:style 'inset]{@itemlist[
Ask yourself: what are you likely to get when you run the file?
@item{You @bold{wanted} Plutonium — you @italic{got} it.}
OK, now run the file.
@item{@link["https://google.com/search?q=Plutonium"]{search for Plutonium}}
]}}
The result will be @code{Hello 3 Worlds}. Hopefully, that's what you expected.
Pollen is handling two tasks here: interpreting the commands in the source, and then converting the Markdown to HTML. But what if you wanted to use Pollen as a preprocessor that outputs a Markdown file? No problem — just change the source name from @tt{story.html.pmd} to @tt{story.md.pp}. Changing the extension from @tt{.pmd} to @tt{.pp} switches Pollen from Markdown mode back to preprocessor mode. And changing the base name from @tt{story.html} to @tt{story.md} updates the name of the output file.
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.
@section{Markup mode}
Type this: @code{
◊(define name "Roxy")
If all you need to do is produce basic HTML, Markdown is great. But if you need to do semantic markup or other kinds of custom markup, it's not flexible enough.
Hello ◊name}.
In that case, you can use Pollen markup mode. To use Pollen markup, insert @tt{#lang pollen} as the first line of your source file, and add a @tt{.pm} file extension.
What do you suppose you'll get this time?
Compared to Markdown mode, Pollen markup mode is wide open. Markdown mode gives you a limited set of formatting tools (i.e., the ones supported by Markdown). But in markup mode, you can use any tags you want. Markdown mode decodes the source in a fixed way (i.e., with the Markdown decoder). But markup mode lets you build any decoder you want.
Run the file. You'll see @code{Hello Roxy}.
Let's convert our Markdown example into Pollen markup. Marking up content is simple: insert the lozenge character (@tt{◊}) followed by the name of the tag (@tt{◊tag}), followed by the content of the tag in curly braces (@tt{◊tag{content}}). In DrRacket, create a new file called @tt{markup.html.pm} as follows:
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}
@racketmod[#:file "markup.html.pm" 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.
◊headline{Pollen markup}
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.
◊items{
◊item{You ◊strong{wanted} it — you ◊em{got} it.}
◊item{◊link["https://google.com/search?q=racket"]{search for Racket}}
}]
Go to the @link["http://localhost:8080/index.ptree"]{project dashboard} and click on @link["http://localhost:8080/markup.html"]{@tt{markup.html}}. You'll see something like this:
@nested[#:style 'code-inset]{
Pollen markup You @bold{wanted} it — you @italic{got} it. https://google.com/search?q=racketsearch for Racket}
That's not right. What happened?
We marked up the source using a combination of standard HTML tags (@tt{strong}, @tt{em}) and nonstandard ones (@tt{headline}, @tt{items}, @tt{item}, @tt{link}). This is valid Pollen markup. (In fact, if you look at @link["http://localhost:8080/out/markup.html"]{the HTML source}, you'll see that they didn't disappear.) But since we're targeting HTML, we'll convert our custom tags into valid HTML tags.
For that, we'll make a special file called @tt{project-require.rkt}. This is a file in the standard Racket language that provides helper functions to decode the source. The definitions won't make sense yet. But this is the quick start, so all you need to do is copy, paste, and save:
@racketmod[#:file "project-require.rkt" racket/base
(require pollen/tag)
(provide (all-defined-out))
(define headline (make-tag-function 'h2))
(define items (make-tag-function 'ul))
(define item (make-tag-function 'li 'p))
(define (link url text) `(a [[href ,url]] ,text))
]
@margin-note{That means Pollen can act as a preprocessor for CSS, JavaScript, XML — and even source files for other programming languages.}
Return to the @link["http://localhost:8080/index.ptree"]{project dashboard} and click on @link["http://localhost:8080/markup.html"]{@tt{markup.html}}. Now you'll get the right result:
@nested[#:style 'code-inset]{
@bold{@larger{Pollen markup}}
@subsection{Making an HTML page with Pollen in decoder mode}
@nested[#:style 'inset]{@itemlist[
@subsection{Using the Pollen dashboard}
@item{You @bold{wanted} it — you @italic{got} it.}
@item{@link["https://google.com/search?q=racket"]{search for Racket}}
]}}
Markup mode takes a little more effort to set up. But it also allows you more flexibility. If you want to do semantic markup, or convert your source into multiple output formats, or handle complex page layouts — it's the way to go.
@section{Templates}
One last stop in the quick tour. The HTML pages we made in Markdown mode and markup mode looked pretty dull. Let's fix that.

@ -1 +1 @@
◊(->html (html (head (meta 'charset: "UTF-8")) (body "Rendered without template:" (hr) doc)))
◊(->html (html (head (meta 'charset: "UTF-8")) (body doc)))

@ -2,25 +2,30 @@
(provide make-tag-function)
(define (make-tag-function id)
(λ x
(define reversed-pieces ; list of attribute pairs, and last element holds a list of everything else, then reversed
(reverse (let chomp ([x x])
(define result+regexp (and ((length x) . >= . 2)
(symbol? (car x))
;; accept strings only
;; numbers are difficult because they don't parse as cleanly.
;; string will read as a string even if there's no space to the left.
(or (string? (cadr x)))
;; Looking for symbol ending with a colon
(regexp-match #rx"^(.*?):$" (symbol->string (car x)))))
(if result+regexp
; reuse result value. cadr is first group in match.
(cons (list (string->symbol (cadr result+regexp))(cadr x)) (chomp (cddr x)))
(list x)))))
(define-values (body attrs) (if (equal? null reversed-pieces)
(values null null)
(values (car reversed-pieces) (cdr reversed-pieces))))
`(,id ,@(if (equal? attrs null) null (list (reverse attrs))) ,@body)))
(define (make-tag-function . ids)
(define (make-one-tag id)
(λ x
(define reversed-pieces ; list of attribute pairs, and last element holds a list of everything else, then reversed
(reverse (let chomp ([x x])
(define result+regexp (and ((length x) . >= . 2)
(symbol? (car x))
;; accept strings only
;; numbers are difficult because they don't parse as cleanly.
;; string will read as a string even if there's no space to the left.
(or (string? (cadr x)))
;; Looking for symbol ending with a colon
(regexp-match #rx"^(.*?):$" (symbol->string (car x)))))
(if result+regexp
; reuse result value. cadr is first group in match.
(cons (list (string->symbol (cadr result+regexp))(cadr x)) (chomp (cddr x)))
(list x)))))
(define-values (body attrs) (if (equal? null reversed-pieces)
(values null null)
(values (car reversed-pieces) (cdr reversed-pieces))))
`(,id ,@(if (equal? attrs null) null (list (reverse attrs))) ,@body)))
(apply compose1 (map make-one-tag ids)))

Loading…
Cancel
Save