Compare commits

..

No commits in common. "0a1d0daa601125ee5e5f2bbe680c1f4a8def5dc7" and "454e8fa5b0914e1b71e4c32191d589447a384d30" have entirely different histories.

7 changed files with 70 additions and 621 deletions

View File

@ -9,54 +9,12 @@ 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 configuration with default values
// New creates a new empty configuration
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),
}
}
@ -70,16 +28,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); err != nil {
if err := extractGlobals(state, config.values); err != nil {
return nil, err
}
@ -87,7 +45,7 @@ func Load(filePath string) (*Config, error) {
}
// extractGlobals extracts global variables from the Lua state
func extractGlobals(state *luajit.State, config *Config) error {
func extractGlobals(state *luajit.State, values map[string]any) error {
// Get the globals table (_G)
state.GetGlobal("_G")
if !state.IsTable(-1) {
@ -165,8 +123,68 @@ func extractGlobals(state *luajit.State, config *Config) error {
}
}
// Process based on key and type
processConfigValue(state, config, key, valueType)
// 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)
}
// Pop the globals table
@ -175,147 +193,6 @@ func extractGlobals(state *luajit.State, config *Config) 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]
@ -323,19 +200,6 @@ 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
@ -351,15 +215,6 @@ 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
@ -396,24 +251,6 @@ 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
@ -429,15 +266,6 @@ 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
@ -453,15 +281,6 @@ 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
@ -550,11 +369,6 @@ 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
@ -579,44 +393,4 @@ 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
}
}
}

View File

