package config import ( "errors" "fmt" "runtime" "strconv" "strings" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) // Config represents a configuration loaded from a Lua file type Config struct { // Server settings Server struct { Port int Debug bool LogLevel string HTTPLogging bool } // Runner settings Runner struct { PoolSize int } // Directory paths Dirs struct { Routes string Static string FS string Data string Override string Libs []string } // Raw values map for custom values values map[string]any } // New creates a new configuration with default values func New() *Config { config := &Config{ // Initialize values map values: make(map[string]any), } // Server defaults config.Server.Port = 3117 config.Server.Debug = false config.Server.LogLevel = "info" config.Server.HTTPLogging = false // Runner defaults config.Runner.PoolSize = runtime.GOMAXPROCS(0) // Dirs defaults config.Dirs.Routes = "./routes" config.Dirs.Static = "./static" config.Dirs.FS = "./fs" config.Dirs.Data = "./data" config.Dirs.Override = "./override" config.Dirs.Libs = []string{"./libs"} return config } // Load loads configuration from a Lua file func Load(filePath string) (*Config, error) { // Create Lua state state := luajit.New(true) if state == nil { return nil, errors.New("failed to create Lua state") } defer state.Close() defer state.Cleanup() // Create config with default values config := New() // Execute the config file if err := state.DoFile(filePath); err != nil { return nil, fmt.Errorf("failed to load config file: %w", err) } // Store values directly to the config config.values = make(map[string]any) // Extract top-level tables tables := []string{"server", "runner", "dirs"} for _, table := range tables { state.GetGlobal(table) if state.IsTable(-1) { tableMap, err := state.ToTable(-1) if err == nil { config.values[table] = tableMap } } state.Pop(1) } // Apply configuration values applyConfig(config, config.values) return config, nil } // applyConfig applies configuration values from the globals map func applyConfig(config *Config, values map[string]any) { // Apply server settings if serverTable, ok := values["server"].(map[string]any); ok { if v, ok := serverTable["port"].(float64); ok { config.Server.Port = int(v) } if v, ok := serverTable["debug"].(bool); ok { config.Server.Debug = v } if v, ok := serverTable["log_level"].(string); ok { config.Server.LogLevel = v } if v, ok := serverTable["http_logging"].(bool); ok { config.Server.HTTPLogging = v } } // Apply runner settings if runnerTable, ok := values["runner"].(map[string]any); ok { if v, ok := runnerTable["pool_size"].(float64); ok { config.Runner.PoolSize = int(v) } } // Apply dirs settings if dirsTable, ok := values["dirs"].(map[string]any); ok { if v, ok := dirsTable["routes"].(string); ok { config.Dirs.Routes = v } if v, ok := dirsTable["static"].(string); ok { config.Dirs.Static = v } if v, ok := dirsTable["fs"].(string); ok { config.Dirs.FS = v } if v, ok := dirsTable["data"].(string); ok { config.Dirs.Data = v } if v, ok := dirsTable["override"].(string); ok { config.Dirs.Override = v } // Handle libs array if libs, ok := dirsTable["libs"]; ok { if libsArray := extractStringArray(libs); len(libsArray) > 0 { config.Dirs.Libs = libsArray } } } } // extractStringArray extracts a string array from a Lua table func extractStringArray(value any) []string { // Direct array case if arr, ok := value.([]any); ok { result := make([]string, 0, len(arr)) for _, v := range arr { if str, ok := v.(string); ok { result = append(result, str) } } return result } // Map with numeric keys case if tableMap, ok := value.(map[string]any); ok { result := make([]string, 0, len(tableMap)) for _, v := range tableMap { if str, ok := v.(string); ok { result = append(result, str) } } return result } return nil } // GetCustomValue returns any custom configuration value by key // Key can be a dotted path like "server.port" func (c *Config) GetCustomValue(key string) any { parts := strings.Split(key, ".") if len(parts) == 1 { return c.values[key] } current := c.values for _, part := range parts[:len(parts)-1] { next, ok := current[part].(map[string]any) if !ok { return nil } current = next } return current[parts[len(parts)-1]] } // GetCustomString returns a custom string configuration value func (c *Config) GetCustomString(key string, defaultValue string) string { value := c.GetCustomValue(key) if value == nil { return defaultValue } // Convert to string switch v := value.(type) { case string: return v case float64: return fmt.Sprintf("%g", v) case int: return strconv.Itoa(v) case bool: if v { return "true" } return "false" default: return defaultValue } } // GetCustomInt returns a custom integer configuration value func (c *Config) GetCustomInt(key string, defaultValue int) int { value := c.GetCustomValue(key) if value == nil { return defaultValue } // Convert to int switch v := value.(type) { case int: return v case float64: return int(v) case string: if i, err := strconv.Atoi(v); err == nil { return i } case bool: if v { return 1 } return 0 default: return defaultValue } return defaultValue } // GetCustomBool returns a custom boolean configuration value func (c *Config) GetCustomBool(key string, defaultValue bool) bool { value := c.GetCustomValue(key) if value == nil { return defaultValue } // Convert to bool switch v := value.(type) { case bool: return v case string: switch v { case "true", "yes", "1": return true case "false", "no", "0", "": return false } case int: return v != 0 case float64: return v != 0 default: return defaultValue } return defaultValue }