summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormaxim nikonov <maxim.nikonov@hqo.co>2025-11-21 01:00:41 +0500
committermaxim nikonov <maxim.nikonov@hqo.co>2025-11-21 01:00:41 +0500
commitc2094944cfaa82436f2268e7fad0dc9184ad0bcb (patch)
treed40b20343ff6b28664feeb80947bb7583e036e73
parentb7f1fffaac08795d853ba95334ffe7cee6a4ad58 (diff)
feat: rewrote on go for cross OS builds
-rw-r--r--Makefile37
-rw-r--r--README.md2
-rw-r--r--draft.md22
-rw-r--r--main.c609
-rw-r--r--main.go517
5 files changed, 518 insertions, 669 deletions
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 4bd922c..0000000
--- a/Makefile
+++ /dev/null
@@ -1,37 +0,0 @@
-CC=gcc
-CFLAGS=-Wall -Wextra -std=c99 -O2
-TARGET=jelly-cms
-SOURCE=main.c
-
-# Default target
-$(TARGET): $(SOURCE)
- $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE)
-
-# Clean build artifacts
-clean:
- rm -f $(TARGET)
- rm -rf build
-
-# Install (copy to /usr/local/bin)
-install: $(TARGET)
- sudo cp $(TARGET) /usr/local/bin/
-
-# Uninstall
-uninstall:
- sudo rm -f /usr/local/bin/$(TARGET)
-
-# Test build (run build command)
-test: $(TARGET)
- ./$(TARGET) build
-
-# Help
-help:
- @echo "Available targets:"
- @echo " $(TARGET) - Build the jelly-cms executable"
- @echo " clean - Remove build artifacts and executable"
- @echo " install - Install jelly-cms to /usr/local/bin"
- @echo " uninstall - Remove jelly-cms from /usr/local/bin"
- @echo " test - Build and run jelly-cms build command"
- @echo " help - Show this help message"
-
-.PHONY: clean install uninstall test help \ No newline at end of file
diff --git a/README.md b/README.md
index d207a6d..9dd244f 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ A fast, lightweight static site generator written in C that supports multilingua
1. Clone or download the project files (`main.c` and `Makefile`)
2. Build the executable:
```bash
- make
+ go build main.go
```
### Project Structure
diff --git a/draft.md b/draft.md
deleted file mode 100644
index 8402b52..0000000
--- a/draft.md
+++ /dev/null
@@ -1,22 +0,0 @@
-conflict betwee content and pages then do not build and show error
-on templates files *.html
-with syntax
-
-<title>%content.title%</title>
-
-
-if markdown file in frontmatter table dont have this meta info then fail build with error
-markdown example:
-
----
-title: Телефоны
-
----
-
-# Hello
-
-![img](/assets/images/car.png)
-
-
-TODO: продумать локали (локаль по папкам файлов мд или по названию файла или в контенте во фронтматтере) и конфликты и возможно есть локаль в страницах но нет в контенте
-мб сделать два режима без локали и с локалью
diff --git a/main.c b/main.c
deleted file mode 100644
index f46c0d5..0000000
--- a/main.c
+++ /dev/null
@@ -1,609 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <unistd.h>
-
-#define MAX_PATH 1024
-#define MAX_CONTENT 65536
-#define MAX_LOCALES 10
-#define MAX_LOCALE_ENTRIES 100
-
-typedef struct {
- char key[256];
- char value[1024];
-} LocaleEntry;
-
-typedef struct {
- char code[8];
- LocaleEntry entries[MAX_LOCALE_ENTRIES];
- int count;
-} Locale;
-
-typedef struct {
- Locale locales[MAX_LOCALES];
- int count;
- int has_locales;
-} LocaleData;
-
-// Safe string copy
-void safe_strcpy(char *dest, const char *src, size_t dest_size) {
- if (!dest || !src || dest_size == 0) return;
- strncpy(dest, src, dest_size - 1);
- dest[dest_size - 1] = '\0';
-}
-
-// Utility functions
-int create_directory(const char *path) {
- if (!path) return -1;
-
- char tmp[MAX_PATH];
- char *p = NULL;
- size_t len;
-
- safe_strcpy(tmp, path, sizeof(tmp));
- len = strlen(tmp);
- if (len > 0 && tmp[len - 1] == '/')
- tmp[len - 1] = 0;
-
- for (p = tmp + 1; *p; p++) {
- if (*p == '/') {
- *p = 0;
- if (mkdir(tmp, 0755) != 0 && errno != EEXIST) {
- return -1;
- }
- *p = '/';
- }
- }
- if (mkdir(tmp, 0755) != 0 && errno != EEXIST) {
- return -1;
- }
- return 0;
-}
-
-int copy_file(const char *src, const char *dest) {
- if (!src || !dest) return -1;
-
- FILE *source = fopen(src, "rb");
- if (!source) {
- printf("Warning: Could not open source file: %s\n", src);
- return -1;
- }
-
- // Create directory for destination file
- char dest_dir[MAX_PATH];
- safe_strcpy(dest_dir, dest, sizeof(dest_dir));
- char *last_slash = strrchr(dest_dir, '/');
- if (last_slash) {
- *last_slash = '\0';
- create_directory(dest_dir);
- }
-
- FILE *target = fopen(dest, "wb");
- if (!target) {
- printf("Warning: Could not create destination file: %s\n", dest);
- fclose(source);
- return -1;
- }
-
- char buffer[4096];
- size_t bytes;
- while ((bytes = fread(buffer, 1, sizeof(buffer), source)) > 0) {
- fwrite(buffer, 1, bytes, target);
- }
-
- fclose(source);
- fclose(target);
- return 0;
-}
-
-int copy_directory_recursive(const char *src, const char *dest) {
- if (!src || !dest) return -1;
-
- DIR *dir = opendir(src);
- if (!dir) {
- printf("Warning: Could not open directory: %s\n", src);
- return -1;
- }
-
- if (create_directory(dest) != 0) {
- printf("Warning: Could not create directory: %s\n", dest);
- closedir(dir);
- return -1;
- }
-
- struct dirent *entry;
- while ((entry = readdir(dir)) != NULL) {
- if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
- continue;
-
- char src_path[MAX_PATH];
- char dest_path[MAX_PATH];
- snprintf(src_path, sizeof(src_path), "%s/%s", src, entry->d_name);
- snprintf(dest_path, sizeof(dest_path), "%s/%s", dest, entry->d_name);
-
- struct stat statbuf;
- if (stat(src_path, &statbuf) == 0) {
- if (S_ISDIR(statbuf.st_mode)) {
- copy_directory_recursive(src_path, dest_path);
- } else {
- copy_file(src_path, dest_path);
- }
- }
- }
-
- closedir(dir);
- return 0;
-}
-
-int load_locale_file(const char *filepath, Locale *locale) {
- if (!filepath || !locale) return -1;
-
- FILE *file = fopen(filepath, "r");
- if (!file) {
- printf("Warning: Could not open locale file: %s\n", filepath);
- return -1;
- }
-
- fseek(file, 0, SEEK_END);
- long length = ftell(file);
- fseek(file, 0, SEEK_SET);
-
- if (length <= 0 || length > 100000) {
- printf("Warning: Invalid file size for locale: %s\n", filepath);
- fclose(file);
- return -1;
- }
-
- char *content = malloc(length + 1);
- if (!content) {
- printf("Error: Memory allocation failed\n");
- fclose(file);
- return -1;
- }
-
- size_t read_bytes = fread(content, 1, length, file);
- content[read_bytes] = '\0';
- fclose(file);
-
- // Simple JSON parsing - extract key-value pairs
- locale->count = 0;
- char *pos = content;
-
- while (*pos && locale->count < MAX_LOCALE_ENTRIES) {
- // Find opening quote for key
- char *quote1 = strchr(pos, '"');
- if (!quote1) break;
- quote1++;
-
- // Find closing quote for key
- char *quote2 = strchr(quote1, '"');
- if (!quote2) break;
-
- size_t key_len = quote2 - quote1;
- if (key_len >= sizeof(locale->entries[0].key) || key_len == 0) {
- pos = quote2 + 1;
- continue;
- }
-
- // Copy key
- strncpy(locale->entries[locale->count].key, quote1, key_len);
- locale->entries[locale->count].key[key_len] = '\0';
-
- // Find colon
- char *colon = strchr(quote2, ':');
- if (!colon) {
- pos = quote2 + 1;
- continue;
- }
-
- // Find opening quote for value
- char *quote3 = strchr(colon, '"');
- if (!quote3) {
- pos = quote2 + 1;
- continue;
- }
- quote3++;
-
- // Find closing quote for value
- char *quote4 = strchr(quote3, '"');
- if (!quote4) {
- pos = quote3;
- continue;
- }
-
- size_t value_len = quote4 - quote3;
- if (value_len >= sizeof(locale->entries[0].value)) {
- pos = quote4 + 1;
- continue;
- }
-
- // Copy value
- strncpy(locale->entries[locale->count].value, quote3, value_len);
- locale->entries[locale->count].value[value_len] = '\0';
-
- locale->count++;
- pos = quote4 + 1;
- }
-
- free(content);
- printf("Loaded locale with %d entries from %s\n", locale->count, filepath);
- return 0;
-}
-
-int load_locales(LocaleData *locale_data) {
- if (!locale_data) return -1;
-
- locale_data->has_locales = 0;
- locale_data->count = 0;
-
- DIR *dir = opendir("locale");
- if (!dir) {
- printf("No locale directory found\n");
- return 0;
- }
-
- locale_data->has_locales = 1;
-
- struct dirent *entry;
- while ((entry = readdir(dir)) != NULL && locale_data->count < MAX_LOCALES) {
- if (strstr(entry->d_name, ".json")) {
- char filepath[MAX_PATH];
- snprintf(filepath, sizeof(filepath), "locale/%s", entry->d_name);
-
- // Extract locale code from filename
- char locale_code[256];
- safe_strcpy(locale_code, entry->d_name, sizeof(locale_code));
- char *dot = strrchr(locale_code, '.');
- if (dot) *dot = '\0';
-
- if (strlen(locale_code) < sizeof(locale_data->locales[0].code)) {
- safe_strcpy(locale_data->locales[locale_data->count].code, locale_code,
- sizeof(locale_data->locales[locale_data->count].code));
-
- if (load_locale_file(filepath, &locale_data->locales[locale_data->count]) == 0) {
- locale_data->count++;
- }
- }
- }
- }
-
- closedir(dir);
- return 0;
-}
-
-char* get_locale_value(const Locale *locale, const char *key) {
- if (!locale || !key) return NULL;
-
- for (int i = 0; i < locale->count; i++) {
- if (strcmp(locale->entries[i].key, key) == 0) {
- return locale->entries[i].value;
- }
- }
- return NULL;
-}
-
-char* load_file(const char *path) {
- if (!path) return NULL;
-
- FILE *file = fopen(path, "r");
- if (!file) return NULL;
-
- fseek(file, 0, SEEK_END);
- long length = ftell(file);
- fseek(file, 0, SEEK_SET);
-
- if (length <= 0 || length > MAX_CONTENT) {
- fclose(file);
- return NULL;
- }
-
- char *content = malloc(length + 1);
- if (!content) {
- fclose(file);
- return NULL;
- }
-
- size_t read_bytes = fread(content, 1, length, file);
- content[read_bytes] = '\0';
- fclose(file);
-
- return content;
-}
-
-char* load_partial(const char *name) {
- if (!name) return NULL;
-
- char path[MAX_PATH];
- snprintf(path, sizeof(path), "src/partials/%s.html", name);
- return load_file(path);
-}
-
-char* process_template(const char *content, const Locale *locale) {
- if (!content) return NULL;
-
- size_t content_len = strlen(content);
- char *result = malloc(MAX_CONTENT);
- if (!result) return NULL;
-
- char *output = result;
- const char *input = content;
- size_t remaining = MAX_CONTENT - 1;
-
- while (*input && remaining > 1) {
- if (strncmp(input, "<!-- %include.", 14) == 0) {
- // Handle includes
- input += 14;
- const char *end = strstr(input, "% -->");
- if (end) {
- size_t name_len = end - input;
- if (name_len < 255) {
- char partial_name[256];
- strncpy(partial_name, input, name_len);
- partial_name[name_len] = '\0';
-
- char *partial_content = load_partial(partial_name);
- if (partial_content) {
- char *processed_partial = process_template(partial_content, locale);
- if (processed_partial) {
- size_t partial_len = strlen(processed_partial);
- if (partial_len < remaining) {
- strcpy(output, processed_partial);
- output += partial_len;
- remaining -= partial_len;
- }
- free(processed_partial);
- }
- free(partial_content);
- }
- }
- input = end + 5;
- } else {
- *output++ = *input++;
- remaining--;
- }
- } else if (strncmp(input, "%locale.", 8) == 0) {
- // Handle locale variables
- input += 8;
- const char *end = strchr(input, '%');
- if (end && locale) {
- size_t key_len = end - input;
- if (key_len < 255) {
- char key[256];
- strncpy(key, input, key_len);
- key[key_len] = '\0';
-
- char *value = get_locale_value(locale, key);
- if (value) {
- size_t value_len = strlen(value);
- if (value_len < remaining) {
- strcpy(output, value);
- output += value_len;
- remaining -= value_len;
- }
- }
- }
- input = end + 1;
- } else {
- *output++ = *input++;
- remaining--;
- }
- } else {
- *output++ = *input++;
- remaining--;
- }
- }
-
- *output = '\0';
- return result;
-}
-
-int process_pages_directory(const char *src_dir, const char *build_dir,
- const char *relative_path, const LocaleData *locale_data) {
- if (!src_dir || !build_dir || !relative_path || !locale_data) return -1;
-
- char full_src_path[MAX_PATH];
- if (strlen(relative_path) > 0) {
- snprintf(full_src_path, sizeof(full_src_path), "%s/%s", src_dir, relative_path);
- } else {
- safe_strcpy(full_src_path, src_dir, sizeof(full_src_path));
- }
-
- DIR *dir = opendir(full_src_path);
- if (!dir) {
- printf("Warning: Could not open pages directory: %s\n", full_src_path);
- return -1;
- }
-
- struct dirent *entry;
- while ((entry = readdir(dir)) != NULL) {
- if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
- continue;
-
- char src_path[MAX_PATH];
- char rel_path[MAX_PATH];
-
- snprintf(src_path, sizeof(src_path), "%s/%s", full_src_path, entry->d_name);
-
- if (strlen(relative_path) > 0) {
- snprintf(rel_path, sizeof(rel_path), "%s/%s", relative_path, entry->d_name);
- } else {
- safe_strcpy(rel_path, entry->d_name, sizeof(rel_path));
- }
-
- struct stat statbuf;
- if (stat(src_path, &statbuf) == 0) {
- if (S_ISDIR(statbuf.st_mode)) {
- process_pages_directory(src_dir, build_dir, rel_path, locale_data);
- } else if (strstr(entry->d_name, ".html")) {
- printf("Processing: %s\n", rel_path);
-
- char *content = load_file(src_path);
- if (!content) {
- printf("Warning: Could not load file: %s\n", src_path);
- continue;
- }
-
- if (locale_data->has_locales && locale_data->count > 0) {
- for (int i = 0; i < locale_data->count; i++) {
- char output_path[MAX_PATH];
- snprintf(output_path, sizeof(output_path), "%s/%s/%s",
- build_dir, locale_data->locales[i].code, rel_path);
-
- // Create directory structure
- char output_dir[MAX_PATH];
- safe_strcpy(output_dir, output_path, sizeof(output_dir));
- char *last_slash = strrchr(output_dir, '/');
- if (last_slash) {
- *last_slash = '\0';
- create_directory(output_dir);
- }
-
- char *processed = process_template(content, &locale_data->locales[i]);
- if (processed) {
- FILE *output_file = fopen(output_path, "w");
- if (output_file) {
- fputs(processed, output_file);
- fclose(output_file);
- } else {
- printf("Warning: Could not write file: %s\n", output_path);
- }
- free(processed);
- }
- }
- } else {
- char output_path[MAX_PATH];
- snprintf(output_path, sizeof(output_path), "%s/%s", build_dir, rel_path);
-
- // Create directory structure
- char output_dir[MAX_PATH];
- safe_strcpy(output_dir, output_path, sizeof(output_dir));
- char *last_slash = strrchr(output_dir, '/');
- if (last_slash) {
- *last_slash = '\0';
- create_directory(output_dir);
- }
-
- char *processed = process_template(content, NULL);
- if (processed) {
- FILE *output_file = fopen(output_path, "w");
- if (output_file) {
- fputs(processed, output_file);
- fclose(output_file);
- } else {
- printf("Warning: Could not write file: %s\n", output_path);
- }
- free(processed);
- }
- }
-
- free(content);
- }
- }
- }
-
- closedir(dir);
- return 0;
-}
-
-int main(int argc, char *argv[]) {
- if (argc != 2 || strcmp(argv[1], "build") != 0) {
- printf("Usage: %s build\nDocs: %s help\n", argv[0], argv[0]);
- return 1;
- }
-
- printf("Building Jelly CMS...\n");
-
- // Create build directory
- if (create_directory("build") != 0) {
- printf("Error: Could not create build directory\n");
- return 1;
- }
-
- // Copy vendor directory
- if (access("vendor", F_OK) == 0) {
- printf("Copying vendor directory...\n");
- copy_directory_recursive("vendor", "build/vendor");
- } else {
- printf("No vendor directory found\n");
- }
-
- // Copy cgi directory
- if (access("src/cgi", F_OK) == 0) {
- printf("Copying cgi directory...\n");
- copy_directory_recursive("src/cgi", "build/cgi");
- } else {
- printf("No cgi directory found\n");
- }
-
- // Copy public directory contents
- if (access("public", F_OK) == 0) {
- printf("Copying public directory...\n");
- DIR *public_dir = opendir("public");
- if (public_dir) {
- struct dirent *entry;
- while ((entry = readdir(public_dir)) != NULL) {
- if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
- continue;
-
- char src_path[MAX_PATH];
- char dest_path[MAX_PATH];
- snprintf(src_path, sizeof(src_path), "public/%s", entry->d_name);
- snprintf(dest_path, sizeof(dest_path), "build/%s", entry->d_name);
-
- struct stat statbuf;
- if (stat(src_path, &statbuf) == 0) {
- if (S_ISDIR(statbuf.st_mode)) {
- copy_directory_recursive(src_path, dest_path);
- } else {
- copy_file(src_path, dest_path);
- }
- }
- }
- closedir(public_dir);
- }
- } else {
- printf("No public directory found\n");
- }
-
- // Copy assets directory
- if (access("assets", F_OK) == 0) {
- printf("Copying assets directory...\n");
- copy_directory_recursive("assets", "build/assets");
- } else {
- printf("No assets directory found\n");
- }
-
- // Load locales
- LocaleData locale_data;
- if (load_locales(&locale_data) != 0) {
- printf("Error loading locales\n");
- return 1;
- }
-
- if (locale_data.has_locales && locale_data.count > 0) {
- printf("Found %d locales: ", locale_data.count);
- for (int i = 0; i < locale_data.count; i++) {
- printf("%s ", locale_data.locales[i].code);
- }
- printf("\n");
- } else {
- printf("No locales found, building single language version\n");
- }
-
- // Process pages
- if (access("src/pages", F_OK) == 0) {
- printf("Processing pages...\n");
- if (process_pages_directory("src/pages", "build", "", &locale_data) != 0) {
- printf("Error processing pages\n");
- return 1;
- }
- } else {
- printf("No src/pages directory found\n");
- }
-
- printf("Build completed successfully!\n");
- return 0;
-} \ No newline at end of file
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..ce1d142
--- /dev/null
+++ b/main.go
@@ -0,0 +1,517 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+const (
+ MAX_CONTENT = 65536
+ MAX_LOCALES = 10
+ MAX_LOCALE_ENTRIES = 100
+ MAX_LOCALE_KEY_SIZE = 256
+ MAX_LOCALE_VAL_SIZE = 1024
+ MAX_LOCALE_CODE = 8
+ MAX_LOCALE_FILE = 100000
+ COPY_BUFFER_SIZE = 4096
+)
+
+type LocaleEntry struct {
+ key string
+ value string
+}
+
+type Locale struct {
+ code string
+ entries []LocaleEntry
+ count int
+}
+
+var locales []Locale
+var localeCount int
+
+func main() {
+ if len(os.Args) != 2 || os.Args[1] != "build" {
+ fmt.Println("Usage: ./program build")
+ os.Exit(1)
+ }
+
+ fmt.Println("Building Jelly CMS...")
+
+ // Create build directory
+ if err := mkdirRecursive("build", 0755); err != nil {
+ fmt.Printf("Error: Could not create build directory: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Copy vendor directory
+ fmt.Println("Copying vendor directory...")
+ if err := copyDirectory("vendor", "build/vendor"); err != nil {
+ if os.IsNotExist(err) {
+ fmt.Println("No vendor directory found")
+ } else {
+ fmt.Printf("Warning: Error copying vendor directory: %v\n", err)
+ }
+ }
+
+ // Copy cgi directory
+ fmt.Println("Copying cgi directory...")
+ if err := copyDirectory("src/cgi", "build/cgi"); err != nil {
+ if os.IsNotExist(err) {
+ fmt.Println("No cgi directory found")
+ } else {
+ fmt.Printf("Warning: Error copying cgi directory: %v\n", err)
+ }
+ }
+
+ // Copy public directory contents to build root
+ fmt.Println("Copying public directory...")
+ if err := copyPublicDirectory("public", "build"); err != nil {
+ if os.IsNotExist(err) {
+ fmt.Println("No public directory found")
+ } else {
+ fmt.Printf("Warning: Error copying public directory: %v\n", err)
+ }
+ }
+
+ // Copy assets directory
+ if err := copyDirectory("assets", "build/assets"); err != nil {
+ if os.IsNotExist(err) {
+ fmt.Println("No assets directory found")
+ } else {
+ fmt.Printf("Warning: Error copying assets directory: %v\n", err)
+ }
+ }
+
+ // Load locales
+ loadLocales()
+
+ if localeCount > 0 {
+ fmt.Printf("Found %d locales:", localeCount)
+ for i := 0; i < localeCount; i++ {
+ fmt.Printf(" %s", locales[i].code)
+ }
+ fmt.Println()
+ }
+
+ // Process pages
+ fmt.Println("Processing pages...")
+ if err := processPages(); err != nil {
+ fmt.Printf("Error processing pages: %v\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Println("Build completed successfully!")
+}
+
+func mkdirRecursive(path string, perm os.FileMode) error {
+ err := os.MkdirAll(path, perm)
+ if err != nil && !os.IsExist(err) {
+ return err
+ }
+ return nil
+}
+
+func copyFile(src, dst string) error {
+ // Create destination directory
+ dstDir := filepath.Dir(dst)
+ if err := mkdirRecursive(dstDir, 0755); err != nil {
+ return err
+ }
+
+ // Open source file
+ srcFile, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer srcFile.Close()
+
+ // Create destination file
+ dstFile, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer dstFile.Close()
+
+ // Copy in chunks
+ buffer := make([]byte, COPY_BUFFER_SIZE)
+ for {
+ n, err := srcFile.Read(buffer)
+ if n > 0 {
+ if _, err := dstFile.Write(buffer[:n]); err != nil {
+ return err
+ }
+ }
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func copyDirectory(src, dst string) error {
+ // Check if source exists
+ srcInfo, err := os.Stat(src)
+ if err != nil {
+ return err
+ }
+
+ if !srcInfo.IsDir() {
+ return fmt.Errorf("source is not a directory")
+ }
+
+ // Create destination directory
+ if err := mkdirRecursive(dst, 0755); err != nil {
+ return err
+ }
+
+ // Read source directory
+ entries, err := os.ReadDir(src)
+ if err != nil {
+ return err
+ }
+
+ // Copy each entry
+ for _, entry := range entries {
+ srcPath := filepath.Join(src, entry.Name())
+ dstPath := filepath.Join(dst, entry.Name())
+
+ if entry.IsDir() {
+ if err := copyDirectory(srcPath, dstPath); err != nil {
+ fmt.Printf("Warning: Error copying directory %s: %v\n", srcPath, err)
+ }
+ } else {
+ if err := copyFile(srcPath, dstPath); err != nil {
+ fmt.Printf("Warning: Error copying file %s: %v\n", srcPath, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func copyPublicDirectory(src, dst string) error {
+ // Check if source exists
+ srcInfo, err := os.Stat(src)
+ if err != nil {
+ return err
+ }
+
+ if !srcInfo.IsDir() {
+ return fmt.Errorf("source is not a directory")
+ }
+
+ // Read source directory
+ entries, err := os.ReadDir(src)
+ if err != nil {
+ return err
+ }
+
+ // Copy each entry to dst root
+ for _, entry := range entries {
+ srcPath := filepath.Join(src, entry.Name())
+ dstPath := filepath.Join(dst, entry.Name())
+
+ if entry.IsDir() {
+ if err := copyDirectory(srcPath, dstPath); err != nil {
+ fmt.Printf("Warning: Error copying directory %s: %v\n", srcPath, err)
+ }
+ } else {
+ if err := copyFile(srcPath, dstPath); err != nil {
+ fmt.Printf("Warning: Error copying file %s: %v\n", srcPath, err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func loadLocales() {
+ locales = make([]Locale, MAX_LOCALES)
+ localeCount = 0
+
+ // Check if locale directory exists
+ _, err := os.Stat("locale")
+ if err != nil {
+ return
+ }
+
+ // Read locale directory
+ entries, err := os.ReadDir("locale")
+ if err != nil {
+ return
+ }
+
+ // Process each .json file
+ for _, entry := range entries {
+ if entry.IsDir() {
+ continue
+ }
+
+ filename := entry.Name()
+ if !strings.HasSuffix(filename, ".json") {
+ continue
+ }
+
+ if localeCount >= MAX_LOCALES {
+ break
+ }
+
+ // Extract locale code (remove .json extension)
+ code := strings.TrimSuffix(filename, ".json")
+ if len(code) > MAX_LOCALE_CODE {
+ code = code[:MAX_LOCALE_CODE]
+ }
+
+ // Load locale file
+ path := filepath.Join("locale", filename)
+ if loadLocaleFile(path, code) {
+ localeCount++
+ }
+ }
+}
+
+func loadLocaleFile(path, code string) bool {
+ // Read file
+ content, err := os.ReadFile(path)
+ if err != nil {
+ return false
+ }
+
+ if len(content) > MAX_LOCALE_FILE {
+ return false
+ }
+
+ // Parse JSON
+ locale := &locales[localeCount]
+ locale.code = code
+ locale.entries = make([]LocaleEntry, MAX_LOCALE_ENTRIES)
+ locale.count = 0
+
+ text := string(content)
+ i := 0
+
+ for i < len(text) && locale.count < MAX_LOCALE_ENTRIES {
+ // Find opening quote for key
+ keyStart := strings.IndexByte(text[i:], '"')
+ if keyStart == -1 {
+ break
+ }
+ keyStart += i + 1
+
+ // Find closing quote for key
+ keyEnd := strings.IndexByte(text[keyStart:], '"')
+ if keyEnd == -1 {
+ break
+ }
+ keyEnd += keyStart
+
+ key := text[keyStart:keyEnd]
+ if len(key) > MAX_LOCALE_KEY_SIZE-1 {
+ key = key[:MAX_LOCALE_KEY_SIZE-1]
+ }
+
+ // Find colon
+ colonPos := strings.IndexByte(text[keyEnd:], ':')
+ if colonPos == -1 {
+ break
+ }
+ i = keyEnd + colonPos + 1
+
+ // Find opening quote for value
+ valStart := strings.IndexByte(text[i:], '"')
+ if valStart == -1 {
+ break
+ }
+ valStart += i + 1
+
+ // Find closing quote for value
+ valEnd := strings.IndexByte(text[valStart:], '"')
+ if valEnd == -1 {
+ break
+ }
+ valEnd += valStart
+
+ value := text[valStart:valEnd]
+ if len(value) > MAX_LOCALE_VAL_SIZE-1 {
+ value = value[:MAX_LOCALE_VAL_SIZE-1]
+ }
+
+ // Store entry
+ locale.entries[locale.count].key = key
+ locale.entries[locale.count].value = value
+ locale.count++
+
+ i = valEnd + 1
+ }
+
+ fmt.Printf("Loaded locale with %d entries from %s\n", locale.count, path)
+ return true
+}
+
+func processPages() error {
+ // Check if src/pages exists
+ _, err := os.Stat("src/pages")
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil // No pages directory, nothing to process
+ }
+ return err
+ }
+
+ return processDirectory("src/pages", "")
+}
+
+func processDirectory(srcDir, relPath string) error {
+ entries, err := os.ReadDir(srcDir)
+ if err != nil {
+ return err
+ }
+
+ for _, entry := range entries {
+ name := entry.Name()
+ srcPath := filepath.Join(srcDir, name)
+ newRelPath := relPath
+ if newRelPath == "" {
+ newRelPath = name
+ } else {
+ newRelPath = filepath.Join(relPath, name)
+ }
+
+ if entry.IsDir() {
+ if err := processDirectory(srcPath, newRelPath); err != nil {
+ fmt.Printf("Warning: Error processing directory %s: %v\n", srcPath, err)
+ }
+ } else if strings.HasSuffix(name, ".html") {
+ fmt.Printf("Processing: %s\n", newRelPath)
+ if err := processPage(srcPath, newRelPath); err != nil {
+ fmt.Printf("Warning: Could not open source file: %s\n", srcPath)
+ }
+ }
+ }
+
+ return nil
+}
+
+func processPage(srcPath, relPath string) error {
+ // Read source file
+ content, err := os.ReadFile(srcPath)
+ if err != nil {
+ return err
+ }
+
+ if len(content) > MAX_CONTENT {
+ return fmt.Errorf("file too large")
+ }
+
+ contentStr := string(content)
+
+ // Process for each locale or once without locale
+ if localeCount > 0 {
+ for i := 0; i < localeCount; i++ {
+ locale := &locales[i]
+ dstPath := filepath.Join("build", locale.code, relPath)
+
+ processed := processTemplate(contentStr, locale)
+
+ // Create destination directory
+ dstDir := filepath.Dir(dstPath)
+ if err := mkdirRecursive(dstDir, 0755); err != nil {
+ return err
+ }
+
+ // Write output
+ if err := os.WriteFile(dstPath, []byte(processed), 0644); err != nil {
+ return err
+ }
+ }
+ } else {
+ dstPath := filepath.Join("build", relPath)
+
+ processed := processTemplate(contentStr, nil)
+
+ // Create destination directory
+ dstDir := filepath.Dir(dstPath)
+ if err := mkdirRecursive(dstDir, 0755); err != nil {
+ return err
+ }
+
+ // Write output
+ if err := os.WriteFile(dstPath, []byte(processed), 0644); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func processTemplate(content string, locale *Locale) string {
+ result := strings.Builder{}
+ result.Grow(len(content))
+
+ i := 0
+ for i < len(content) {
+ // Check for include directive: <!-- %include.
+ if i+14 < len(content) && content[i:i+14] == "<!-- %include." {
+ // Find end of include directive: % -->
+ endPos := strings.Index(content[i+14:], "% -->")
+ if endPos != -1 {
+ endPos += i + 14
+ partialName := content[i+14 : endPos]
+
+ // Load and process partial
+ partialPath := filepath.Join("src/partials", partialName+".html")
+ partialContent, err := os.ReadFile(partialPath)
+ if err == nil && len(partialContent) <= MAX_CONTENT {
+ // Recursively process partial
+ processed := processTemplate(string(partialContent), locale)
+ result.WriteString(processed)
+ }
+ // Skip past the include directive
+ i = endPos + 4 // Skip "% -->"
+ continue
+ }
+ }
+
+ // Check for locale variable: %locale.
+ if i+8 < len(content) && content[i:i+8] == "%locale." {
+ // Find end of locale variable: %
+ endPos := strings.IndexByte(content[i+8:], '%')
+ if endPos != -1 {
+ endPos += i + 8
+ key := content[i+8 : endPos]
+
+ // Look up key in locale
+ if locale != nil {
+ found := false
+ for j := 0; j < locale.count; j++ {
+ if locale.entries[j].key == key {
+ result.WriteString(locale.entries[j].value)
+ found = true
+ break
+ }
+ }
+ if !found {
+ // Key not found, remove directive (write nothing)
+ }
+ }
+ // Skip past the locale variable
+ i = endPos + 1
+ continue
+ }
+ }
+
+ // Regular character
+ result.WriteByte(content[i])
+ i++
+ }
+
+ return result.String()
+} \ No newline at end of file