chore: copy over project files
						commit
						7c63b3db1d
					
				| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
root = true
 | 
			
		||||
 | 
			
		||||
[*.{c,cpp,h}]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
build/
 | 
			
		||||
.cache/
 | 
			
		||||
compile_commands.json
 | 
			
		||||
.cache/
 | 
			
		||||
vgcore.*
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,133 @@
 | 
			
		|||
# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great
 | 
			
		||||
# base for this Makefile
 | 
			
		||||
 | 
			
		||||
-include config.mk
 | 
			
		||||
 | 
			
		||||
LIB := $(BUILD_DIR)/$(LIB_FILENAME)
 | 
			
		||||
 | 
			
		||||
SRCS != find '$(SRC_DIR)' -iname '*.c'
 | 
			
		||||
SRCS_H != find include -iname '*.h'
 | 
			
		||||
SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h'
 | 
			
		||||
SRCS_TEST != find '$(TEST_DIR)' -iname '*.c'
 | 
			
		||||
SRCS_EXAMPLE != find '$(EXAMPLE_DIR)' -iname '*.c'
 | 
			
		||||
 | 
			
		||||
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
 | 
			
		||||
OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o)
 | 
			
		||||
OBJS_EXAMPLE := $(SRCS_EXAMPLE:%=$(BUILD_DIR)/%.o)
 | 
			
		||||
DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d)
 | 
			
		||||
 | 
			
		||||
BINS_TEST := $(OBJS_TEST:%.c.o=%)
 | 
			
		||||
BINS_EXAMPLE := $(OBJS_EXAMPLE:%.c.o=%)
 | 
			
		||||
 | 
			
		||||
TARGETS_TEST := $(BINS_TEST:%=test-%)
 | 
			
		||||
TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%)
 | 
			
		||||
TARGETS_EXAMPLE := $(BINS_EXAMPLE:%=test-%)
 | 
			
		||||
 | 
			
		||||
_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra
 | 
			
		||||
 | 
			
		||||
.PHONY: all
 | 
			
		||||
all: lib
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# =====COMPILATION=====
 | 
			
		||||
# Utility used by the CI to lint
 | 
			
		||||
.PHONY: objs
 | 
			
		||||
objs: $(OBJS)
 | 
			
		||||
 | 
			
		||||
.PHONY: lib
 | 
			
		||||
lib: $(LIB)
 | 
			
		||||
$(LIB): $(OBJS)
 | 
			
		||||
	ar -rcs $@ $(OBJS)
 | 
			
		||||
 | 
			
		||||
$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c
 | 
			
		||||
	mkdir -p $(dir $@)
 | 
			
		||||
	$(CC) -c $(_CFLAGS) $< -o $@
 | 
			
		||||
 | 
			
		||||
# =====TESTING=====
 | 
			
		||||
.PHONY: test
 | 
			
		||||
test: $(TARGETS_TEST)
 | 
			
		||||
 | 
			
		||||
.PHONY: test-mem
 | 
			
		||||
test-mem: $(TARGETS_MEM_TEST)
 | 
			
		||||
 | 
			
		||||
.PHONY: $(TARGETS_TEST)
 | 
			
		||||
$(TARGETS_TEST): test-%: %
 | 
			
		||||
	./$^
 | 
			
		||||
 | 
			
		||||
.PHONY: $(TARGETS_MEM_TEST)
 | 
			
		||||
$(TARGETS_MEM_TEST): test-mem-%: %
 | 
			
		||||
	valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^
 | 
			
		||||
 | 
			
		||||
.PHONY: build-test
 | 
			
		||||
build-test: $(BINS_TEST)
 | 
			
		||||
 | 
			
		||||
$(BINS_TEST): %: %.c.o $(LIB)
 | 
			
		||||
	$(CC) \
 | 
			
		||||
		$^ -o $@
 | 
			
		||||
 | 
			
		||||
# Along with the include directory, each test includes $(TEST_DIR) (which
 | 
			
		||||
# contains the acutest.h header file), and the src directory of the module it's
 | 
			
		||||
# testing. This allows tests to access internal methods, which aren't publicly
 | 
			
		||||
# exposed.
 | 
			
		||||
$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
 | 
			
		||||
	mkdir -p $(dir $@)
 | 
			
		||||
	$(CC) $(_CFLAGS) -I$(TEST_DIR) \
 | 
			
		||||
		-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
 | 
			
		||||
		-c $< -o $@
 | 
			
		||||
 | 
			
		||||
# =====EXAMPLES=====
 | 
			
		||||
.PHONY: build-example
 | 
			
		||||
build-example: $(BINS_EXAMPLE)
 | 
			
		||||
 | 
			
		||||
$(BINS_EXAMPLE): %: %.c.o $(LIB)
 | 
			
		||||
	$(CC) \
 | 
			
		||||
		$^ -o $@
 | 
			
		||||
 | 
			
		||||
# Example binaries link the resulting library
 | 
			
		||||
$(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c
 | 
			
		||||
	mkdir -p $(dir $@)
 | 
			
		||||
	$(CC) $(_CFLAGS) -I$(PUB_INC_DIR) -c $< -o $@
 | 
			
		||||
 | 
			
		||||
# =====MAINTENANCE=====
 | 
			
		||||
.PHONY: lint
 | 
			
		||||
lint:
 | 
			
		||||
	clang-format -n --Werror \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS)) \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS_H)) \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL))
 | 
			
		||||
 | 
			
		||||
.PHONY: fmt
 | 
			
		||||
fmt:
 | 
			
		||||
	clang-format -i \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS)) \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS_H)) \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL))
 | 
			
		||||
 | 
			
		||||
.PHONY: check
 | 
			
		||||
check:
 | 
			
		||||
	mkdir -p $(BUILD_DIR)/cppcheck
 | 
			
		||||
	cppcheck \
 | 
			
		||||
		$(addprefix -I,$(INC_DIRS)) \
 | 
			
		||||
		--cppcheck-build-dir=$(BUILD_DIR)/cppcheck \
 | 
			
		||||
		--error-exitcode=1 \
 | 
			
		||||
		--enable=warning,style \
 | 
			
		||||
		--inline-suppr \
 | 
			
		||||
		--check-level=exhaustive \
 | 
			
		||||
		--quiet \
 | 
			
		||||
		-j$(shell nproc) \
 | 
			
		||||
		$(filter-out $(THIRDPARTY),$(SRCS))
 | 
			
		||||
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
clean:
 | 
			
		||||
	rm -rf '$(BUILD_DIR)'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.PHONY: bear
 | 
			
		||||
