diff options
| author | mnik01 <maksimgrinberg01@gmail.com> | 2025-09-10 05:11:33 +0500 |
|---|---|---|
| committer | mnik01 <maksimgrinberg01@gmail.com> | 2025-09-10 05:11:33 +0500 |
| commit | d8a7a5d5aeabdcb1e45ef4de3714d55b6b7e7cf7 (patch) | |
| tree | 44ecd3082ea7f7d550a4457ebfc4c5dbf3ae75af | |
initial
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 37 | ||||
| -rw-r--r-- | README.md | 337 | ||||
| -rw-r--r-- | draft.md | 22 | ||||
| -rw-r--r-- | main.c | 601 |
5 files changed, 998 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ae7a33 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +jelly-cms diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4bd922c --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +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 new file mode 100644 index 0000000..cd6dc62 --- /dev/null +++ b/README.md @@ -0,0 +1,337 @@ +# Jelly CMS + +A fast, lightweight static site generator written in C that supports multilingual content, template partials, and asset management. + +## Features + +- [x] Multilingual Support**: Automatic locale detection and site generation +- [x] Template Partials**: Reusable HTML components with includes +- [x] Zero Dependencies**: Single executable with no external requirements +- [ ] Markdown support for content +- [ ] CGI support for auth, captcha, comments features +- [ ] Watch mode +- [ ] Serve mode +- [ ] Help command that shows readme + +## Quick Start + +### Installation + +1. Clone or download the project files (`main.c` and `Makefile`) +2. Build the executable: + ```bash + make + ``` + +### Project Structure + +``` +your-project/ +├── assets/ # Static assets (images, fonts, etc.) +│ └── images/ +│ └── logo.png +├── locale/ # Locale files (optional) +│ ├── en.json +│ ├── ru.json +│ └── kk.json +├── public/ # Public files copied as-is +│ ├── robots.txt +│ ├── sitemap.xml +│ └── main.css +├── src/ +│ ├── pages/ # Your HTML pages +│ │ ├── index.html +│ │ └── about/ +│ │ └── contacts.html +│ └── partials/ # Reusable components +│ ├── header.html +│ └── footer.html +├── vendor/ # Third-party libraries +│ └── alpinejs.min.js +└── jelly-cms # The built executable +``` + +### Build Your Site + +```bash +./jelly-cms build +``` + +This creates a `build/` directory with your generated site. + +## Templating + +### Including Partials + +Use the include syntax to embed reusable components: + +```html +<!DOCTYPE html> +<html> +<head> + <title>My Site</title> +</head> +<body> + <!-- %include.header% --> + + <main> + <h1>Welcome to my site</h1> + </main> + + <!-- %include.footer% --> +</body> +</html> +``` + +### Locale Variables + +Reference locale-specific content using the locale syntax: + +```html +<h1>%locale.welcome_title%</h1> +<p>Contact us: %locale.phone%</p> +<address>%locale.address%</address> +``` + +## Localization + +### Setting Up Locales + +1. Create a `locale/` directory +2. Add JSON files for each language (e.g., `en.json`, `ru.json`, `kk.json`) + +**Example `locale/en.json`:** +```json +{ + "welcome_title": "Welcome to Our Website", + "phone": "+1 555 123 4567", + "address": "123 Main St, City, Country", + "nav_home": "Home", + "nav_about": "About", + "footer_copyright": "© 2024 My Company" +} +``` + +**Example `locale/ru.json`:** +```json +{ + "welcome_title": "Добро пожаловать на наш сайт", + "phone": "+7 123 456 7890", + "address": "ул. Главная 123, Город, Страна", + "nav_home": "Главная", + "nav_about": "О нас", + "footer_copyright": "© 2024 Моя Компания" +} +``` + +### Build Output + +With locales, Jelly CMS generates: +``` +build/ +├── en/ +│ ├── index.html +│ └── about/ +│ └── contacts.html +├── ru/ +│ ├── index.html +│ └── about/ +│ └── contacts.html +├── assets/ +│ └── images/ +│ └── logo.png +├── vendor/ +│ └── alpinejs.min.js +├── robots.txt +├── sitemap.xml +└── main.css +``` + +Without locales: +``` +build/ +├── index.html +├── about/ +│ └── contacts.html +├── assets/ +├── vendor/ +├── robots.txt +├── sitemap.xml +└── main.css +``` + +## Directory Behavior + +| Source Directory | Build Behavior | +|-----------------|----------------| +| `assets/` | Copied to `build/assets/` | +| `public/` | Contents copied directly to `build/` root | +| `vendor/` | Copied to `build/vendor/` | +| `src/pages/` | Processed as templates, structure preserved | +| `src/partials/` | Used for includes, not copied to build | +| `locale/` | Used for templating, not copied to build | + +## Example Templates + +### Main Page (`src/pages/index.html`) +```html +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>%locale.site_title%</title> + <link rel="stylesheet" href="/main.css"> +</head> +<body> + <!-- %include.header% --> + + <main> + <h1>%locale.welcome_title%</h1> + <p>%locale.welcome_message%</p> + </main> + + <!-- %include.footer% --> + <script src="/vendor/alpinejs.min.js"></script> +</body> +</html> +``` + +### Header Partial (`src/partials/header.html`) +```html +<header> + <nav> + <img src="/assets/images/logo.png" alt="Logo"> + <ul> + <li><a href="/">%locale.nav_home%</a></li> + <li><a href="/about/">%locale.nav_about%</a></li> + </ul> + </nav> +</header> +``` + +### Footer Partial (`src/partials/footer.html`) +```html +<footer> + <p>%locale.footer_copyright%</p> + <p>%locale.contact_info%</p> +</footer> +``` + +## Build Commands + +### Basic Commands +```bash +# Build the site +./jelly-cms build + +# Clean build artifacts +make clean + +# Rebuild everything +make clean && make && ./jelly-cms build +``` + +### Make Targets +```bash +make # Build jelly-cms executable +make clean # Remove build artifacts +make install # Install to /usr/local/bin (requires sudo) +make uninstall # Remove from /usr/local/bin (requires sudo) +make test # Build and run test +make help # Show available targets +``` + +## Advanced Usage + +### Nested Partials +Partials can include other partials: + +**`src/partials/layout.html`:** +```html +<!DOCTYPE html> +<html> +<head> + <!-- %include.meta% --> +</head> +<body> + <!-- %include.header% --> + <main> + <!-- Content will be here --> + </main> + <!-- %include.footer% --> +</body> +</html> +``` + +### Complex Directory Structures +``` +src/pages/ +├── index.html +├── blog/ +│ ├── index.html +│ └── posts/ +│ ├── first-post.html +│ └── second-post.html +└── products/ + ├── index.html + └── category/ + └── item.html +``` + +This structure is preserved in the build output for each locale. + +## Troubleshooting + +### Common Issues + +**Segmentation Fault:** +- Check that all referenced partials exist in `src/partials/` +- Ensure locale JSON files are valid JSON +- Verify file permissions for all source directories + +**Missing Files in Build:** +- Ensure source directories exist (`src/pages/`, etc.) +- Check file paths are correct (case-sensitive) +- Verify JSON syntax in locale files + +**Locale Variables Not Replaced:** +- Check JSON syntax in locale files +- Ensure locale keys match exactly (case-sensitive) +- Verify locale files are in `locale/` directory + +### Debug Mode +Run with verbose output to see detailed processing: +```bash +./jelly-cms build 2>&1 | tee build.log +``` + +### File Size Limits +- Maximum file size: 64KB per template +- Maximum locale entries: 100 per language +- Maximum locales: 10 + +## Performance + +- **Build Speed**: Processes hundreds of pages in milliseconds +- **Memory Usage**: Minimal RAM footprint (~1-2MB) +- **File Size**: Single ~50KB executable +- **Dependencies**: None (statically linked) + +## License + +This project is provided as-is. Feel free to modify and distribute according to your needs. + +## Contributing + +Since this is a simple C program, contributions are welcome: +1. Fork the project +2. Make your changes to `main.c` +3. Test thoroughly +4. Submit a pull request + +For bug reports or feature requests, please provide: +- Your project structure +- Locale files (if using multilingual features) +- Error messages or unexpected behavior +- Operating system and compiler version diff --git a/draft.md b/draft.md new file mode 100644 index 0000000..8402b52 --- /dev/null +++ b/draft.md @@ -0,0 +1,22 @@ +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 + + + + +TODO: продумать локали (локаль по папкам файлов мд или по названию файла или в контенте во фронтматтере) и конфликты и возможно есть локаль в страницах но нет в контенте +мб сделать два режима без локали и с локалью @@ -0,0 +1,601 @@ +#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\n", 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 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 |
