checkpoint
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 '------------------------------------';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user