Building a Website with Scribble

:: Scribble, tutorial, by Ben Greenman

The source code for the PRL website is written using Scribble, the Racket documentation tool. I am very happy with this choice, and you should be too!

The Story so Far

Last Fall, I took a flight to Chicago (on my way to RacketCon 2016). When I landed, there was a new message in my inbox:

    Subject: Web Page
    Date: 2016-09-15

    You have been nominated webmaster by public acclamation. Congratulations!

Emboldened by the trust of my people, I promptly converted the PRL website from Racket-generating-HTML to the fine scribble/html preprocessor language (commit a0600d) This bold action polarized the community.

I can’t read the source anymore! Is this really an improvement?

Fear not, citizens. The switch to scribble/html was the right choice, and you too can learn to read the source code.

How to Read scribble/html Programs

Basics

Scribble is a language for writing Racket documentation. The key innovation in Scribble is the @-expression (read: “at expression”). The scribble/html language combines @-expression syntax with functions that generate HTML.

@-syntax

Greg Hendershott and the Scribble Documentation explain @-expressions properly. Here’s a short tutorial (Part 1 of 2, “the basics”):

  • Scribble programs start in “text mode”. Every character you type goes straight to the document you are building.
  • The @-sign toggles to “Racket mode” for the next expression. In Racket mode, the characters you type will be evaluated as a Racket program to produce part of the document.

Examples: Evaluating "Hello Dave" puts “Hello Dave” in your document. Evaluating "Hello @Dave" puts “Hello ???” in your document, where "???" is the value of the variable Dave. Finally if Dave is the name of a function, then "Hello @(Dave)" calls the Dave function with zero arguments and puts whatever it returns into your document.

To make it easy to interleave text, function calls, and code, Scribble discriminates between 4 kinds of parentheses when they follow an @-sign (Part 2 of 2, “the parens”):

  • @(f A B) is just like the function call (f A B) in Racket
  • @f[A B] is the same as @(f A B), but typically more useful because …
  • @f[A B]{....} evaluates the .... in “text mode” to a list of words w*, then calls f just like (apply f A B w*)
  • @f{....} evaluates the .... in “text mode” and calls f with the results
  • @f|{....}| is similar, but the .... are in “unescapable text mode”

“Unescapable text mode” treats @-signs as text instead of toggling between modes.

Generating HTML

The scribble/html language comes with functions that render HTML. These functions have the same name as the corresponding HTML tag.

Example program:

1
2
#lang scribble/html
@p{Hello World}

Running this program prints:

1
<p>Hello World</p>

No surprises.

One thing that is surprising is how scribble/html handles tag attributes. Every tag-rendering function accepts “Racket mode” arguments that specify an attribute name and attribute value.

For example:

1
2
#lang scribble/html
@p[style: "color:red"]{Hello World}

Prints:

1
<p style="color:red">Hello World</p>

Hope the output looks familiar. The input syntax is strange, but that’s what it is.

Larger programs print larger webpages. Each page on the PRL website is HTML generated by one scribble/html program.

Why scribble/html is an Improvement

Before scribble/html, the PRL website was implemented in scribble/text. A scribble/text program renders and prints text. There is no extra support for HTML.

To compare, here’s the start of the old homepage:

1
2
3
4
5
6
7
8
9
#lang scribble/text
@(require "templates.rkt")

<!DOCTYPE html>
<html lang="en">
  @(header "Home")
  <body id="pn-top">
    @(navbar "Home")
    <div class="jumbotron">

And here is the start of the scribble/html’d homepage:

1
2
3
4
5
6
7
8
9
#lang scribble/html
@require["templates.rkt"]

@doctype{html}
@html[lang: "en"]{
  @header{Home}
    @body[id: "pn-top"]{
      @navbar{Home}
      @div[class: "jumbotron"]{

The pages look similar. The new one has more @-signs and parentheses, the old one has more <-signs and quotes. If you were able to edit the old page, you should be able to edit the new page.

The key improvement in the new page is that common mistakes are now compile-time errors.

  • Before, a typo like <hmtl> would generate an ugly webpage. After, a typo like @hmtl is a syntax error.

  • Before, a typo like <b>.... with no closing tag would generate an ugly webpage. After, a typo like @b{.... is a syntax error.

Both flavors of error message come with source-code line numbers. This is very very helpful.

Small Improvements

1. More Functions

Before, the Teaching page contained some interesting HTML for rendering vertical text (look for the word “Semantics” to see how this was used):

1
<span class="how-to-design-programs">S<br />e<br />m<br />a<br />n<br />t<br />i<br />c<br />s<br /><br /></span>

After, the same text is generated from a function call:

1
@span[class: "how-to-design-programs"]{@vertical-text{Semantics}}

The vertical-text function is simple:

1
2
3
4
@require[(only-in racket/list add-between)]

@(define (vertical-text . str*)
   (add-between (string->list (append* str*)) (br)))

2. More Structure, Less Boilerplate

Here’s part of the old definition of “Ben Greenman” on the People page:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="row pn-person">
  <div class="col-md-12 pn-row-eq-height">
    <div class="col-md-3 pn-photo">
      <div class="img-wrapper">
        <img src="img/ben_greenman.jpg" title="Ben Greenman" alt="Ben Greenman" />
      </div>
    </div>
    <div class="col-md-9">
      <div class="col-md-4 pn-contact">
        <span class="pn-name">Ben Greenman</span><br />
        Advisor: Matthias Felleisen<br />
        <a href="mailto:types@"@"ccs.neu.edu">types@"@"ccs.neu.edu</a><br />
        <a href="http://www.ccs.neu.edu/home/types">www.ccs.neu.edu/home/types</a>
      </div>
      <div class="col-md-3 pn-muted col-md-offset-5">
        Joined 2014
      </div>
      <div class="col-md-12 pn-bio">
        <p>I like constructions .... </p>
      </div>
    </div>
  </div>
</div>

The new definition uses a helper function with keyword arguments for each “field” of the person:

1
2
3
4
5
6
7
8
@person[#:name "Ben Greenman"
        #:title "Advisor: Matthias Felleisen"
        #:e-mail "types@ccs.neu.edu"
        #:website "http://ccs.neu.edu/home/types"
        #:history @list["Joined 2014"]
        #:img "ben_greenman.jpg"]{
  I like constructions ....
}

3. Less String-Formatting

Before, the code did a lot of string formatting (link):

1
2
(define (link url body)
  (string-append "<a href=\"" url "\">" body "</a>"))

The new code has no need for such helper functions.

1
@a[href: url body]

Bottom Line

Scribble is a good language for making static HTML pages.


If you liked this post, you may also be interested in: