commit 09a6ec4d59f4030a1b1ff7a7b2646eb5292d47b3
Author: Sophie <info@soophie.de>
Date: Sat, 14 Dec 2024 22:54:45 +0000
feat: Initial commit
Diffstat:
A | Makefile | | | 13 | +++++++++++++ |
A | README.md | | | 3 | +++ |
A | src/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;
+}