package play
import "git.sr.ht/~shulhan/pakakeh.go/lib/play"
Package play provides callable APIs and HTTP handlers to format, run, and test Go code, similar to Go playground but using HTTP instead of WebSocket.
For HTTP API, this package expose handlers: [HTTPHandleFormat], [HTTPHandleRun], and [HTTPHandleTest].
Formatting and running Go code
The HTTP APIs for formatting and running Go code accept the following JSON request format,
{ "goversion": <string>, // For run only. "without_race": <boolean>, // For run only. "body": <string> }
The "goversion" field define the Go tools and toolchain version to be used to compile the code. The default "goversion" is defined in global variable GoVersion in this package. If "without_race" is true, the Run command will not run with "-race" option. The "body" field contains the Go code to be formatted or run.
Both request return the following JSON response format,
{ "code": <integer, HTTP status code>, "name": <string, error type>, "message": <string, optional message>, "data": <string> }
For the Go.HTTPHandleFormat, the response "data" contains the formatted Go code. For the Go.HTTPHandleRun, the response "data" contains the output from running the Go code. The "message" field contains an error on pre-Run, like bad request or file system related error.
Unsafe run
As exceptional, the Go.Run and Go.HTTPHandleRun accept the following request for running program inside custom "go.mod",
{ "unsafe_run": <path> }
The "unsafe_run" define the path to directory relative to play.GoOptions.Root working directory. Once the request is accepted it will change the directory into "unsafe_run" first and then execute "go run ." directly. Go code that executed inside "unsafe_run" should be not modifiable and safe from mallicious execution.
Testing
The Go.Test or Go.HTTPHandleTest must run inside the directory that contains the Go code file to be tested. The Go.HTTPHandleTest API accept the following request format,
{ "goversion": <string>, "file": <string>, "body": <string>, "without_race": <boolean> }
The "file" field define the path to the "_test.go" file, default to "test_test.go" if its empty. The "body" field contains the Go code that will be saved to that "file". The test will run, by default, with "go test -count=1 -race $dirname" where "$dirname" is the path to directory where "file" located, must be under the play.GoOptions.Root directory. If "without_race" is true, the test command will not run with "-race" option.
Index ¶
- Constants
- Variables
- type Go
- func NewGo(opts GoOptions) (playgo *Go, err error)
- func (playgo *Go) Format(req Request) (out []byte, err error)
- func (playgo *Go) HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request)
- func (playgo *Go) HTTPHandleRun( httpresw http.ResponseWriter, httpreq *http.Request, )
- func (playgo *Go) HTTPHandleTest( httpresw http.ResponseWriter, httpreq *http.Request, )
- func (playgo *Go) Run(req *Request) (out []byte, err error)
- func (playgo *Go) Test(req *Request) (out []byte, err error)
- type GoOptions
- type Request
Examples ¶
Constants ¶
const GoVersion = `1.23.2`
GoVersion define the Go tool version for go.mod to be used to run the code.
Timeout define the maximum time the program can be run until it get terminated.
Variables ¶
ErrEmptyFile error when running Go.Test with empty play.Request.File.
Types ¶
type Go ¶
type Go struct {
// contains filtered or unexported fields
}
Go define the type that can format, run, and test Go code.
func NewGo ¶
NewGo create and initialize new Go tools.
func (*Go) Format ¶
Format the Go code in the play.Request.Body and return the result to out.
Any syntax error on the code will be returned as error.
Code:
Output:Example¶
{
const codeIndentMissingImport = `
package main
func main() {
fmt.Println("Hello, world")
}
`
var playgo *Go
var err error
playgo, err = NewGo(GoOptions{
Root: os.TempDir(),
})
if err != nil {
log.Fatal(err)
}
var req = Request{
Body: codeIndentMissingImport,
}
var out []byte
out, err = playgo.Format(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", out)
//Output:
//package main
//
//import "fmt"
//
//func main() {
// fmt.Println("Hello, world")
//}
}
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
func (*Go) HTTPHandleFormat ¶
func (playgo *Go) HTTPHandleFormat(httpresw http.ResponseWriter, httpreq *http.Request)
HTTPHandleFormat define the HTTP handler for Go.Format.
Code:
Output:Example¶
{
const codeIndentMissingImport = `
package main
func main() {
fmt.Println("Hello, world")
}
`
var playgo *Go
var err error
playgo, err = NewGo(GoOptions{
Root: os.TempDir(),
})
if err != nil {
log.Fatal(err)
}
var req = Request{
Body: codeIndentMissingImport,
}
var rawbody []byte
rawbody, err = json.Marshal(&req)
if err != nil {
log.Fatal(err)
}
var resprec = httptest.NewRecorder()
var httpreq = httptest.NewRequest(`POST`, `/api/play/format`,
bytes.NewReader(rawbody))
httpreq.Header.Set(`Content-Type`, `application/json`)
var mux = http.NewServeMux()
mux.HandleFunc(`POST /api/play/format`, playgo.HTTPHandleFormat)
mux.ServeHTTP(resprec, httpreq)
var resp = resprec.Result()
rawbody, err = io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf(`%s`, rawbody)
// Output:
// {"data":"package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"Hello, world\")\n}\n","code":200}
}
{"data":"package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"Hello, world\")\n}\n","code":200}
func (*Go) HTTPHandleRun ¶
func (playgo *Go) HTTPHandleRun( httpresw http.ResponseWriter, httpreq *http.Request, )
HTTPHandleRun define the HTTP handler for Go.Run.
Each client is identified by unique cookie, so if two requests come from
the same client, the previous run will be cancelled.
Code:
Output:Example¶
{
const code = `
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
`
var playgo *Go
var err error
playgo, err = NewGo(GoOptions{
Root: os.TempDir(),
})
if err != nil {
log.Fatal(err)
}
var req = Request{
Body: code,
}
var rawbody []byte
rawbody, err = json.Marshal(&req)
if err != nil {
log.Fatal(err)
}
var resprec = httptest.NewRecorder()
var httpreq = httptest.NewRequest(`POST`, `/api/play/run`,
bytes.NewReader(rawbody))
httpreq.Header.Set(`Content-Type`, `application/json`)
var mux = http.NewServeMux()
mux.HandleFunc(`POST /api/play/run`, playgo.HTTPHandleRun)
mux.ServeHTTP(resprec, httpreq)
var resp = resprec.Result()
rawbody, err = io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf(`%s`, rawbody)
// Output:
// {"data":"Hello, world\n","code":200}
}
{"data":"Hello, world\n","code":200}
func (*Go) HTTPHandleTest ¶
func (playgo *Go) HTTPHandleTest( httpresw http.ResponseWriter, httpreq *http.Request, )
HTTPHandleTest define the HTTP handler for testing Go.Test.
Each client is identified by unique cookie, so if two requests come
from the same client, the previous Test will be cancelled.
Code:
Output:Example¶
{
const code = `
package test
import "testing"
func TestSum(t *testing.T) {
var total = sum(1, 2, 3)
if total != 6 {
t.Fatalf("got %d, want 6", total)
}
}`
var playgo *Go
var err error
playgo, err = NewGo(GoOptions{
Root: `testdata/`,
})
if err != nil {
log.Fatal(err)
}
var req = Request{
Body: code,
File: `/test_test.go`,
}
var rawbody []byte
rawbody, err = json.Marshal(&req)
if err != nil {
log.Fatal(err)
}
var mux = http.NewServeMux()
mux.HandleFunc(`POST /api/play/test`, playgo.HTTPHandleTest)
var resprec = httptest.NewRecorder()
var httpreq = httptest.NewRequest(`POST`, `/api/play/test`,
bytes.NewReader(rawbody))
httpreq.Header.Set(`Content-Type`, `application/json`)
mux.ServeHTTP(resprec, httpreq)
var resp = resprec.Result()
rawbody, err = io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var rexDuration = regexp.MustCompile(`(?m)\\t(\d+\.\d+)s`)
rawbody = rexDuration.ReplaceAll(rawbody, []byte(`\tXs`))
fmt.Printf(`%s`, rawbody)
// Output:
// {"data":"ok \tgit.sr.ht/~shulhan/pakakeh.go/lib/play/testdata\tXs\n","code":200}
}
{"data":"ok \tgit.sr.ht/~shulhan/pakakeh.go/lib/play/testdata\tXs\n","code":200}
func (*Go) Run ¶
Run the Go code in the play.Request.Body.
Code:
Output:Example¶
{
const codeRun = `
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}`
var playgo *Go
var err error
playgo, err = NewGo(GoOptions{
Root: os.TempDir(),
})
if err != nil {
log.Fatal(err)
}
var req = Request{
Body: codeRun,
}
var out []byte
out, err = playgo.Run(&req)
if err != nil {
log.Fatal(err)
}
fmt.Printf(`%s`, out)
//Output:
//Hello, world
}
Hello, world
func (*Go) Test ¶
Test the Go code in the play.Request.Body.
Code:
Output:Example¶
{
const codeTest = `
package test
import "testing"
func TestSum(t *testing.T) {
var total = sum(1, 2, 3)
if total != 6 {
t.Fatalf("got %d, want 6", total)
}
}`
var rexDuration = regexp.MustCompile(`(?m)\s+(\d+\.\d+)s$`)
var playgo *Go
var err error
playgo, err = NewGo(GoOptions{
Root: `testdata/`,
})
if err != nil {
log.Fatal(err)
}
var req = Request{
Body: codeTest,
File: `/test_test.go`,
}
var out []byte
out, err = playgo.Test(&req)
if err != nil {
log.Fatal(err)
}
// Replace the test duration.
out = rexDuration.ReplaceAll(out, []byte(" Xs"))
fmt.Printf(`%s`, out)
//Output:
//ok git.sr.ht/~shulhan/pakakeh.go/lib/play/testdata Xs
}
ok git.sr.ht/~shulhan/pakakeh.go/lib/play/testdata Xs
type GoOptions ¶
type GoOptions struct { // Root directory of where the Go code to be written, run, or test. // Default to [os.UserCacheDir] if its not set. Root string // Version define the Go tool version in go.mod to be used to run the // code. // Default to package [GoVersion] if its not set. Version string // Timeout define the maximum time the program can be run until it // gets terminated. // Default to package [Timeout] if its not set. Timeout time.Duration // contains filtered or unexported fields }
GoOptions define the options for running and test Go code.
type Request ¶
type Request struct { // The Go version that will be used in go.mod. GoVersion string `json:"goversion"` // File define the path to test "_test.go" file. // This field is for Test. File string `json:"file"` // Body contains the Go code to be Format-ed or Run. Body string `json:"body"` // UnsafeRun define the working directory where "go run ." will be // executed directly. UnsafeRun string `json:"unsafe_run"` // WithoutRace define the field to opt out the "-race" option when // running Go code. WithoutRace bool `json:"without_race"` // contains filtered or unexported fields }
Request for calling Go.Format, Go.Run, or Go.Test.
Source Files ¶
command.go go.go http.go play.go request.go run_manager.go
- Version
- v0.60.0 (latest)
- Published
- Feb 1, 2025
- Platform
- linux/amd64
- Imports
- 20 packages
- Last checked
- 20 minutes ago –
Tools for package owners.