// 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" "runtime" "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, 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" bin := x if runtime.GOOS == "windows" { bin += ".exe" } // 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 } // build x.go, creating x dir, file := filepath.Split(src) out, err = run(dir, "go", "build", "-o", bin, file) defer os.Remove(bin) if err != nil { return } // run x return run("", bin) } // 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 = `
{{printf "%s" . |html}}
` var frontPageText = `
(Shift-Enter to compile and run.)     Compile and run after each keystroke
` var helloWorld = []byte(`package main import "fmt" func main() { fmt.Println("hello, world") } `)