package config import ( "errors" "fmt" "runtime" 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 // Number of Lua states in the pool // Feature flags HTTPLoggingEnabled bool Watchers map[string]bool // Raw values map for all values including custom ones 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 PoolSize: runtime.NumCPU(), // Feature flag defaults HTTPLoggingEnabled: true, Watchers: map[string]bool{ "routes": false, "static": false, "modules": false, }, // Initialize values map values: make(map[string]any), } } // Load loads configuration from a Lua file func Load(filePath string) (*Config, error) { // Create a new Lua state without standard libraries state := luajit.New(false) if state == nil { return nil, errors.New("failed to create Lua state") } defer state.Close() // Create config with default values config := New() // Execute the Lua file if err := state.DoFile(filePath); err != nil { return nil, fmt.Errorf("failed to load config file: %w", err) } // Extract all globals from the Lua state if err := extractGlobals(state, config); err != nil { return nil, err } return config, nil } // extractGlobals extracts global variables from the Lua state func extractGlobals(state *luajit.State, config *Config) error { // Get the globals table state.GetGlobal("_G") if !state.IsTable(-1) { state.Pop(1) return errors.New("failed to get globals table") } // Iterate through the globals table state.PushNil() // Start iteration for state.Next(-2) { // Stack now has key at -2 and value at -1 // Skip non-string keys if !state.IsString(-2) { state.Pop(1) // Pop value, leave key for next iteration continue } // Get key and value type key := state.ToString(-2) valueType := state.GetType(-1) // Skip functions, userdata, and threads - we don't need these if valueType == luajit.TypeFunction || valueType == luajit.TypeUserData || valueType == luajit.TypeThread { state.Pop(1) continue } // Process the value processConfigValue(state, config, key) } // Pop the globals table state.Pop(1) return nil } // processConfigValue processes a specific config value from Lua func processConfigValue(state *luajit.State, config *Config, key string) { // Get the value as its natural type value, err := state.ToValue(-1) if err != nil { state.Pop(1) return } // Store in the values map config.values[key] = value // Process special cases and config fields switch key { case "log_level": if strVal, ok := luajit.ConvertValue[string](value); ok { config.LogLevel = strVal } case "port": if intVal, ok := luajit.ConvertValue[int](value); ok { config.Port = intVal } case "debug": if boolVal, ok := luajit.ConvertValue[bool](value); ok { config.Debug = boolVal } case "routes_dir": if strVal, ok := luajit.ConvertValue[string](value); ok { config.RoutesDir = strVal } case "static_dir": if strVal, ok := luajit.ConvertValue[string](value); ok { config.StaticDir = strVal } case "override_dir": if strVal, ok := luajit.ConvertValue[string](value); ok { config.OverrideDir = strVal } case "pool_size": if intVal, ok := luajit.ConvertValue[int](value); ok { config.PoolSize = intVal } case "http_logging_enabled": if boolVal, ok := luajit.ConvertValue[bool](value); ok { config.HTTPLoggingEnabled = boolVal } case "watchers": if table, ok := value.(map[string]any); ok { for k, v := range table { if boolVal, ok := luajit.ConvertValue[bool](v); ok { config.Watchers[k] = boolVal } } } case "lib_dirs": if dirs := extractStringArray(value); len(dirs) > 0 { config.LibDirs = dirs } } state.Pop(1) // Pop value, leave key for next iteration } // 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 := luajit.ConvertValue[string](v); 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 := luajit.ConvertValue[string](v); 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 } // Get returns a configuration value by key func (c *Config) Get(key string) any { return c.values[key] } // GetString returns a string configuration value func (c *Config) GetString(key string, defaultValue string) string { // Check for specific struct fields first for better performance switch key { case "log_level": return c.LogLevel case "routes_dir": return c.RoutesDir case "static_dir": return c.StaticDir case "override_dir": return c.OverrideDir } // Fall back to values map for other keys value, ok := c.values[key] if !ok { return defaultValue } result, ok := luajit.ConvertValue[string](value) if !ok { return defaultValue } return result } // GetInt returns an integer configuration value func (c *Config) GetInt(key string, defaultValue int) int { // Check for specific struct fields first for better performance switch key { case "port": return c.Port case "pool_size": return c.PoolSize } // Fall back to values map for other keys value, ok := c.values[key] if !ok { return defaultValue } result, ok := luajit.ConvertValue[int](value) if !ok { return defaultValue } return result } // GetFloat returns a float configuration value func (c *Config) GetFloat(key string, defaultValue float64) float64 { value, ok := c.values[key] if !ok { return defaultValue } result, ok := luajit.ConvertValue[float64](value) if !ok { return defaultValue } return result } // GetBool returns a boolean configuration value func (c *Config) GetBool(key string, defaultValue bool) bool { // Check for specific struct fields first for better performance switch key { case "debug": return c.Debug case "http_logging_enabled": return c.HTTPLoggingEnabled } // Special case for watcher settings if len(key) > 9 && key[:9] == "watchers." { watcherKey := key[9:] val, ok := c.Watchers[watcherKey] if ok { return val } return defaultValue } // Fall back to values map for other keys value, ok := c.values[key] if !ok { return defaultValue } result, ok := luajit.ConvertValue[bool](value) if !ok { return defaultValue } return result } // GetMap returns a map configuration value func (c *Config) GetMap(key string) map[string]any { // Special case for watchers if key == "watchers" { result := make(map[string]any, len(c.Watchers)) for k, v := range c.Watchers { result[k] = v } return result } value, ok := c.values[key] if !ok { return nil } table, ok := value.(map[string]any) if !ok { return nil } return table } // GetArray returns an array of values from a Lua array func (c *Config) GetArray(key string) []any { // Special case for lib_dirs if key == "lib_dirs" { result := make([]any, len(c.LibDirs)) for i, v := range c.LibDirs { result[i] = v } return result } value := c.Get(key) if value == nil { return nil } // Direct array if arr, ok := value.([]any); ok { return arr } // Arrays in Lua might be represented as maps with an empty string key valueMap, ok := value.(map[string]any) if !ok { return nil } // The array data is stored with an empty key arr, ok := valueMap[""] if !ok { return nil } // Check if it's a float64 array (common for Lua numeric arrays) if floatArr, ok := arr.([]float64); ok { // Convert to []any result := make([]any, len(floatArr)) for i, v := range floatArr { result[i] = v } return result } // Otherwise, try to return as is anyArr, ok := arr.([]any) if !ok { return nil } return anyArr } // GetStringArray returns an array of strings from a Lua array func (c *Config) GetStringArray(key string) []string { // Special case for lib_dirs if key == "lib_dirs" { return c.LibDirs } arr := c.GetArray(key) if arr == nil { return nil } result := make([]string, 0, len(arr)) for _, v := range arr { if str, ok := luajit.ConvertValue[string](v); ok { result = append(result, str) } } return result } // Values returns all configuration values func (c *Config) Values() map[string]any { return c.values }