450 lines
9.2 KiB
Go
450 lines
9.2 KiB
Go
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
|
|
}
|