diff --git a/core/config/config.go b/core/config/config.go index a2407d7..50e67a2 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -9,12 +9,54 @@ import ( // 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 + BufferSize int + + // Feature flags + HTTPLoggingEnabled bool + Watchers map[string]bool + + // Raw values map for backward compatibility and custom values values map[string]any } -// New creates a new empty configuration +// 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 + BufferSize: 20, + + // Feature flag defaults + HTTPLoggingEnabled: true, + Watchers: map[string]bool{ + "routes": false, + "static": false, + "modules": false, + }, + + // Initialize values map values: make(map[string]any), } } @@ -28,16 +70,16 @@ func Load(filePath string) (*Config, error) { } 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) } - // Create the config instance - config := New() - // Extract values from the Lua state - if err := extractGlobals(state, config.values); err != nil { + if err := extractGlobals(state, config); err != nil { return nil, err } @@ -45,7 +87,7 @@ func Load(filePath string) (*Config, error) { } // extractGlobals extracts global variables from the Lua state -func extractGlobals(state *luajit.State, values map[string]any) error { +func extractGlobals(state *luajit.State, config *Config) error { // Get the globals table (_G) state.GetGlobal("_G") if !state.IsTable(-1) { @@ -123,68 +165,8 @@ func extractGlobals(state *luajit.State, values map[string]any) error { } } - // Handle primitive types directly - switch valueType { - case luajit.TypeBoolean: - values[key] = state.ToBoolean(-1) - state.Pop(1) - continue - case luajit.TypeNumber: - values[key] = state.ToNumber(-1) - state.Pop(1) - continue - case luajit.TypeString: - values[key] = state.ToString(-1) - state.Pop(1) - continue - case luajit.TypeTable: - // For tables, use the existing conversion logic - default: - // Skip unsupported types - state.Pop(1) - continue - } - - // Handle tables (arrays and maps) - if valueType == luajit.TypeTable { - // Check if it looks like an array first - arrLen := state.GetTableLength(-1) - if arrLen > 0 { - // Process as array - arr := make([]any, arrLen) - for i := 1; i <= arrLen; i++ { - state.PushNumber(float64(i)) - state.GetTable(-2) // Get t[i] - - switch state.GetType(-1) { - case luajit.TypeBoolean: - arr[i-1] = state.ToBoolean(-1) - case luajit.TypeNumber: - arr[i-1] = state.ToNumber(-1) - case luajit.TypeString: - arr[i-1] = state.ToString(-1) - default: - // For complex elements, try to convert - if val, err := state.ToValue(-1); err == nil { - arr[i-1] = val - } - } - - state.Pop(1) // Pop value - } - values[key] = arr - state.Pop(1) - continue - } - - // Try normal table conversion for non-array tables - if table, err := state.ToTable(-1); err == nil { - values[key] = table - } - } - - // Pop value, leave key for next iteration - state.Pop(1) + // Process based on key and type + processConfigValue(state, config, key, valueType) } // Pop the globals table @@ -193,6 +175,147 @@ func extractGlobals(state *luajit.State, values map[string]any) error { return nil } +// processConfigValue processes a specific config value from Lua +func processConfigValue(state *luajit.State, config *Config, key string, valueType luajit.LuaType) { + // Store in the values map first (for backward compatibility) + var value any + + // Extract the value based on its type + switch valueType { + case luajit.TypeBoolean: + value = state.ToBoolean(-1) + case luajit.TypeNumber: + value = state.ToNumber(-1) + case luajit.TypeString: + value = state.ToString(-1) + case luajit.TypeTable: + // For tables, use the existing conversion logic + if table, err := state.ToTable(-1); err == nil { + value = table + + // Special case for watchers table + if key == "watchers" { + processWatchersTable(config, table) + } + } + default: + // Skip unsupported types + state.Pop(1) + return + } + + // Store in the values map + config.values[key] = value + + // Now set specific struct fields based on key + switch key { + case "log_level": + if strVal, ok := value.(string); ok { + config.LogLevel = strVal + } + case "port": + if numVal, ok := value.(float64); ok { + config.Port = int(numVal) + } + case "debug": + if boolVal, ok := value.(bool); ok { + config.Debug = boolVal + } + case "routes_dir": + if strVal, ok := value.(string); ok { + config.RoutesDir = strVal + } + case "static_dir": + if strVal, ok := value.(string); ok { + config.StaticDir = strVal + } + case "override_dir": + if strVal, ok := value.(string); ok { + config.OverrideDir = strVal + } + case "buffer_size": + if numVal, ok := value.(float64); ok { + config.BufferSize = int(numVal) + } + case "http_logging_enabled": + if boolVal, ok := value.(bool); ok { + config.HTTPLoggingEnabled = boolVal + } + case "lib_dirs": + // Handle lib_dirs array + processLibDirs(config, value) + } + + state.Pop(1) // Pop value, leave key for next iteration +} + +// processWatchersTable processes the watchers table configuration +func processWatchersTable(config *Config, watchersTable map[string]any) { + for key, value := range watchersTable { + if boolVal, ok := value.(bool); ok { + config.Watchers[key] = boolVal + } + } +} + +// processLibDirs processes the lib_dirs array configuration +func processLibDirs(config *Config, value any) { + // 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) + } + } + if len(result) > 0 { + config.LibDirs = result + } + return + } + + // Check if it's in our special array format (map with empty key) + valueMap, ok := value.(map[string]any) + if !ok { + return + } + + arr, ok := valueMap[""] + if !ok { + return + } + + // Handle array format + if strArray := extractStringArray(arr); len(strArray) > 0 { + config.LibDirs = strArray + } +} + +// extractStringArray extracts a string array from various possible formats +func extractStringArray(arr any) []string { + // Check different possible array formats + 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: + // Unlikely but handle numeric arrays too + 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] @@ -200,6 +323,19 @@ func (c *Config) Get(key string) any { // GetString returns a string configuration value func (c *Config) GetString(key string, defaultValue string) string { + // Check for specific struct fields first + 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 @@ -215,6 +351,15 @@ func (c *Config) GetString(key string, defaultValue string) string { // GetInt returns an integer configuration value func (c *Config) GetInt(key string, defaultValue int) int { + // Check for specific struct fields first + switch key { + case "port": + return c.Port + case "buffer_size": + return c.BufferSize + } + + // Fall back to values map for other keys value, ok := c.values[key] if !ok { return defaultValue @@ -251,6 +396,24 @@ func (c *Config) GetFloat(key string, defaultValue float64) float64 { // GetBool returns a boolean configuration value func (c *Config) GetBool(key string, defaultValue bool) bool { + // Check for specific struct fields first + switch key { + case "debug": + return c.Debug + case "http_logging_enabled": + return c.HTTPLoggingEnabled + } + + // Special case for watcher settings + if key == "watchers.routes" { + return c.Watchers["routes"] + } else if key == "watchers.static" { + return c.Watchers["static"] + } else if key == "watchers.modules" { + return c.Watchers["modules"] + } + + // Fall back to values map for other keys value, ok := c.values[key] if !ok { return defaultValue @@ -266,6 +429,15 @@ func (c *Config) GetBool(key string, defaultValue bool) bool { // 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) + for k, v := range c.Watchers { + result[k] = v + } + return result + } + value, ok := c.values[key] if !ok { return nil @@ -281,6 +453,15 @@ func (c *Config) GetMap(key string) map[string]any { // 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 @@ -369,6 +550,11 @@ func (c *Config) GetIntArray(key string) []int { // 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 @@ -393,4 +579,44 @@ func (c *Config) Values() map[string]any { // Set sets a configuration value func (c *Config) Set(key string, value any) { c.values[key] = value + + // Also update the struct field if applicable + switch key { + case "log_level": + if strVal, ok := value.(string); ok { + c.LogLevel = strVal + } + case "port": + if numVal, ok := value.(float64); ok { + c.Port = int(numVal) + } else if intVal, ok := value.(int); ok { + c.Port = intVal + } + case "debug": + if boolVal, ok := value.(bool); ok { + c.Debug = boolVal + } + case "routes_dir": + if strVal, ok := value.(string); ok { + c.RoutesDir = strVal + } + case "static_dir": + if strVal, ok := value.(string); ok { + c.StaticDir = strVal + } + case "override_dir": + if strVal, ok := value.(string); ok { + c.OverrideDir = strVal + } + case "buffer_size": + if numVal, ok := value.(float64); ok { + c.BufferSize = int(numVal) + } else if intVal, ok := value.(int); ok { + c.BufferSize = intVal + } + case "http_logging_enabled": + if boolVal, ok := value.(bool); ok { + c.HTTPLoggingEnabled = boolVal + } + } } diff --git a/core/http/server.go b/core/http/server.go index 5930f43..5a80f3f 100644 --- a/core/http/server.go +++ b/core/http/server.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "git.sharkk.net/Sky/Moonshark/core/config" "git.sharkk.net/Sky/Moonshark/core/logger" "git.sharkk.net/Sky/Moonshark/core/routers" "git.sharkk.net/Sky/Moonshark/core/runner" @@ -22,13 +23,14 @@ type Server struct { httpServer *http.Server loggingEnabled bool debugMode bool // Controls whether to show error details + config *config.Config errorConfig utils.ErrorPageConfig } // New creates a new HTTP server with optimized connection settings func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, runner *runner.LuaRunner, log *logger.Logger, - loggingEnabled bool, debugMode bool, overrideDir string) *Server { + loggingEnabled bool, debugMode bool, overrideDir string, config *config.Config) *Server { server := &Server{ luaRouter: luaRouter, @@ -38,6 +40,7 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, httpServer: &http.Server{}, loggingEnabled: loggingEnabled, debugMode: debugMode, + config: config, errorConfig: utils.ErrorPageConfig{ OverrideDir: overrideDir, DebugMode: debugMode, @@ -297,7 +300,7 @@ func setContentTypeIfMissing(w http.ResponseWriter, contentType string) { // handleDebugStats displays debug statistics func (s *Server) handleDebugStats(w http.ResponseWriter, r *http.Request) { // Collect system stats - stats := utils.CollectSystemStats() + stats := utils.CollectSystemStats(s.config) // Add component stats routeCount, bytecodeBytes := s.luaRouter.GetRouteStats() diff --git a/core/metadata/metadata.go b/core/metadata/metadata.go new file mode 100644 index 0000000..d4140b5 --- /dev/null +++ b/core/metadata/metadata.go @@ -0,0 +1,11 @@ +package metadata + +// Version holds the current Moonshark version +const Version = "0.1" + +// Build time information +var ( + BuildTime = "unknown" + GitCommit = "unknown" + GoVersion = "unknown" +) diff --git a/core/utils/debug.go b/core/utils/debug.go index 9439b02..4c5491f 100644 --- a/core/utils/debug.go +++ b/core/utils/debug.go @@ -6,6 +6,9 @@ import ( "runtime" "strings" "time" + + "git.sharkk.net/Sky/Moonshark/core/config" + "git.sharkk.net/Sky/Moonshark/core/metadata" ) // ComponentStats holds stats from various system components @@ -22,10 +25,12 @@ type SystemStats struct { GoRoutines int Memory runtime.MemStats Components ComponentStats + Version string + Config *config.Config // Configuration information } // CollectSystemStats gathers basic system statistics -func CollectSystemStats() SystemStats { +func CollectSystemStats(cfg *config.Config) SystemStats { var stats SystemStats var mem runtime.MemStats @@ -33,6 +38,8 @@ func CollectSystemStats() SystemStats { stats.Timestamp = time.Now() stats.GoVersion = runtime.Version() stats.GoRoutines = runtime.NumGoroutine() + stats.Version = metadata.Version + stats.Config = cfg // Collect memory stats runtime.ReadMemStats(&mem) @@ -62,8 +69,8 @@ h1 { box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.2); margin-top: 0; } -h2 { margin-top: 25px; } -table { width: 100%; border-collapse: collapse; margin: 15px 0; } +h2 { margin-top: 0; margin-bottom: 0.5rem; } +table { width: 100%; border-collapse: collapse; } th { width: 1%; white-space: nowrap; border-right: 1px solid rgba(0, 0, 0, 0.1); } th, td { text-align: left; padding: 0.75rem 0.5rem; border-bottom: 1px solid #ddd; } tr:last-child th, tr:last-child td { border-bottom: none; } @@ -72,10 +79,10 @@ table tr:nth-child(even), tbody tr:nth-child(even) { background-color: rgba(0, 0 background: #F2F2F2; color: #333; border-radius: 4px; - margin-bottom: 20px; + margin-bottom: 1rem; box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.2); } -.timestamp { color: #999; font-size: 0.9em; } +.timestamp { color: #999; font-size: 0.9em; margin-bottom: 1rem; } .section { margin-bottom: 30px; } @@ -83,6 +90,15 @@ table tr:nth-child(even), tbody tr:nth-child(even) { background-color: rgba(0, 0

Moonshark

Generated at: {{.Timestamp.Format "2006-01-02 15:04:05"}}
+
+

Server

+
+ + +
Version{{.Version}}
+
+
+

System

@@ -106,15 +122,36 @@ table tr:nth-child(even), tbody tr:nth-child(even) { background-color: rgba(0, 0
-

Lua Engine

+

LuaRunner

+
InterpreterLuaJIT 2.1 (Lua 5.1)
Active Routes{{.Components.RouteCount}}
Bytecode Size{{ByteCount .Components.BytecodeBytes}}
Loaded Modules{{.Components.ModuleCount}}
+ +
+

Config

+
+ + + + + + + + + + + + + +
Port{{.Config.Port}}
Debug Mode{{.Config.Debug}}
Log Level{{.Config.LogLevel}}
Routes Directory{{.Config.RoutesDir}}
Static Directory{{.Config.StaticDir}}
Override Directory{{.Config.OverrideDir}}
Buffer Size{{.Config.BufferSize}}
HTTP Logging{{.Config.HTTPLoggingEnabled}}
Lib Directories{{range .Config.LibDirs}}{{.}}
{{end}}
Watch Routes{{index .Config.Watchers "routes"}}
Watch Static{{index .Config.Watchers "static"}}
Watch Modules{{index .Config.Watchers "modules"}}
+
+
` diff --git a/moonshark.go b/moonshark.go index a572edb..9805f07 100644 --- a/moonshark.go +++ b/moonshark.go @@ -237,7 +237,7 @@ func main() { debugMode := cfg.GetBool("debug", false) // Create HTTP server - server := http.New(luaRouter, staticRouter, luaRunner, log, httpLoggingEnabled, debugMode, overrideDir) + server := http.New(luaRouter, staticRouter, luaRunner, log, httpLoggingEnabled, debugMode, overrideDir, cfg) // Handle graceful shutdown stop := make(chan os.Signal, 1)