blob: 65a61072cd4bb4cf32606fc6b1f17868b9aef222 [file] [log] [blame] [raw]
#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