checkpoint

This commit is contained in:
2025-05-21 19:50:47 +00:00
parent d1b0d7154d
commit 3b7c4d71e7
5 changed files with 206 additions and 59 deletions

View File

@@ -58,29 +58,39 @@ find_prev_line_start(const char *str, const char *current)
static bool static bool
is_tag_on_own_line(const char *start, const char *tag_start, const TemplateConfig *config) is_tag_on_own_line(const char *start, const char *tag_start, const TemplateConfig *config)
{ {
const char *line_start = find_prev_line_start(start, tag_start); const char *p;
const char *p = line_start;
/* Check if there's only whitespace before the tag */ // Find start of line or buffer
while (p < tag_start && isspace((unsigned char)*p)) const char *line_start = tag_start;
p++; while (line_start > start && line_start[-1] != '\n')
if (p != tag_start) line_start--;
// Check all characters before tag_start are whitespace
for (p = line_start; p < tag_start; p++) {
if (!isspace((unsigned char)*p))
return false; return false;
}
/* Find the end of the tag */ // Move p from tag_start to after closing braces
p = tag_start; p = tag_start;
while (*p && *p != '\n') { while (*p) {
if (strncmp(p, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) { if (strncmp(p, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) {
p += strlen(config->Syntax.Braces.close); p += strlen(config->Syntax.Braces.close);
break; break;
} }
if (*p == '\n') // tag broken across lines
return false;
p++; p++;
} }
/* Check if there's only whitespace or newline after the tag */ // Check all characters after closing braces until newline or end are whitespace
while (*p && *p != '\n' && isspace((unsigned char)*p)) while (*p && *p != '\n') {
if (!isspace((unsigned char)*p))
return false;
p++; p++;
return *p == '\n' || *p == '\0'; }
return true;
} }
/* Helper function to trim newline from previous text node */ /* Helper function to trim newline from previous text node */
@@ -618,9 +628,11 @@ template_parse_section(MemoryContext context, const char **s_ptr,
/* Initialize body_len to the full content length */ /* Initialize body_len to the full content length */
size_t body_len = end_tag_start - body_start; size_t body_len = end_tag_start - body_start;
if ((*is_end_on_own_line = is_tag_on_own_line(end_tag_start, end_tag_end, config))) { elog(NOTICE, "TPS: end_tag_start: %.9s", end_tag_start);
if ((*is_end_on_own_line = is_tag_on_own_line(body_start, end_tag_start, config))) {
/* Find the start of the line containing the end tag */ /* Find the start of the line containing the end tag */
const char *line_start = find_prev_line_start(end_tag_start, end_tag_end); const char *line_start = find_prev_line_start(body_start, end_tag_start);
/* Update body_len to exclude the line containing the end tag */ /* Update body_len to exclude the line containing the end tag */
body_len = line_start - body_start; body_len = line_start - body_start;
} }
@@ -704,6 +716,8 @@ template_parse_section(MemoryContext context, const char **s_ptr,
*s_ptr = *s + strlen(config->Syntax.Braces.close); *s_ptr = *s + strlen(config->Syntax.Braces.close);
elog(DEBUG1, "TPS: Successfully parsed section, returning at position: %s", *s_ptr); elog(DEBUG1, "TPS: Successfully parsed section, returning at position: %s", *s_ptr);
elog(LOG, "TPS: is_end_on_own_line: %s", *is_end_on_own_line ? "true" : "false");
return node; return node;
} }
@@ -974,6 +988,21 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
/* Choose the tag parser based on the matched type */ /* Choose the tag parser based on the matched type */
if (matched_type == 1) { if (matched_type == 1) {
/*
FIXME(yukkop): This writen as shit coz I strupid monkey
Now it, probably, make many excesive actions.
Steps to prase, for future rework:
1. if `control` tag, then remove all this line from result
1. remove whitespaces before (in previous node if it is text node)
2. remove whitespaces after until \n (in section body before parse it to nodes)
3. remove \n (as previous step)
2. if `end` tag, then remove all this line from result
1. remove whitespaces before (in last body node if it is text node)
2. remove whitespaces after until \n (skip it before parse next nodes)
3. remove \n (also skip)
3. render sections body
*/
/* Section tag */ /* Section tag */
elog(LOG, "TPE: Parsing section tag at position: %.50s", *s); elog(LOG, "TPE: Parsing section tag at position: %.50s", *s);
@@ -981,13 +1010,40 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
bool is_end_on_own_line = false, bool is_end_on_own_line = false,
is_control_on_own_line = is_tag_on_own_line(start, *s, config); is_control_on_own_line = is_tag_on_own_line(start, *s, config);
if (tag_node && is_control_on_own_line) { elog(LOG, "TPE: is_control_on_own_line: %s", is_control_on_own_line ? "true" : "false");
/* If we have a previous text node, trim its trailing newline */
trim_newline_from_prev_text(current); if (is_control_on_own_line && current && current->type == TEMPLATE_NODE_TEXT && current->value->text.content) {
/* Find the last newline in the text node */
char *content = current->value->text.content;
size_t len = strlen(content);
size_t last_newline = 0;
for (size_t i = 0; i < len; i++) {
if (content[i] == '\n') {
last_newline = i;
}
}
elog(LOG, "TPE: Last newline: %zu", last_newline);
/* If we found a newline, trim everything after it */
if (last_newline > 0) {
content[last_newline+1] = '\0';
}
} }
/* Parse the section tag */ /* Parse the section tag */
tag_node = template_parse_section(context, s, config, error_code, is_control_on_own_line, &is_end_on_own_line); tag_node = template_parse_section(context, s, config, error_code, is_control_on_own_line, &is_end_on_own_line);
elog(LOG, "TPE: is_end_on_own_line: %s", is_end_on_own_line ? "true" : "false");
if (is_end_on_own_line) {
/* Remove the end tag from the result */
while (**s != '\n') {
(*s)++;
}
(*s)++;
}
} else if (matched_type == 2) { } else if (matched_type == 2) {
/* Include tag */ /* Include tag */
elog(LOG, "TPE: Parsing include tag at position: %.50s", *s); elog(LOG, "TPE: Parsing include tag at position: %.50s", *s);

View File

@@ -29,7 +29,7 @@ BEGIN;
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
\ir test_jsonb_path.sql \ir test_jsonb_path.sql
\ir test_template_parser.sql --\ir test_template_parser.sql
\ir test_render_exec.sql \ir test_render_exec.sql
\ir test_render_interpolate.sql \ir test_render_interpolate.sql
\ir test_render_section.sql \ir test_render_section.sql

View File

@@ -192,7 +192,7 @@ BEGIN
RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests; RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests;
ELSE ELSE
RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"', RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"',
total_tests, expected, test_result; total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF; END IF;
EXCEPTION WHEN OTHERS THEN EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM; RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;

View File

@@ -217,8 +217,7 @@ BEGIN
); );
expected := ' item expected := ' item
item item
item item';
';
IF test_result = expected THEN IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 2: PASSED', total_tests; RAISE NOTICE 'Test %: Section whitespaces 2: PASSED', total_tests;
passed_tests := passed_tests + 1; passed_tests := passed_tests + 1;
@@ -239,8 +238,7 @@ BEGIN
); );
expected := ' item expected := ' item
item item
item item';
';
IF test_result = expected THEN IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 3: PASSED', total_tests; RAISE NOTICE 'Test %: Section whitespaces 3: PASSED', total_tests;
passed_tests := passed_tests + 1; passed_tests := passed_tests + 1;
@@ -283,7 +281,6 @@ BEGIN
expected := ' item expected := ' item
item item
item item
'; ';
IF test_result = expected THEN IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 5: PASSED', total_tests; RAISE NOTICE 'Test %: Section whitespaces 5: PASSED', total_tests;
@@ -295,6 +292,68 @@ BEGIN
RAISE WARNING 'Test %: Section whitespaces 5: FAILED with error: %', total_tests, SQLERRM; RAISE WARNING 'Test %: Section whitespaces 5: FAILED with error: %', total_tests, SQLERRM;
END; END;
-- Test 16: Tabs
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'
identation1
{{for item in array}}
identation2
{{end}}
identation1
'
);
expected := '
identation1
identation2
identation2
identation2
identation1
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Tabs: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Tabs: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Tabs: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 17: Tabs 2
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'
identation1
{{for item in array}}
identation2
{{end}}
identation1
'
);
expected := '
identation1
identation2
identation2
identation2
identation1
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Tabs: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Tabs: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Tabs: FAILED with error: %', total_tests, SQLERRM;
END;
-- Print summary -- Print summary
IF passed_tests = total_tests THEN IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------'; RAISE NOTICE '------------------------------------';

View File

@@ -50,6 +50,38 @@ BEGIN
PERFORM pg_sleep(2); PERFORM pg_sleep(2);
RAISE NOTICE 'Starting template parser tests...'; RAISE NOTICE 'Starting template parser tests...';
-- Test 0: bruh
total_tests := total_tests + 1;
result := test_template_parse(
$hemar1$
text
{{ for i in a }}
item
item
item
{{ end }}
text
$hemar1$,
$expected1$Template parsed successfully. Structure:
TEXT: "
text"
SECTION: iterator="i", collection="a"
TEXT: " item
item
item
"
TEXT: "
text
"
$expected1$
);
IF result THEN
passed_tests := passed_tests + 1;
RAISE NOTICE 'Test %: bruh - PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: bruh - FAILED', total_tests;
END IF;
-- Test 1: Simple interpolation -- Test 1: Simple interpolation
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := test_template_parse( result := test_template_parse(