package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "sort" "strings" "sync" ) // Mutex to serialize calculation evaluations and capture output safely var evalMu sync.Mutex type CalculateResponse struct { Output string `json:"output"` Exit bool `json:"exit"` } type VariableResponse struct { Name string `json:"name"` Value string `json:"value"` Dim string `json:"dim"` } type FunctionResponse struct { Name string `json:"name"` Args []string `json:"args"` Expr string `json:"expr"` } // evaluateExpressionSafe redirects stdout for the duration of the evaluation to capture output safely func evaluateExpressionSafe(line string, variablesFile string, functionsFile string) (string, bool) { evalMu.Lock() defer evalMu.Unlock() // Keep backup of original stdout oldStdout := os.Stdout defer func() { os.Stdout = oldStdout }() // Create pipe to intercept stdout r, w, err := os.Pipe() if err != nil { return "Error capturing stdout", false } os.Stdout = w // Run evaluation (prints to stdout) exit := handleLine(line, false, variablesFile, functionsFile) // Close write end of pipe and read captured output w.Close() var buf bytes.Buffer _, _ = io.Copy(&buf, r) _ = r.Close() return buf.String(), exit } // startWebServer boots the HTTP server serving the Web GUI and APIs func startWebServer(port string) { home, _ := os.UserHomeDir() variablesFile := filepath.Join(home, ".goca_variables.json") functionsFile := filepath.Join(home, ".goca_functions.json") // Load persisted variables and custom functions _ = loadUserFuncs(functionsFile) _ = loadVariables(variablesFile) // Fetch current currency rates in the background to ensure units work _ = fetchRates() // Serve the interactive web dashboard http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" && r.URL.Path != "/index.html" { http.NotFound(w, r) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") html := strings.ReplaceAll(indexHTML, "{{VERSION}}", Version) _, _ = w.Write([]byte(html)) }) // Evaluation endpoint http.HandleFunc("/api/calculate", func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") if query == "" { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(CalculateResponse{Output: "Empty expression", Exit: false}) return } output, exit := evaluateExpressionSafe(query, variablesFile, functionsFile) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(CalculateResponse{ Output: output, Exit: exit, }) }) // List variables endpoint http.HandleFunc("/api/variables", func(w http.ResponseWriter, r *http.Request) { evalMu.Lock() defer evalMu.Unlock() list := []VariableResponse{} for k, v := range variables { // Skip the internal answer variables from bloating list if k == "ans" || k == "_" { continue } list = append(list, VariableResponse{ Name: k, Value: fmt.Sprintf("%v", v.Value), Dim: fmt.Sprintf("%v", v.Dim), }) } sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(list) }) // List custom functions endpoint http.HandleFunc("/api/functions", func(w http.ResponseWriter, r *http.Request) { evalMu.Lock() defer evalMu.Unlock() list := []FunctionResponse{} for k, v := range userFuncs { list = append(list, FunctionResponse{ Name: k, Args: v.Args, Expr: v.Expr, }) } sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name }) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(list) }) // Clear state endpoint http.HandleFunc("/api/reset", func(w http.ResponseWriter, r *http.Request) { evalMu.Lock() defer evalMu.Unlock() variables = make(map[string]Result) _ = saveVariables(variablesFile) userFuncs = make(map[string]UserFunc) _ = saveUserFuncs(functionsFile) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"status":"success"}`)) }) fmt.Printf("Go-Ca web server started on http://localhost:%s\n", port) if err := http.ListenAndServe(":"+port, nil); err != nil { fmt.Printf("❌ Error starting web server: %v\n", err) } } // Beautiful cyber-retro dashboard HTML layout string const indexHTML = ` Go-Ca | Scientific Cyber Calculator
SERVER CONSOLE ACTIVE
12:00:00
goca {{VERSION}}, type 'help' for examples.
goca>
`