-
Notifications
You must be signed in to change notification settings - Fork 3
The OKWS publishing system is a way for programmers to separate their C++ code and their HTML, and to keep the HTML on the file system where it's most convenient to manage. There are two sides to this system, then:
- C++ bindings - in your OKWS services, you'll use these to read HTML templates off the file system, parse them, and publish them.
- The templates themselves - OKWS templates look a lot like HTML, JS, CSS, or whatever else you're trying to publish, but with some important differences and enhancements.
The first half of this document covers the template language (its enhancements), and the second half covers the C++ bindings.
All the text in your pub documents can be classified into two regions. We say those regions are in HTML Mode and Pub Mode. HTML mode is where you find normal HTML and variable substitution. Pub mode is where you find all advanced pub logic.
Pub Mode is activated with the {% .. %}
closing markers. For example:
This is some normal text
{%
include("somefile1.html")
include("somefile2.html")
%}
Inside pub mode, some commands -- covered later in detail -- switch back into HTML mode, using the double curly brace markers.
{%
if(x==1) {{ This is some normal text }}
include("somefile.html")
%}
The HTML mode inside the double curly braces is just as powerful as the top-level HTML mode. So it, too, can switch back into Pub Mode, with yet another {% .. %}
region. You can nest arbitrarily deep:
{%
if(x==1) {{
This text will be output if x == 1.
{% if (y==1) {{ This text will be output if x == 1 and y == 1. }} %}
}}
include("somefile.html")
%}
Inside HTML Mode,
%{X}
is interpreted as a variable that should be resolved at runtime. A practical example:
Welcome home, %{screenname}. You're %{age} years old.
Inside Pub mode, variables should not be wrapped in %{}
.
{%
if (age > 18) {{ You're an adult because you're %{age} years old. }}
%}
Variables can be set either in the C++ side of things -- passed along in calls to pub -- or elsewhere in the HTML templates, which we'll get to in a little while.
In earlier versions of pub, a template var could only be resolved to a string, an integer, a float, or NULL (when unset). However, in pub3 a var may be any of those things, or an array of other vars, or a dictionary of key-value pairs, where the key is a string, and the value is another var. Those familiar with JavaScript Object Notation or Python should be at home with this kind of object representation:
user : {
name : "maxwell",
age : 22,
emails : ["[email protected]", "[email protected]"],
pi : 3.14
}
In pub3 %{user}
might very well be an object such as the one above. If you try to print %{user}
in your HTML, Pub will output object notation as exampled above. However, most likely you'll want to use the components, like so:
Hi, %{user.name}, welcome back.
Your primary email address is %{user.emails[0]}.
On your world, pi is ${user.pi}.
With associative arrays, you can use either dot notation or array notation:
Hi, %{user["name"]}, a.k.a., %{user.name}.
Later in this doc we'll cover looping (iterating) over arrays.
Templates can include each other much like the old Server-Side Includes (SSIs) of yore. The syntax is:
{% include (<filename> [, dictionary assignments ]) %}
Here's a simple example:
{% include("header.html") %}
And here's an example that sets a var in the included file:
{% include("header.html", { bodystyle : "old-fashioned" }) %}
Inside header.html, we might see something like this:
<body class="%{bodystyle}">
What's going on here? The include
command asks the runtime system to suck in the file subfile.html
(in the same directory as the current template) and while so doing, to substitute all instances of %{bodystyle}
for ``old-fashioned'' in the included template. Of course, templates can be nested, and there are checks at runtime to make sure there are no circular inclusions. Note it's possible to have assignments of the form:
{% include("subfile.html", { "X" : "some%{foo}bar" }) %}
That is, the value half of a name-value pair can have interesting resolutions in it too. Also useful, the filename of the include can also be dynamic:
{% include("subfile.%{LANG}.html", { "X" : "some%{foo}bar" }) %}
This is useful, for instance, when displaying pages in different languages based on user preferences, etc.
Pub supports the if
statement, which expects a series of conditionals and then output. It's best thought of as a series of if, else, else, etc. statements:
{% if (cond1) {{ output1 }}
elif (cond2) {{ output2 }}
elif (cond3) {{ output3 }}
...etc.
else {{ output4 }}
%}
As soon as a condition is met, the appropriate pub code is executed, and the if
statement ends. Here's an example:
{% if (user.age < 18) {{ Major burns, minor! }}
elif (user.age < 22) {{ Welcome to college. }}
elif (user.age < 30) {{ Get a job! }}
elif (user.age > 65) {{ Relax... }}
else {{ You're working for the man. }} %}
A default case (collected with ''true'' above) is not necessary.
Boolean logic is supported in conditionals:
{% if (user.age < 18 && user.gender == "female") {{ Jailbait! }} %}
Boolean operators can be strung together, and order of operation can be controlled through parentheses:
{% if ( (a < 12 && b >= 10) || c == "foo") {{ Awesome. }} %}
Vars that are NULL (i.e., they haven't been set or failed a lookup) will fail comparisons and make messy warnings. You can test whether a variable is null without generating a warning:
{% if (isnull(A)) {{ "A" is not set. }} %}
Double curly braces switch you back to HTML mode:
{% if (is_logged_in)
{{ You are logged in!!! }} %}
Single curly braces keep you in Pub Mode:
{% if (is_logged_in) {
include("logged_in_header.html")
} %}
Of course you can still switch modes inside either:
{% if (is_logged_in)
{{ You are logged in!!!
{% include ("logged_in_header.html") %}
}} %}
You can update or create a var within a template with the ''globals'' construct.
{% globals { <assignment1> [,<assignment2>, <assignment3>,... } %}
For example, to set a site_url
var:
{% globals { site_url : "http://www.okcupid.com" } %}
This sets two different vars, age
and screenname
, in one call:
{% globals { age : 10, screenname : "Dr. Who" } %}
And, since variables can be much more complex than just floats, integers, and strings, you might be wondering how to set a big object. It looks like like the object notation at the top of this page, with a set around it:
{% globals {
user : {
name : "maxwell",
age : 22,
emails : ["[email protected]", "[email protected]"],
pi : 3.14
}
%}
You can make right-hand assignments that are objects, too.
{% globals {
old-fashioned : {
color : "cornsilk",
font-size : "2.0em"
}
globals {
user : {
name : "maxwell",
age : 22,
template : old-fashioned
}
%}
Strings can be expanded at run-time:
{% globals {
user : {
name : "Maxwell",
usa_address : "%{street}\n%{city}, %{state} %{zip}"
}
%}
The most powerful addition to pub is the shift from simple scalar vars to full-blown objects. An OKWS service can provide a complicated object or array of objects as a var, and pub can handle it nicely. For example, let's say a var %{buddies}
has been populated with the following data, in object notation:
buddies : [
{name : "Adam", age : "20", gender : "m" },
{name : "Bill", age : "24", gender : "m" },
{name : "Caty", age : "25", gender : "f" },
{name : "Debb", age : "26", gender : "f" }
]
We can print a nicely formatted list of them like so:
{% for (buddy, buddies) {{
<li>%{buddy.name} is online.
{% if (buddy.gender == "m") {{ <a href="#">Send him a message</a> }}
else {{ <a href="#">Send her a message</a> }}
%}
</li>
}}
%}
Don't forget you can include another HTML file inside this loop. You can also pass vars to it, even ones that are objects and local to the loop:
{% for (buddy, buddies) {
include("person_display.html", { person_to_display : buddy } )
}
%}
When you set a var, by default that variable is scoped globally
. The key here is that a parent document will get the variable too, as well as any brother or cousin documents, after the set happens.
For example, imagine a parent file:
{%
globals { x : 10 }
include ("child.html")
print "2:%{x}"
include ("brother.html")
%}
And the child file ("child.html") is:
```python
{%
globals { x : 20 }
include ("grandchild.html")
%}
And the brother file ("brother.html") is:
{%
print "3:%{x}"
%}
And the grandchild file ("grandchild.html") is:
{%
print "1:%{x}"
%}
This above example will print 1:20 2:20 3:20
, since the setting of x
to 20
in the child document will persisnt into the grandchild, and also persist past the end of the child document, into the parent's scope, and subsequently into any files included by the parent (like "brother.html").
Now image we changed the child file to use locals
rather than globals
. Then, the effects of setting x will be available in the file that does the set and all of its descendants, but won't propagate to parents and brothers. The effect of publishing the parent file would be "1:20 2:10 3:10".
Passing vars inside an include is equivalent to putting a locals
statement at the top of the include:
{%
// Locally set age to 20 *inside* foo.html
include("foo.html", {age : 20})
%}
For obvious reasons, assignments inside for
loops are equivalent to locals
and therefore only work inside the loop:
{% for(buddy,buddies) {{
buddy.name
}} %}
<!-- the following will be null -->
%{buddy.name}
Because assignments are global, it works to include files that have a series of global
statements inside them.
{% include("country_facts.html") %}
The center of mass of the USA is %{usa.center_of_mass}.
By convention, you might like to name such a file with the .dict
extension, although that's optional.
To suppress all output from the include -- such as unwanted whitespace (especially important before a doctype
tag in HTML) -- use the load
command, which is identical to include
sans output:
{% load("site_vars.dict" %}<?doctype ... bleah bleha bleah.
It's good style to use load
and .dict
if you know you're simply setting global vars inside an include.
Anything inside triple square brackets in pub is stripped. Gone are the days of HTML comments your users can read.
<div>
[[[ Here's something no one will ever see. ]]]
</div>
Comments inside commands are not currently allowed, but use C-style comments for those:
{% globals { foo1 : "bar",
foo2 : "bar2", /* this is a comment (ERROR!) */
foo3 : "bar3" } %}
Double square brackets are removed, leaving their contents in place. This can be helpful for tagging plain-language content inside HTML for translators to find:
<h1>[[Welcome back, %{screenname}.]]</h1>
Double square brackets inside of a double square brackets are stripped. This feature is used by OkCupid.com in its translation software; the inner comments are shown to translators.
<a href="#">[[ [[ Here "log" means a document, not a felled tree. ]] View the log]]</a>
The above simply outputs:
<a href="#">View the log</a>
{% if (1 < 20) {{ 1 < 20 }}
if (-5 == 1 - 6) {{ -5 == 1 - 6 }} else {{ bad 2}}
if (!(1 >= 20)) {{ ! 1 >= 20 }}
if (20 + 30 > 40) {{ 20 + 30 > 40 }}
if (30 != -30) {{ 30 != -30}} %}
Caveat: due to parsing difficulty, currently len(str)-1
will give you a syntax error, but len(str)- 1
will work, or you can try len(str) - 1
.
Pub3 has support for perl-compatible regular expressions, supporting most fancy features you use. Regular expressions can be specified with either simple string syntax:
{% locals { regex : "a+b?c+" } %}
or special regular-expression syntax if that floats your boat:
{% locals { regex1 : r{a+b?c+}, regex2 : r/a+b?c+/i, regex3 : r#^a+b?c+$#g, regex4 : r[a+b?c+] } %}
and so forth. As in perl, pub3 allows delimiting regular expressions with many symbols. At the end of the day, the characters you use as delimiters don't really matter, they're just referred to in the parsing step. Also as in perl, you can give commands to the regex after the closing delimiter. Once you have a regular expression, you can feed it to the ''match'' or ''search'' functions:
{% if (match (regex, "aabccc")) {{ "should print!" }} %} {% if (match (regex1, "aabcc")) {{ "me too!" }} %}
''match'' and ''search'' are largely equivalent, except ''match'' looks to make sure that the whole string is matched, while ''search'' will be happy to find your regex anywhere in the given string. Of course, no need for variable assignments, you can call match directly with a regular expression:
{% if (match (r/a+b?c+/i, "aabccc")) {{ "should print!" }} %} {% if (search ("a+b?c+", "XaabccY")) {{ "me too!" }} %}
Finally, 'match' and 'search' come in two different prototypes. The first, we've already seen, take two arguments:
- match(/regex/, /text/)
- search(/regex/, /text/)
The other takes three arguments, the middle argument being the /options/ to feed to regular expression matcher:
- match(/regex//, /options/, /text/)
- search(/regex//, /options/, /text/)
All filters are available via the standard syntax:
toupper (html_escape (""))
or via a Django-inspired "filter" syntax:
""|html_escape()|toupper()
or more succinctly:
"<tag>"|html_escape|toupper
If calling with the Django-style filter syntax, then the value coming through the filter is the /this/ parameter, the first argument to the function.
You can call the following functions and filters from Pub templates MK NOTE Need pointer to DOCS
If ever you want to use OKWS to output JSON, don't handroll your own:
if (do_json) {{
{
"my_foo" : %{foo|json},
"my_bar" : %{bar|json},
}
}}
This is what I call "hand-rolling JSON" and there's a much better way in pub:
if (do_json) {
locals { tmp : { my_foo : foo, my_bar : bar } }
print tmp
}
There are also nice tecniques for putting the results of including a file into your JSON object. Instead of this:
if (do_json) {{
{
"file_out" : {% include ("x.html") %}
}
}}
You can do this:
if (do_json) {
locals { file_out : {} }
load ("x.html", { ret : file_out })
print ( { file_out : file_out })
}
Then in x.html, you store whatever values into the local variable 'ret':
ret["dog"] = 10;
ret["cat"] = [1,2,3];
And the output json will give you:
{ "file_out" : { "dog" : 10, "cat" : [1,2,3} }