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"}}
+
+
System
@@ -106,15 +122,36 @@ table tr:nth-child(even), tbody tr:nth-child(even) { background-color: rgba(0, 0
-
Lua Engine
+
LuaRunner
+ Interpreter | LuaJIT 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"}} |
+
+
+