Amps is my new toy project. I’ve been writing it after reading the Crafting Interpreters, an excellent book by the way. Amps is a simple text template engine (under development, of course) developed in C++17.
As a side note, I’m impressed with modern C++ expressiveness. Despite of critics that I’ve read recently, it’s becoming a lot easier than old C++. Features like optional
and variant
give us the power to compose different types in order to create type safe complex structures, almost like dynamically type languages do.
For instance:
amps::user_map ht {
{"name", "My name"},
{"cities", vector{
"Sao Paulo",
"Paris",
"NYC",
"London",
"Lisbon"}},
{"songs", unordered_map{
{"guns and roses", "patience"},
{"aerosmith", "crazy"},
{"led zeppelin", "immigrant song"},
{"pink floyd", "high hopes"}}},
};
Isn’t it somewhat similar to Python?
ht = {
"name": "My name",
"cities": ["Sao Paulo", "Paris"],
"songs": {"aerosmith": "crazy"}}
To make Amps more useful I’ve integrated it to a module for Apache HTTP server. It basically offers dynamic web pages without a real programming language (like PHP, Ruby, Python) behind it. This could be good for small projects, however I’ve not measure the performance yet.
In order to build Apache’s HTTPd I referred to my old post. Then, I wrote a wrapper to connect my C++17 project with an plain C Apache module and nothing else.
$ cat amps_wrapper.cpp
1 #ifdef __cplusplus
2
3 #include "engine.h"
4
5 #include "httpd.h"
6
7 #include <unordered_map>
8 #include <vector>
9 #include <string>
10 #include <cstring>
11
12 using std::vector;
13 using std::string;
14 using std::unordered_map;
15
16 unordered_map<string, string> query_to_map(const char *query)
17 {
18 unordered_map<string, string> result;
19 if (query == nullptr) {
20 return result;
21 }
22
23 char *tmp = strdup(query);
24 for (char *tok = strtok(tmp, "&"); tok != NULL; tok = strtok(NULL, "&")) {
25 char *value = strchr(tok, '=');
26 if (value == nullptr) {
27 continue;
28 }
29
30 result[string(tok, value - tok)] = string(&value[1]);
31 }
32
33 free(tmp);
34 return result;
35 }
36
37 static void get_custom_template(request_rec *r, char **result)
38 {
39 if (r->args == 0) {
40 return;
41 }
42
43 amps::error err;
44 amps::engine engine(err);
45 engine.set_template_directory("/tmp");
46
47 amps::user_map ht {{"user_data", query_to_map(r->args)}};
48
49 // html template is the default, xml returned when content=xml
50 auto content = user.find("content");
51 if (content == user.end() || content->second == "html") {
52 engine.prepare_template("template.tpl");
53 r->content_type = "text/html";
54 }
55 else {
56 engine.prepare_template("template_xml.tpl");
57 r->content_type = "text/xml";
58 }
59 string rendered = engine.render(ht);
60
61 *result = (char*)malloc(sizeof(char) * rendered.size() + 1);
62 strcpy(*result, rendered.c_str());
63 (*result)[rendered.size()] = '\0';
64 }
65 #endif
66
67 extern "C" {
68 #include "amps_wrapper.h"
69
70 void get_template(request_rec *r, char **result)
71 {
72 get_custom_template(r, result);
73 }
74 }
The HTTPd module simply calls the get_template
function.
1 static int get_handler(request_rec *r)
2 {
3 char *result = NULL;
4
5 if (r->header_only || r->args == 0) {
6 return OK;
7 }
8
9 get_template(r, &result);
10
11 /* something bad happened */
12 if (result == NULL) {
13 return DECLINED;
14 }
15
16 ap_rputs(result, r);
17
18 free(result);
19
20 return OK;
21 }
I compile it all with:
$ g++ -std=c++17 -I/home/ziviani/amps/include \
-I/home/ziviani/httpd/www/include \
-fPIC amps_wrapper.cpp -o wrapper.o -c -g3
$ ../bin/apxs -I/home/ziviani/amps/include -c -i mod_cool_framework.c wrapper.o libamps-static.a
And I finally get this (html template):
1 <html>
2 <head>
3 <meta charset="utf-8">
4 <head>
5
6 <body>
7 <h2>Welcome {= user_data["name"] =}<h2>
8 <ul>
9 <li>ALL DATA:<ul>
10 {% for key, value in user_data %}
11 {% if value eq "<null>" %}
12 <li>ops, something wrong here<li>
13 {% else %}
14 <li>{= value =}<li>
15 {% endif %}
16 {% endfor %}
17 <ul>
18 <body>
19 <html>
and this (xml template):
1 <xml>
2 <user_name>{= user_data["name"] =}</user_name>
3 </xml>
Amps is at its initial stage but it’s been very fun to develop. I intend to continue writing about it in the near future.
Thank you for reading it.