#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "template.h" /*----------------------------------------------------------------------------- * Memory utils */ static char * xstrdup(const char *str) { char *s = strdup(str); if (s == NULL) err(1, "libtemplate"); return s; } static void * xcalloc(size_t number, size_t size) { errno = 0; void *ptr = calloc(number, size); if (ptr == NULL) err(1, "libtemplate"); return ptr; } /*----------------------------------------------------------------------------- * JSON utils */ static struct json * json_search(struct json *json, const char *name) { if (json == NULL) return NULL; if (json_is_obj(json)) { struct json *node = json_obj_get(json, name); if (node) return node; } return json_search(json_parent(json), name); } /*----------------------------------------------------------------------------- * Parse utils */ static char * parse_filename(const char *str) { const char *end = str; while (*end != '\0') { if ((*end >= 'a' && *end <= 'z') || (*end >= 'A' && *end <= 'Z') || (*end >= '0' && *end <= '9') || (*end == '_') || (*end == '-') || (*end == '.') || (*end == ':') || (*end == '/') || (*end == '\\')) { end++; continue; } break; } if (end == str) return NULL; char *filename = xcalloc(end - str + 1, sizeof(char)); strncpy(filename, str, end - str); return filename; } static char * parse_var(const char *str) { const char *end = str; while (*end != '\0') { if ((*end >= 'a' && *end <= 'z') || (*end >= 'A' && *end <= 'Z') || (*end >= '0' && *end <= '9') || (*end == '-') || (*end == '_')) { end++; continue; } break; } if (end == str) return NULL; char *var = xcalloc(end - str + 1, sizeof(char)); strncpy(var, str, end - str); return var; } /*----------------------------------------------------------------------------- * Buffer */ #define MAX(a,b) (a) < (b) ? (b) : (a) struct buffer { size_t cap; size_t size; char *data; }; static void buffer_init(struct buffer *buffer) { buffer->cap = 0; buffer->size = 0; buffer->data = NULL; } static void buffer_reset(struct buffer *buffer) { buffer->size = 0; if (buffer->data) *buffer->data = '\0'; } static void buffer_append(struct buffer *buffer, const char *buf, size_t size) { if (buffer->cap - buffer->size <= size) { buffer->cap += MAX(BUFSIZ, size + 1 - (buffer->cap - buffer->size)); buffer->data = realloc(buffer->data, buffer->cap); if (buffer->data == NULL) err(1, "buffer_append(): realloc() error"); } memcpy(buffer->data + buffer->size, buf, size); buffer->size += size; buffer->data[buffer->size] = '\0'; } static void buffer_append_str(struct buffer *buffer, const char *str) { buffer_append(buffer, str, strlen(str)); } static void buffer_append_sprintf(struct buffer *buffer, const char *format, ...) { va_list argp; char *str = NULL; int res; va_start(argp, format); res = vasprintf(&str, format, argp); if (res == -1) err(1, "libtemplate"); va_end(argp); buffer_append_str(buffer, str); free(str); } /*----------------------------------------------------------------------------- * File */ static char * slurp_file(const char *filename) { struct buffer buffer; buffer_init(&buffer); FILE *file = fopen(filename, "r"); if (file == NULL) return NULL; char buf[BUFSIZ]; size_t nread; while ((nread = fread(buf, sizeof(char), BUFSIZ, file)) > 0) buffer_append(&buffer, buf, nread); fclose(file); return buffer.data; } /*----------------------------------------------------------------------------- * Tokens */ enum token_type { TOKEN_TEXT, TOKEN_VAR, TOKEN_COMMENT, TOKEN_SECTION, TOKEN_INVERTED_SECTION, TOKEN_PARTIAL }; struct token { enum token_type type; union { /* TEXT && COMMENT */ struct { const char *start; const char *end; } text; /* VAR */ struct { char *name; char *formatter; } var; /* SECTION / INVERTED SECTION */ struct { char *name; STAILQ_HEAD(tokens, token) tokens; } section; /* PARTIAL */ struct { struct template *tpl; } partial; } value; STAILQ_ENTRY(token) next; }; static struct token * token_new(enum token_type type) { struct token *token = xcalloc(1, sizeof(struct token)); token->type = type; return token; } static void token_free(struct token *token) { struct token *t; if (token == NULL) return; switch(token->type) { case TOKEN_TEXT: case TOKEN_COMMENT: break; case TOKEN_VAR: free(token->value.var.name); free(token->value.var.formatter); break; case TOKEN_SECTION: case TOKEN_INVERTED_SECTION: while (! STAILQ_EMPTY(&token->value.section.tokens)) { t = STAILQ_FIRST(&token->value.section.tokens); STAILQ_REMOVE_HEAD(&token->value.section.tokens, next); token_free(t); } free(token->value.section.name); break; case TOKEN_PARTIAL: template_free(token->value.partial.tpl); break; } free(token); } /*----------------------------------------------------------------------------- * Positions */ struct position { size_t line; size_t col; const char *str; STAILQ_ENTRY(position) next; }; STAILQ_HEAD(positions, position); struct position_stack { struct position *cur; /* current position */ struct positions saved; /* saved positions */ }; static struct position * position_new(void) { struct position *position = xcalloc(1, sizeof(struct position)); position->line = 1; position->col = 1; position->str = NULL; return position; } static struct position * position_clone(struct position *position) { struct position *clone = xcalloc(1, sizeof(struct position)); clone->line = position->line; clone->col = position->col; clone->str = position->str; return clone; } static void position_skip(struct position *position, size_t count) { bool stop = false; while (!stop && count-- > 0) { switch (*position->str) { case '\n': position->line++; position->col = 1; position->str++; break; case '\0': stop = true; break; default: position->col++; position->str++; break; } } } static void positions_push(struct positions *positions, struct position *position) { STAILQ_INSERT_HEAD(positions, position, next); } static struct position * positions_pop(struct positions *positions) { if (STAILQ_EMPTY(positions)) return NULL; struct position *position = STAILQ_FIRST(positions); STAILQ_REMOVE_HEAD(positions, next); return position; } static void positions_free_content(struct positions *positions) { while (! STAILQ_EMPTY(positions)) { struct position *position = STAILQ_FIRST(positions); STAILQ_REMOVE_HEAD(positions, next); free(position); } } /*----------------------------------------------------------------------------- * Positions stack */ struct parser { struct position *pos; /* current position */ struct positions saved_pos; /* saved positions */ }; static void parser_init(struct parser *parser) { parser->pos = position_new(); STAILQ_INIT(&parser->saved_pos); } static void parser_reset(struct parser *parser) { free(parser->pos); positions_free_content(&parser->saved_pos); parser_init(parser); } static void parser_save_position(struct parser *parser) { struct position *position = position_clone(parser->pos); positions_push(&parser->saved_pos, position); } static void parser_drop_position(struct parser *parser) { free(positions_pop(&parser->saved_pos)); } static void parser_rewind_position(struct parser *parser) { struct position *oldpos = positions_pop(&parser->saved_pos); if (oldpos == NULL) errx(1, "can't rewind"); free(parser->pos); parser->pos = oldpos; } void parser_free_content(struct parser *parser) { positions_free_content(&parser->saved_pos); free(parser->pos); } /*----------------------------------------------------------------------------- * Parser consume */ static bool parser_consume(struct parser *parser, const char *str) { if (strncmp(parser->pos->str, str, strlen(str)) != 0) return false; position_skip(parser->pos, strlen(str)); return true; } static void parser_consume_until(struct parser *parser, const char *str) { char *found = strstr(parser->pos->str, str); if (found) position_skip(parser->pos, found - parser->pos->str); else position_skip(parser->pos, strlen(parser->pos->str)); } static int isspace_posix(int c) { return (c == ' ' || c == '\f' || c == '\r' || c == '\n' || c == '\t' || c == '\v'); } static void parser_consume_spaces(struct parser *parser) { while (*parser->pos->str != '\0' && isspace_posix(*parser->pos->str)) position_skip(parser->pos, 1); } static void parser_consume_spaces_to_eol(struct parser *parser) { const char *p = parser->pos->str; while (*p != '\0') { if (*p == '\n') { p++; break; } else if (isspace_posix(*p)) { p++; continue; } else { /* * end of line doesn't contain spaces only, * so keep the input untouched */ return; } } position_skip(parser->pos, p - parser->pos->str); } /*----------------------------------------------------------------------------- * Parser generate tokens */ enum parse_status { PARSE_SUCCEED, PARSE_FAILED, PARSE_ABORT }; static enum parse_status parser_parse_token(struct parser *parser, struct token **token); /* * Parse plain text section */ static enum parse_status parser_parse_text(struct parser *parser, struct token **token) { struct position *pos = parser->pos; if (*pos->str == '\0') return PARSE_FAILED; char *end = strstr(pos->str, "{{"); if (end == NULL || end > pos->str) { *token = token_new(TOKEN_TEXT); (*token)->value.text.start = pos->str; (*token)->value.text.end = end ? end : pos->str + strlen(pos->str); position_skip(parser->pos, (*token)->value.text.end - pos->str); /* discard last carriage return at the end of the template if any */ if (end == NULL && *((*token)->value.text.end - 1) == '\n') (*token)->value.text.end--; return PARSE_SUCCEED; } return PARSE_FAILED; } /* * Parse {{variable}} */ static enum parse_status parser_parse_var(struct parser *parser, struct token **token) { parser_save_position(parser); if (! parser_consume(parser, "{{")) { parser_drop_position(parser); return PARSE_FAILED; } parser_consume_spaces(parser); char *var = parse_var(parser->pos->str); if (var == NULL) { parser_rewind_position(parser); return PARSE_FAILED; } position_skip(parser->pos, strlen(var)); parser_drop_position(parser); *token = token_new(TOKEN_VAR); (*token)->value.var.name = var; parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) { token_free(*token); return PARSE_ABORT; } return PARSE_SUCCEED; } /* * Parse {{f:formatter variable}} */ static enum parse_status parser_parse_var_formatter(struct parser *parser, struct token **token) { if (! parser_consume(parser, "{{f:")) return PARSE_FAILED; char *formatter_name = parse_var(parser->pos->str); if (formatter_name == NULL) return PARSE_ABORT; position_skip(parser->pos, strlen(formatter_name)); parser_consume_spaces(parser); char *var = parse_var(parser->pos->str); if (var == NULL) { free(formatter_name); return PARSE_ABORT; } position_skip(parser->pos, strlen(var)); *token = token_new(TOKEN_VAR); (*token)->value.var.name = var; (*token)->value.var.formatter = formatter_name; parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) { token_free(*token); return PARSE_ABORT; } return PARSE_SUCCEED; } /* * Parse {{& variable}} */ static enum parse_status parser_parse_var_formatter_html(struct parser *parser, struct token **token) { if (! parser_consume(parser, "{{&")) return PARSE_FAILED; parser_consume_spaces(parser); char *var = parse_var(parser->pos->str); if (var == NULL) return PARSE_ABORT; position_skip(parser->pos, strlen(var)); *token = token_new(TOKEN_VAR); (*token)->value.var.name = var; (*token)->value.var.formatter = xstrdup("html"); parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) { token_free(*token); return PARSE_ABORT; } return PARSE_SUCCEED; } /* * Parse {{% variable}} */ static enum parse_status parser_parse_var_formatter_url(struct parser *parser, struct token **token) { if (! parser_consume(parser, "{{%")) return PARSE_FAILED; parser_consume_spaces(parser); char *var = parse_var(parser->pos->str); if (var == NULL) return PARSE_ABORT; position_skip(parser->pos, strlen(var)); *token = token_new(TOKEN_VAR); (*token)->value.var.name = var; (*token)->value.var.formatter = xstrdup("url"); parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) { token_free(*token); return PARSE_ABORT; } return PARSE_SUCCEED; } /* * Parse {{> filename}} */ static enum parse_status parser_parse_partial(struct parser *parser, struct token **token) { if (! parser_consume(parser, "{{>")) return PARSE_FAILED; parser_consume_spaces(parser); char *filename = parse_filename(parser->pos->str); if (filename == NULL) return PARSE_ABORT; position_skip(parser->pos, strlen(filename)); parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) { free(filename); return PARSE_ABORT; } *token = token_new(TOKEN_PARTIAL); (*token)->value.partial.tpl = template_new(); if (! template_parse_file((*token)->value.partial.tpl, filename)) { free(filename); token_free(*token); return PARSE_ABORT; } free(filename); return PARSE_SUCCEED; } /* * Parse {{! this is a comment}} */ static enum parse_status parser_parse_comment(struct parser *parser, struct token **token) { if (! parser_consume(parser, "{{!")) return PARSE_FAILED; const char *start = parser->pos->str; parser_consume_until(parser, "}}"); if (! parser_consume(parser, "}}")) return PARSE_ABORT; const char *end = parser->pos->str - 2; parser_consume_spaces_to_eol(parser); *token = token_new(TOKEN_COMMENT); (*token)->value.text.start = start; (*token)->value.text.end = end; return PARSE_SUCCEED; } /* * Parse block sections: * * {{#block}} * ... * {{/block}} * * or inverted block sections: * * {{^block}} * ... * {{/block}} */ static enum parse_status parser_parse_section(struct parser *parser, struct token **token) { char *var = NULL; enum token_type token_type; parser_save_position(parser); /* parse section open tag */ if (! parser_consume(parser, "{{")) { parser_drop_position(parser); return PARSE_FAILED; } if (*(parser->pos->str) == '#') { token_type = TOKEN_SECTION; position_skip(parser->pos, 1); } else if (*(parser->pos->str) == '^') { token_type = TOKEN_INVERTED_SECTION; position_skip(parser->pos, 1); } else { parser_rewind_position(parser); return PARSE_FAILED; } parser_drop_position(parser); parser_consume_spaces(parser); var = parse_var(parser->pos->str); if (var == NULL) return PARSE_ABORT; position_skip(parser->pos, strlen(var)); parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) return PARSE_ABORT; parser_consume_spaces_to_eol(parser); *token = token_new(token_type); (*token)->value.section.name = var; STAILQ_INIT(&(*token)->value.section.tokens); /* parse section content */ struct token *child; bool loop = true; while (loop) { switch (parser_parse_token(parser, &child)) { case PARSE_ABORT: goto abort; break; case PARSE_FAILED: loop = false; break; case PARSE_SUCCEED: STAILQ_INSERT_TAIL(&(*token)->value.section.tokens, child, next); break; } } /* parse section end tag */ if (! parser_consume(parser, "{{/")) goto abort; parser_consume_spaces(parser); var = parse_var(parser->pos->str); if (var == NULL) goto abort; position_skip(parser->pos, strlen(var)); if (strcmp(var, (*token)->value.section.name) != 0) { free(var); goto abort; } free(var); parser_consume_spaces(parser); if (! parser_consume(parser, "}}")) goto abort; parser_consume_spaces_to_eol(parser); parser_drop_position(parser); return PARSE_SUCCEED; abort: token_free(*token); return PARSE_ABORT; } /* * Parse next token */ static enum parse_status parser_parse_token(struct parser *parser, struct token **token) { enum parse_status (*parsers[]) (struct parser *, struct token **) = { parser_parse_comment, parser_parse_partial, parser_parse_section, parser_parse_var_formatter, parser_parse_var_formatter_html, parser_parse_var_formatter_url, parser_parse_var, parser_parse_text, NULL }; enum parse_status (**p) (struct parser *, struct token **); for (p = parsers; *p != NULL; p++) { enum parse_status status = (*p)(parser, token); if (status != PARSE_FAILED) return status; } return PARSE_FAILED; } /*----------------------------------------------------------------------------- * Writer */ enum writer_type { WRITER_BUFFER, WRITER_FILE }; struct writer { enum writer_type type; union { FILE *fh; struct buffer buffer; } backend; }; void writer_write(struct writer *writer, void *data, size_t len) { switch (writer->type) { case WRITER_BUFFER: buffer_append(&writer->backend.buffer, data, len); break; case WRITER_FILE: fwrite(data, 1, len, writer->backend.fh); break; } } void writer_write_string(struct writer *writer, const char *str) { writer_write(writer, (void *) str, strlen(str)); } /*----------------------------------------------------------------------------- * Formaters */ struct formatter { char *name; void (*cb) (struct json *, struct writer *); STAILQ_ENTRY(formatter) next; }; STAILQ_HEAD(formatters, formatter); static struct formatter * formatter_new(const char *name, void(*cb)(struct json *, struct writer *)) { struct formatter *formatter = xcalloc(1, sizeof(struct formatter)); formatter->name = xstrdup(name); formatter->cb = cb; return formatter; } static void formatter_free(struct formatter *formatter) { free(formatter->name); free(formatter); } static void formatter_default_cb(struct json *json, struct writer *writer) { if (json == NULL) return; if (json_is_str(json)) { writer_write_string(writer, json_get_value_str(json)); } else { char *str = json_render(json); writer_write_string(writer, str); free(str); } } static void formatter_html_cb(struct json *json, struct writer *writer) { if (json == NULL) return; char *value = NULL; if (json_is_str(json)) value = json_get_value_str(json); else value = json_render(json); for (; *value != '\0'; value++) { switch (*value) { case '&': writer_write_string(writer, "&"); break; case '<': writer_write_string(writer, "<"); break; case '>': writer_write_string(writer, ">"); break; case '"': writer_write_string(writer, """); break; case '\'': writer_write_string(writer, "'"); break; case '\n': writer_write_string(writer, " "); break; case '\r': writer_write_string(writer, " "); break; default: writer_write(writer, value, 1); break; } } if (! json_is_str(json)) free(value); } static void formatter_url_cb(struct json *json, struct writer *writer) { if (json_is_str(json)) { char *value = json_get_value_str(json); for (; *value != '\0'; value++) { if (('0' <= *value && *value <= '9') || ('a' <= *value && *value <= 'z') || ('A' <= *value && *value <= 'Z') || (*value == '.') || (*value == '-') || (*value == '_')) writer_write(writer, value, 1); else { char str[4] = {0}; snprintf(str, 4, "%%%02X", (unsigned char) *value); writer_write_string(writer, str); } } } } /*----------------------------------------------------------------------------- * Template */ enum template_errcode { TEMPLATE_ERR_NONE, TEMPLATE_ERR_FILE, TEMPLATE_ERR_PARSING }; struct template { const char *file_content; /* parsed file content */ struct parser parser; /* template parser */ struct tokens tokens; /* list of parsed tokens */ struct formatters formatters; /* list of registered formatters */ bool borrowed_formatters; /* formatters borrowed from another tpl? */ enum template_errcode errcode; /* error code */ struct buffer errmsg; /* error message */ }; struct template * template_new(void) { struct template *tpl = xcalloc(1, sizeof(struct template)); tpl->file_content = NULL; parser_init(&tpl->parser); STAILQ_INIT(&tpl->tokens); STAILQ_INIT(&tpl->formatters); tpl->borrowed_formatters = false; tpl->errcode = TEMPLATE_ERR_NONE; buffer_init(&tpl->errmsg); return tpl; } static void template_borrow_formatters(struct template *tpl, struct template *src) { tpl->borrowed_formatters = true; tpl->formatters = src->formatters; } static void template_reset(struct template *tpl) { if (tpl == NULL) return; parser_reset(&tpl->parser); buffer_reset(&tpl->errmsg); while (! STAILQ_EMPTY(&tpl->tokens)) { struct token *token = STAILQ_FIRST(&tpl->tokens); STAILQ_REMOVE_HEAD(&tpl->tokens, next); token_free(token); } } void template_free(struct template *tpl) { if (tpl == NULL) return; free((void *) tpl->file_content); parser_free_content(&tpl->parser); free(tpl->errmsg.data); while (! STAILQ_EMPTY(&tpl->tokens)) { struct token *token = STAILQ_FIRST(&tpl->tokens); STAILQ_REMOVE_HEAD(&tpl->tokens, next); token_free(token); } if (! tpl->borrowed_formatters) { while (! STAILQ_EMPTY(&tpl->formatters)) { struct formatter *formatter = STAILQ_FIRST(&tpl->formatters); STAILQ_REMOVE_HEAD(&tpl->formatters, next); formatter_free(formatter); } } free(tpl); } const char * template_error_msg(struct template *tpl) { buffer_reset(&tpl->errmsg); switch (tpl->errcode) { case TEMPLATE_ERR_NONE: buffer_append_str(&tpl->errmsg, "no error"); break; case TEMPLATE_ERR_PARSING: buffer_append_sprintf(&tpl->errmsg, "parse error at line %ld column %ld", tpl->parser.pos->line, tpl->parser.pos->col); break; case TEMPLATE_ERR_FILE: buffer_append_sprintf(&tpl->errmsg, "unable to read file at line %ld", tpl->parser.pos->line); break; } return tpl->errmsg.data; } /* * Parse a string */ bool template_parse_string(struct template *tpl, const char *str) { template_reset(tpl); tpl->parser.pos->str = str; while (*tpl->parser.pos->str != '\0') { struct token *token; enum parse_status status = parser_parse_token(&tpl->parser, &token); if (status == PARSE_SUCCEED) STAILQ_INSERT_TAIL(&tpl->tokens, token, next); else break; } if (*tpl->parser.pos->str != '\0') { if (tpl->errcode == TEMPLATE_ERR_NONE) tpl->errcode = TEMPLATE_ERR_PARSING; return false; } return true; } /* * Parse a file */ bool template_parse_file(struct template *tpl, const char *filename) { free((void *) tpl->file_content); tpl->file_content = slurp_file(filename); if (tpl->file_content == NULL) { tpl->errcode = TEMPLATE_ERR_FILE; return false; } return template_parse_string(tpl, tpl->file_content); } /*----------------------------------------------------------------------------- * Template formatters */ static struct formatter base_formatters[] = { { .name = "url", .cb = &formatter_url_cb }, { .name = "html", .cb = &formatter_html_cb }, { .name = NULL, .cb = NULL } }; static struct formatter * template_get_formatter(struct template *tpl, const char *name) { struct formatter *f; /* search in registered formatters */ STAILQ_FOREACH(f, &tpl->formatters, next) { if (strcmp(name, f->name) == 0) return f; } /* search in base formatters */ for (f = base_formatters; f->name != NULL; f++) { if (strcmp(name, f->name) == 0) return f; } return NULL; } void template_set_formatter(struct template *tpl, const char *name, void(*cb)(struct json *, struct writer *)) { /* * not supposed to set formatter on a template that * borrow its formatters from another template. */ assert(! tpl->borrowed_formatters); struct formatter *formatter = template_get_formatter(tpl, name); if (formatter) { formatter->cb = cb; } else { formatter = formatter_new(name, cb); STAILQ_INSERT_TAIL(&tpl->formatters, formatter, next); } } /*----------------------------------------------------------------------------- * Template rendering */ static void template_render_raw(struct template *tpl, struct json *json, struct writer *writer); static void template_render_token(struct template *tpl, struct token *token, struct json *json, struct writer *writer) { const char *p; struct token *t; struct json *node; switch (token->type) { case TOKEN_TEXT: p = token->value.text.start; while (p < token->value.text.end) writer_write(writer, (void *) p++, 1); break; case TOKEN_VAR: node = json_search(json, token->value.var.name); if (node) { if (token->value.var.formatter) { struct formatter *f = template_get_formatter(tpl, token->value.var.formatter); if (f == NULL) errx(1, "unkown formatter '%s'", token->value.var.formatter); f->cb(node, writer); } else { formatter_default_cb(node, writer); } } break; case TOKEN_COMMENT: break; case TOKEN_SECTION: node = json_search(json, token->value.section.name); if (node == NULL) return; if (json_is_arr(node)) { struct json_iter iter = json_iter_init(node); struct json *child; while ((child = json_iter_next(&iter))) { STAILQ_FOREACH(t, &token->value.section.tokens, next) { template_render_token(tpl, t, child, writer); } } } else if (json_is_obj(node) || json_is_str(node) || json_is_nbr(node) || (json_is_bool(node) && json_get_value_bool(node))) { STAILQ_FOREACH(t, &token->value.section.tokens, next) { template_render_token(tpl, t, node, writer); } } break; case TOKEN_INVERTED_SECTION: node = json_search(json, token->value.section.name); if (node == NULL || json_is_null(node) || (json_is_bool(node) && !json_get_value_bool(node)) || (json_is_arr(node) && json_arr_size(node) == 0)) { STAILQ_FOREACH(t, &token->value.section.tokens, next) { template_render_token(tpl, t, node, writer); } } break; case TOKEN_PARTIAL: template_borrow_formatters(token->value.partial.tpl, tpl); template_render_raw(token->value.partial.tpl, json, writer); break; } } static void template_render_raw(struct template *tpl, struct json *json, struct writer *writer) { struct token *token; STAILQ_FOREACH(token, &tpl->tokens, next) template_render_token(tpl, token, json, writer); } char * template_render_to_string(struct template *tpl, struct json *json) { struct writer writer = { .type = WRITER_BUFFER }; buffer_init(&writer.backend.buffer); template_render_raw(tpl, json, &writer); return writer.backend.buffer.data; } bool template_render_to_file(struct template *tpl, struct json *json, const char *filename) { FILE *fh = fopen(filename, "w"); if (fh == NULL) return false; struct writer writer = { .type = WRITER_FILE, .backend.fh = fh }; template_render_raw(tpl, json, &writer); fclose(fh); return true; } void template_render(struct template *tpl, struct json *json) { struct writer writer = { .type = WRITER_FILE, .backend.fh = stdout }; template_render_raw(tpl, json, &writer); } bool template_render_file(const char *filename, struct json *json) { struct template *tpl = template_new(); if (! template_parse_file(tpl, filename)) { template_free(tpl); return false; } template_render(tpl, json); template_free(tpl); return true; }