summaryrefslogtreecommitdiff
path: root/main.go
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 /main.go
parentb7f1fffaac08795d853ba95334ffe7cee6a4ad58 (diff)
feat: rewrote on go for cross OS builds
Diffstat (limited to 'main.go')
-rw-r--r--main.go517
1 files changed, 517 insertions, 0 deletions
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