package config import ( "errors" "fmt" "runtime" "strconv" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) // Config represents a configuration loaded from a Lua file type Config struct { // Server settings LogLevel string Port int Debug bool // Directory paths RoutesDir string StaticDir string OverrideDir string LibDirs []string // Performance settings PoolSize int // Feature flags HTTPLoggingEnabled bool Watchers struct { Routes bool Modules bool } // Raw values map for custom values values map[string]any } // New creates a new configuration with default values func New() *Config { return &Config{ // Server defaults LogLevel: "info", Port: 3117, Debug: false, // Directory defaults RoutesDir: "./routes", StaticDir: "./static", OverrideDir: "./override", LibDirs: []string{"./libs"}, // Performance defaults - respect GOMAXPROCS PoolSize: runtime.GOMAXPROCS(0), // Feature flag defaults HTTPLoggingEnabled: true, // Initialize values map values: make(map[string]any), } } // 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 all globals individually globalKeys := []string{ "debug", "log_level", "port", "routes_dir", "static_dir", "override_dir", "pool_size", "http_logging_enabled", "watchers", "lib_dirs", } for _, key := range globalKeys { state.GetGlobal(key) if !state.IsNil(-1) { value, err := state.ToValue(-1) if err == nil { config.values[key] = value } } 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, globals map[string]any) { // Server settings if v, ok := globals["debug"].(bool); ok { config.Debug = v } if v, ok := globals["log_level"].(string); ok { config.LogLevel = v } if v, ok := globals["port"].(float64); ok { config.Port = int(v) } // Directory paths if v, ok := globals["routes_dir"].(string); ok { config.RoutesDir = v } if v, ok := globals["static_dir"].(string); ok { config.StaticDir = v } if v, ok := globals["override_dir"].(string); ok { config.OverrideDir = v } // Performance settings if v, ok := globals["pool_size"].(float64); ok { config.PoolSize = int(v) } // Feature flags if v, ok := globals["http_logging_enabled"].(bool); ok { config.HTTPLoggingEnabled = v } // Handle lib_dirs array more efficiently if libDirs, ok := globals["lib_dirs"]; ok { if dirs := extractStringArray(libDirs); len(dirs) > 0 { config.LibDirs = dirs } } // Handle watchers table if watchersVal, ok := globals["watchers"]; ok { if watchers, ok := watchersVal.(map[string]any); ok { if v, ok := watchers["routes"].(bool); ok { config.Watchers.Routes = v } if v, ok := watchers["modules"].(bool); ok { config.Watchers.Modules = v } } } } // 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 func (c *Config) GetCustomValue(key string) any { return c.values[key] } // GetCustomString returns a custom string configuration value func (c *Config) GetCustomString(key string, defaultValue string) string { value := c.values[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.values[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.values[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 }