Skip to content

Commit

Permalink
Merge pull request #56 from valum-framework/ctpl-as-view
Browse files Browse the repository at this point in the history
Fully integrate CTPL as a templating engine
  • Loading branch information
arteymix committed Apr 28, 2015
2 parents 9ab79b1 + a2a4708 commit 51e71c7
Show file tree
Hide file tree
Showing 18 changed files with 1,018 additions and 266 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ compiler:
before_install:
- sudo add-apt-repository --yes ppa:vala-team
- sudo apt-get update --quiet
- sudo apt-get install --yes valac-0.26 libglib2.0-dev libsoup2.4-dev
libgee-0.8-dev libfcgi-dev libmemcached-dev libluajit-5.1-dev
- sudo apt-get install --yes --force-yes valac-0.26 libglib2.0-bin
libglib2.0-dev libsoup2.4-dev libgee-0.8-dev libfcgi-dev libmemcached-dev
libluajit-5.1-dev
libctpl-dev python-pip
- sudo pip install mkdocs

Expand Down
80 changes: 80 additions & 0 deletions docs/ctpl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
Valum provides
[CTPL](http://ctpl.tuxfamily.org/doc/unstable/ctpl-CtplEnviron.html) as a view
engine.

Three primitive types and one composite type are supported:

- `int`
- `float`
- `string`
- `array` of preceeding types (but not of `array`)

Creating views
--------------

The `View` class provides constructors to create views from `string`, file path
and `InputStream`.

```java
var template = new View.from_string ("{a}");
```

```java
var template = new View.from_path ("path/to/your/template.tpl");
```

It is a good practice to bundle static data in the executable using
[GLib.Resource](http://valadoc.org/#!api=gio-2.0/GLib.Resource).
```java
var template = new View.from_stream (resources_open_stream ("/your/template.tpl"));
```

Environment
-----------

A `View` instance provides an `Ctpl.Environ` environment from which you can push
and pop variables. CTPL environment operations are fully documented at
[ctpl.tuxfamily.org](http://ctpl.tuxfamily.org/doc/unstable/ctpl-CtplEnviron.html).

```java
var template = new View.from_string ("{a}");

template.environment.push_int ("a", 1);
```

Valum provides helpers for dumping `GLib.HashTable`, `Gee.Collection`, `Gee.Map`
and `Gee.MultiMap` as well as array of `double`, `long` and `string`.

```
double[] dbs = {8.2, 12.3, 2};
template.push_string ("key", "value");
template.push_doubles ("key", dbs);
```

`HashTable`, `Map` and `MultiMap` are pushed by pushing all their entries
ony-by-one. Generated environment keys are the simple concatenation of the
providen key, a underscore (`_`) and the entry key.

```java
var map = new HashMap<string, string> ();

map["key"] = "value";
map["key2"] = "value2";

template.push_map ("map", map); // map_key and map_key2 will be pushed
```

Streaming views
---------------

The best way of rendering a view is by streaming it directly into a `Response`
instance with the `splice` function. This way, your application can produce very
big output efficiently.

```java
app.get ("", (req, res) => {
var template = new View.from_string ("");
template.splice (res);
});
```
6 changes: 3 additions & 3 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ You can easily install the dependencies from a package manager.
### Debian and Ubuntu

```bash
apt-get install git-core build-essential python valac libglib2.0-dev \
libsoup2.4-dev libgee-0.8-dev libfcgi-dev memcached \
libmemcached-dev libluajit-5.1-dev libctpl-dev
apt-get install git-core build-essential python valac libglib2.0-bin \
libglib2.0-dev libsoup2.4-dev libgee-0.8-dev libfcgi-dev \
memcached libmemcached-dev libluajit-5.1-dev libctpl-dev
```


Expand Down
60 changes: 60 additions & 0 deletions docs/recipes/static-resource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
GLib provides an [resource api](http://valadoc.org/#!api=gio-2.0/GLib.Resource)
for bundling static resources and link them in the executable.

An efficient approach to serve static content with Valum is to link and stream.

It has a few advantages:

- content is compiled with the executable or bundled separately, so it loads
lightning fast
- resource api is simpler than file api
- application do not have to deal with resource location or minimally if a
separate bundle is used

This only applies to small and static resources as it will grow the size of the
executable. If you have to change your resources at runtime, it will require an
additionnal compilation step.

# Integration

Let's say your project has a few resources:

* CTPL templates in a `templates` folder
* CSS, JavaScript files in `static` folder

Setup a `app.gresource.xml` file that defines what resources will to be bundled.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource>
<file>templates/home.html</file>
<file>templates/404.html</file>
<file>static/css/bootstrap.min.css</file>
</gresource>
</gresources>
```

You can test your setup with:

```bash
glib-compile-resource app.gresource.xml
```

Latest version of `waf` automatically link `*.gresource.xml` if you load the
`glib2` plugin and add the file to your sources.

```python
bld.load('glib2')

bld.program(
packages = ['glib-2.0', 'libsoup-2.4', 'gee-0.8', 'ctpl', 'lua', 'libmemcached'],
target = 'app',
use = 'valum',
source = bld.path.ant_glob('**/*.vala') + ['app.gresource.xml'],
uselib = ['GLIB', 'CTPL', 'GEE', 'SOUP', 'LUA', 'MEMCACHED'],
vapi_dirs = ['../../vapi', 'vapi'])
```

The sample application example serve its static resources this way if you need
a more concrete example.
11 changes: 11 additions & 0 deletions examples/app/app.gresource.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource>
<file>templates/home.html</file>
<file>templates/404.html</file>
<file>script/lua.vala</file>
<file>haml.lua</file>
<file>static/css/bootstrap.min.css</file>
<file>static/img/valum.png</file>
</gresource>
</gresources>
114 changes: 51 additions & 63 deletions examples/app/app.vala
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ app.handle.connect_after ((req, res) => {

// default route
app.get("", (req, res) => {
var template = new View.Tpl.from_path("examples/app/templates/home.html");
var template = new View.from_stream(resources_open_stream ("/templates/home.html", ResourceLookupFlags.NONE));

template.vars["path"] = req.uri.get_path ();
template.vars["headers"] = req.headers;
template.environment.push_string ("path", req.uri.get_path ());
req.headers.foreach ((k, v) => {
template.environment.push_string ("headers_%s".printf(k), v);
});

template.stream (res);
template.splice (res);
});

app.get ("query", (req, res) => {
Expand Down Expand Up @@ -98,28 +100,27 @@ app.scope("urlencoded-data", (inner) => {
var writer = new DataOutputStream(res);
writer.put_string(
"""
<!DOCTYPE html>
<html>
<body>
<form method="post">
<textarea name="data"></textarea>
<button type="submit">submit</button>
</form>
</body>
</html>
"""
);
<!DOCTYPE html>
<html>
<body>
<form method="post">
<textarea name="data"></textarea>
<button type="submit">submit</button>
</form>
</body>
</html>
""");
});

inner.post("", (req, res) => {
var writer = new DataOutputStream (res);
var data = new MemoryOutputStream (null, realloc, free);

/*
var data = Soup.Form.decode ((string) req.body.data);
data.splice (req, OutputStreamSpliceFlags.CLOSE_SOURCE);

data.foreach((key, value) => {
res.append ("%s: %s".printf(key, value));
Soup.Form.decode ((string) data.get_data ()).foreach ((k, v) => {
writer.put_string ("%s: %s".printf (k, v));
});
*/
});
});

Expand Down Expand Up @@ -152,35 +153,22 @@ app.get("lua.haml", (req, res) => {

// Ctpl template rendering
app.get("ctpl/<foo>/<bar>", (req, res) => {

var tpl = new View.Tpl.from_string("""
<p> hello {foo} </p>
<p> hello {bar} </p>
var tpl = new View.from_string("""
<p>hello {foo}</p>
<p>hello {bar}</p>
<ul>
{ for el in arr }
<li> { el } </li>
{ end }
{for el in strings}
<li>{el}</li>
{end}
</ul>
""");

var arr = new Gee.ArrayList<Value?>();
arr.add("omg");
arr.add("typed hell");

tpl.vars["foo"] = req.params["foo"];
tpl.vars["bar"] = req.params["bar"];
tpl.vars["arr"] = arr;
tpl.vars["int"] = 1;

tpl.stream (res);
});

// streamed Ctpl template
app.get("ctpl/streamed", (req, res) => {

var tpl = new View.Tpl.from_path("examples/app/templates/home.html");
tpl.push_string ("foo", req.params["foo"]);
tpl.push_string ("bar", req.params["bar"]);
tpl.push_strings ("strings", {"a", "b", "c"});
tpl.push_int ("int", 1);

tpl.stream(res);
tpl.splice (res);
});

// memcached
Expand Down Expand Up @@ -211,15 +199,15 @@ app.get("memcached/set/<key>/<value>", (req, res) => {
app.scope("admin", (adm) => {
adm.scope("fun", (fun) => {
fun.get("hack", (req, res) => {
var time = new DateTime.now_utc();
var writer = new DataOutputStream(res);
res.headers.set_content_type ("text/plain", null);
writer.put_string("It's %s around here!\n".printf(time.format("%H:%M")));
var time = new DateTime.now_utc();
var writer = new DataOutputStream(res);
res.headers.set_content_type ("text/plain", null);
writer.put_string("It's %s around here!\n".printf(time.format("%H:%M")));
});
fun.get("heck", (req, res) => {
var writer = new DataOutputStream(res);
res.headers.set_content_type ("text/plain", null);
writer.put_string("Wuzzup!");
var writer = new DataOutputStream(res);
res.headers.set_content_type ("text/plain", null);
writer.put_string("Wuzzup!");
});
});
});
Expand All @@ -230,23 +218,25 @@ app.get("static/<path:resource>.<any:type>", (req, res) => {
var resource = req.params["resource"];
var type = req.params["type"];
var contents = new uint8[128];
var path = "/static/%s.%s".printf (resource, type);
bool uncertain;

try {
var file = File.new_for_path ("examples/app/static/%s.%s".printf(resource, type));
var lookup = resources_lookup_data (path, ResourceLookupFlags.NONE);

// read 128 bytes for the content-type guess
file.read ().read (contents);
res.headers.set_content_type (ContentType.guess("%s.%s".printf(resource, type), contents, out uncertain), null);
// set the content-type based on a good guess
res.headers.set_content_type (ContentType.guess(path, lookup.get_data (), out uncertain), null);

if (uncertain)
warning ("could not infer content type of file %s.%s with certainty".printf (resource, type));

var file = resources_open_stream (path, ResourceLookupFlags.NONE);

// transfer the file
res.splice (file.read (), OutputStreamSpliceFlags.CLOSE_SOURCE);
} catch (FileError fe) {
res.splice (file, OutputStreamSpliceFlags.CLOSE_SOURCE);
} catch (Error e) {
res.status = 404;
writer.put_string (fe.message);
writer.put_string (e.message);
}
});

Expand Down Expand Up @@ -275,11 +265,9 @@ app.matcher (VSGI.Request.GET, (req) => { return req.uri.get_path () == "/custom

app.handle.connect_after ((req, res) => {
if (res.status == 404) {
var template = new View.Tpl.from_path("examples/app/templates/404.html");

template.vars["path"] = req.uri.get_path ();

template.stream (res);
var template = new View.from_stream (resources_open_stream ("/templates/404.html", ResourceLookupFlags.NONE));
template.environment.push_string ("path", req.uri.get_path ());
template.splice (res);
}
});

Expand Down
Loading

0 comments on commit 51e71c7

Please sign in to comment.