]> Cypherpunks repositories - gostls13.git/commitdiff
misc: add goplay
authorAndrew Gerrand <adg@golang.org>
Thu, 14 Oct 2010 03:06:02 +0000 (14:06 +1100)
committerAndrew Gerrand <adg@golang.org>
Thu, 14 Oct 2010 03:06:02 +0000 (14:06 +1100)
R=rsc, r
CC=golang-dev
https://golang.org/cl/2473041

misc/goplay/Makefile [new file with mode: 0644]
misc/goplay/README [new file with mode: 0644]
misc/goplay/doc.go [new file with mode: 0644]
misc/goplay/goplay.go [new file with mode: 0644]

diff --git a/misc/goplay/Makefile b/misc/goplay/Makefile
new file mode 100644 (file)
index 0000000..28d0245
--- /dev/null
@@ -0,0 +1,13 @@
+# 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.
+
+include ../../src/Make.inc
+
+TARG=goplay
+
+GOFILES=\
+       goplay.go\
+
+include ../../src/Make.cmd
+
diff --git a/misc/goplay/README b/misc/goplay/README
new file mode 100644 (file)
index 0000000..e8a1d29
--- /dev/null
@@ -0,0 +1 @@
+See doc.go.
diff --git a/misc/goplay/doc.go b/misc/goplay/doc.go
new file mode 100644 (file)
index 0000000..9685551
--- /dev/null
@@ -0,0 +1,25 @@
+// 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.
+
+// Goplay is a web interface for experimenting with Go code.
+// It is similar to the Go Playground: http://golang.org/doc/play/
+// 
+// To use goplay, first build and install it:
+//   $ cd $GOROOT/misc/goplay
+//   $ gomake install
+// Then, run it:
+//   $ goplay
+// and load http://localhost:3999/ in a web browser.
+// 
+// You should see a Hello World program, which you can compile and run by
+// pressing shift-enter. There is also a "compile-on-keypress" feature that can
+// be enabled by checking a checkbox.
+// 
+// WARNING! CUIDADO! ACHTUNG! ATTENZIONE!
+// A note on security: anyone with access to the goplay web interface can run
+// arbitrary code on your computer. Goplay is not a sandbox, and has no other
+// security mechanisms. Do not deploy it in untrusted environments. 
+// By default, goplay listens only on localhost. This can be overridden with 
+// the -http parameter. Do so at your own risk.
+package documentation
diff --git a/misc/goplay/goplay.go b/misc/goplay/goplay.go
new file mode 100644 (file)
index 0000000..0214806
--- /dev/null
@@ -0,0 +1,305 @@
+// 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"
+       "exec"
+       "flag"
+       "http"
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "runtime"
+       "strconv"
+       "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)
+       // the architecture-identifying character of the tool chain, 5, 6, or 8
+       archChar string
+)
+
+func main() {
+       flag.Parse()
+
+       // set archChar
+       switch runtime.GOARCH {
+       case "arm":
+               archChar = "5"
+       case "amd64":
+               archChar = "6"
+       case "386":
+               archChar = "8"
+       default:
+               log.Exitln("unrecognized GOARCH:", runtime.GOARCH)
+       }
+
+       // source of unique numbers
+       go func() {
+               for i := 0; ; i++ {
+                       uniq <- i
+               }
+       }()
+
+       http.HandleFunc("/", FrontPage)
+       http.HandleFunc("/compile", Compile)
+       log.Exit(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(data, w)
+}
+
+// Compile is an HTTP handler that reads Go source code from the request,
+// compiles and links the code (returning any errors), runs the program, 
+// and sends the program's output as the HTTP response.
+func Compile(w http.ResponseWriter, req *http.Request) {
+       // x is the base name for .go, .6, executable files
+       x := "/tmp/compile" + strconv.Itoa(<-uniq)
+
+       // write request Body to x.go
+       f, err := os.Open(x+".go", os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0666)
+       if err != nil {
+               error(w, nil, err)
+               return
+       }
+       defer os.Remove(x + ".go")
+       defer f.Close()
+       _, err = io.Copy(f, req.Body)
+       if err != nil {
+               error(w, nil, err)
+               return
+       }
+       f.Close()
+
+       // build x.go, creating x.6
+       out, err := run(archChar+"g", "-o", x+"."+archChar, x+".go")
+       defer os.Remove(x + "." + archChar)
+       if err != nil {
+               error(w, out, err)
+               return
+       }
+
+       // link x.6, creating x (the program binary)
+       out, err = run(archChar+"l", "-o", x, x+"."+archChar)
+       defer os.Remove(x)
+       if err != nil {
+               error(w, out, err)
+               return
+       }
+
+       // run x
+       out, err = run(x)
+       if err != nil {
+               error(w, out, err)
+       }
+
+       // write the output of x as the http response
+       if *htmlOutput {
+               w.Write(out)
+       } else {
+               output.Execute(out, w)
+       }
+}
+
+// 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 os.Error) {
+       w.WriteHeader(404)
+       if out != nil {
+               output.Execute(out, w)
+       } else {
+               output.Execute(err.String(), w)
+       }
+}
+
+// run executes the specified command and returns its output and an error.
+func run(cmd ...string) ([]byte, os.Error) {
+       // find the specified binary
+       bin, err := exec.LookPath(cmd[0])
+       if err != nil {
+               // report binary as well as the error
+               return nil, os.NewError(cmd[0] + ": " + err.String())
+       }
+
+       // run the binary and read its combined stdout and stderr into a buffer
+       p, err := exec.Run(bin, cmd, os.Environ(), "", exec.DevNull, exec.Pipe, exec.MergeWithStdout)
+       if err != nil {
+               return nil, err
+       }
+       var buf bytes.Buffer
+       io.Copy(&buf, p.Stdout)
+       w, err := p.Wait(0)
+       p.Close()
+
+       // set the error return value if the program had a non-zero exit status
+       if !w.Exited() || w.ExitStatus() != 0 {
+               err = os.ErrorString("running " + cmd[0] + ": " + w.String())
+       }
+
+       return buf.Bytes(), err
+}
+
+var frontPage, output *template.Template // HTML templates
+
+func init() {
+       frontPage = template.New(nil)
+       frontPage.SetDelims("«", "»")
+       if err := frontPage.Parse(frontPageText); err != nil {
+               panic(err)
+       }
+       output = template.MustParse(outputText, nil)
+}
+
+var outputText = `<pre>{@|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 keyHandler() {
+       var e = window.event;
+       if (e.keyCode == 9) { // tab
+               insertTabs(1);
+               e.preventDefault();
+               return false;
+       }
+       if (e.keyCode == 13) { // enter
+               if (e.shiftKey) { // +shift
+                       compile(e.target);
+                       e.preventDefault();
+                       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", 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();" onkeyup="autocompile();">«@|html»</textarea>
+<div class="hints">
+(Shift-Enter to compile and run.)&nbsp;&nbsp;&nbsp;&nbsp;
+<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")
+}
+`)