@ -7,7 +7,6 @@ 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"
@ -23,14 +22,13 @@ 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, config *config.Config) *Server {
loggingEnabled bool, debugMode bool, overrideDir string) *Server {
server := &Server{
luaRouter: luaRouter,
@ -40,7 +38,6 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter,
httpServer: &http.Server{},
loggingEnabled: loggingEnabled,
debugMode: debugMode,
config: config,
errorConfig: utils.ErrorPageConfig{
OverrideDir: overrideDir,
DebugMode: debugMode,
@ -77,18 +74,6 @@ func (s *Server) Shutdown(ctx context.Context) error {
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Special case for debug stats when debug mode is enabled
if s.debugMode && r.URL.Path == "/debug/stats" {
s.handleDebugStats(w, r)
// Calculate and log request duration
duration := time.Since(start)
if s.loggingEnabled {
LogRequest(s.logger, http.StatusOK, r, duration)
}
return
}
// Wrap the ResponseWriter to capture status code
wrappedWriter := newStatusCaptureWriter(w)
@ -296,27 +281,3 @@ func setContentTypeIfMissing(w http.ResponseWriter, contentType string) {
w.Header().Set("Content-Type", contentType)
}
}
// handleDebugStats displays debug statistics
func (s *Server) handleDebugStats(w http.ResponseWriter, r *http.Request) {
// Collect system stats
stats := utils.CollectSystemStats(s.config)
// Add component stats
routeCount, bytecodeBytes := s.luaRouter.GetRouteStats()
moduleCount := s.luaRunner.GetModuleCount()
stats.Components = utils.ComponentStats{
RouteCount: routeCount,
BytecodeBytes: bytecodeBytes,
ModuleCount: moduleCount,
}
// Generate HTML page
html := utils.DebugStatsPage(stats)
// Send the response
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(html))
}

View File

@ -1,11 +0,0 @@
package metadata
// Version holds the current Moonshark version
const Version = "0.1"
// Build time information
var (
BuildTime = "unknown"
GitCommit = "unknown"
GoVersion = "unknown"
)

View File

@ -349,50 +349,3 @@ func (r *LuaRouter) GetNodeWithError(method, path string, params *Params) (*Node
Error: node.err,
}, true
}
// GetRouteStats returns statistics about the router
func (r *LuaRouter) GetRouteStats() (int, int64) {
r.mu.RLock()
defer r.mu.RUnlock()
routeCount := 0
bytecodeBytes := int64(0)
// Count routes and bytecode size
for _, root := range r.routes {
count, bytes := countNodesAndBytecode(root)
routeCount += count
bytecodeBytes += bytes
}
return routeCount, bytecodeBytes
}
// countNodesAndBytecode traverses the tree and counts nodes and bytecode size
func countNodesAndBytecode(n *node) (count int, bytecodeBytes int64) {
if n == nil {
return 0, 0
}
// Count this node if it has a handler
if n.handler != "" {
count = 1
bytecodeBytes = int64(len(n.bytecode))
}
// Count static children
for _, child := range n.staticChild {
childCount, childBytes := countNodesAndBytecode(child)
count += childCount
bytecodeBytes += childBytes
}
// Count parameter child
if n.paramChild != nil {
childCount, childBytes := countNodesAndBytecode(n.paramChild)
count += childCount
bytecodeBytes += childBytes
}
return count, bytecodeBytes
}

View File

@ -305,29 +305,3 @@ func (r *LuaRunner) RefreshModuleByName(modName string) bool {
func (r *LuaRunner) AddModule(name string, module any) {
r.sandbox.AddModule(name, module)
}
// GetModuleCount returns the number of loaded modules
func (r *LuaRunner) GetModuleCount() int {
r.mu.RLock()
defer r.mu.RUnlock()
count := 0
// Get module count from Lua
if r.state != nil {
// Execute a Lua snippet to count modules
if res, err := r.state.ExecuteWithResult(`
local count = 0
for _ in pairs(package.loaded) do
count = count + 1
end
return count
`); err == nil {
if num, ok := res.(float64); ok {
count = int(num)
}
}
}
return count
}

View File

@ -1,202 +0,0 @@
package utils
import (
"fmt"
"html/template"
"runtime"
"strings"
"time"
"git.sharkk.net/Sky/Moonshark/core/config"
"git.sharkk.net/Sky/Moonshark/core/metadata"
)
// ComponentStats holds stats from various system components
type ComponentStats struct {
RouteCount int // Number of routes
BytecodeBytes int64 // Total size of bytecode in bytes
ModuleCount int // Number of loaded modules
}
// SystemStats represents system statistics for debugging
type SystemStats struct {
Timestamp time.Time
GoVersion string
GoRoutines int
Memory runtime.MemStats
Components ComponentStats
Version string
Config *config.Config // Configuration information
}
// CollectSystemStats gathers basic system statistics
func CollectSystemStats(cfg *config.Config) SystemStats {
var stats SystemStats
var mem runtime.MemStats
// Collect basic system info
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)
stats.Memory = mem
return stats
}
// DebugStatsPage generates an HTML debug stats page
func DebugStatsPage(stats SystemStats) string {
const debugTemplate = `
<!DOCTYPE html>
<html>
<head>
<title>Moonshark</title>
<style>
body {
font-family: sans-serif;
max-width: 900px;
margin: 0 auto;
background-color: #333;
color: white;
}
h1 {
padding: 1rem;
background-color: #4F5B93;
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.2);
margin-top: 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; }
table tr:nth-child(even), tbody tr:nth-child(even) { background-color: rgba(0, 0, 0, 0.1); }
.card {
background: #F2F2F2;
color: #333;
border-radius: 4px;
margin-bottom: 1rem;
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.2);
}
.timestamp { color: #999; font-size: 0.9em; margin-bottom: 1rem; }
.section { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>Moonshark</h1>
<div class="timestamp">Generated at: {{.Timestamp.Format "2006-01-02 15:04:05"}}</div>
<div class="section">
<h2>Server</h2>
<div class="card">
<table>
<tr><th>Version</th><td>{{.Version}}</td></tr>
</table>
</div>
</div>
<div class="section">
<h2>System</h2>
<div class="card">
<table>
<tr><th>Go Version</th><td>{{.GoVersion}}</td></tr>
<tr><th>Goroutines</th><td>{{.GoRoutines}}</td></tr>
</table>
</div>
</div>
<div class="section">
<h2>Memory</h2>
<div class="card">
<table>
<tr><th>Allocated</th><td>{{ByteCount .Memory.Alloc}}</td></tr>
<tr><th>Total Allocated</th><td>{{ByteCount .Memory.TotalAlloc}}</td></tr>
<tr><th>System Memory</th><td>{{ByteCount .Memory.Sys}}</td></tr>
<tr><th>GC Cycles</th><td>{{.Memory.NumGC}}</td></tr>
</table>
</div>
</div>
<div class="section">
<h2>LuaRunner</h2>
<div class="card">
<table>
<tr><th>Interpreter</th><td>LuaJIT 2.1 (Lua 5.1)</td></tr>
<tr><th>Active Routes</th><td>{{.Components.RouteCount}}</td></tr>
<tr><th>Bytecode Size</th><td>{{ByteCount .Components.BytecodeBytes}}</td></tr>
<tr><th>Loaded Modules</th><td>{{.Components.ModuleCount}}</td></tr>
</table>
</div>
</div>
<div class="section">
<h2>Config</h2>
<div class="card">
<table>
<tr><th>Port</th><td>{{.Config.Port}}</td></tr>
<tr><th>Debug Mode</th><td>{{.Config.Debug}}</td></tr>
<tr><th>Log Level</th><td>{{.Config.LogLevel}}</td></tr>
<tr><th>Routes Directory</th><td>{{.Config.RoutesDir}}</td></tr>
<tr><th>Static Directory</th><td>{{.Config.StaticDir}}</td></tr>
<tr><th>Override Directory</th><td>{{.Config.OverrideDir}}</td></tr>
<tr><th>Buffer Size</th><td>{{.Config.BufferSize}}</td></tr>
<tr><th>HTTP Logging</th><td>{{.Config.HTTPLoggingEnabled}}</td></tr>
<tr><th>Lib Directories</th><td>{{range .Config.LibDirs}}{{.}}<br>{{end}}</td></tr>
<tr><th>Watch Routes</th><td>{{index .Config.Watchers "routes"}}</td></tr>
<tr><th>Watch Static</th><td>{{index .Config.Watchers "static"}}</td></tr>
<tr><th>Watch Modules</th><td>{{index .Config.Watchers "modules"}}</td></tr>
</table>
</div>
</div>
</body>
</html>
`
// Create a template function map
funcMap := template.FuncMap{
"ByteCount": func(b interface{}) string {
var bytes uint64
// Convert the value to uint64
switch v := b.(type) {
case uint64:
bytes = v
case int64:
bytes = uint64(v)
case int:
bytes = uint64(v)
default:
return "Unknown"
}
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := uint64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
},
}
// Parse the template
tmpl, err := template.New("debug").Funcs(funcMap).Parse(debugTemplate)
if err != nil {
return fmt.Sprintf("Error parsing template: %v", err)
}
// Execute the template
var output strings.Builder
if err := tmpl.Execute(&output, stats); err != nil {
return fmt.Sprintf("Error executing template: %v", err)
}
return output.String()
}

View File

@ -237,7 +237,7 @@ func main() {
debugMode := cfg.GetBool("debug", false)
// Create HTTP server
server := http.New(luaRouter, staticRouter, luaRunner, log, httpLoggingEnabled, debugMode, overrideDir, cfg)
server := http.New(luaRouter, staticRouter, luaRunner, log, httpLoggingEnabled, debugMode, overrideDir)
// Handle graceful shutdown
stop := make(chan os.Signal, 1)