bear: clean
 | 
			
		||||
	bear -- make
 | 
			
		||||
	bear --append -- make build-test
 | 
			
		||||
	bear --append -- make build-example
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Make make aware of the .d files
 | 
			
		||||
-include $(DEPS)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
# LTM - Lander Template Module
 | 
			
		||||
 | 
			
		||||
This module provides an interface for generating `char` streams from a given
 | 
			
		||||
template.
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<body>
 | 
			
		||||
    <h1>{{ title }}</h1>
 | 
			
		||||
    <ul>
 | 
			
		||||
{{ item start }}
 | 
			
		||||
        <li><a href="{{ url }}">{{ name }}</a></li>
 | 
			
		||||
{{ item end }}
 | 
			
		||||
    </ul>
 | 
			
		||||
</body>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The above snippet shows everything the templating language currently offers;
 | 
			
		||||
either singular variables (here: `title`) or nested templates (here: `item`).
 | 
			
		||||
Note the absence of a loop construct. This is because each variable and nested
 | 
			
		||||
template is implicitely already a loop; a variable or a nested template can be
 | 
			
		||||
added as many times as needed, and they will appear in the order they were
 | 
			
		||||
added.
 | 
			
		||||
 | 
			
		||||
Template strings are compiled into templates. From a template, an instance can
 | 
			
		||||
be created to which the required data can be added to fill in the template.
 | 
			
		||||
Instantiating a template does not modify the original template, meaning
 | 
			
		||||
templates can be safely used concurrently.
 | 
			
		||||
 | 
			
		||||
Each inserted variable can get its data from a variety of sources, namely
 | 
			
		||||
`char` buffers, files or a custom reader function. The resulting `char` stream
 | 
			
		||||
is never fully loaded into memory; an instance can stream its content to a
 | 
			
		||||
provided buffer to prevent unnecessary memory usage.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
LIB_FILENAME = libltm.a
 | 
			
		||||
 | 
			
		||||
BUILD_DIR = build
 | 
			
		||||
SRC_DIR = src
 | 
			
		||||
TEST_DIR = test
 | 
			
		||||
EXAMPLE_DIR = example
 | 
			
		||||
THIRDPARTY = 
 | 
			
		||||
 | 
			
		||||
PUB_INC_DIR = include
 | 
			
		||||
INC_DIRS = $(PUB_INC_DIR) src/_include
 | 
			
		||||
 | 
			
		||||
# -MMD: generate a .d file for every source file. This file can be imported by
 | 
			
		||||
#  make and makes make aware that a header file has been changed, ensuring an
 | 
			
		||||
#  object file is also recompiled if only a header is changed.
 | 
			
		||||
# -MP: generate a dummy target for every header file (according to the  docs it
 | 
			
		||||
#  prevents some errors when removing header files)
 | 
			
		||||
CFLAGS ?= -MMD -MP -g -Wall -Werror
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
 | 
			
		||||
#include "ltm/template.h"
 | 
			
		||||
 | 
			
		||||
