Syntax

Pushup is a mix of a new syntax consisting of Pushup directives and keywords, Go code, and HTML markup.

How it works

Parsing a .up file always starts out in HTML mode, so you can just put plain HTML in a file and that's a valid Pushup page.

When the parser encounters a '^' character (caret, ASCII 0x5e) while in HTML mode, it switches to parsing Pushup syntax, which consists of simple directives, control flow statements, block delimiters, and Go expressions. It then switches to the Go code parser. Once it detects the end of the directive, statement, or expression, it switches back to HTML mode, and parsing continues in a similar fashion.

Pushup uses the tokenizers from the go/scanner and golang.org/x/net/html packages, so it should be able to handle any valid syntax from either language.

Directives

^import

Use ^import to import a Go package into the current Pushup page. The syntax for ^import is the same as a regular Go import declaration

Example:

^import "strings"
^import "strconv"
^import . "strings"

^layout

Layouts are HTML templates that enclose the contents of a Pushup page.

The ^layout directive instructs Pushup what layout to apply the contents of the current page.

The name of the layout following the directive is the filename in the layouts directory minus the .up extension. For example, ^layout main would try to apply the layout located at app/layouts/main.up.

^layout is optional - if it is not specified, pages automatically get the "default" layout (app/layouts/default.up).

Example:

^layout homepage
^layout ! - no layout

A page may choose to have no layout applied - that is, the contents of the page itself are sent directly to the client with no enclosing template. In this case, use the ! name:

^layout !

Go code blocks

^{

To include statements of Go in a Pushup page, type ^{ followed by your Go code, terminating with a closing }.

The scope of a ^{ ... } in the compiled Go code is equal to its surrounding markup, so you can define a variable and immediately use it:

^{
	name := "world"
}
<h1>Hello, ^name!</h1>

Because the Pushup parser is only looking for a balanced closing }, blocks can be one-liners:

^{ name := "world"; greeting := "Hello" }
<h1>^greeting, ^name!</h1>

A Pushup page can have zero or many ^{ ... } blocks.

^handler

A handler is similar to ^{ ... }. The difference is that there may be at most one handler per page, and it is run prior to any other code or markup on the page.

A handler is the appropriate place to do "controller"-like (in the MVC sense) actions, such as HTTP redirects and errors. In other words, any control flow based on the nature of the request, for example, redirecting after a successful POST to create a new object in a CRUD operation.

Example:

^handler {
    if req.Method == "POST" && formValid(req) {
		if err := createObjectFromForm(req.Form); err == nil {
			return http.Redirect(w, req, "/success/", http.StatusSeeOther)
			return nil
		} else {
			// error handling
			...
	}
	...
}
...

Note that handlers (and all Pushup code) run in a method on a receiver that implements Pushup's Responder interface, which is

interface Responder {
	Respond(http.ResponseWriter, *http.Request) error
}

To exit from a page early in a handler (i.e., prior to any normal content being rendered), return from the method with a nil (for success) or an error (which will in general respond with HTTP 500 to the client).

Control flow statements

^if

^if takes a boolean Go expression and a block to conditionally render.

Example:

^if query := req.FormValue("query"); query != "" {
	<p>Query: ^query</p>
}

^for

^for takes a Go "for" statement condition, clause, or range, and a block, and repeatedly executes the block.

Example:

^for i := 0; i < 10; i++ {
	<p>Number ^i</p>
}

Expressions

Simple expressions

Simple Go expressions can be written with just ^ followed by the expression. "Simple" means:

Example:

^{ name := "Paul" }
<p>Hello, ^name!</p>

Outputs:

<p>Hello, Paul!</p>

Notice that the parser stops on the "!" because it knows it is not part of a Go variable name.

Example:

<p>The URL path: ^req.URL.Path</p>

Outputs:

<p>The URL path: /foo/bar</p>

Example:

^import "strings"
<p>^strings.Repeat("Hello", 3)</p>

Outputs:

<p>HelloHelloHello</p>

Explicit expressions

Explicit expressions are written with ^ and followed by any valid Go expression grouped by parentheses.

Example:

^{ numPeople := 4 }
<p>With ^numPeople people there are ^(numPeople * 2) hands</p>

Outputs:

<p>With 4 people there are 8 hands</p>

Layout and templates

^section

Pushup layouts can have sections within the HTML document that Pushup pages can define with their own content to be rendered into those locations.

For example, a layout could have a sidebar section, and each page can set its own sidebar content.

In a Pushup page, sections are defined with the keyword like so:

^section sidebar {
    <article>
        <h1>This is my sidebar content</h1>
        <p>More to come</p>
    </article>
}

Layouts can output sections with the outputSection function.

<aside>
    ^outputSection("sidebar")
</aside>

Layouts can also make sections optional, by first checking if a page has set a section with sectionDefined(), which returns a boolean.

^if sectionDefined("sidebar") {
    <aside>
        ^outputSection("sidebar")
    </aside>
}

Checking for if a section was set by a page lets a layout designer provide default markup that can be overridden by a page.

^if sectionDefined("title") {
    <title>
        ^outputSection("title")
    </title>
} ^else {
    <title>Welcome to our site</title>
}

^partial

Pushup pages can declare and define inline partials with the ^partial keyword.

...
<section>
    <p>Elements</p>
    ^partial list {
            <ul>
                <li>Ag</li>
                <li>Na</li>
                <li>C</li>
            </ul>
    }
</section>
...

A request to the page containing the initial partial will render normally, as if the block where not wrapped in ^partial list { ... }.

A request to the page with the name of the partial appended to the URL path will respond with just the content scoped by the partial block.

For example, if the page above had the route /elements/, then a request to /elements/list would output:

<ul>
    <li>Ag</li>
    <li>Na</li>
    <li>C</li>
</ul>

Inline partials can nest arbitrarily deep.

...
^partial leagues {
    <p>Leagues</p>
    ^partial teams {
        <p>Teams</p>
        ^partial players {
            <p>Players</p>
        }
    }
}
...

To request a nested partial, make sure the URL path is preceded by each containing partial's name and a forward slash, for example, /sports/leagues/teams/players.