Created
September 30, 2013 14:41
-
-
Save tcard/6764781 to your computer and use it in GitHub Desktop.
misc/goplay modified so you can pass gcflags. Eg.: http://cl.ly/image/441U2n0G321W/Image%202013.09.30%2016%3A38%3A50.png
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2010 The Go Authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
package main | |
import ( | |
"bytes" | |
"flag" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"regexp" | |
"strconv" | |
"text/template" | |
) | |
var ( | |
httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on") | |
htmlOutput = flag.Bool("html", false, "render program output as HTML") | |
) | |
var ( | |
// a source of numbers, for naming temporary files | |
uniq = make(chan int) | |
) | |
func main() { | |
flag.Parse() | |
// source of unique numbers | |
go func() { | |
for i := 0; ; i++ { | |
uniq <- i | |
} | |
}() | |
http.HandleFunc("/", FrontPage) | |
http.HandleFunc("/compile", Compile) | |
log.Fatal(http.ListenAndServe(*httpListen, nil)) | |
} | |
// FrontPage is an HTTP handler that renders the goplay interface. | |
// If a filename is supplied in the path component of the URI, | |
// its contents will be put in the interface's text area. | |
// Otherwise, the default "hello, world" program is displayed. | |
func FrontPage(w http.ResponseWriter, req *http.Request) { | |
data, err := ioutil.ReadFile(req.URL.Path[1:]) | |
if err != nil { | |
data = helloWorld | |
} | |
frontPage.Execute(w, map[string]interface{}{ | |
"gcflags": req.FormValue("gcflags"), | |
"data": data, | |
}) | |
} | |
// Compile is an HTTP handler that reads Go source code from the request, | |
// runs the program (returning any errors), | |
// and sends the program's output as the HTTP response. | |
func Compile(w http.ResponseWriter, req *http.Request) { | |
out, err := compile(req) | |
if err != nil { | |
error_(w, out, err) | |
return | |
} | |
// write the output of x as the http response | |
if *htmlOutput { | |
w.Write(out) | |
} else { | |
output.Execute(w, out) | |
} | |
} | |
var ( | |
commentRe = regexp.MustCompile(`(?m)^#.*\n`) | |
tmpdir string | |
) | |
func init() { | |
// find real temporary directory (for rewriting filename in output) | |
var err error | |
tmpdir, err = filepath.EvalSymlinks(os.TempDir()) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
func compile(req *http.Request) (out []byte, err error) { | |
// x is the base name for .go, .6, executable files | |
x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq)) | |
src := x + ".go" | |
// rewrite filename in error output | |
defer func() { | |
if err != nil { | |
// drop messages from the go tool like '# _/compile0' | |
out = commentRe.ReplaceAll(out, nil) | |
} | |
out = bytes.Replace(out, []byte(src+":"), []byte("main.go:"), -1) | |
}() | |
// write body to x.go | |
body := new(bytes.Buffer) | |
if _, err = body.ReadFrom(req.Body); err != nil { | |
return | |
} | |
defer os.Remove(src) | |
if err = ioutil.WriteFile(src, body.Bytes(), 0666); err != nil { | |
return | |
} | |
// go run x.go | |
dir, file := filepath.Split(src) | |
gcflags := req.FormValue("gcflags") | |
args := []string{"go", "run"} | |
if len(gcflags) > 0 { | |
args = append(args, "-gcflags", gcflags) | |
} | |
out, err = run(dir, append(args, file)...) | |
if err != nil { | |
return | |
} | |
return out, nil | |
} | |
// error writes compile, link, or runtime errors to the HTTP connection. | |
// The JavaScript interface uses the 404 status code to identify the error. | |
func error_(w http.ResponseWriter, out []byte, err error) { | |
w.WriteHeader(404) | |
if out != nil { | |
output.Execute(w, out) | |
} else { | |
output.Execute(w, err.Error()) | |
} | |
} | |
// run executes the specified command and returns its output and an error. | |
func run(dir string, args ...string) ([]byte, error) { | |
var buf bytes.Buffer | |
cmd := exec.Command(args[0], args[1:]...) | |
cmd.Dir = dir | |
cmd.Stdout = &buf | |
cmd.Stderr = cmd.Stdout | |
err := cmd.Run() | |
return buf.Bytes(), err | |
} | |
var frontPage = template.Must(template.New("frontPage").Parse(frontPageText)) // HTML template | |
var output = template.Must(template.New("output").Parse(outputText)) // HTML template | |
var outputText = `<pre>{{printf "%s" . |html}}</pre>` | |
var frontPageText = `<!doctype html> | |
<html> | |
<head> | |
<style> | |
pre, textarea { | |
font-family: Monaco, 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; | |
font-size: 100%; | |
} | |
.hints { | |
font-size: 0.8em; | |
text-align: right; | |
} | |
#edit, #output, #errors { width: 100%; text-align: left; } | |
#edit { height: 500px; } | |
#output { color: #00c; } | |
#errors { color: #c00; } | |
</style> | |
<script> | |
function insertTabs(n) { | |
// find the selection start and end | |
var cont = document.getElementById("edit"); | |
var start = cont.selectionStart; | |
var end = cont.selectionEnd; | |
// split the textarea content into two, and insert n tabs | |
var v = cont.value; | |
var u = v.substr(0, start); | |
for (var i=0; i<n; i++) { | |
u += "\t"; | |
} | |
u += v.substr(end); | |
// set revised content | |
cont.value = u; | |
// reset caret position after inserted tabs | |
cont.selectionStart = start+n; | |
cont.selectionEnd = start+n; | |
} | |
function autoindent(el) { | |
var curpos = el.selectionStart; | |
var tabs = 0; | |
while (curpos > 0) { | |
curpos--; | |
if (el.value[curpos] == "\t") { | |
tabs++; | |
} else if (tabs > 0 || el.value[curpos] == "\n") { | |
break; | |
} | |
} | |
setTimeout(function() { | |
insertTabs(tabs); | |
}, 1); | |
} | |
function preventDefault(e) { | |
if (e.preventDefault) { | |
e.preventDefault(); | |
} else { | |
e.cancelBubble = true; | |
} | |
} | |
function keyHandler(event) { | |
var e = window.event || event; | |
if (e.keyCode == 9) { // tab | |
insertTabs(1); | |
preventDefault(e); | |
return false; | |
} | |
if (e.keyCode == 13) { // enter | |
if (e.shiftKey) { // +shift | |
compile(e.target); | |
preventDefault(e); | |
return false; | |
} else { | |
autoindent(e.target); | |
} | |
} | |
return true; | |
} | |
var xmlreq; | |
function autocompile() { | |
if(!document.getElementById("autocompile").checked) { | |
return; | |
} | |
compile(); | |
} | |
function compile() { | |
var prog = document.getElementById("edit").value; | |
var req = new XMLHttpRequest(); | |
xmlreq = req; | |
req.onreadystatechange = compileUpdate; | |
req.open("POST", "/compile{{with .gcflags}}?gcflags={{.}}{{end}}", true); | |
req.setRequestHeader("Content-Type", "text/plain; charset=utf-8"); | |
req.send(prog); | |
} | |
function compileUpdate() { | |
var req = xmlreq; | |
if(!req || req.readyState != 4) { | |
return; | |
} | |
if(req.status == 200) { | |
document.getElementById("output").innerHTML = req.responseText; | |
document.getElementById("errors").innerHTML = ""; | |
} else { | |
document.getElementById("errors").innerHTML = req.responseText; | |
document.getElementById("output").innerHTML = ""; | |
} | |
} | |
</script> | |
</head> | |
<body> | |
<table width="100%"><tr><td width="60%" valign="top"> | |
<textarea autofocus="true" id="edit" spellcheck="false" onkeydown="keyHandler(event);" onkeyup="autocompile();">{{printf "%s" .data |html}}</textarea> | |
<div class="hints"> | |
(Shift-Enter to compile and run.) | |
<input type="checkbox" id="autocompile" value="checked" /> Compile and run after each keystroke | |
</div> | |
<td width="3%"> | |
<td width="27%" align="right" valign="top"> | |
<div id="output"></div> | |
</table> | |
<div id="errors"></div> | |
</body> | |
</html> | |
` | |
var helloWorld = []byte(`package main | |
import "fmt" | |
func main() { | |
fmt.Println("hello, world") | |
} | |
`) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment