# HG changeset patch # User Olaf Wintermann # Date 1763826282 -3600 # Node ID 9728d3a2ac97d9c76fd3af0c29995d116a200a58 # Parent 392ec9026b07e378d8940966b4c6ab701495723e add simple string template function diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/test/main.c --- a/src/server/test/main.c Sat Nov 22 14:27:01 2025 +0100 +++ b/src/server/test/main.c Sat Nov 22 16:44:42 2025 +0100 @@ -50,6 +50,7 @@ #include "object.h" #include "io.h" #include "event.h" +#include "strreplace.h" void register_pg_tests(int argc, char **argv, CxTestSuite *suite); @@ -140,6 +141,12 @@ cx_test_register(suite, test_writer_flush); cx_test_register(suite, test_writer_put); + // strreplace tests + cx_test_register(suite, test_string_template_compile); + cx_test_register(suite, test_string_template_compile_error); + cx_test_register(suite, test_string_template_write_to); + cx_test_register(suite, test_string_template_build_string); + // xml tests cx_test_register(suite, test_wsxml_iterator); cx_test_register(suite, test_wsxml_get_required_namespaces); diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/test/objs.mk --- a/src/server/test/objs.mk Sat Nov 22 14:27:01 2025 +0100 +++ b/src/server/test/objs.mk Sat Nov 22 16:44:42 2025 +0100 @@ -41,6 +41,7 @@ TESTOBJ += object.o TESTOBJ += io.o TESTOBJ += event.o +TESTOBJ += strreplace.o TESTOBJS = $(TESTOBJ:%=$(TEST_OBJPRE)%) TESTSOURCE = $(TESTOBJ:%.o=test/%.c) diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/test/strreplace.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/strreplace.c Sat Nov 22 16:44:42 2025 +0100 @@ -0,0 +1,278 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "strreplace.h" +#include "../util/strreplace.h" +#include + +CX_TEST(test_string_template_compile) { + CxMempool *mp = cxMempoolCreate(100, CX_MEMPOOL_TYPE_ADVANCED); + const CxAllocator *a = mp->allocator; + + StringTemplate *tpl = NULL; + StringTemplateSegment *s0 = NULL; + StringTemplateSegment *s1 = NULL; + StringTemplateSegment *s2 = NULL; + StringTemplateSegment *s3 = NULL; + CX_TEST_DO { + // single segment tests + + tpl = string_template_compile(a, cx_str("")); + CX_TEST_ASSERT(tpl); // empty str + CX_TEST_ASSERT(!tpl->segments); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("static")); + CX_TEST_ASSERT(tpl); // static + CX_TEST_ASSERT(tpl->segments); + CX_TEST_ASSERT(!tpl->segments->next); + CX_TEST_ASSERT(!cx_strcmp(tpl->segments->str, "static")); + CX_TEST_ASSERT(tpl->segments->type == STRING_SEGMENT_STR); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$$")); + CX_TEST_ASSERT(tpl); // $ + CX_TEST_ASSERT(tpl->segments); + CX_TEST_ASSERT(!tpl->segments->next); + CX_TEST_ASSERT(!cx_strcmp(tpl->segments->str, "$")); + CX_TEST_ASSERT(tpl->segments->type == STRING_SEGMENT_STR); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$var")); + CX_TEST_ASSERT(tpl); // var + CX_TEST_ASSERT(tpl->segments); + CX_TEST_ASSERT(!tpl->segments->next); + CX_TEST_ASSERT(!cx_strcmp(tpl->segments->str, "var")); + CX_TEST_ASSERT(tpl->segments->type == STRING_SEGMENT_VAR_PLACEHOLDER); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$12")); + CX_TEST_ASSERT(tpl); // 12 + CX_TEST_ASSERT(tpl->segments); + CX_TEST_ASSERT(!tpl->segments->next); + CX_TEST_ASSERT(!cx_strcmp(tpl->segments->str, "12")); + CX_TEST_ASSERT(tpl->segments->type == STRING_SEGMENT_NUM_PLACEHOLDER); + CX_TEST_ASSERT(tpl->segments->num == 12); + string_template_free(tpl); + + // double segment tests + tpl = string_template_compile(a, cx_str("test $var")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "test ")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s1->str, "var")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_VAR_PLACEHOLDER); + CX_TEST_ASSERT(s1->next == NULL); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("test ${var}")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "test ")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s1->str, "var")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_VAR_PLACEHOLDER); + CX_TEST_ASSERT(s1->next == NULL); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$var test")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "var")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_VAR_PLACEHOLDER); + CX_TEST_ASSERT(!cx_strcmp(s1->str, " test")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(s1->next == NULL); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$13 test")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "13")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_NUM_PLACEHOLDER); + CX_TEST_ASSERT(!cx_strcmp(s1->str, " test")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(s1->next == NULL); + string_template_free(tpl); + + // multi segment tests + tpl = string_template_compile(a, cx_str("test$var1$var2")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + s2 = s1->next; + CX_TEST_ASSERT(2); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "test")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s1->str, "var1")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_VAR_PLACEHOLDER); + CX_TEST_ASSERT(!cx_strcmp(s2->str, "var2")); + CX_TEST_ASSERT(s2->type == STRING_SEGMENT_VAR_PLACEHOLDER); + CX_TEST_ASSERT(s2->next == NULL); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("test/$1/$2")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + s2 = s1->next; + CX_TEST_ASSERT(2); + s3 = s2->next; + CX_TEST_ASSERT(s3); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "test/")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s1->str, "1")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_NUM_PLACEHOLDER); + CX_TEST_ASSERT(!cx_strcmp(s2->str, "/")); + CX_TEST_ASSERT(s2->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s3->str, "2")); + CX_TEST_ASSERT(s3->type == STRING_SEGMENT_NUM_PLACEHOLDER); + CX_TEST_ASSERT(s3->next == NULL); + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("ab$$cd/${1}/${2}")); + CX_TEST_ASSERT(tpl); + s0 = tpl->segments; + CX_TEST_ASSERT(s0); + s1 = s0->next; + CX_TEST_ASSERT(s1); + s2 = s1->next; + CX_TEST_ASSERT(2); + s3 = s2->next; + CX_TEST_ASSERT(s3); + CX_TEST_ASSERT(!cx_strcmp(s0->str, "ab$cd/")); + CX_TEST_ASSERT(s0->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s1->str, "1")); + CX_TEST_ASSERT(s1->type == STRING_SEGMENT_NUM_PLACEHOLDER); + CX_TEST_ASSERT(!cx_strcmp(s2->str, "/")); + CX_TEST_ASSERT(s2->type == STRING_SEGMENT_STR); + CX_TEST_ASSERT(!cx_strcmp(s3->str, "2")); + CX_TEST_ASSERT(s3->type == STRING_SEGMENT_NUM_PLACEHOLDER); + CX_TEST_ASSERT(s3->next == NULL); + string_template_free(tpl); + } + + cxMempoolFree(mp); +} + +CX_TEST(test_string_template_compile_error) { + // TODO +} + +static cxmutstr get_var(const CxAllocator *a, StringTemplateSegment *seg, void *userdata, WSBool *free_str) { + cxmutstr var_value = cx_strcat_a(a, 3, cx_str("var("), seg->str, cx_str(")")); + *free_str = TRUE; + return var_value; +} + +CX_TEST(test_string_template_write_to) { + CxMempool *mp = cxMempoolCreate(100, CX_MEMPOOL_TYPE_ADVANCED); + const CxAllocator *a = mp->allocator; + + CxBuffer buf; + cxBufferInit(&buf, NULL, 1024, a, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + StringTemplate *tpl = NULL; + CX_TEST_DO { + tpl = string_template_compile(a, cx_str("hello world")); + ssize_t r = string_template_write_to(tpl, a, get_var, NULL, &buf, (cx_write_func)cxBufferWrite); + CX_TEST_ASSERT(r > 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, buf.pos), cx_str("hello world"))); + buf.pos = 0; + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("insert $var here")); + r = string_template_write_to(tpl, a, get_var, NULL, &buf, (cx_write_func)cxBufferWrite); + CX_TEST_ASSERT(r > 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, buf.pos), cx_str("insert var(var) here"))); + buf.pos = 0; + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$1$2$3$4$5")); + r = string_template_write_to(tpl, a, get_var, NULL, &buf, (cx_write_func)cxBufferWrite); + CX_TEST_ASSERT(r > 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, buf.pos), cx_str("var(1)var(2)var(3)var(4)var(5)"))); + buf.pos = 0; + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$$escape$$$myvar$$end")); + r = string_template_write_to(tpl, a, get_var, NULL, &buf, (cx_write_func)cxBufferWrite); + CX_TEST_ASSERT(r > 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, buf.pos), cx_str("$escape$var(myvar)$end"))); + buf.pos = 0; + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("$$$$${test}")); + r = string_template_write_to(tpl, a, get_var, NULL, &buf, (cx_write_func)cxBufferWrite); + CX_TEST_ASSERT(r > 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, buf.pos), cx_str("$$var(test)"))); + buf.pos = 0; + string_template_free(tpl); + + tpl = string_template_compile(a, cx_str("${123}end")); + r = string_template_write_to(tpl, a, get_var, NULL, &buf, (cx_write_func)cxBufferWrite); + CX_TEST_ASSERT(r > 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, buf.pos), cx_str("var(123)end"))); + buf.pos = 0; + string_template_free(tpl); + } + cxBufferDestroy(&buf); + cxMempoolFree(mp); +} + +CX_TEST(test_string_template_build_string) { + CxMempool *mp = cxMempoolCreate(100, CX_MEMPOOL_TYPE_ADVANCED); + const CxAllocator *a = mp->allocator; + + StringTemplate *tpl = NULL; + CX_TEST_DO { + tpl = string_template_compile(a, cx_str("insert $var here")); + cxmutstr str = string_template_build_string(tpl, a, get_var, NULL); + CX_TEST_ASSERT(str.ptr); + CX_TEST_ASSERT(!cx_strcmp(str, cx_str("insert var(var) here"))); + string_template_free(tpl); + } + + cxMempoolFree(mp); +} diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/test/strreplace.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/strreplace.h Sat Nov 22 16:44:42 2025 +0100 @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_STRREPLACE_H +#define TEST_STRREPLACE_H + +#include "test.h" + +#ifdef __cplusplus +extern "C" { +#endif + +CX_TEST(test_string_template_compile); +CX_TEST(test_string_template_compile_error); +CX_TEST(test_string_template_write_to); +CX_TEST(test_string_template_build_string); + + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_STRREPLACE_H */ + diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/util/objs.mk --- a/src/server/util/objs.mk Sat Nov 22 14:27:01 2025 +0100 +++ b/src/server/util/objs.mk Sat Nov 22 16:44:42 2025 +0100 @@ -37,6 +37,7 @@ UTILOBJ += pool.o UTILOBJ += shexp.o UTILOBJ += strbuf.o +UTILOBJ += strreplace.o UTILOBJ += system.o UTILOBJ += systhr.o UTILOBJ += thrpool.o diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/util/strreplace.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/util/strreplace.c Sat Nov 22 16:44:42 2025 +0100 @@ -0,0 +1,227 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "strreplace.h" + +#include +#include +#include + +#include + + + +StringTemplate* string_template_compile(const CxAllocator *a, cxstring tpl) { + StringTemplateSegment *end = NULL; // segment list end + int var = FALSE; + int error = FALSE; + + CxBuffer buf; // tmp buffer + if(cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) { + return NULL; + } + + StringTemplate *t = cxMalloc(a, sizeof(StringTemplate)); + if(!t) { + cxBufferDestroy(&buf); + return NULL; + } + t->a = a ? a : cxDefaultAllocator; + t->segments = NULL; + + StringTemplateSegment *seg = NULL; + + for(size_t i=0;itype = var ? STRING_SEGMENT_VAR_PLACEHOLDER : STRING_SEGMENT_STR; + seg->str = (cxmutstr){NULL, 0}; + seg->num = 0; + seg->next = NULL; + // add segment to segment list + if(end) { + end->next = seg; + } else { + t->segments = seg; + } + end = seg; + } else { + error = TRUE; + break; + } + } + + if(var) { + // current segment is a var + if(c == '}') { + var = FALSE; + finish_seg = TRUE; + } else if(c == '{') { + // noop + } else if(!isalnum(c)) { + var = FALSE; + finish_seg = TRUE; + i--; + } else { + add_char = TRUE; + } + } else { + if(c == '$') { + if(i+1 $ + i++; + add_char = TRUE; + } else { + var = TRUE; + if(buf.pos == 0) { + // reuse current segment + seg->type = STRING_SEGMENT_VAR_PLACEHOLDER; + } else { + // create new segment + finish_seg = TRUE; + } + } + } else { + add_char = TRUE; + } + } + + if(add_char) { + if(cxBufferPut(&buf, c) != c) { + error = TRUE; + break; + } + } else if(finish_seg) { + // copy buffer content + cxmutstr seg_str = cx_strdup_a(a, cx_strn(buf.space, buf.pos)); + if(!seg_str.ptr) { + error = TRUE; + break; + } + seg->str = seg_str; + if(seg->type == STRING_SEGMENT_VAR_PLACEHOLDER) { + // is the var segment an integer reference? + if(!cx_strtoi(seg_str, &seg->num, 10)) { + seg->type = STRING_SEGMENT_NUM_PLACEHOLDER; + } + } + buf.pos = 0; + seg = NULL; + } + } + + // finish last segment + if(seg) { + cxmutstr seg_str = cx_strdup_a(a, cx_strn(buf.space, buf.pos)); + if(!seg_str.ptr) { + error = TRUE; + } else { + seg->str = seg_str; + if(seg->type == STRING_SEGMENT_VAR_PLACEHOLDER) { + if(!cx_strtoi(seg_str, &seg->num, 10)) { + seg->type = STRING_SEGMENT_NUM_PLACEHOLDER; + } + } + } + } + + cxBufferDestroy(&buf); + if(error) { + string_template_free(t); + return NULL; + } + + return t; +} + +void string_template_free(StringTemplate *tpl) { + StringTemplateSegment *seg = tpl->segments; + while(seg) { + StringTemplateSegment *next = seg->next; + cxFree(tpl->a, seg->str.ptr); + cxFree(tpl->a, seg); + seg = next; + } + cxFree(tpl->a, tpl); +} + +ssize_t string_template_write_to(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata, void *stream, cx_write_func writef) { + if(!tpl) { + return -1; + } + + // write each segment to the stream + StringTemplateSegment *seg = tpl->segments; + ssize_t w = 0; + while(seg) { + if(seg->type == STRING_SEGMENT_STR) { + // just write the segment string + if(seg->str.length > 0) { + size_t r = writef(seg->str.ptr, 1, seg->str.length, stream); + if(r != seg->str.length) { + return -1; + } + w += r; + } + } else if(varfunc) { + // convert var segment to value + WSBool free_str = FALSE; + cxmutstr str = varfunc(a, seg, userdata, &free_str); + if(str.length > 0) { + size_t r = writef(str.ptr, 1, str.length, stream); + if(r != str.length) { + return -1; + } + w += r; + } + if(free_str) { + cxFree(a, str.ptr); + } + } + seg = seg->next; + } + return w; +} + +cxmutstr string_template_build_string(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata) { + CxBuffer buf; + cxBufferInit(&buf, NULL, 1024, a, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + + ssize_t w = string_template_write_to(tpl, a, varfunc, userdata, &buf, (cx_write_func)cxBufferWrite); + if(w < 0 || cxBufferTerminate(&buf)) { + cxBufferDestroy(&buf); + return (cxmutstr){ NULL, 0 }; + } + return (cxmutstr){ buf.space, buf.size }; +} diff -r 392ec9026b07 -r 9728d3a2ac97 src/server/util/strreplace.h --- a/src/server/util/strreplace.h Sat Nov 22 14:27:01 2025 +0100 +++ b/src/server/util/strreplace.h Sat Nov 22 16:44:42 2025 +0100 @@ -29,6 +29,8 @@ #ifndef STRINGREPLACE_H #define STRINGREPLACE_H +#include "../public/nsapi.h" + #include #include @@ -65,7 +67,7 @@ * STRING_SEGMENT_NUM_PLACEHOLDER: null * STRING_SEGMENT_VAR_PLACEHOLDER: variable name */ - cxstring str; + cxmutstr str; /* * Segment type @@ -99,9 +101,8 @@ * * a: The allocator to use for building the compiled template * tpl: Semplate string - * delim: Delimiter chars for variable names */ -StringTemplate string_template_compile(const CxAllocator *a, cxstring tpl, char *delim); +StringTemplate* string_template_compile(const CxAllocator *a, cxstring tpl); /* * Builds a string using the provided template and writes it to the stream. @@ -113,14 +114,18 @@ * userdata: The userdata pointer passed to the strtpl_var_func callback * stream: The stream object to which the resulting string should be written * writef: Stream write function + * + * returns the number of written bytes or -1 on error */ -int string_template_write_to(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata, void *stream, cx_write_func writef); +ssize_t string_template_write_to(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata, void *stream, cx_write_func writef); /* * Builds a string, using the provided template and allocator */ cxmutstr string_template_build_string(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata); +void string_template_free(StringTemplate *tpl); + #ifdef __cplusplus } #endif