diff options
| author | maxim nikonov <maxim.nikonov@hqo.co> | 2025-11-21 01:00:41 +0500 |
|---|---|---|
| committer | maxim nikonov <maxim.nikonov@hqo.co> | 2025-11-21 01:00:41 +0500 |
| commit | c2094944cfaa82436f2268e7fad0dc9184ad0bcb (patch) | |
| tree | d40b20343ff6b28664feeb80947bb7583e036e73 /main.go | |
| parent | b7f1fffaac08795d853ba95334ffe7cee6a4ad58 (diff) | |
feat: rewrote on go for cross OS builds
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 517 |
1 files changed, 517 insertions, 0 deletions
@@ -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 |
