diff --git a/.gitignore b/.gitignore
index 9d79b46..c5abb80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,7 @@
!/.env.sample
!/.cruft.json
!/.goreleaser.yaml
-
+!/test/*
!.github/workflows/*.yml
!*.go
diff --git a/go.mod b/go.mod
index 39e56d1..67d5232 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,16 @@
-module github.com/dyne/fouter
-
+module github.com/ForkbombEu/fouter
go 1.22
+
+require (
+ github.com/FilippoTrotter/slangroom-go v0.0.0-20241003095650-aada02d0ab90
+ github.com/gorilla/mux v1.8.1
+ github.com/stretchr/testify v1.9.0
+)
+
+require (
+ github.com/amenzhinsky/go-memexec v0.7.1 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7e9206c
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,18 @@
+github.com/FilippoTrotter/slangroom-go v0.0.0-20241003095650-aada02d0ab90 h1:nuq9jkWM+wY4UfvXHpE0mN/C/W3eIzQp2bds0L9Zao4=
+github.com/FilippoTrotter/slangroom-go v0.0.0-20241003095650-aada02d0ab90/go.mod h1:+hMvWLDLPvTCwpx1BFBIoSX/cIlIZDWPvzQ+U1pP3X0=
+github.com/amenzhinsky/go-memexec v0.7.1 h1:DVm4cXzklaNWZoTJgZUi/dlXtelhC7QBtX4luKjl1qk=
+github.com/amenzhinsky/go-memexec v0.7.1/go.mod h1:ApTO9/i2bcii7kvIXi74gum+/zYDzkiOXtuBZoYOKVE=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
+golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/router.go b/router.go
new file mode 100644
index 0000000..5366aaa
--- /dev/null
+++ b/router.go
@@ -0,0 +1,137 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+
+ slang "github.com/FilippoTrotter/slangroom-go" // Change this to dyne/slangroom-exec once merged
+ "github.com/gorilla/mux"
+)
+
+const baseDir = "."
+
+// Function to find all .slang files recursively in the directory and return a map of directories to their files
+func findSlangFiles(dir string) (map[string][]string, error) {
+ slangFiles := make(map[string][]string)
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() && strings.HasSuffix(info.Name(), ".slang") {
+ relativePath, _ := filepath.Rel(baseDir, path) // Get the relative path
+ dir := filepath.Dir(relativePath)
+ slangFiles[dir] = append(slangFiles[dir], relativePath)
+ }
+ return nil
+ })
+ return slangFiles, err
+}
+
+// Handler to print the content of the .slang file and provide a button to execute it
+func slangFilePageHandler(filePath string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ http.Error(w, "Error reading file", http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html")
+ fmt.Fprintf(w, "
%s %s ", filepath.Base(filePath), string(content))
+
+ relativePath, _ := filepath.Rel(baseDir, filePath)
+ fmt.Fprintf(w, ``, relativePath, filepath.Base(filePath))
+
+ fmt.Fprintln(w, "")
+ }
+}
+
+// Handler to execute the .slang file content on a POST request
+func executeSlangFileHandler(filePath string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
+ return
+ }
+
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ http.Error(w, "Error reading file", http.StatusInternalServerError)
+ return
+ }
+
+ result, success := slang.SlangroomExec("", string(content), "", "", "", "")
+ if !success {
+ http.Error(w, "Error executing slang file: "+result.Logs, http.StatusInternalServerError)
+ return
+ }
+
+ output := map[string]interface{}{
+ "output": result.Output,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(output); err != nil {
+ http.Error(w, "Error encoding output to JSON", http.StatusInternalServerError)
+ }
+ }
+}
+
+// Handler to list all available .slang files, grouped by directory
+func listSlangFilesHandler(slangFiles map[string][]string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ fmt.Fprintln(w, "Available contract files: ")
+
+ for dir, files := range slangFiles {
+ fmt.Fprintf(w, "Directory: %s ", dir)
+
+ for _, file := range files {
+ fileName := filepath.Base(file)
+ // Create the correct link that includes the directory
+ // Here, we assume the path is already relative to the baseDir
+ // and it should include the directory in the href
+ link := fmt.Sprintf("/slang/%s/%s", dir, fileName) // Construct the correct href
+
+ // Display the link with the correct href and text
+ fmt.Fprintf(w, `%s/%s `, link, dir, fileName)
+ }
+
+ fmt.Fprintln(w, " ")
+ }
+
+ fmt.Fprintln(w, "")
+ }
+}
+
+func main() {
+ r := mux.NewRouter()
+
+ slangFiles, err := findSlangFiles(baseDir)
+ if err != nil {
+ log.Fatalf("Error finding .slang files: %v", err)
+ }
+
+ r.HandleFunc("/slang/", listSlangFilesHandler(slangFiles)).Methods("GET")
+
+ // For each file, create an API endpoint to show its content and add an execution button
+ for _, files := range slangFiles {
+ for _, file := range files {
+ // Create a handler to show file content and an execute button
+ r.HandleFunc(fmt.Sprintf("/slang/%s", file), slangFilePageHandler(file)).Methods("GET")
+
+ // Create a handler to execute the file
+ r.HandleFunc(fmt.Sprintf("/slang/execute/%s", file), executeSlangFileHandler(file)).Methods("POST")
+ }
+ }
+
+ fmt.Println("Starting server on :3000")
+ fmt.Println("Access the contract files at: http://localhost:3000/slang/")
+ log.Fatal(http.ListenAndServe(":3000", r))
+}
diff --git a/router_test.go b/router_test.go
new file mode 100644
index 0000000..b1b6dca
--- /dev/null
+++ b/router_test.go
@@ -0,0 +1,117 @@
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestListSlangFiles(t *testing.T) {
+ // Sample slang files data for testing
+ slangFiles := map[string][]string{
+ "testdir": {"file1.slang", "file2.slang"},
+ "testdir2": {"file3.slang"},
+ }
+
+ // Create a request to pass to the handler
+ req, err := http.NewRequest("GET", "/slang/", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Create a ResponseRecorder to record the response
+ rr := httptest.NewRecorder()
+
+ // Create the handler
+ handler := listSlangFilesHandler(slangFiles)
+
+ // Call the handler
+ handler.ServeHTTP(rr, req)
+
+ // Check the response status code
+ assert.Equal(t, http.StatusOK, rr.Code)
+
+ // Check that the response contains the expected content
+ assert.Contains(t, rr.Body.String(), "Available contract files: ")
+ assert.Contains(t, rr.Body.String(), "Directory: testdir ")
+ assert.Contains(t, rr.Body.String(), "Directory: testdir2 ")
+ assert.Contains(t, rr.Body.String(), "testdir/file1.slang ")
+ assert.Contains(t, rr.Body.String(), "testdir/file2.slang ")
+ assert.Contains(t, rr.Body.String(), "testdir2/file3.slang ")
+}
+
+func TestSlangFilePage(t *testing.T) {
+ // Set up a test slang file
+ testFileName := "testfile.slang"
+ testFileContent := "Given nothing\nThen print the string 'Test Successful'"
+ _ = os.WriteFile(testFileName, []byte(testFileContent), 0644)
+ defer os.Remove(testFileName) // Cleanup after test
+
+ req, err := http.NewRequest("GET", "/slang/"+testFileName, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler := slangFilePageHandler(testFileName)
+
+ handler.ServeHTTP(rr, req)
+
+ // Check the response code
+ assert.Equal(t, http.StatusOK, rr.Code)
+
+ // Check if the file content is in the response
+ assert.Contains(t, rr.Body.String(), testFileContent)
+
+ // Check for the execution button in the response
+ assert.Contains(t, rr.Body.String(), `Execute testfile.slang `)
+}
+
+func TestExecuteSlangFile(t *testing.T) {
+ // Set up a test slang file
+ testFileName := "testexecfile.slang"
+ testFileContent := `Given nothing
+Then print the string 'Execution Successful'`
+ _ = os.WriteFile(testFileName, []byte(testFileContent), 0644)
+ defer os.Remove(testFileName) // Cleanup after test
+
+ // Simulate a POST request to execute the slang file
+ req, err := http.NewRequest("POST", "/slang/execute/"+testFileName, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler := executeSlangFileHandler(testFileName)
+
+ handler.ServeHTTP(rr, req)
+
+ // Check the response code
+ assert.Equal(t, http.StatusOK, rr.Code)
+
+ // Check the output of the execution (adapt based on your expected output)
+ var result map[string]interface{}
+ err = json.Unmarshal(rr.Body.Bytes(), &result)
+ assert.NoError(t, err, "Failed to unmarshal output")
+ assert.Contains(t, result["output"], "Execution_Successful")
+}
+
+func TestInvalidRequestMethod(t *testing.T) {
+ req, err := http.NewRequest("GET", "/slang/execute/testfile.slang", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler := executeSlangFileHandler("testfile.slang") // Assuming a dummy file for handler
+
+ handler.ServeHTTP(rr, req)
+
+ // Check the response code for invalid method
+ assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
+ assert.Contains(t, rr.Body.String(), "Invalid request method")
+}
diff --git a/test/hello .slang b/test/hello .slang
new file mode 100644
index 0000000..6d37788
--- /dev/null
+++ b/test/hello .slang
@@ -0,0 +1,2 @@
+Given nothing
+Then print the string 'Hello World!'
diff --git a/test/test.slang b/test/test.slang
new file mode 100644
index 0000000..6f234a0
--- /dev/null
+++ b/test/test.slang
@@ -0,0 +1,5 @@
+Rule unknown ignore
+Given I fetch the local timestamp in seconds and output into 'timestamp'
+Given I have a 'time' named 'timestamp'
+Then print the string 'Welcome to the Slangroom World'
+Then print the 'timestamp'
diff --git a/test/welcome.slang b/test/welcome.slang
new file mode 100644
index 0000000..e9656f2
--- /dev/null
+++ b/test/welcome.slang
@@ -0,0 +1,4 @@
+Rule unknown ignore
+Given I fetch the local timestamp in seconds and output into 'timestamp'
+Given I have a 'number' named 'timestamp'
+Then print the 'timestamp'