mirror of
https://github.com/zigzap/zap.git
synced 2025-10-21 07:34:08 +00:00
308 lines
9.8 KiB
C
308 lines
9.8 KiB
C
/*
|
|
Copyright: Boaz Segev, 2018-2019
|
|
License: MIT
|
|
*/
|
|
#define INCLUDE_MUSTACHE_IMPLEMENTATION 1
|
|
#include <mustache_parser.h>
|
|
|
|
#include <fiobj_ary.h>
|
|
#include <fiobj_hash.h>
|
|
#include <fiobj_mustache.h>
|
|
#include <fiobj_str.h>
|
|
|
|
#ifndef FIO_IGNORE_MACRO
|
|
/**
|
|
* This is used internally to ignore macros that shadow functions (avoiding
|
|
* named arguments when required).
|
|
*/
|
|
#define FIO_IGNORE_MACRO
|
|
#endif
|
|
|
|
/**
|
|
* Loads a mustache template, converting it into an opaque instruction array.
|
|
*
|
|
* Returns a pointer to the instruction array.
|
|
*
|
|
* The `folder` argument should contain the template's root folder which would
|
|
* also be used to search for any required partial templates.
|
|
*
|
|
* The `filename` argument should contain the template's file name.
|
|
*/
|
|
mustache_s *fiobj_mustache_load(fio_str_info_s filename) {
|
|
return mustache_load(.filename = filename.data, .filename_len = filename.len);
|
|
}
|
|
|
|
/**
|
|
* Loads a mustache template, converting it into an opaque instruction array.
|
|
*
|
|
* Returns a pointer to the instruction array.
|
|
*
|
|
* The `folder` argument should contain the template's root folder which would
|
|
* also be used to search for any required partial templates.
|
|
*
|
|
* The `filename` argument should contain the template's file name.
|
|
*/
|
|
mustache_s *fiobj_mustache_new FIO_IGNORE_MACRO(mustache_load_args_s args) {
|
|
return mustache_load FIO_IGNORE_MACRO(args);
|
|
}
|
|
|
|
/** Free the mustache template */
|
|
void fiobj_mustache_free(mustache_s *mustache) { mustache_free(mustache); }
|
|
|
|
/**
|
|
* Renders a template into an existing FIOBJ String (`dest`'s end), using the
|
|
* information in the `data` object.
|
|
*
|
|
* Returns FIOBJ_INVALID if an error occured and a FIOBJ String on success.
|
|
*/
|
|
FIOBJ fiobj_mustache_build2(FIOBJ dest, mustache_s *mustache, FIOBJ data) {
|
|
mustache_build(mustache, .udata1 = (void *)dest, .udata2 = (void *)data);
|
|
return dest;
|
|
}
|
|
|
|
/**
|
|
* Creates a FIOBJ String containing the rendered template using the information
|
|
* in the `data` object.
|
|
*
|
|
* Returns FIOBJ_INVALID if an error occured and a FIOBJ String on success.
|
|
*/
|
|
FIOBJ fiobj_mustache_build(mustache_s *mustache, FIOBJ data) {
|
|
if (!mustache)
|
|
return FIOBJ_INVALID;
|
|
return fiobj_mustache_build2(fiobj_str_buf(mustache->u.read_only.data_length),
|
|
mustache, data);
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
Mustache Callbacks
|
|
***************************************************************************** */
|
|
|
|
static inline FIOBJ fiobj_mustache_find_obj_absolute(FIOBJ parent, FIOBJ key) {
|
|
if (!FIOBJ_TYPE_IS(parent, FIOBJ_T_HASH))
|
|
return FIOBJ_INVALID;
|
|
FIOBJ o = FIOBJ_INVALID;
|
|
o = fiobj_hash_get(parent, key);
|
|
return o;
|
|
}
|
|
|
|
static inline FIOBJ fiobj_mustache_find_obj_tree(mustache_section_s *section,
|
|
const char *name,
|
|
uint32_t name_len) {
|
|
FIOBJ key = fiobj_str_tmp();
|
|
fiobj_str_write(key, name, name_len);
|
|
do {
|
|
FIOBJ tmp = fiobj_mustache_find_obj_absolute((FIOBJ)section->udata2, key);
|
|
if (tmp != FIOBJ_INVALID) {
|
|
return tmp;
|
|
}
|
|
} while ((section = mustache_section_parent(section)));
|
|
return FIOBJ_INVALID;
|
|
}
|
|
|
|
static inline FIOBJ fiobj_mustache_find_obj(mustache_section_s *section,
|
|
const char *name,
|
|
uint32_t name_len) {
|
|
FIOBJ tmp = fiobj_mustache_find_obj_tree(section, name, name_len);
|
|
if (tmp != FIOBJ_INVALID)
|
|
return tmp;
|
|
/* interpolate sections... */
|
|
uint32_t dot = 0;
|
|
while (dot < name_len && name[dot] != '.')
|
|
++dot;
|
|
if (dot == name_len)
|
|
return FIOBJ_INVALID;
|
|
tmp = fiobj_mustache_find_obj_tree(section, name, dot);
|
|
if (!tmp) {
|
|
return FIOBJ_INVALID;
|
|
}
|
|
++dot;
|
|
for (;;) {
|
|
FIOBJ key = fiobj_str_tmp();
|
|
fiobj_str_write(key, name + dot, name_len - dot);
|
|
FIOBJ obj = fiobj_mustache_find_obj_absolute(tmp, key);
|
|
if (obj != FIOBJ_INVALID)
|
|
return obj;
|
|
name += dot;
|
|
name_len -= dot;
|
|
dot = 0;
|
|
while (dot < name_len && name[dot] != '.')
|
|
++dot;
|
|
if (dot == name_len) {
|
|
return FIOBJ_INVALID;
|
|
}
|
|
key = fiobj_str_tmp();
|
|
fiobj_str_write(key, name, dot);
|
|
tmp = fiobj_mustache_find_obj_absolute(tmp, key);
|
|
if (tmp == FIOBJ_INVALID)
|
|
return FIOBJ_INVALID;
|
|
++dot;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when an argument name was detected in the current section.
|
|
*
|
|
* A conforming implementation will search for the named argument both in the
|
|
* existing section and all of it's parents (walking backwards towards the root)
|
|
* until a value is detected.
|
|
*
|
|
* A missing value should be treated the same as an empty string.
|
|
*
|
|
* A conforming implementation will output the named argument's value (either
|
|
* HTML escaped or not, depending on the `escape` flag) as a string.
|
|
*/
|
|
static int mustache_on_arg(mustache_section_s *section, const char *name,
|
|
uint32_t name_len, unsigned char escape) {
|
|
FIOBJ o = fiobj_mustache_find_obj(section, name, name_len);
|
|
if (!o)
|
|
return 0;
|
|
fio_str_info_s i = fiobj_obj2cstr(o);
|
|
if (!i.len)
|
|
return 0;
|
|
return mustache_write_text(section, i.data, i.len, escape);
|
|
}
|
|
|
|
/**
|
|
* Called when simple template text (string) is detected.
|
|
*
|
|
* A conforming implementation will output data as a string (no escaping).
|
|
*/
|
|
static int mustache_on_text(mustache_section_s *section, const char *data,
|
|
uint32_t data_len) {
|
|
FIOBJ dest = (FIOBJ)section->udata1;
|
|
fiobj_str_write(dest, data, data_len);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Called for nested sections, must return the number of objects in the new
|
|
* subsection (depending on the argument's name).
|
|
*
|
|
* Arrays should return the number of objects in the array.
|
|
*
|
|
* `true` values should return 1.
|
|
*
|
|
* `false` values should return 0.
|
|
*
|
|
* A return value of -1 will stop processing with an error.
|
|
*
|
|
* Please note, this will handle both normal and inverted sections.
|
|
*/
|
|
static int32_t mustache_on_section_test(mustache_section_s *section,
|
|
const char *name, uint32_t name_len,
|
|
uint8_t callable) {
|
|
FIOBJ o = fiobj_mustache_find_obj(section, name, name_len);
|
|
if (!o || FIOBJ_TYPE_IS(o, FIOBJ_T_FALSE))
|
|
return 0;
|
|
if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY))
|
|
return fiobj_ary_count(o);
|
|
return 1;
|
|
(void)callable; /* FIOBJ doesn't support lambdas */
|
|
}
|
|
|
|
/**
|
|
* Called when entering a nested section.
|
|
*
|
|
* `index` is a zero based index indicating the number of repetitions that
|
|
* occurred so far (same as the array index for arrays).
|
|
*
|
|
* A return value of -1 will stop processing with an error.
|
|
*
|
|
* Note: this is a good time to update the subsection's `udata` with the value
|
|
* of the array index. The `udata` will always contain the value or the parent's
|
|
* `udata`.
|
|
*/
|
|
static int mustache_on_section_start(mustache_section_s *section,
|
|
char const *name, uint32_t name_len,
|
|
uint32_t index) {
|
|
FIOBJ o = fiobj_mustache_find_obj(section, name, name_len);
|
|
if (!o)
|
|
return -1;
|
|
if (FIOBJ_TYPE_IS(o, FIOBJ_T_ARRAY))
|
|
section->udata2 = (void *)fiobj_ary_index(o, index);
|
|
else
|
|
section->udata2 = (void *)o;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Called for cleanup in case of error.
|
|
*/
|
|
static void mustache_on_formatting_error(void *udata1, void *udata2) {
|
|
(void)udata1;
|
|
(void)udata2;
|
|
}
|
|
|
|
/* *****************************************************************************
|
|
Testing
|
|
***************************************************************************** */
|
|
|
|
#if DEBUG
|
|
static inline void mustache_save2file(char const *filename, char const *data,
|
|
size_t length) {
|
|
int fd = open(filename, O_CREAT | O_RDWR, 0);
|
|
if (fd == -1) {
|
|
perror("Couldn't open / create file for template testing");
|
|
exit(-1);
|
|
}
|
|
fchmod(fd, 0777);
|
|
if (pwrite(fd, data, length, 0) != (ssize_t)length) {
|
|
perror("Mustache template write error");
|
|
exit(-1);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
void fiobj_mustache_test(void) {
|
|
#define TEST_ASSERT(cond, ...) \
|
|
if (!(cond)) { \
|
|
fprintf(stderr, "* " __VA_ARGS__); \
|
|
fprintf(stderr, "\n !!! Testing failed !!!\n"); \
|
|
exit(-1); \
|
|
}
|
|
|
|
char const *template =
|
|
"{{=<< >>=}}* Users:\r\n<<#users>><<id>>. <<& name>> "
|
|
"(<<name>>)\r\n<</users>>\r\nNested: <<& nested.item >>.";
|
|
char const *template_name = "mustache_test_template.mustache";
|
|
mustache_save2file(template_name, template, strlen(template));
|
|
mustache_s *m =
|
|
fiobj_mustache_load((fio_str_info_s){.data = (char *)template_name});
|
|
unlink(template_name);
|
|
TEST_ASSERT(m, "fiobj_mustache_load failed.\n");
|
|
FIOBJ data = fiobj_hash_new();
|
|
FIOBJ key = fiobj_str_new("users", 5);
|
|
FIOBJ ary = fiobj_ary_new2(4);
|
|
fiobj_hash_set(data, key, ary);
|
|
fiobj_free(key);
|
|
for (int i = 0; i < 4; ++i) {
|
|
FIOBJ id = fiobj_str_buf(4);
|
|
fiobj_str_write_i(id, i);
|
|
FIOBJ name = fiobj_str_buf(4);
|
|
fiobj_str_write(name, "User ", 5);
|
|
fiobj_str_write_i(name, i);
|
|
FIOBJ usr = fiobj_hash_new2(2);
|
|
key = fiobj_str_new("id", 2);
|
|
fiobj_hash_set(usr, key, id);
|
|
fiobj_free(key);
|
|
key = fiobj_str_new("name", 4);
|
|
fiobj_hash_set(usr, key, name);
|
|
fiobj_free(key);
|
|
fiobj_ary_push(ary, usr);
|
|
}
|
|
key = fiobj_str_new("nested", 6);
|
|
ary = fiobj_hash_new2(2);
|
|
fiobj_hash_set(data, key, ary);
|
|
fiobj_free(key);
|
|
key = fiobj_str_new("item", 4);
|
|
fiobj_hash_set(ary, key, fiobj_str_new("dot notation success", 20));
|
|
fiobj_free(key);
|
|
key = fiobj_mustache_build(m, data);
|
|
fiobj_free(data);
|
|
TEST_ASSERT(key, "fiobj_mustache_build failed!\n");
|
|
fprintf(stderr, "%s\n", fiobj_obj2cstr(key).data);
|
|
fiobj_free(key);
|
|
fiobj_mustache_free(m);
|
|
}
|
|
|
|
#endif
|