Skip to content

Commit

Permalink
feat: add first file system router
Browse files Browse the repository at this point in the history
  • Loading branch information
FilippoTrotter committed Oct 8, 2024
1 parent 7f76c51 commit 8f81d14
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
!/.env.sample
!/.cruft.json
!/.goreleaser.yaml

!/test/*
!.github/workflows/*.yml

!*.go
Expand Down
17 changes: 15 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
137 changes: 137 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
@@ -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, "<html><body><h1>%s</h1><pre>%s</pre>", filepath.Base(filePath), string(content))

relativePath, _ := filepath.Rel(baseDir, filePath)
fmt.Fprintf(w, `<form method="POST" action="/slang/execute/%s">
<button type="submit">Execute %s</button>
</form>`, relativePath, filepath.Base(filePath))

fmt.Fprintln(w, "</body></html>")
}
}

// 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, "<html><body><h1>Available contract files:</h1>")

for dir, files := range slangFiles {
fmt.Fprintf(w, "<h2>Directory: %s</h2><ul>", 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, `<li><a href="%s">%s/%s</a></li>`, link, dir, fileName)
}

fmt.Fprintln(w, "</ul>")
}

fmt.Fprintln(w, "</body></html>")
}
}

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))
}
117 changes: 117 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
@@ -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(), "<h1>Available contract files:</h1>")
assert.Contains(t, rr.Body.String(), "<h2>Directory: testdir</h2>")
assert.Contains(t, rr.Body.String(), "<h2>Directory: testdir2</h2>")
assert.Contains(t, rr.Body.String(), "<a href=\"/slang/testdir/file1.slang\">testdir/file1.slang</a>")
assert.Contains(t, rr.Body.String(), "<a href=\"/slang/testdir/file2.slang\">testdir/file2.slang</a>")
assert.Contains(t, rr.Body.String(), "<a href=\"/slang/testdir2/file3.slang\">testdir2/file3.slang</a>")
}

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(), `<button type="submit">Execute testfile.slang</button>`)
}

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")
}
2 changes: 2 additions & 0 deletions test/hello .slang
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Given nothing
Then print the string 'Hello World!'
5 changes: 5 additions & 0 deletions test/test.slang
Original file line number Diff line number Diff line change
@@ -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'
4 changes: 4 additions & 0 deletions test/welcome.slang
Original file line number Diff line number Diff line change
@@ -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'

0 comments on commit 8f81d14

Please sign in to comment.