feat: hectic C: logging settings
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#include "hmpl.h"
|
||||
|
||||
Json *eval_object(Arena *arena, const Json * const context, const char * const query) {
|
||||
raise_debug("eval_object(%p, %s, %s)", arena, json_to_string(arena, context), query);
|
||||
raise_debug("eval_object(%p, %s, %s)", arena, json_to_string(DISPOSABLE_ARENA, context), query);
|
||||
if (!context || !query) return NULL;
|
||||
|
||||
const Json *res = context;
|
||||
@@ -9,14 +9,14 @@ Json *eval_object(Arena *arena, const Json * const context, const char * const q
|
||||
|
||||
while ((dot = strchr(key, '.')) != NULL) {
|
||||
*dot = '\0';
|
||||
raise_debug("res: %s, key: %s, query: %s", json_to_string(arena, res), key, query);
|
||||
raise_debug("eval_object: key: %s", key);
|
||||
res = json_get_object_item(res, key);
|
||||
if (!res)
|
||||
return NULL;
|
||||
key = dot + 1;
|
||||
}
|
||||
|
||||
raise_debug("res: %s, key: %s, query: %s", json_to_string(arena, res), key, query);
|
||||
raise_debug("eval_object: final key: %s", key);
|
||||
return json_get_object_item(res, key);
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json *
|
||||
continue;
|
||||
}
|
||||
|
||||
// Вычисляем длину замены от начала {{[prefix] до конца }}
|
||||
int replace_length = (end - start) + 2; // +2 для "}}"
|
||||
// Calculate the replacement length from the beginning of {{[prefix] to the end }}
|
||||
int replace_length = (end - start) + 2; // +2 for "}}"
|
||||
|
||||
char *new_text = arena_repstr(arena, current_text,
|
||||
start_index,
|
||||
@@ -74,6 +74,10 @@ void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json *
|
||||
}
|
||||
}
|
||||
|
||||
void hmpl_render_interpolation_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplInterpolationTagsOptions *options) {
|
||||
hmpl_render_interpolation_tags(arena, text_ptr, context, options->prefix);
|
||||
}
|
||||
|
||||
// {{item#array}}...{{/array}}
|
||||
void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix_start, const char * const prefix_end, const char * const separator_pattern) {
|
||||
raise_debug("hmpl_render_section_tags(%p, %s, <optimized>, %s, %s, %s)", arena, *text_ptr, prefix_start, prefix_end, separator_pattern);
|
||||
@@ -207,9 +211,15 @@ void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, cons
|
||||
char *prefix = arena_alloc(arena, element_name_length + 2);
|
||||
snprintf(prefix, element_name_length + 2, "%s.", element_name);
|
||||
|
||||
raise_debug("Processing element with prefix: %s", prefix);
|
||||
raise_debug("Block before processing: %s", block);
|
||||
|
||||
hmpl_render_interpolation_tags(arena, &block, elem, prefix);
|
||||
raise_trace("block after: %s", block);
|
||||
raise_debug("Block after interpolation: %s", block);
|
||||
|
||||
// Recursively process nested sections
|
||||
hmpl_render_section_tags(arena, &block, elem, prefix_start, prefix_end, separator_pattern);
|
||||
raise_debug("Block after section processing: %s", block);
|
||||
|
||||
size_t block_len = strlen(block);
|
||||
memcpy(replacement + replacement_offset, block, block_len);
|
||||
@@ -239,19 +249,25 @@ void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, cons
|
||||
}
|
||||
}
|
||||
|
||||
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context) {
|
||||
void hmpl_render_section_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplSectionTagsOptions *options) {
|
||||
// Create a copy of the context without const qualifier for compatibility with hmpl_render_section_tags
|
||||
hmpl_render_section_tags(arena, text_ptr, (Json*)context, options->prefix_start, options->prefix_end, options->separator_pattern);
|
||||
}
|
||||
|
||||
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context, const HmplOptions * const options) {
|
||||
if (context->type != JSON_OBJECT) {
|
||||
raise_exception("Malformed context: context is not json");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
hmpl_render_interpolation_tags(arena, text, context, "");
|
||||
hmpl_render_interpolation_tags_opts(arena, text, context, &options->interpolation_tags_options);
|
||||
hmpl_render_section_tags_opts(arena, text, context, &options->section_tags_options);
|
||||
}
|
||||
|
||||
void hmpl_render(char **text, const Json * const context) {
|
||||
void hmpl_render(char **text, const Json * const context, const HmplOptions * const options) {
|
||||
Arena arena = arena_init(MEM_MiB);
|
||||
|
||||
hmpl_render_with_arena(&arena, text, context);
|
||||
hmpl_render_with_arena(&arena, text, context, options);
|
||||
|
||||
arena_free(&arena);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,32 @@
|
||||
#include <assert.h>
|
||||
#include "hectic.h"
|
||||
|
||||
typedef struct {
|
||||
char *prefix_start;
|
||||
char *prefix_end;
|
||||
char *separator_pattern;
|
||||
} HmplSectionTagsOptions;
|
||||
|
||||
typedef struct {
|
||||
char *prefix;
|
||||
} HmplInterpolationTagsOptions;
|
||||
|
||||
typedef struct {
|
||||
HmplSectionTagsOptions section_tags_options;
|
||||
HmplInterpolationTagsOptions interpolation_tags_options;
|
||||
} HmplOptions;
|
||||
|
||||
static const HmplOptions DEFAULT_OPTIONS = {
|
||||
.section_tags_options = {
|
||||
.prefix_start = "",
|
||||
.prefix_end = "/",
|
||||
.separator_pattern = "#"
|
||||
},
|
||||
.interpolation_tags_options = {
|
||||
.prefix = ""
|
||||
}
|
||||
};
|
||||
|
||||
void init_cjson_with_arenas(Arena *arena);
|
||||
|
||||
char *eval_string(Arena *arena, const Json * const context, const char * const key);
|
||||
@@ -15,10 +41,14 @@ char *eval_string(Arena *arena, const Json * const context, const char * const k
|
||||
/* Modified: text is passed by reference so we can update it and free old allocations */
|
||||
void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json * const context, const char * const prefix);
|
||||
|
||||
void hmpl_render_interpolation_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplInterpolationTagsOptions *options);
|
||||
|
||||
void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix_start, const char * const prefix_end, const char * const separator_pattern);
|
||||
|
||||
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context);
|
||||
void hmpl_render_section_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplSectionTagsOptions *options);
|
||||
|
||||
void hmpl_render(char **text, const Json * const context);
|
||||
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context, const HmplOptions * const options);
|
||||
|
||||
void hmpl_render(char **text, const Json * const context, const HmplOptions * const options);
|
||||
|
||||
#endif // EPRINTF_HMPL
|
||||
|
||||
@@ -38,7 +38,7 @@ int main(int argc, char *argv[]) {
|
||||
text = arena_strdup(&arena, "");
|
||||
}
|
||||
|
||||
hmpl_render_with_arena(&arena, &text, context);
|
||||
hmpl_render_with_arena(&arena, &text, context, &DEFAULT_OPTIONS);
|
||||
printf("%s", text);
|
||||
|
||||
arena_free(&arena);
|
||||
|
||||
313
package/c/hmpl/test/test_section_tags.c
Executable file
313
package/c/hmpl/test/test_section_tags.c
Executable file
@@ -0,0 +1,313 @@
|
||||
#include "../hmpl.h"
|
||||
#include "hectic.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
const HmplOptions options = {
|
||||
.section_tags_options = {
|
||||
.prefix_start = "",
|
||||
.prefix_end = "/",
|
||||
.separator_pattern = "#"
|
||||
},
|
||||
.interpolation_tags_options = {
|
||||
.prefix = ""
|
||||
}
|
||||
};
|
||||
|
||||
// Function for comparing results
|
||||
void assert_rendered(const char* template, const char* expected, const char* json_str) {
|
||||
Arena arena = arena_init(MEM_KiB);
|
||||
|
||||
// Parse JSON string into an object
|
||||
Json *json = json_parse(&arena, &json_str);
|
||||
if (!json) {
|
||||
printf("ERROR: Failed to parse JSON: %s\n", json_str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Create a copy of the template for modification
|
||||
char *result = arena_strdup(&arena, template);
|
||||
|
||||
// Render the template
|
||||
hmpl_render_with_arena(&arena, &result, json, &options);
|
||||
|
||||
// Check the result
|
||||
if (strcmp(result, expected) != 0) {
|
||||
printf("ERROR:\n");
|
||||
printf("Template: %s\n", template);
|
||||
printf("Expected: %s\n", expected);
|
||||
printf("Received: %s\n", result);
|
||||
exit(1);
|
||||
} else {
|
||||
printf("SUCCESS: Template correctly rendered\n");
|
||||
}
|
||||
|
||||
arena_free(&arena);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// -- Simple section tag with surrounding text --
|
||||
// --------------------------------------------------
|
||||
|
||||
#define TEST_SIMPLE_SECTION_CONTEXT \
|
||||
"{" \
|
||||
" \"users\": [" \
|
||||
" {\"name\": \"John\", \"age\": 30}," \
|
||||
" {\"name\": \"Mary\", \"age\": 25}," \
|
||||
" {\"name\": \"Alex\", \"age\": 35}" \
|
||||
" ]," \
|
||||
" \"count\": 3" \
|
||||
"}"
|
||||
|
||||
#define TEST_SIMPLE_SECTION_TEMPLATE \
|
||||
"User list:\n" \
|
||||
"{{item#users}}<li>{{item.name}}, age: {{item.age}}</li>{{/users}}\n" \
|
||||
"Total users: {{count}}"
|
||||
|
||||
#define TEST_SIMPLE_SECTION_RESULT \
|
||||
"User list:\n" \
|
||||
"<li>John, age: 30</li><li>Mary, age: 25</li><li>Alex, age: 35</li>\n" \
|
||||
"Total users: 3"
|
||||
|
||||
void test_simple_section_tags(Arena *arena) {
|
||||
raise_notice("Testing simple section tag with surrounding text");
|
||||
const char *context_text = arena_strdup(arena, TEST_SIMPLE_SECTION_CONTEXT);
|
||||
Json *context = json_parse(arena, &context_text);
|
||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
||||
|
||||
char *text = arena_strdup(arena, TEST_SIMPLE_SECTION_TEMPLATE);
|
||||
raise_notice("Template:\n%s", text);
|
||||
raise_notice("Context: %s", json_to_string(arena, context));
|
||||
|
||||
hmpl_render_with_arena(arena, &text, context, &options);
|
||||
raise_notice("Result:\n%s", text);
|
||||
|
||||
assert(strcmp(text, TEST_SIMPLE_SECTION_RESULT) == 0);
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// -- Nested section tags --
|
||||
// -----------------------------------
|
||||
|
||||
#define TEST_NESTED_SECTION_CONTEXT \
|
||||
"{" \
|
||||
" \"department\": \"Development\"," \
|
||||
" \"teams\": [" \
|
||||
" {" \
|
||||
" \"name\": \"Frontend\"," \
|
||||
" \"members\": [" \
|
||||
" {\"name\": \"John\", \"role\": \"Developer\"}," \
|
||||
" {\"name\": \"Mary\", \"role\": \"Designer\"}" \
|
||||
" ]" \
|
||||
" }," \
|
||||
" {" \
|
||||
" \"name\": \"Backend\"," \
|
||||
" \"members\": [" \
|
||||
" {\"name\": \"Alex\", \"role\": \"Developer\"}," \
|
||||
" {\"name\": \"Helen\", \"role\": \"Tester\"}" \
|
||||
" ]" \
|
||||
" }" \
|
||||
" ]" \
|
||||
"}"
|
||||
|
||||
#define TEST_NESTED_SECTION_TEMPLATE \
|
||||
"Department: {{department}}\n" \
|
||||
"{{item#teams}}Team: {{item.name}}\n" \
|
||||
" {{item#item.members}}Member: {{item.name}} ({{item.role}}){{/item.members}}\n" \
|
||||
"{{/teams}}"
|
||||
|
||||
#define TEST_NESTED_SECTION_RESULT \
|
||||
"Department: Development\n" \
|
||||
"Team: Frontend\n" \
|
||||
" Member: John (Developer)Member: Mary (Designer)\n" \
|
||||
"Team: Backend\n" \
|
||||
" Member: Alex (Developer)Member: Helen (Tester)\n"
|
||||
|
||||
void test_nested_section_tags(Arena *arena) {
|
||||
raise_notice("Testing nested section tags");
|
||||
const char *context_text = arena_strdup(arena, TEST_NESTED_SECTION_CONTEXT);
|
||||
Json *context = json_parse(arena, &context_text);
|
||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
||||
|
||||
char *text = arena_strdup(arena, TEST_NESTED_SECTION_TEMPLATE);
|
||||
raise_notice("Template:\n%s", text);
|
||||
raise_notice("Context: %s", json_to_string(arena, context));
|
||||
|
||||
hmpl_render_with_arena(arena, &text, context, &options);
|
||||
raise_notice("Result:\n%s", text);
|
||||
|
||||
assert(strcmp(text, TEST_NESTED_SECTION_RESULT) == 0);
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// -- Empty array in section tag --
|
||||
// -----------------------------------
|
||||
|
||||
#define TEST_EMPTY_ARRAY_CONTEXT \
|
||||
"{" \
|
||||
" \"tasks\": []" \
|
||||
"}"
|
||||
|
||||
#define TEST_EMPTY_ARRAY_TEMPLATE \
|
||||
"Tasks: {{item#tasks}}ID: {{item.id}} - {{item.description}}{{/tasks}}"
|
||||
|
||||
#define TEST_EMPTY_ARRAY_RESULT \
|
||||
"Tasks: "
|
||||
|
||||
void test_empty_array_section_tags(Arena *arena) {
|
||||
raise_notice("Testing empty array in section tag");
|
||||
const char *context_text = arena_strdup(arena, TEST_EMPTY_ARRAY_CONTEXT);
|
||||
Json *context = json_parse(arena, &context_text);
|
||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
||||
|
||||
char *text = arena_strdup(arena, TEST_EMPTY_ARRAY_TEMPLATE);
|
||||
raise_notice("Template:\n%s", text);
|
||||
raise_notice("Context: %s", json_to_string(arena, context));
|
||||
|
||||
hmpl_render_with_arena(arena, &text, context, &options);
|
||||
raise_notice("Result:\n%s", text);
|
||||
|
||||
assert(strcmp(text, TEST_EMPTY_ARRAY_RESULT) == 0);
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// -- HTML template with section tags --
|
||||
// -----------------------------------
|
||||
|
||||
#define TEST_HTML_CONTEXT \
|
||||
"{" \
|
||||
" \"title\": \"My List\"," \
|
||||
" \"items\": [" \
|
||||
" {\"name\": \"Item 1\", \"type\": \"important\"}," \
|
||||
" {\"name\": \"Item 2\", \"type\": \"normal\"}," \
|
||||
" {\"name\": \"Item 3\", \"type\": \"normal\"}" \
|
||||
" ]," \
|
||||
" \"footer\": \"© 2023\"" \
|
||||
"}"
|
||||
|
||||
#define TEST_HTML_TEMPLATE \
|
||||
"<!DOCTYPE html>\n" \
|
||||
"<html>\n" \
|
||||
"<head>\n" \
|
||||
" <title>{{title}}</title>\n" \
|
||||
"</head>\n" \
|
||||
"<body>\n" \
|
||||
" <h1>{{title}}</h1>\n" \
|
||||
" <ul>\n" \
|
||||
" {{item#items}}<li class=\"{{item.type}}\">{{item.name}}</li>{{/items}}\n" \
|
||||
" </ul>\n" \
|
||||
" <footer>{{footer}}</footer>\n" \
|
||||
"</body>\n" \
|
||||
"</html>"
|
||||
|
||||
#define TEST_HTML_RESULT \
|
||||
"<!DOCTYPE html>\n" \
|
||||
"<html>\n" \
|
||||
"<head>\n" \
|
||||
" <title>My List</title>\n" \
|
||||
"</head>\n" \
|
||||
"<body>\n" \
|
||||
" <h1>My List</h1>\n" \
|
||||
" <ul>\n" \
|
||||
" <li class=\"important\">Item 1</li><li class=\"normal\">Item 2</li><li class=\"normal\">Item 3</li>\n" \
|
||||
" </ul>\n" \
|
||||
" <footer>© 2023</footer>\n" \
|
||||
"</body>\n" \
|
||||
"</html>"
|
||||
|
||||
void test_html_section_tags(Arena *arena) {
|
||||
raise_notice("Testing HTML template with section tags");
|
||||
const char *context_text = arena_strdup(arena, TEST_HTML_CONTEXT);
|
||||
Json *context = json_parse(arena, &context_text);
|
||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
||||
|
||||
char *text = arena_strdup(arena, TEST_HTML_TEMPLATE);
|
||||
raise_notice("Template:\n%s", text);
|
||||
raise_notice("Context: %s", json_to_string(arena, context));
|
||||
|
||||
hmpl_render_with_arena(arena, &text, context, &options);
|
||||
raise_notice("Result:\n%s", text);
|
||||
|
||||
assert(strcmp(text, TEST_HTML_RESULT) == 0);
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// -- Report with nested sections --
|
||||
// -----------------------------------
|
||||
|
||||
#define TEST_REPORT_CONTEXT \
|
||||
"{" \
|
||||
" \"period\": \"March 2023\"," \
|
||||
" \"data\": [" \
|
||||
" {" \
|
||||
" \"title\": \"Sales\"," \
|
||||
" \"value\": 1000," \
|
||||
" \"details\": [" \
|
||||
" {\"name\": \"Product A\", \"value\": 500}," \
|
||||
" {\"name\": \"Product B\", \"value\": 300}," \
|
||||
" {\"name\": \"Product C\", \"value\": 200}" \
|
||||
" ]" \
|
||||
" }," \
|
||||
" {" \
|
||||
" \"title\": \"Expenses\"," \
|
||||
" \"value\": 700," \
|
||||
" \"details\": [" \
|
||||
" {\"name\": \"Rent\", \"value\": 300}," \
|
||||
" {\"name\": \"Salary\", \"value\": 400}" \
|
||||
" ]" \
|
||||
" }" \
|
||||
" ]," \
|
||||
" \"summary\": 300" \
|
||||
"}"
|
||||
|
||||
#define TEST_REPORT_TEMPLATE \
|
||||
"Report for {{period}}\n\n" \
|
||||
"{{row#data}}* {{row.title}}: {{row.value}}\n" \
|
||||
" {{detail#row.details}} - {{detail.name}}: {{detail.value}}\n{{/row.details}}\n" \
|
||||
"{{/data}}\n" \
|
||||
"Total: {{summary}}"
|
||||
|
||||
#define TEST_REPORT_RESULT \
|
||||
"Report for March 2023\n\n" \
|
||||
"* Sales: 1000\n" \
|
||||
" - Product A: 500\n - Product B: 300\n - Product C: 200\n\n" \
|
||||
"* Expenses: 700\n" \
|
||||
" - Rent: 300\n - Salary: 400\n\n" \
|
||||
"Total: 300"
|
||||
|
||||
void test_report_section_tags(Arena *arena) {
|
||||
raise_notice("Testing report with nested sections");
|
||||
const char *context_text = arena_strdup(arena, TEST_REPORT_CONTEXT);
|
||||
Json *context = json_parse(arena, &context_text);
|
||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
||||
|
||||
char *text = arena_strdup(arena, TEST_REPORT_TEMPLATE);
|
||||
raise_notice("Template:\n%s", text);
|
||||
raise_notice("Context: %s", json_to_string(arena, context));
|
||||
|
||||
hmpl_render_with_arena(arena, &text, context, &options);
|
||||
raise_notice("Result:\n%s", text);
|
||||
|
||||
assert(strcmp(text, TEST_REPORT_RESULT) == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_logger();
|
||||
Arena arena = arena_init(MEM_MiB);
|
||||
|
||||
test_simple_section_tags(&arena);
|
||||
arena_reset(&arena);
|
||||
test_nested_section_tags(&arena);
|
||||
//arena_reset(&arena);
|
||||
//test_empty_array_section_tags(&arena);
|
||||
//arena_reset(&arena);
|
||||
//test_html_section_tags(&arena);
|
||||
//arena_reset(&arena);
|
||||
//test_report_section_tags(&arena);
|
||||
|
||||
printf("All tests passed successfully!\n");
|
||||
|
||||
arena_free(&arena);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user