all: $(APP_BIN) $(TEST_BIN)
$(APP_BIN): $(MAIN_OBJ) $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
- $(LD) -o $(APP_BIN) $(MAIN_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
+ $(LD) -o $(APP_BIN) $(MAIN_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c -lnoteutils $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
$(TEST_BIN): $(OBJ) $(TEST_OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
- $(CC) -o $(TEST_BIN) $(TEST_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
+ $(CC) -o $(TEST_BIN) $(TEST_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) -lidav -ldbutils -lmd4c -lnoteutils $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS)
../build/application/%$(OBJ_EXT): %.c
$(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
include config.mk
BUILD_DIRS = build/bin build/lib
-BUILD_DIRS += build/application build/application/tests build/ucx build/libidav build/dbutils build/md4c
+BUILD_DIRS += build/application build/application/tests build/ucx build/libidav build/dbutils build/md4c build/utils
BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
all: $(BUILD_DIRS) ucx dbutils ui libidav application
md4c: FORCE
cd md4c; $(MAKE) all
+utils: FORCE
+ cd utils; $(MAKE) all
+
dbutils: ucx FORCE
cd dbutils; $(MAKE) all
libidav: ucx FORCE
cd libidav; $(MAKE) all
-application: ui libidav md4c FORCE
+application: ui libidav md4c utils FORCE
cd application; $(MAKE)
FORCE:
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2018 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 ../config.mk
+
+# list of source files
+SRC = strreplace.c
+
+OBJ = $(SRC:%.c=../build/utils/%$(OBJ_EXT))
+
+LIBNOTEUTILS = ../build/lib/libnoteutils$(LIB_EXT)
+
+all: ../build/ucx $(LIBNOTEUTILS)
+
+$(LIBNOTEUTILS): $(OBJ)
+ $(AR) $(ARFLAGS) $(AOFLAGS)$@ $(OBJ)
+
+../build/utils/%$(OBJ_EXT): %.c
+ $(CC) -I../ucx $(CFLAGS) $(DBU_CFLAGS) -c -o $@ $<
+
+../build/ucx:
+ test -d '$@'
+
+
--- /dev/null
+/*
+ * 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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include <cx/buffer.h>
+
+
+
+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, NULL, 128, 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;i<tpl.length;i++) {
+ char c = tpl.ptr[i];
+ int add_char = false; // add current char to the buffer
+ int finish_seg = false; // copy buffer to segment string and start new segment
+
+ if(!seg) {
+ // start new segment
+ seg = cxMalloc(a, sizeof(StringTemplateSegment));
+ if(seg) {
+ seg->type = 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<tpl.length && tpl.ptr[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
+ bool 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, a, NULL, 1024, 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 };
+}
--- /dev/null
+/*
+ * 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 STRINGREPLACE_H
+#define STRINGREPLACE_H
+
+#include <stdbool.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum StringSegmentType {
+ /*
+ * Static string segment
+ */
+ STRING_SEGMENT_STR = 0,
+ /*
+ * Placeholder referenced by number, e.g., $1, $2, ...
+ */
+ STRING_SEGMENT_NUM_PLACEHOLDER,
+ /*
+ * Placeholder referenced by name, e.g., ${varname} or $varname
+ */
+ STRING_SEGMENT_VAR_PLACEHOLDER
+} StringSegmentType;
+
+typedef struct StringTemplateSegment StringTemplateSegment;
+typedef struct StringTemplate StringTemplate;
+
+struct StringTemplate {
+ const CxAllocator *a;
+ StringTemplateSegment *segments;
+};
+
+struct StringTemplateSegment {
+ /*
+ * STRING_SEGMENT_STR: static string segment
+ * STRING_SEGMENT_NUM_PLACEHOLDER: null
+ * STRING_SEGMENT_VAR_PLACEHOLDER: variable name
+ */
+ cxmutstr str;
+
+ /*
+ * Segment type
+ */
+ StringSegmentType type;
+
+ /*
+ * reference number if type is STRING_SEGMENT_NUM_PLACEHOLDER
+ */
+ int num;
+
+ /*
+ * Next segment
+ */
+ StringTemplateSegment *next;
+};
+
+/*
+ * Callback for converting a var segment into a string
+ *
+ * a: The allocator passed to string_template_write_to
+ * seg: Placeholder segment
+ * userdata: The userdata pointer passed to string_template_write_to
+ * free_str: If set to true, the returned string pointer will be freed
+ * using the allocator's free function.
+ */
+typedef cxmutstr (*strtpl_var_func)(const CxAllocator *a, StringTemplateSegment *seg, void *userdata, bool *free_str);
+
+/*
+ * Compiles a string template
+ *
+ * a: The allocator to use for building the compiled template
+ * tpl: Semplate string
+ */
+StringTemplate* string_template_compile(const CxAllocator *a, cxstring tpl);
+
+/*
+ * Builds a string using the provided template and writes it to the stream.
+ *
+ * tpl: Template
+ * a: aThe allocator passed to the strtpl_var_func callback
+ * varfunc: The callback used for converting
+ * STRING_SEGMENT_NUM_PLACEHOLDER and STRING_SEGMENT_VAR_PLACEHOLDER segments
+ * 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
+ */
+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
+
+#endif /* STRINGREPLACE_H */
+