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: 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 + 5 // Skip "% -->" (5 characters) 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() }