Static template generator for C
git clone git://soophie.de/ductape
8
install: build9
cp ctl /usr/local/bin/10
cp src/libctl.h /usr/local/include/10
mkdir -p /usr/local/include/so11
cp src/ctl.h /usr/local/include/so/12
rm ./ctl14
clean:
5
## Usage7
```html8
<h1>Hello, % STRING(name) %!</h1>9
<ul>10
% for (int i = 0; i < len; i++) { %11
<li>Item % NUMBER(i) %</li>12
% } %13
</ul>8
% #include "src/ctl_macro.h" %10
% CTL_TEMPLDEF(11
example,12
char *title;13
) %15
<!DOCTYPE html>16
<html lang="en">17
<head>18
<title>% STRING(ctx->title) %</title>19
</head>20
</html>22
% CTL_ENDTEMPL %23
```25
```bash17
ctl example.html > ctl_example.h26
ctl -h example.html > ctl_example.h27
ctl -o example.html > ctl_example.so28
```30
```c21
#define LIB_CTL_IMPL22
#include <libctl.h>31
#include <so/ctl.h>32
#include "ctl/ctl_example.h"24
#define CTL_OUT(out, x) { \25
str_append(out, x); \26
}27
#define STRING(x) str_append(ctl_out, x);28
#define NUMBER(x) { \29
str_appendf(ctl_out, "%d", x); \34
#define CTL_DL_PATH($name) "./ctl_" #$name ".so"36
char *__out = NULL;38
void __ctl_write(const char *str) {39
str_append(&__out, (char *) str);40
}32
void ctl_fn_example(char **ctl_out, ctl_ctx_t ctx) {33
(void) ctx;34
char *name = "world";35
int len = 10;36
#include "ctl_example.h"42
ctl_t __ctl = {43
.write = __ctl_write,44
};46
HTTP_GET("/example", example_page)47
example_t example = {48
.title = title,49
};50
return ctl_serve(&example, example_render, CTL_DL_PATH(example));51
}52
```54
```c55
#define STRING(x) ctl->write(x);56
```
1
#ifndef SO_CTL_H2
#define SO_CTL_H4
#include <stdio.h>6
typedef void (*ctl_write_fn_t)(const char *);8
typedef struct {9
ctl_write_fn_t write;10
} ctl_t;12
typedef int (*ctl_render_fn_t)(ctl_t *, void *, const char *);14
typedef struct {15
ctl_render_fn_t fn;16
void *data;17
const char *dl_path;18
} ctl_templref_t;20
#define __CTL_TEMPLDEF_MACRO($name, $params) \21
typedef struct { \22
$params \23
} $name##_t; \24
\25
typedef void (*$name##_fn_t)(ctl_t *, $name##_t *); \26
\27
int $name##_render(ctl_t *ctl, void *ctx, const char *dl_path) { \28
void *handle = dlopen(dl_path, RTLD_NOW); \29
if (!handle) { \30
fprintf(stderr, "%s\n", dlerror()); \31
return 1; \32
} \33
$name##_fn_t fn = ($name##_fn_t) dlsym(handle, "ctl_" #$name); \34
char *err = dlerror(); \35
if (err) { \36
fprintf(stderr, "%s\n", err); \37
dlclose(handle); \38
return 1; \39
} \40
fn(ctl, ($name##_t *) ctx); \41
dlclose(handle); \42
return 0; \43
}45
#define __CTL_TEMPLDEF_HEADER($name, $params) \46
__CTL_TEMPLDEF_MACRO($name, $params) \47
\48
void ctl_##$name(ctl_t *ctl, $name##_t *ctx);50
#define CTL_TEMPLDEF($name, $params) \51
__CTL_TEMPLDEF_MACRO($name, $params) \52
\53
void ctl_##$name(ctl_t *ctl, $name##_t *ctx) {55
#define CTL_ENDTEMPL }57
#define CTL_TEMPLBIND($name, $data, $dl_path) \58
(ctl_templref_t) { \59
.fn = $name##_render, \60
.data = $data, \61
.dl_path = $dl_path, \62
},64
#define CTL_WRITE($str) ctl->write($str);66
#define CTL_INCLUDE($name) ctx->$name.fn(ctl, ctx->$name.data, ctx->$name.dl_path);68
#endif /* SO_CTL_H */
1
#ifndef LIB_CTL_H2
#define LIB_CTL_H4
#include <libhttp.h>5
#include <libstr.h>7
typedef struct ctl_ctx ctl_ctx_t;8
typedef void (*ctl_fn_t)(char **ctl_out, ctl_ctx_t ctx);10
struct ctl_ctx {11
http_req_t *request;12
char *title;13
ctl_fn_t fn;14
void *data;15
};17
void ctl_init(ctl_fn_t fn);18
http_resp_t ctl_serve_page(http_req_t *request, const char *title, ctl_fn_t fn, void *data);19
http_resp_t ctl_serve_component(http_req_t *request, ctl_fn_t fn, void *data);21
#ifdef LIB_CTL_IMPL23
static ctl_fn_t ctl_fn = NULL;25
void ctl_init(ctl_fn_t fn) {26
ctl_fn = fn;27
}29
http_resp_t ctl_serve_page(http_req_t *request, const char *title, ctl_fn_t fn, void *data) {30
char *html = NULL;31
ctl_ctx_t ctx = {32
.request = request,33
.title = title,34
.fn = fn,35
.data = data,36
};37
ctl_fn(&html, ctx);38
http_resp_t response = http_resp_create(HTTP_STATUS_OK);39
http_resp_header(&response, "Content-Type", "text/html");40
http_resp_body(&response, html, strlen(html));41
free(html);42
return response;43
}45
http_resp_t ctl_serve_component(http_req_t *request, ctl_fn_t fn, void *data) {46
char *html = NULL;47
ctl_ctx_t ctx = {48
.request = request,49
.data = data,50
};51
fn(&html, ctx);52
http_resp_t response = http_resp_create(HTTP_STATUS_OK);53
http_resp_header(&response, "Content-Type", "text/html");54
http_resp_body(&response, html, strlen(html));55
free(html);56
return response;57
}59
#endif /* LIB_CTL_IMPL */60
#endif /* LIB_CTL_H */
1
#include <stdio.h>2
#include <stdlib.h>3
#include <string.h>3
#include <getopt.h>5
#define LIB_STR_IMPL6
#include <libstr.h>8
#define CTL_TMP_C_PATH ".ctl_tmp.c"9
#define CTL_TMP_SO_PATH ".ctl_tmp.so"11
typedef enum {12
CTL_ERR_OK,13
CTL_ERR_NO_ARGS,14
CTL_ERR_NO_FILE,15
CTL_ERR_NO_WRITE,16
CTL_ERR_UNCLOSED_CODE,17
CTL_ERR_NO_COMPILE,18
} ctl_error_t;20
typedef enum {21
CTL_MODE_HEADER,22
CTL_MODE_MODULE,23
} ctl_mode_t;25
void ctl_error_str(ctl_error_t err) {26
switch (err) {27
case CTL_ERR_OK:28
break;29
case CTL_ERR_NO_ARGS:20
printf("Usage: ctl <filename>\n");30
fprintf(stderr, "Usage: ctl -h <templ_path> (HEADER), ctl -o <templ_path> (MODULE)\n");31
break;32
case CTL_ERR_NO_FILE:23
printf("Error: No file found!\n");33
fprintf(stderr, "Error: No file found!\n");34
break;35
case CTL_ERR_NO_WRITE:36
fprintf(stderr, "Error: Can't write to file!\n");37
break;38
case CTL_ERR_UNCLOSED_CODE:26
printf("Error: Unclosed code block!\n");39
fprintf(stderr, "Error: Unclosed code block!\n");40
break;41
case CTL_ERR_NO_COMPILE:42
fprintf(stderr, "Error: Failed to compile!\n");43
break;44
}45
}
71
return ret;72
}58
ctl_error_t ctl_render(char **template, char *content) {74
ctl_error_t ctl_render(ctl_mode_t mode, char **template, char *content) {75
int len = strlen(content);76
int set = 0;77
int is_code = 0;62
str_append(template, "{\n");78
int templ = 0;79
str_append(template, "#include <dlfcn.h>\n");80
str_append(template, "#include <so/ctl.h>\n");81
for (int i = 0; i < len; i++) {82
if (content[i] == '%') {83
// ignore escaped %
86
continue;87
}88
if (is_code) {71
str_append_len(template, content + set, i - set);72
str_append(template, "\n");89
for (int j = 0; j < i - set; j++) {90
if (strncmp(content + set + j, "TEMPLDEF", 8) == 0) {91
templ = 1;92
}93
if (strncmp(content + set + j, "ENDTEMPL", 8) == 0) {94
templ = 0;95
}96
}97
if (mode == CTL_MODE_MODULE) {98
str_append_len(template, content + set, i - set);99
str_append(template, "\n");100
}101
if (mode == CTL_MODE_HEADER && templ == 1) {102
templ = 0;103
char *templdef = NULL;104
str_append_len(&templdef, content + set, i - set);105
str_append(&templdef, "\n");106
templdef = str_replace_all(templdef, "CTL_TEMPLDEF", "__CTL_TEMPLDEF_HEADER");107
str_append(template, templdef);108
free(templdef);109
}110
}111
else {112
const int STRING_LIMIT = 4095;
124
rem_len -= hex_len;125
char *str = str_replace_all_len(content + set + idx, "%%", "%", hex_len);126
char *hex = str_hex_len(str, strlen(str));90
str_append(template, "CTL_OUT(ctl_out, \"");91
str_append(template, hex);92
str_append(template, "\");\n");127
if (mode == CTL_MODE_MODULE && templ == 1) {128
str_append(template, "ctl->write(\"");129
str_append(template, hex);130
str_append(template, "\");\n");131
}132
free(str);133
free(hex);134
idx += hex_len;
157
rem_len -= hex_len;158
char *str = str_replace_all_len(content + set + idx, "%%", "%", hex_len);159
char *hex = str_hex_len(str, strlen(str));121
str_append(template, "CTL_OUT(ctl_out, \"");122
str_append(template, hex);123
str_append(template, "\");\n");160
if (mode == CTL_MODE_MODULE && templ == 1) {161
str_append(template, "ctl->write(\"");162
str_append(template, hex);163
str_append(template, "\");\n");164
}165
free(str);166
free(hex);167
idx += hex_len;168
}169
}129
str_append(template, "}\n");170
*template = str_replace_all(*template, "%%", "%");171
return CTL_ERR_OK;172
}
186
return CTL_ERR_OK;187
}189
ctl_error_t ctl_file_read_binary(char **binary, size_t *len, const char *filename) {190
FILE *fp = fopen(filename, "rb");191
if (fp == NULL) {192
return CTL_ERR_NO_FILE;193
}194
fseek(fp, 0, SEEK_END);195
*len = ftell(fp);196
fseek(fp, 0, SEEK_SET);197
*binary = calloc(*len, sizeof(char));198
fread(*binary, sizeof(char), *len, fp);199
fclose(fp);200
return CTL_ERR_OK;201
}203
ctl_error_t ctl_file_write(const char *content, const char *filename) {204
FILE *fp = fopen(filename, "w");205
if (fp == NULL) {206
return CTL_ERR_NO_FILE;207
}208
if (fwrite(content, sizeof(char), strlen(content), fp) < 0) {209
return CTL_ERR_NO_WRITE;210
}211
fclose(fp);212
return CTL_ERR_OK;213
}215
int main(int argc, char **argv) {216
ctl_error_t err;151
if (argc < 2) {217
opterr = 0;218
ctl_mode_t mode;219
char *path = NULL;220
int opt;221
while ((opt = getopt(argc, argv, "h:o:")) != -1) {222
switch (opt) {223
case 'h':224
mode = CTL_MODE_HEADER;225
path = optarg;226
break;227
case 'o':228
mode = CTL_MODE_MODULE;229
path = optarg;230
break;231
default:232
err = CTL_ERR_NO_ARGS;233
ctl_error_str(err);234
return err;235
}236
}237
if (path == NULL) {238
err = CTL_ERR_NO_ARGS;239
ctl_error_str(err);240
return err;241
}156
char *filename = argv[1];242
char *content = NULL;158
err = ctl_file_read(&content, filename);243
err = ctl_file_read(&content, path);244
if (err != CTL_ERR_OK) {245
ctl_error_str(err);246
return err;247
}248
char *template = NULL;164
err = ctl_render(&template, content);249
err = ctl_render(mode, &template, content);250
free(content);251
if (err != CTL_ERR_OK) {166
ctl_error_str(err);167
free(content);252
if (template != NULL) {253
free(template);254
}255
ctl_error_str(err);256
return err;257
}173
printf("%s\n", template);174
free(template);175
free(content);176
return CTL_ERR_OK;258
if (mode == CTL_MODE_HEADER) {259
fprintf(stdout, "%s", template);260
free(template);261
return CTL_ERR_OK;262
}263
if (mode == CTL_MODE_MODULE) {264
err = ctl_file_write(template, CTL_TMP_C_PATH);265
free(template);266
char cmd[500];267
snprintf(cmd, sizeof(cmd), "cc -shared -fPIC -o %s %s", CTL_TMP_SO_PATH, CTL_TMP_C_PATH);268
int rc = system(cmd);269
remove(CTL_TMP_C_PATH);270
if (rc != 0) {271
err = CTL_ERR_NO_COMPILE;272
ctl_error_str(err);273
return err;274
}275
char *binary = NULL;276
size_t len = 0;277
err = ctl_file_read_binary(&binary, &len, CTL_TMP_SO_PATH);278
remove(CTL_TMP_SO_PATH);279
if (err != CTL_ERR_OK) {280
ctl_error_str(err);281
return err;282
}283
fwrite(binary, sizeof(char), len, stdout);284
free(binary);285
return CTL_ERR_OK;286
}287
}