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;
// Find start of line or buffer
/* Check if there's only whitespace before the tag */ const char *line_start = tag_start;
while (p < tag_start && isspace((unsigned char)*p)) while (line_start > start && line_start[-1] != '\n')
p++; line_start--;
if (p != tag_start)
return false; // Check all characters before tag_start are whitespace
for (p = line_start; p < tag_start; p++) {
/* Find the end of the tag */ if (!isspace((unsigned char)*p))
return false;
}
// 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 */
@@ -589,9 +599,9 @@ template_parse_section(MemoryContext context, const char **s_ptr,
{ {
/* This is our matching end tag */ /* This is our matching end tag */
end_tag_start = *s; end_tag_start = *s;
break; break;
} }
} }
} }
(*s)++; (*s)++;
@@ -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;
} }
@@ -703,6 +715,8 @@ template_parse_section(MemoryContext context, const char **s_ptr,
/* Set the pointer to after the closing brace */ /* Set the pointer to after the closing brace */
*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;
} }
@@ -829,7 +843,7 @@ template_parse_execute(MemoryContext context, const char **s_ptr,
/* If we've reached the matching closing brace, we're done */ /* If we've reached the matching closing brace, we're done */
if (brace_level == 0) { if (brace_level == 0) {
break; break;
} }
*s += strlen(config->Syntax.Braces.close) - 1; /* -1 because we'll increment s below */ *s += strlen(config->Syntax.Braces.close) - 1; /* -1 because we'll increment s below */
@@ -974,20 +988,62 @@ 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);
/* Check if this is a section tag on its own line */ /* Check if this is a section tag on its own line */
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);
elog(LOG, "TPE: is_control_on_own_line: %s", is_control_on_own_line ? "true" : "false");
if (tag_node && is_control_on_own_line) { if (is_control_on_own_line && current && current->type == TEMPLATE_NODE_TEXT && current->value->text.content) {
/* If we have a previous text node, trim its trailing newline */ /* Find the last newline in the text node */
trim_newline_from_prev_text(current); 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

@@ -137,25 +137,25 @@ BEGIN
<header>{{ include header }}</header> <header>{{ include header }}</header>
<main> <main>
{{ for section in page.sections }} {{ for section in page.sections }}
<section id="{{ section.id }}"> <section id="{{ section.id }}">
<h2>{{ section.title }}</h2> <h2>{{ section.title }}</h2>
{{ for item in section.items }} {{ for item in section.items }}
<div class="item {{ item.status }}"> <div class="item {{ item.status }}">
{{ include item.template }} {{ include item.template }}
{{ exec {{ exec
DECLARE DECLARE
v_status TEXT; v_status TEXT;
BEGIN BEGIN
v_status := context->'item'->>'status'; v_status := context->'item'->>'status';
RETURN CASE RETURN CASE
WHEN v_status = 'active' THEN ' (Active Item)' WHEN v_status = 'active' THEN ' (Active Item)'
ELSE ' (Inactive Item)' ELSE ' (Inactive Item)'
END; END;
END; END;
}} }}
</div> </div>
{{ end }} {{ end }}
</section> </section>
{{ end }} {{ end }}
</main> </main>
<footer>{{ include footer }}</footer> <footer>{{ include footer }}</footer>
@@ -172,15 +172,15 @@ BEGIN
<body> <body>
<header>Welcome to My Page!</header> <header>Welcome to My Page!</header>
<main> <main>
<section id="section1"> <section id="section1">
<h2>Section 1</h2> <h2>Section 1</h2>
<div class="item active"> <div class="item active">
Status: active, Content: Item 1 Content (Active Item) Status: active, Content: Item 1 Content (Active Item)
</div> </div>
<div class="item inactive"> <div class="item inactive">
Status: inactive, Content: Item 2 Content (Inactive Item) Status: inactive, Content: Item 2 Content (Inactive Item)
</div> </div>
</section> </section>
</main> </main>
<footer><footer>Copyright 2024</footer></footer> <footer><footer>Copyright 2024</footer></footer>
</body> </body>
@@ -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

@@ -49,6 +49,38 @@ DECLARE
BEGIN 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;