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 Static 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) { // For simple configs, use standard libraries to ensure proper loading state := luajit.New(true) if state == nil { return nil, errors.New("failed to create Lua state") } defer state.Close() // 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) } // Extract configuration values extractConfig(state, config) return config, nil } // extractConfig extracts configuration values directly by name func extractConfig(state *luajit.State, config *Config) { // Process known config values directly by key if value := extractValue(state, "debug"); value != nil { if boolVal, ok := value.(bool); ok { config.Debug = boolVal } } if value := extractValue(state, "log_level"); value != nil { if strVal, ok := value.(string); ok { config.LogLevel = strVal } } if value := extractValue(state, "port"); value != nil { if numVal, ok := value.(float64); ok { config.Port = int(numVal) } } if value := extractValue(state, "routes_dir"); value != nil { if strVal, ok := value.(string); ok { config.RoutesDir = strVal } } if value := extractValue(state, "static_dir"); value != nil { if strVal, ok := value.(string); ok { config.StaticDir = strVal } } if value := extractValue(state, "override_dir"); value != nil { if strVal, ok := value.(string); ok { config.OverrideDir = strVal } } if value := extractValue(state, "pool_size"); value != nil { if numVal, ok := value.(float64); ok { config.PoolSize = int(numVal) } } if value := extractValue(state, "http_logging_enabled"); value != nil { if boolVal, ok := value.(bool); ok { config.HTTPLoggingEnabled = boolVal } } // Handle watchers table if value := extractValue(state, "watchers"); value != nil { if table, ok := value.(map[string]any); ok { if routes, ok := table["routes"].(bool); ok { config.Watchers.Routes = routes } if static, ok := table["static"].(bool); ok { config.Watchers.Static = static } if modules, ok := table["modules"].(bool); ok { config.Watchers.Modules = modules } } } // Handle lib_dirs array if value := extractValue(state, "lib_dirs"); value != nil { if dirs := extractStringArray(value); len(dirs) > 0 { config.LibDirs = dirs } } } // extractValue extracts a specific global value from the Lua state func extractValue(state *luajit.State, key string) any { state.GetGlobal(key) defer state.Pop(1) if state.IsNil(-1) { return nil } value, err := state.ToValue(-1) if err != nil { return nil } return value } // extractStringArray extracts a string array from various possible formats func extractStringArray(value any) []string { // Check if it's a direct array 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 } // Check if it's in special array format (map with empty key) valueMap, ok := value.(map[string]any) if !ok { return nil } arr, ok := valueMap[""] if !ok { return nil } // Handle different array types switch arr := arr.(type) { case []string: return arr case []any: result := make([]string, 0, len(arr)) for _, v := range arr { if str, ok := v.(string); ok { result = append(result, str) } } return result case []float64: result := make([]string, 0, len(arr)) for _, v := range arr { result = append(result, fmt.Sprintf("%g", v)) } 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 }