QUOTE: Be the change, make a difference.

feat: Initial commit - ctl - Static template generator for C

ctl

Static template generator for C
git clone git@soophie.de:/srv/git/ctl
log | files | refs | readme

commit 09a6ec4d59f4030a1b1ff7a7b2646eb5292d47b3
Author: Sophie <info@soophie.de>
Date:   Sat, 14 Dec 2024 22:54:45 +0000

feat: Initial commit

Diffstat:
AMakefile | 13+++++++++++++
AREADME.md | 3+++
Asrc/main.c | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 193 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,13 @@ +CC=cc +CFLAGS=-Wall -Wextra -Werror -pedantic +LIBS= + +build: src/*.c + $(CC) -o ctl $(CLFAGS) $(LIBS) $^ + +install: build + cp ctl /usr/local/bin/ + rm ./ctl + +clean: + rm ./ctl diff --git a/README.md b/README.md @@ -0,0 +1,3 @@ +# ctl + +Static template generator for C diff --git a/src/main.c b/src/main.c @@ -0,0 +1,177 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LIB_STR_IMPL +#include <libstr.h> + +typedef enum { + CTL_ERR_OK, + CTL_ERR_NO_ARGS, + CTL_ERR_NO_FILE, + CTL_ERR_UNCLOSED_CODE, +} ctl_error_t; + +void ctl_error_str(ctl_error_t err) { + switch (err) { + case CTL_ERR_OK: + break; + case CTL_ERR_NO_ARGS: + printf("Usage: ctl <filename>\n"); + break; + case CTL_ERR_NO_FILE: + printf("Error: No file found!\n"); + break; + case CTL_ERR_UNCLOSED_CODE: + printf("Error: Unclosed code block!\n"); + break; + } +} + +char *str_replace_all_len(char *buffer, char *str, char *replacement, int len) { + char *ret = NULL; + int str_len = strlen(str); + for (int i = 0; i < len; i++) { + char *ptr = &buffer[i]; + if (strncmp(ptr, str, str_len) == 0) { + str_append(&ret, replacement); + i += str_len - 1; + } + else { + str_append_len(&ret, &buffer[i], 1); + } + } + return ret; +} + +char *str_hex_len(char *str, int len) { + const int HEX = 4; + char *ret = calloc(HEX * len + 1, sizeof(char)); + for (int i = 0; i < len; i++) { + unsigned char hex = str[i] & 0xff; + snprintf(ret + HEX * i, HEX * len, "\\x%02x", hex); + } + ret[HEX * len] = '\0'; + return ret; +} + +ctl_error_t ctl_render(char **template, char *content) { + int len = strlen(content); + int set = 0; + int is_code = 0; + str_append(template, "{\n"); + for (int i = 0; i < len; i++) { + if (content[i] == '%') { + // ignore escaped % + if (i + 1 < len && content[i + 1] == '%') { + i += 1; + continue; + } + if (is_code) { + str_append_len(template, content + set, i - set); + str_append(template, "\n"); + } + else { + const int STRING_LIMIT = 4095; + int str_len = i - set; + int rem_len = str_len; + int hex_len = 0; + int idx = 0; + while (rem_len > 0) { + if (rem_len > STRING_LIMIT) { + hex_len = STRING_LIMIT; + } + else { + hex_len = str_len - idx; + } + rem_len -= hex_len; + char *str = str_replace_all_len(content + set + idx, "%%", "%", hex_len); + char *hex = str_hex_len(str, strlen(str)); + str_append(template, "CTL_OUT(ctl_out, \""); + str_append(template, hex); + str_append(template, "\");\n"); + free(str); + free(hex); + idx += hex_len; + } + } + is_code = !is_code; + set = i + 1; + } + } + if (is_code) { + return CTL_ERR_UNCLOSED_CODE; + } + else { + const int STRING_LIMIT = 4095; + int str_len = len - set; + int rem_len = str_len; + int hex_len = 0; + int idx = 0; + while (rem_len > 0) { + if (rem_len > STRING_LIMIT) { + hex_len = STRING_LIMIT; + } + else { + hex_len = str_len - idx; + } + rem_len -= hex_len; + char *str = str_replace_all_len(content + set + idx, "%%", "%", hex_len); + char *hex = str_hex_len(str, strlen(str)); + str_append(template, "CTL_OUT(ctl_out, \""); + str_append(template, hex); + str_append(template, "\");\n"); + free(str); + free(hex); + idx += hex_len; + } + } + str_append(template, "}\n"); + *template = str_replace_all(*template, "%%", "%"); + return CTL_ERR_OK; +} + +ctl_error_t ctl_file_read(char **content, const char *filename) { + FILE *fp = fopen(filename, "r"); + if (fp == NULL) { + return CTL_ERR_NO_FILE; + } + fseek(fp, 0, SEEK_END); + int len = ftell(fp); + fseek(fp, 0, SEEK_SET); + *content = calloc(len + 1, sizeof(char)); + fread(*content, sizeof(char), len, fp); + (*content)[len] = '\0'; + fclose(fp); + return CTL_ERR_OK; +} + +int main(int argc, char **argv) { + ctl_error_t err; + if (argc < 2) { + err = CTL_ERR_NO_ARGS; + ctl_error_str(err); + return err; + } + char *filename = argv[1]; + char *content = NULL; + err = ctl_file_read(&content, filename); + if (err != CTL_ERR_OK) { + ctl_error_str(err); + return err; + } + char *template = NULL; + err = ctl_render(&template, content); + if (err != CTL_ERR_OK) { + ctl_error_str(err); + free(content); + if (template != NULL) { + free(template); + } + return err; + } + printf("%s\n", template); + free(template); + free(content); + return CTL_ERR_OK; +}