const char *s =  "<head><link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css\">\n"
 | 
			
		||||
  "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js\"></script>\n"
 | 
			
		||||
  "<script>hljs.highlightAll();</script></head>\n"
 | 
			
		||||
  "<body><pre><code>{{ paste }}</code></pre></body>";
 | 
			
		||||
 | 
			
		||||
  ltm_err reader(size_t *written, char *buf, size_t len, void *data) {
 | 
			
		||||
    FILE *f = data;
 | 
			
		||||
 | 
			
		||||
    *written = fread(buf, 1, len, f);
 | 
			
		||||
 | 
			
		||||
    return ltm_err_ok;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  ltm_template_compile(&template, s);
 | 
			
		||||
  ltm_instance *instance;
 | 
			
		||||
  ltm_template_instantiate(&instance, template);
 | 
			
		||||
 | 
			
		||||
  const char *filename = "src/ltm_instance.c";
 | 
			
		||||
 | 
			
		||||
  struct stat sb;
 | 
			
		||||
  stat(filename, &sb);
 | 
			
		||||
 | 
			
		||||
  FILE *f = fopen(filename, "rb");
 | 
			
		||||
 | 
			
		||||
  ltm_instance_block_add_var_fn(instance, "paste", reader, f, sb.st_size);
 | 
			
		||||
  /* ltm_instance_block_add_var(instance, "paste", ltm_instance_block_type_file_owned, f, sb.st_size); */
 | 
			
		||||
  /* ltm_instance_block_add_var(instance, "paste", ltm_instance_block_type_buf, "hello\n", 6); */
 | 
			
		||||
  /* ltm_instance_block_add_var(instance, "paste", ltm_instance_block_type_buf, "world\n", 6); */
 | 
			
		||||
  
 | 
			
		||||
  char buf[128];
 | 
			
		||||
  size_t written = 0;
 | 
			
		||||
 | 
			
		||||
  while (ltm_instance_write(&written, buf, 128, instance) != ltm_err_done) {
 | 
			
		||||
    printf("%.*s", (int)written, buf);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  printf("%.*s", (int)written, buf);
 | 
			
		||||
 | 
			
		||||
  ltm_instance_free(instance);
 | 
			
		||||
  ltm_template_free(template);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
#ifndef LTM_COMMON
 | 
			
		||||
#define LTM_COMMON
 | 
			
		||||
 | 
			
		||||
#define LTM_RES(x)                                                             \
 | 
			
		||||
  {                                                                            \
 | 
			
		||||
    ltm_err res = x;                                                           \
 | 
			
		||||
    if (res != ltm_err_ok)                                                     \
 | 
			
		||||
      return res;                                                              \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define LTM_RES2(x, e)                                                         \
 | 
			
		||||
  {                                                                            \
 | 
			
		||||
    ltm_err res = x;                                                           \
 | 
			
		||||
    if (res != ltm_err_ok) {                                                   \
 | 
			
		||||
      e;                                                                       \
 | 
			
		||||
      return res;                                                              \
 | 
			
		||||
    }                                                                          \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#define LTM_MIN(x, y) ((x) < (y) ? (x) : (y))
 | 
			
		||||
#define LTM_MAX(x, y) ((x) > (y) ? (x) : (y))
 | 
			
		||||
 | 
			
		||||
typedef enum ltm_err {
 | 
			
		||||
  ltm_err_ok = 0,
 | 
			
		||||
  ltm_err_invalid_template,
 | 
			
		||||
  ltm_err_failed_alloc,
 | 
			
		||||
  ltm_err_failed_io,
 | 
			
		||||
  ltm_err_not_found,
 | 
			
		||||
  ltm_err_wrong_block_type,
 | 
			
		||||
  ltm_err_done,
 | 
			
		||||
} ltm_err;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
#ifndef LTM_TEMPLATE
 | 
			
		||||
#define LTM_TEMPLATE
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include "ltm/common.h"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a compiled template
 | 
			
		||||
 */
 | 
			
		||||
typedef struct ltm_template ltm_template;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Compile the given template.
 | 
			
		||||
 *
 | 
			
		||||
 * @param out where to store pointer to newly allocated `ltm_template`
 | 
			
		||||
 * @param template nul-terminated string containing the template
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_template_compile(ltm_template **out, const char *template);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Compile the given template with a given length.
 | 
			
		||||
 *
 | 
			
		||||
 * @param out where to store pointer to newly allocated `ltm_template`
 | 
			
		||||
 * @param template char buffer containing the template
 | 
			
		||||
 * @param len length of the char buffer
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_template_compile_n(ltm_template **out, const char *template,
 | 
			
		||||
                               size_t len);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Free the template instance. After freeing a template, no instances associated
 | 
			
		||||
 * with it are safe to use.
 | 
			
		||||
 */
 | 
			
		||||
void ltm_template_free(ltm_template *template);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a specific instance of a template.
 | 
			
		||||
 */
 | 
			
		||||
typedef struct ltm_instance ltm_instance;
 | 
			
		||||
 | 
			
		||||
typedef ltm_err (*ltm_data_fn)(size_t *written, char *buf, size_t len,
 | 
			
		||||
                               void *data);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new instance of the given template.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_template_instantiate(ltm_instance **out,
 | 
			
		||||
                                 const ltm_template *template);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Free the given instance, as well as all nested instances.
 | 
			
		||||
 */
 | 
			
		||||
void ltm_instance_free(ltm_instance *instance);
 | 
			
		||||
 | 
			
		||||
typedef enum ltm_instance_block_type {
 | 
			
		||||
  ltm_instance_block_type_buf = 0,
 | 
			
		||||
  ltm_instance_block_type_buf_owned,
 | 
			
		||||
  ltm_instance_block_type_file,
 | 
			
		||||
  ltm_instance_block_type_file_owned,
 | 
			
		||||
  ltm_instance_block_type_nested,
 | 
			
		||||
  ltm_instance_block_type_fn,
 | 
			
		||||
} ltm_instance_block_type;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new variable to the template.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_instance_block_add_var(ltm_instance *instance, const char *name,
 | 
			
		||||
                                   ltm_instance_block_type type, void *data,
 | 
			
		||||
                                   size_t len);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new variable to the template whose data is provided by a data function.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_instance_block_add_var_fn(ltm_instance *instance, const char *name,
 | 
			
		||||
                                      ltm_data_fn fn, void *data, size_t len);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new nested instance to the instance, returning a handle to the nested
 | 
			
		||||
 * instance.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_instance_block_add_nested(ltm_instance **out,
 | 
			
		||||
                                      ltm_instance *instance, const char *name);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Calculate the size of the resulting output of this instance.
 | 
			
		||||
 */
 | 
			
		||||
size_t ltm_instance_size(const ltm_instance *instance);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Write at most `len` bytes to the given buffer.
 | 
			
		||||
 *
 | 
			
		||||
 * @param written outputs how many bytes were written to `buf`
 | 
			
		||||
 * @param buf buffer to write to
 | 
			
		||||
 * @param len length of the buffer
 | 
			
		||||
 * @param instance instance to write
 | 
			
		||||
 * @return `ltm_err_ok` if write was successful but there's more to be written,
 | 
			
		||||
 * `ltm_err_done` if everything has been written successfully, or some other
 | 
			
		||||
 * error code
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_instance_write(size_t *written, char *buf, size_t len,
 | 
			
		||||
                           ltm_instance *instance);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,119 @@
 | 
			
		|||
#ifndef LTM_TEMPLATE_INTERNAL
 | 
			
		||||
#define LTM_TEMPLATE_INTERNAL
 | 
			
		||||
 | 
			
		||||
#include "ltm/template.h"
 | 
			
		||||
 | 
			
		||||
typedef enum ltm_placeholder_type {
 | 
			
		||||
  ltm_placeholder_type_invalid = 0,
 | 
			
		||||
  ltm_placeholder_type_var,
 | 
			
		||||
  ltm_placeholder_type_nested_start,
 | 
			
		||||
  ltm_placeholder_type_nested_end,
 | 
			
		||||
} ltm_placeholder_type;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Internal struct used when compiling templates
 | 
			
		||||
 */
 | 
			
		||||
typedef struct ltm_placeholder {
 | 
			
		||||
  const char *start;
 | 
			
		||||
  const char *end;
 | 
			
		||||
  struct {
 | 
			
		||||
    const char *s;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } name;
 | 
			
		||||
  ltm_placeholder_type type;
 | 
			
		||||
} ltm_placeholder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The type of a template block
 | 
			
		||||
 */
 | 
			
		||||
typedef enum ltm_template_block_type {
 | 
			
		||||
  ltm_template_block_type_literal = 0,
 | 
			
		||||
  ltm_template_block_type_var,
 | 
			
		||||
  ltm_template_block_type_nested,
 | 
			
		||||
} ltm_template_block_type;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A block in a template.
 | 
			
		||||
 */
 | 
			
		||||
typedef struct ltm_template_block {
 | 
			
		||||
  ltm_template_block_type type;
 | 
			
		||||
  struct {
 | 
			
		||||
    void *ptr;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } data;
 | 
			
		||||
} ltm_template_block;
 | 
			
		||||
 | 
			
		||||
typedef struct ltm_template_block_name {
 | 
			
		||||
  struct {
 | 
			
		||||
    const char *s;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } name;
 | 
			
		||||
  size_t index;
 | 
			
		||||
} ltm_template_block_name;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a compiled template. A template consists of a list of blocks, of
 | 
			
		||||
 * which some have names that can be indexed (e.g. variables, nested templates).
 | 
			
		||||
 */
 | 
			
		||||
struct ltm_template {
 | 
			
		||||
  struct {
 | 
			
		||||
    ltm_template_block *arr;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } blocks;
 | 
			
		||||
  struct {
 | 
			
		||||
    ltm_template_block_name *arr;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } names;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Allocate a new `ltm_template`.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_template_init(ltm_template **out);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Append a name to the template's list.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_template_name_append(ltm_template *template, const char *s,
 | 
			
		||||
                                 size_t len, size_t index);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Append a block to the template's list.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_template_block_append(ltm_template *template,
 | 
			
		||||
                                  ltm_template_block_type type, void *data,
 | 
			
		||||
                                  size_t len);
 | 
			
		||||
 | 
			
		||||
typedef struct ltm_instance_block {
 | 
			
		||||
  ltm_instance_block_type type;
 | 
			
		||||
  ltm_data_fn fn;
 | 
			
		||||
  struct {
 | 
			
		||||
    void *ptr;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } data;
 | 
			
		||||
  struct ltm_instance_block *next;
 | 
			
		||||
} ltm_instance_block;
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_block_init(ltm_instance_block **out);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a new block to the instance with the given type.
 | 
			
		||||
 */
 | 
			
		||||
ltm_err ltm_instance_block_add(ltm_instance_block **out, ltm_instance *instance,
 | 
			
		||||
                               const char *name, ltm_instance_block_type type);
 | 
			
		||||
 | 
			
		||||
struct ltm_instance {
 | 
			
		||||
  const ltm_template *template;
 | 
			
		||||
  struct {
 | 
			
		||||
    ltm_instance_block *head;
 | 
			
		||||
    ltm_instance_block *tail;
 | 
			
		||||
    ltm_instance_block *current;
 | 
			
		||||
  } blocks;
 | 
			
		||||
  ltm_instance_block ***vars;
 | 
			
		||||
  // How many bytes of the current block have been written
 | 
			
		||||
  size_t written;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_init(ltm_instance **out);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,350 @@
 | 
			
		|||
#include <stdbool.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include "ltm/common.h"
 | 
			
		||||
#include "ltm/template.h"
 | 
			
		||||
#include "ltm/template_internal.h"
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_block_init(ltm_instance_block **out) {
 | 
			
		||||
  ltm_instance_block *block = calloc(1, sizeof(ltm_instance_block));
 | 
			
		||||
 | 
			
		||||
  if (block == NULL) {
 | 
			
		||||
    return ltm_err_failed_alloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *out = block;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_init(ltm_instance **out) {
 | 
			
		||||
  ltm_instance *instance = calloc(1, sizeof(ltm_instance));
 | 
			
		||||
 | 
			
		||||
  if (instance == NULL) {
 | 
			
		||||
    return ltm_err_failed_alloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *out = instance;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ltm_instance_free(ltm_instance *instance) {
 | 
			
		||||
  if (instance->vars != NULL) {
 | 
			
		||||
    free(instance->vars);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_instance_block *block = instance->blocks.head;
 | 
			
		||||
 | 
			
		||||
  while (block != NULL) {
 | 
			
		||||
    switch (block->type) {
 | 
			
		||||
    case ltm_instance_block_type_buf_owned:
 | 
			
		||||
      free(block->data.ptr);
 | 
			
		||||
      break;
 | 
			
		||||
    case ltm_instance_block_type_file_owned:
 | 
			
		||||
      fclose(block->data.ptr);
 | 
			
		||||
      break;
 | 
			
		||||
    case ltm_instance_block_type_nested:
 | 
			
		||||
      ltm_instance_free(block->data.ptr);
 | 
			
		||||
      break;
 | 
			
		||||
    case ltm_instance_block_type_file:
 | 
			
		||||
    case ltm_instance_block_type_fn:
 | 
			
		||||
    case ltm_instance_block_type_buf:;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ltm_instance_block *temp = block->next;
 | 
			
		||||
    free(block);
 | 
			
		||||
    block = temp;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  free(instance);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_instantiate(ltm_instance **out,
 | 
			
		||||
                                 const ltm_template *template) {
 | 
			
		||||
  ltm_instance *instance;
 | 
			
		||||
  LTM_RES(ltm_instance_init(&instance));
 | 
			
		||||
 | 
			
		||||
  instance->template = template;
 | 
			
		||||
 | 
			
		||||
  ltm_instance_block ***vars = NULL;
 | 
			
		||||
 | 
			
		||||
  if (template->names.len > 0) {
 | 
			
		||||
    vars = malloc(template->names.len * sizeof(ltm_instance_block **));
 | 
			
		||||
 | 
			
		||||
    if (vars == NULL) {
 | 
			
		||||
      ltm_instance_free(instance);
 | 
			
		||||
 | 
			
		||||
      return ltm_err_failed_alloc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    instance->vars = vars;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t block_index = 0; block_index < template->blocks.len;
 | 
			
		||||
       block_index++) {
 | 
			
		||||
    ltm_template_block *template_block = &template->blocks.arr[block_index];
 | 
			
		||||
 | 
			
		||||
    switch (template_block->type) {
 | 
			
		||||
    case ltm_template_block_type_literal: {
 | 
			
		||||
      ltm_instance_block *block;
 | 
			
		||||
      LTM_RES2(ltm_instance_block_init(&block), ltm_instance_free(instance));
 | 
			
		||||
 | 
			
		||||
      block->type = ltm_instance_block_type_buf;
 | 
			
		||||
      block->data.ptr = template_block->data.ptr;
 | 
			
		||||
      block->data.len = template_block->data.len;
 | 
			
		||||
 | 
			
		||||
      if (instance->blocks.head == NULL) {
 | 
			
		||||
        instance->blocks.head = block;
 | 
			
		||||
        instance->blocks.tail = block;
 | 
			
		||||
        instance->blocks.current = block;
 | 
			
		||||
      } else {
 | 
			
		||||
        instance->blocks.tail->next = block;
 | 
			
		||||
        instance->blocks.tail = block;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    case ltm_template_block_type_var:
 | 
			
		||||
    case ltm_template_block_type_nested:
 | 
			
		||||
      // Account for the possibility a template starts with a placeholder
 | 
			
		||||
      *vars = instance->blocks.tail != NULL ? &instance->blocks.tail->next
 | 
			
		||||
                                            : &instance->blocks.head;
 | 
			
		||||
      vars++;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *out = instance;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_block_add(ltm_instance_block **out, ltm_instance *instance,
 | 
			
		||||
                               const char *name, ltm_instance_block_type type) {
 | 
			
		||||
  const ltm_template *template = instance->template;
 | 
			
		||||
 | 
			
		||||
  ltm_template_block_name *block_name = NULL;
 | 
			
		||||
  size_t i = 0;
 | 
			
		||||
 | 
			
		||||
  while (i < template->names.len) {
 | 
			
		||||
    block_name = &template->names.arr[i];
 | 
			
		||||
 | 
			
		||||
    if (strncmp(name, block_name->name.s, block_name->name.len) == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    i++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (i == template->names.len) {
 | 
			
		||||
    return ltm_err_not_found;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_template_block *template_block = &template->blocks.arr[block_name->index];
 | 
			
		||||
  bool correct_type = false;
 | 
			
		||||
 | 
			
		||||
  switch (type) {
 | 
			
		||||
  case ltm_instance_block_type_nested:
 | 
			
		||||
    correct_type = template_block->type == ltm_template_block_type_nested;
 | 
			
		||||
    break;
 | 
			
		||||
  case ltm_instance_block_type_buf:
 | 
			
		||||
  case ltm_instance_block_type_buf_owned:
 | 
			
		||||
  case ltm_instance_block_type_file:
 | 
			
		||||
  case ltm_instance_block_type_file_owned:
 | 
			
		||||
  case ltm_instance_block_type_fn:
 | 
			
		||||
    correct_type = template_block->type == ltm_template_block_type_var;
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!correct_type) {
 | 
			
		||||
    return ltm_err_wrong_block_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_instance_block *block;
 | 
			
		||||
  LTM_RES(ltm_instance_block_init(&block));
 | 
			
		||||
 | 
			
		||||
  block->type = type;
 | 
			
		||||
 | 
			
		||||
  // Insert the block in the linked list
 | 
			
		||||
  block->next = *instance->vars[i];
 | 
			
		||||
  *instance->vars[i] = block;
 | 
			
		||||
  instance->vars[i] = &block->next;
 | 
			
		||||
 | 
			
		||||
  *out = block;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_block_add_var(ltm_instance *instance, const char *name,
 | 
			
		||||
                                   ltm_instance_block_type type, void *data,
 | 
			
		||||
                                   size_t len) {
 | 
			
		||||
  ltm_instance_block *block;
 | 
			
		||||
  LTM_RES(ltm_instance_block_add(&block, instance, name, type));
 | 
			
		||||
 | 
			
		||||
  block->data.ptr = data;
 | 
			
		||||
  block->data.len = len;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_block_add_var_fn(ltm_instance *instance, const char *name,
 | 
			
		||||
                                      ltm_data_fn fn, void *data, size_t len) {
 | 
			
		||||
  ltm_instance_block *block;
 | 
			
		||||
  LTM_RES(ltm_instance_block_add(&block, instance, name,
 | 
			
		||||
                                 ltm_instance_block_type_fn));
 | 
			
		||||
 | 
			
		||||
  block->fn = fn;
 | 
			
		||||
  block->data.ptr = data;
 | 
			
		||||
  block->data.len = len;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_block_add_nested(ltm_instance **out,
 | 
			
		||||
                                      ltm_instance *instance,
 | 
			
		||||
                                      const char *name) {
 | 
			
		||||
  const ltm_template *template = instance->template;
 | 
			
		||||
 | 
			
		||||
  ltm_template_block_name *block_name = NULL;
 | 
			
		||||
  size_t i = 0;
 | 
			
		||||
 | 
			
		||||
  while (i < template->names.len) {
 | 
			
		||||
    block_name = &template->names.arr[i];
 | 
			
		||||
 | 
			
		||||
    if (strncmp(name, block_name->name.s, block_name->name.len) == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    i++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (i == template->names.len) {
 | 
			
		||||
    return ltm_err_not_found;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_template_block *template_block = &template->blocks.arr[block_name->index];
 | 
			
		||||
 | 
			
		||||
  if (template_block->type != ltm_template_block_type_nested) {
 | 
			
		||||
    return ltm_err_wrong_block_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_instance_block *block;
 | 
			
		||||
  LTM_RES(ltm_instance_block_init(&block));
 | 
			
		||||
 | 
			
		||||
  ltm_instance *nested;
 | 
			
		||||
  LTM_RES2(ltm_template_instantiate(&nested, template_block->data.ptr),
 | 
			
		||||
           free(block));
 | 
			
		||||
 | 
			
		||||
  block->type = ltm_instance_block_type_nested;
 | 
			
		||||
  block->data.ptr = nested;
 | 
			
		||||
 | 
			
		||||
  // We insert the block in the linked list and replace its next pointer as the
 | 
			
		||||
  // new attachment point for this variable
 | 
			
		||||
  block->next = *instance->vars[i];
 | 
			
		||||
  *instance->vars[i] = block;
 | 
			
		||||
  instance->vars[i] = &block->next;
 | 
			
		||||
 | 
			
		||||
  *out = nested;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ltm_instance_size(const ltm_instance *instance) {
 | 
			
		||||
  size_t total = 0;
 | 
			
		||||
  ltm_instance_block *block = instance->blocks.head;
 | 
			
		||||
 | 
			
		||||
  while (block != NULL) {
 | 
			
		||||
    switch (block->type) {
 | 
			
		||||
    case ltm_instance_block_type_buf:
 | 
			
		||||
    case ltm_instance_block_type_buf_owned:
 | 
			
		||||
    case ltm_instance_block_type_file:
 | 
			
		||||
    case ltm_instance_block_type_file_owned:
 | 
			
		||||
    case ltm_instance_block_type_fn:
 | 
			
		||||
      total += block->data.len;
 | 
			
		||||
      break;
 | 
			
		||||
    case ltm_instance_block_type_nested:
 | 
			
		||||
      total += ltm_instance_size(block->data.ptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    block = block->next;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return total;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_instance_write(size_t *written, char *buf, size_t len,
 | 
			
		||||
                           ltm_instance *instance) {
 | 
			
		||||
  *written = 0;
 | 
			
		||||
 | 
			
		||||
  while ((*written < len) && (instance->blocks.current != NULL)) {
 | 
			
		||||
    ltm_instance_block *current = instance->blocks.current;
 | 
			
		||||
 | 
			
		||||
    switch (current->type) {
 | 
			
		||||
    case ltm_instance_block_type_buf:
 | 
			
		||||
    case ltm_instance_block_type_buf_owned: {
 | 
			
		||||
      size_t cap =
 | 
			
		||||
          LTM_MIN(current->data.len - instance->written, len - *written);
 | 
			
		||||
      memcpy(&buf[*written], &((char *)current->data.ptr)[instance->written],
 | 
			
		||||
             cap);
 | 
			
		||||
      *written += cap;
 | 
			
		||||
      instance->written += cap;
 | 
			
		||||
 | 
			
		||||
      if (instance->written == current->data.len) {
 | 
			
		||||
        instance->blocks.current = current->next;
 | 
			
		||||
        instance->written = 0;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    case ltm_instance_block_type_file:
 | 
			
		||||
    case ltm_instance_block_type_file_owned: {
 | 
			
		||||
      size_t cap =
 | 
			
		||||
          LTM_MIN(current->data.len - instance->written, len - *written);
 | 
			
		||||
      size_t read = fread(&buf[*written], 1, cap, current->data.ptr);
 | 
			
		||||
 | 
			
		||||
      if ((read == 0) && (ferror(current->data.ptr) != 0)) {
 | 
			
		||||
        return ltm_err_failed_io;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      *written += read;
 | 
			
		||||
      instance->written += read;
 | 
			
		||||
 | 
			
		||||
      if (instance->written == current->data.len) {
 | 
			
		||||
        instance->blocks.current = current->next;
 | 
			
		||||
        instance->written = 0;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    case ltm_instance_block_type_fn: {
 | 
			
		||||
      size_t cap =
 | 
			
		||||
          LTM_MIN(current->data.len - instance->written, len - *written);
 | 
			
		||||
      size_t fn_written = 0;
 | 
			
		||||
      LTM_RES(current->fn(&fn_written, &buf[*written], cap, current->data.ptr));
 | 
			
		||||
      *written += fn_written;
 | 
			
		||||
      instance->written += fn_written;
 | 
			
		||||
 | 
			
		||||
      if (instance->written == current->data.len) {
 | 
			
		||||
        instance->blocks.current = current->next;
 | 
			
		||||
        instance->written = 0;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    case ltm_instance_block_type_nested: {
 | 
			
		||||
      size_t nested_written = 0;
 | 
			
		||||
      ltm_err res = ltm_instance_write(&nested_written, &buf[*written],
 | 
			
		||||
                                       len - *written, current->data.ptr);
 | 
			
		||||
      *written += nested_written;
 | 
			
		||||
 | 
			
		||||
      switch (res) {
 | 
			
		||||
      case ltm_err_done:
 | 
			
		||||
        instance->blocks.current = current->next;
 | 
			
		||||
        instance->written = 0;
 | 
			
		||||
        break;
 | 
			
		||||
      case ltm_err_ok:
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        return res;
 | 
			
		||||
      }
 | 
			
		||||
    } break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return instance->blocks.current == NULL ? ltm_err_done : ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
#include "ltm/template.h"
 | 
			
		||||
#include "ltm/template_internal.h"
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_init(ltm_template **out) {
 | 
			
		||||
  ltm_template *template = calloc(1, sizeof(ltm_template));
 | 
			
		||||
 | 
			
		||||
  if (template == NULL) {
 | 
			
		||||
    return ltm_err_failed_alloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *out = template;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ltm_template_free(ltm_template *template) {
 | 
			
		||||
  if (template->blocks.len > 0) {
 | 
			
		||||
    for (size_t i = 0; i < template->blocks.len; i++) {
 | 
			
		||||
      ltm_template_block *block = &template->blocks.arr[i];
 | 
			
		||||
 | 
			
		||||
      switch (block->type) {
 | 
			
		||||
      case ltm_template_block_type_nested:
 | 
			
		||||
        ltm_template_free(block->data.ptr);
 | 
			
		||||
        break;
 | 
			
		||||
      case ltm_template_block_type_var:
 | 
			
		||||
      case ltm_template_block_type_literal:;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    free(template->blocks.arr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (template->names.len > 0) {
 | 
			
		||||
    free(template->names.arr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  free(template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_name_append(ltm_template *template, const char *s,
 | 
			
		||||
                                 size_t len, size_t index) {
 | 
			
		||||
  ltm_template_block_name *names =
 | 
			
		||||
      realloc(template->names.arr,
 | 
			
		||||
              (template->names.len + 1) * sizeof(ltm_template_block_name));
 | 
			
		||||
 | 
			
		||||
  if (names == NULL) {
 | 
			
		||||
    return ltm_err_failed_alloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_template_block_name *name = &names[template->names.len];
 | 
			
		||||
 | 
			
		||||
  name->name.s = s;
 | 
			
		||||
  name->name.len = len;
 | 
			
		||||
  name->index = index;
 | 
			
		||||
 | 
			
		||||
  template->names.arr = names;
 | 
			
		||||
  template->names.len++;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_block_append(ltm_template *template,
 | 
			
		||||
                                  ltm_template_block_type type, void *data,
 | 
			
		||||
                                  size_t len) {
 | 
			
		||||
  ltm_template_block *blocks =
 | 
			
		||||
      realloc(template->blocks.arr,
 | 
			
		||||
              (template->blocks.len + 1) * sizeof(ltm_template_block));
 | 
			
		||||
 | 
			
		||||
  if (blocks == NULL) {
 | 
			
		||||
    return ltm_err_failed_alloc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ltm_template_block *block = &blocks[template->blocks.len];
 | 
			
		||||
 | 
			
		||||
  block->type = type;
 | 
			
		||||
 | 
			
		||||
  switch (type) {
 | 
			
		||||
  case ltm_template_block_type_literal:
 | 
			
		||||
    block->data.ptr = data;
 | 
			
		||||
    block->data.len = len;
 | 
			
		||||
    break;
 | 
			
		||||
  case ltm_template_block_type_nested:
 | 
			
		||||
    block->data.ptr = data;
 | 
			
		||||
    break;
 | 
			
		||||
  // For the other cases, we explicitely ignore the data and len arguments
 | 
			
		||||
  default:;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template->blocks.arr = blocks;
 | 
			
		||||
  template->blocks.len++;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,198 @@
 | 
			
		|||
#include <stdbool.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include "ltm/common.h"
 | 
			
		||||
#include "ltm/template.h"
 | 
			
		||||
#include "ltm/template_internal.h"
 | 
			
		||||
 | 
			
		||||
static bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
 | 
			
		||||
                                          size_t len) {
 | 
			
		||||
  if (len == 0) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ph->start = memchr(s, '{', len - 1);
 | 
			
		||||
 | 
			
		||||
  if ((ph->start == NULL) || (ph->start[1] != '{')) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t new_len = len - (ph->start - s);
 | 
			
		||||
 | 
			
		||||
  // A template can never be valid without at least 5 characters
 | 
			
		||||
  if (new_len < 5) {
 | 
			
		||||
    ph->type = ltm_placeholder_type_invalid;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ph->end = memchr(ph->start + 2, '}', new_len - 3);
 | 
			
		||||
 | 
			
		||||
  // Non-terminated placeholders aren't valid
 | 
			
		||||
  if ((ph->end == NULL) || (ph->end[1] != '}')) {
 | 
			
		||||
    ph->type = ltm_placeholder_type_invalid;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // End should point to final character
 | 
			
		||||
  ph->end++;
 | 
			
		||||
 | 
			
		||||
  // Parse the words
 | 
			
		||||
  ph->name.s = ph->start + 2;
 | 
			
		||||
 | 
			
		||||
  while ((*ph->name.s == ' ') && (ph->name.s != ph->end - 1)) {
 | 
			
		||||
    ph->name.s++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Placeholder is empty
 | 
			
		||||
  if (ph->name.s == ph->end - 1) {
 | 
			
		||||
    ph->type = ltm_placeholder_type_invalid;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const char *ident = ph->name.s;
 | 
			
		||||
 | 
			
		||||
  while ((*ident != ' ') && (ident != ph->end - 1)) {
 | 
			
		||||
    ident++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ph->name.len = ident - ph->name.s;
 | 
			
		||||
 | 
			
		||||
  // Skip whitespace over to next word
 | 
			
		||||
  while ((*ident == ' ') && (ident != ph->end - 1)) {
 | 
			
		||||
    ident++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (ident == ph->end - 1) {
 | 
			
		||||
    ph->type = ltm_placeholder_type_var;
 | 
			
		||||
  } else {
 | 
			
		||||
    // Further parse the identifier
 | 
			
		||||
    const char *temp = ident;
 | 
			
		||||
 | 
			
		||||
    while ((*temp != ' ') && (temp != ph->end - 1)) {
 | 
			
		||||
      temp++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t ident_len = temp - ident;
 | 
			
		||||
 | 
			
		||||
    if (strncmp("start", ident, ident_len) == 0) {
 | 
			
		||||
      ph->type = ltm_placeholder_type_nested_start;
 | 
			
		||||
    } else if (strncmp("end", ident, ident_len) == 0) {
 | 
			
		||||
      ph->type = ltm_placeholder_type_nested_end;
 | 
			
		||||
    } else {
 | 
			
		||||
      ph->type = ltm_placeholder_type_invalid;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) {
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  LTM_RES(ltm_template_init(&template));
 | 
			
		||||
 | 
			
		||||
  ltm_placeholder ph;
 | 
			
		||||
  bool in_nested = false;
 | 
			
		||||
  const char *nested_start = NULL, *nested_name = NULL;
 | 
			
		||||
  size_t nested_depth = 0, nested_name_len = 0;
 | 
			
		||||
 | 
			
		||||
  size_t cur_nested_depth = 0;
 | 
			
		||||
 | 
			
		||||
  while (ltm_template_next_placeholder(&ph, s, len)) {
 | 
			
		||||
    // Add part before placeholder as literal
 | 
			
		||||
    if (!in_nested && (ph.start != s)) {
 | 
			
		||||
      LTM_RES2(ltm_template_block_append(template,
 | 
			
		||||
                                         ltm_template_block_type_literal,
 | 
			
		||||
                                         (void *)s, ph.start - s),
 | 
			
		||||
               ltm_template_free(template));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (ph.type) {
 | 
			
		||||
    // Invalid placeholders can be detected as early as possible
 | 
			
		||||
    case ltm_placeholder_type_invalid:
 | 
			
		||||
      ltm_template_free(template);
 | 
			
		||||
 | 
			
		||||
      return ltm_err_invalid_template;
 | 
			
		||||
    case ltm_placeholder_type_var:
 | 
			
		||||
      if (!in_nested) {
 | 
			
		||||
        LTM_RES2(ltm_template_block_append(
 | 
			
		||||
                     template, ltm_template_block_type_var, NULL, 0),
 | 
			
		||||
                 ltm_template_free(template));
 | 
			
		||||
        LTM_RES2(ltm_template_name_append(template, ph.name.s, ph.name.len,
 | 
			
		||||
                                          template->blocks.len - 1),
 | 
			
		||||
                 ltm_template_free(template));
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case ltm_placeholder_type_nested_start:
 | 
			
		||||
      if (!in_nested) {
 | 
			
		||||
        nested_start = ph.end + 1;
 | 
			
		||||
        nested_depth = cur_nested_depth;
 | 
			
		||||
        nested_name = ph.name.s;
 | 
			
		||||
        nested_name_len = ph.name.len;
 | 
			
		||||
        in_nested = true;
 | 
			
		||||
      }
 | 
			
		||||
      cur_nested_depth++;
 | 
			
		||||
      break;
 | 
			
		||||
    case ltm_placeholder_type_nested_end:
 | 
			
		||||
      // This means there's more nested end placeholders than nested starts
 | 
			
		||||
      if (cur_nested_depth == 0) {
 | 
			
		||||
        ltm_template_free(template);
 | 
			
		||||
 | 
			
		||||
        return ltm_err_invalid_template;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      cur_nested_depth--;
 | 
			
		||||
 | 
			
		||||
      if (in_nested && (cur_nested_depth == nested_depth)) {
 | 
			
		||||
        size_t nested_len = ph.start - nested_start;
 | 
			
		||||
        ltm_template *nested_template;
 | 
			
		||||
        LTM_RES2(
 | 
			
		||||
            ltm_template_compile_n(&nested_template, nested_start, nested_len),
 | 
			
		||||
            ltm_template_free(template));
 | 
			
		||||
        LTM_RES(ltm_template_block_append(
 | 
			
		||||
            template, ltm_template_block_type_nested, nested_template, 0));
 | 
			
		||||
        LTM_RES2(ltm_template_name_append(template, nested_name,
 | 
			
		||||
                                          nested_name_len,
 | 
			
		||||
                                          template->blocks.len - 1),
 | 
			
		||||
                 ltm_template_free(template));
 | 
			
		||||
 | 
			
		||||
        in_nested = false;
 | 
			
		||||
      }
 | 
			
		||||
      // We encountered a nested end without a start
 | 
			
		||||
      else if (!in_nested) {
 | 
			
		||||
        ltm_template_free(template);
 | 
			
		||||
 | 
			
		||||
        return ltm_err_invalid_template;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    len -= ph.end + 1 - s;
 | 
			
		||||
    s = ph.end + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Unfinished nested
 | 
			
		||||
  if (in_nested) {
 | 
			
		||||
    ltm_template_free(template);
 | 
			
		||||
 | 
			
		||||
    return ltm_err_invalid_template;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add remaining trailing literal
 | 
			
		||||
  if (len > 0) {
 | 
			
		||||
    LTM_RES2(ltm_template_block_append(
 | 
			
		||||
                 template, ltm_template_block_type_literal, (void *)s, len),
 | 
			
		||||
             ltm_template_free(template));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *out = template;
 | 
			
		||||
 | 
			
		||||
  return ltm_err_ok;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_compile(ltm_template **out, const char *template) {
 | 
			
		||||
  return ltm_template_compile_n(out, template, strlen(template));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
#include "test.h"
 | 
			
		||||
 | 
			
		||||
#include "ltm/common.h"
 | 
			
		||||
#include "ltm/template.h"
 | 
			
		||||
#include "ltm/template_internal.h"
 | 
			
		||||
 | 
			
		||||
void test_single_placeholder() {
 | 
			
		||||
  const char *s = "Hello, {{ world }}!";
 | 
			
		||||
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  TEST_ASSERT(ltm_template_compile(&template, s) == ltm_err_ok);
 | 
			
		||||
 | 
			
		||||
  ltm_instance *instance;
 | 
			
		||||
  TEST_CHECK(ltm_template_instantiate(&instance, template) == ltm_err_ok);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(ltm_instance_block_add_var(instance, "world", ltm_instance_block_type_buf, "World", 5) == ltm_err_ok);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(ltm_instance_size(instance) == 13);
 | 
			
		||||
 | 
			
		||||
  char buf[13];
 | 
			
		||||
 | 
			
		||||
  size_t written = 0;
 | 
			
		||||
  TEST_CHECK(ltm_instance_write(&written, buf, 5, instance) == ltm_err_ok);
 | 
			
		||||
  TEST_CHECK(written == 5);
 | 
			
		||||
 | 
			
		||||
  written = 0;
 | 
			
		||||
  TEST_CHECK(ltm_instance_write(&written, buf + 5, 5, instance) == ltm_err_ok);
 | 
			
		||||
  TEST_CHECK(written == 5);
 | 
			
		||||
 | 
			
		||||
  written = 0;
 | 
			
		||||
  TEST_CHECK(ltm_instance_write(&written, buf + 10, 5, instance) == ltm_err_done);
 | 
			
		||||
  TEST_CHECK(written == 3);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(strncmp(buf, "Hello, World!", 13) == 0);
 | 
			
		||||
  TEST_DUMP("buf", buf, 13);
 | 
			
		||||
 | 
			
		||||
  ltm_instance_free(instance);
 | 
			
		||||
  ltm_template_free(template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_LIST = {
 | 
			
		||||
  { "instance single placeholder", test_single_placeholder },
 | 
			
		||||
  { NULL, NULL }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,118 @@
 | 
			
		|||
#include "test.h"
 | 
			
		||||
 | 
			
		||||
#include "ltm/common.h"
 | 
			
		||||
#include "ltm/template.h"
 | 
			
		||||
#include "ltm/template_internal.h"
 | 
			
		||||
 | 
			
		||||
void test_single_placeholder() {
 | 
			
		||||
  const char *s = "Hello, {{ world }}";
 | 
			
		||||
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_ok);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.len == 2);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].data.ptr == s);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].data.len == 7);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_var);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->names.len == 1);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].name.s == s + 10);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].name.len == 5);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].index == 1);
 | 
			
		||||
 | 
			
		||||
  ltm_template_free(template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_single_placeholder_trailing() {
 | 
			
		||||
  const char *s = "Hello, {{ world }}!";
 | 
			
		||||
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_ok);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.len == 3);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].data.ptr == s);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].data.len == 7);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_var);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[2].type == ltm_template_block_type_literal);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[2].data.ptr == s + 18);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[2].data.len == 1);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->names.len == 1);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].name.s == s + 10);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].name.len == 5);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].index == 1);
 | 
			
		||||
 | 
			
		||||
  ltm_template_free(template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_single_nested() {
 | 
			
		||||
  const char *s = "abc {{ l start }}some content {{ var }} {{ l end }}";
 | 
			
		||||
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_ok);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->names.len == 1);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].name.s == s + 7);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].name.len == 1);
 | 
			
		||||
  TEST_CHECK(template->names.arr[0].index == 1);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.len == 2);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].data.ptr == s);
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[0].data.len == 4);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_nested);
 | 
			
		||||
  ltm_template *nested_template = template->blocks.arr[1].data.ptr;
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.len == 3);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[0].type == ltm_template_block_type_literal);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[0].data.ptr == s + 17);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[0].data.len == 13);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[1].type == ltm_template_block_type_var);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[2].type == ltm_template_block_type_literal);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[2].data.ptr == s + 39);
 | 
			
		||||
  TEST_CHECK(nested_template->blocks.arr[2].data.len == 1);
 | 
			
		||||
 | 
			
		||||
  TEST_CHECK(nested_template->names.len == 1);
 | 
			
		||||
  TEST_CHECK(nested_template->names.arr[0].name.s == s + 33);
 | 
			
		||||
  TEST_CHECK(nested_template->names.arr[0].name.len == 3);
 | 
			
		||||
  TEST_CHECK(nested_template->names.arr[0].index == 1);
 | 
			
		||||
 | 
			
		||||
  ltm_template_free(template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_unclosed_placeholder() {
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
 | 
			
		||||
  const char *s = "abc {{ var }";
 | 
			
		||||
  TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_invalid_template);
 | 
			
		||||
 | 
			
		||||
  s = "abc {{ var ";
 | 
			
		||||
  TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_invalid_template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void test_unclosed_nested() {
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
 | 
			
		||||
  const char *s = "abc {{ var start }} {{ hello }}";
 | 
			
		||||
  TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_invalid_template);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_LIST = {
 | 
			
		||||
  { "template single placeholder", test_single_placeholder },
 | 
			
		||||
  { "template single placeholder trailing", test_single_placeholder_trailing },
 | 
			
		||||
  { "template single nested", test_single_nested },
 | 
			
		||||
  { "template unclosed placeholder", test_unclosed_placeholder },
 | 
			
		||||
  { "template unclosed nested", test_unclosed_nested },
 | 
			
		||||
  { NULL, NULL }
 | 
			
		||||
};
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue