github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/misc/goplay/goplay.go (about)

     1  // Copyright 2010 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"io/ioutil"
    11  	"log"
    12  	"net/http"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"strconv"
    19  	"text/template"
    20  )
    21  
    22  var (
    23  	httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
    24  	htmlOutput = flag.Bool("html", false, "render program output as HTML")
    25  )
    26  
    27  var (
    28  	// a source of numbers, for naming temporary files
    29  	uniq = make(chan int)
    30  )
    31  
    32  func main() {
    33  	flag.Parse()
    34  
    35  	// source of unique numbers
    36  	go func() {
    37  		for i := 0; ; i++ {
    38  			uniq <- i
    39  		}
    40  	}()
    41  
    42  	http.HandleFunc("/", FrontPage)
    43  	http.HandleFunc("/compile", Compile)
    44  	log.Fatal(http.ListenAndServe(*httpListen, nil))
    45  }
    46  
    47  // FrontPage is an HTTP handler that renders the goplay interface.
    48  // If a filename is supplied in the path component of the URI,
    49  // its contents will be put in the interface's text area.
    50  // Otherwise, the default "hello, world" program is displayed.
    51  func FrontPage(w http.ResponseWriter, req *http.Request) {
    52  	data, err := ioutil.ReadFile(req.URL.Path[1:])
    53  	if err != nil {
    54  		data = helloWorld
    55  	}
    56  	frontPage.Execute(w, data)
    57  }
    58  
    59  // Compile is an HTTP handler that reads Go source code from the request,
    60  // runs the program (returning any errors),
    61  // and sends the program's output as the HTTP response.
    62  func Compile(w http.ResponseWriter, req *http.Request) {
    63  	out, err := compile(req)
    64  	if err != nil {
    65  		error_(w, out, err)
    66  		return
    67  	}
    68  
    69  	// write the output of x as the http response
    70  	if *htmlOutput {
    71  		w.Write(out)
    72  	} else {
    73  		output.Execute(w, out)
    74  	}
    75  }
    76  
    77  var (
    78  	commentRe = regexp.MustCompile(`(?m)^#.*\n`)
    79  	tmpdir    string
    80  )
    81  
    82  func init() {
    83  	// find real temporary directory (for rewriting filename in output)
    84  	var err error
    85  	tmpdir, err = filepath.EvalSymlinks(os.TempDir())
    86  	if err != nil {
    87  		log.Fatal(err)
    88  	}
    89  }
    90  
    91  func compile(req *http.Request) (out []byte, err error) {
    92  	// x is the base name for .go, .6, executable files
    93  	x := filepath.Join(tmpdir, "compile"+strconv.Itoa(<-uniq))
    94  	src := x + ".go"
    95  	bin := x
    96  	if runtime.GOOS == "windows" {
    97  		bin += ".exe"
    98  	}
    99  
   100  	// rewrite filename in error output
   101  	defer func() {
   102  		if err != nil {
   103  			// drop messages from the go tool like '# _/compile0'
   104  			out = commentRe.ReplaceAll(out, nil)
   105  		}
   106  		out = bytes.Replace(out, []byte(src+":"), []byte("main.go:"), -1)
   107  	}()
   108  
   109  	// write body to x.go
   110  	body := new(bytes.Buffer)
   111  	if _, err = body.ReadFrom(req.Body); err != nil {
   112  		return
   113  	}
   114  	defer os.Remove(src)
   115  	if err = ioutil.WriteFile(src, body.Bytes(), 0666); err != nil {
   116  		return
   117  	}
   118  
   119  	// build x.go, creating x
   120  	dir, file := filepath.Split(src)
   121  	out, err = run(dir, "go", "build", "-o", bin, file)
   122  	defer os.Remove(bin)
   123  	if err != nil {
   124  		return
   125  	}
   126  
   127  	// run x
   128  	return run("", bin)
   129  }
   130  
   131  // error writes compile, link, or runtime errors to the HTTP connection.
   132  // The JavaScript interface uses the 404 status code to identify the error.
   133  func error_(w http.ResponseWriter, out []byte, err error) {
   134  	w.WriteHeader(404)
   135  	if out != nil {
   136  		output.Execute(w, out)
   137  	} else {
   138  		output.Execute(w, err.Error())
   139  	}
   140  }
   141  
   142  // run executes the specified command and returns its output and an error.
   143  func run(dir string, args ...string) ([]byte, error) {
   144  	var buf bytes.Buffer
   145  	cmd := exec.Command(args[0], args[1:]...)
   146  	cmd.Dir = dir
   147  	cmd.Stdout = &buf
   148  	cmd.Stderr = cmd.Stdout
   149  	err := cmd.Run()
   150  	return buf.Bytes(), err
   151  }
   152  
   153  var frontPage = template.Must(template.New("frontPage").Parse(frontPageText)) // HTML template
   154  var output = template.Must(template.New("output").Parse(outputText))          // HTML template
   155  
   156  var outputText = `<pre>{{printf "%s" . |html}}</pre>`
   157  
   158  var frontPageText = `<!doctype html>
   159  <html>
   160  <head>
   161  <style>
   162  pre, textarea {
   163  	font-family: Monaco, 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
   164  	font-size: 100%;
   165  }
   166  .hints {
   167  	font-size: 0.8em;
   168  	text-align: right;
   169  }
   170  #edit, #output, #errors { width: 100%; text-align: left; }
   171  #edit { height: 500px; }
   172  #output { color: #00c; }
   173  #errors { color: #c00; }
   174  </style>
   175  <script>
   176  
   177  function insertTabs(n) {
   178  	// find the selection start and end
   179  	var cont  = document.getElementById("edit");
   180  	var start = cont.selectionStart;
   181  	var end   = cont.selectionEnd;
   182  	// split the textarea content into two, and insert n tabs
   183  	var v = cont.value;
   184  	var u = v.substr(0, start);
   185  	for (var i=0; i<n; i++) {
   186  		u += "\t";
   187  	}
   188  	u += v.substr(end);
   189  	// set revised content
   190  	cont.value = u;
   191  	// reset caret position after inserted tabs
   192  	cont.selectionStart = start+n;
   193  	cont.selectionEnd = start+n;
   194  }
   195  
   196  function autoindent(el) {
   197  	var curpos = el.selectionStart;
   198  	var tabs = 0;
   199  	while (curpos > 0) {
   200  		curpos--;
   201  		if (el.value[curpos] == "\t") {
   202  			tabs++;
   203  		} else if (tabs > 0 || el.value[curpos] == "\n") {
   204  			break;
   205  		}
   206  	}
   207  	setTimeout(function() {
   208  		insertTabs(tabs);
   209  	}, 1);
   210  }
   211  
   212  function preventDefault(e) {
   213  	if (e.preventDefault) {
   214  		e.preventDefault();
   215  	} else {
   216  		e.cancelBubble = true;
   217  	}
   218  }
   219  
   220  function keyHandler(event) {
   221  	var e = window.event || event;
   222  	if (e.keyCode == 9) { // tab
   223  		insertTabs(1);
   224  		preventDefault(e);
   225  		return false;
   226  	}
   227  	if (e.keyCode == 13) { // enter
   228  		if (e.shiftKey) { // +shift
   229  			compile(e.target);
   230  			preventDefault(e);
   231  			return false;
   232  		} else {
   233  			autoindent(e.target);
   234  		}
   235  	}
   236  	return true;
   237  }
   238  
   239  var xmlreq;
   240  
   241  function autocompile() {
   242  	if(!document.getElementById("autocompile").checked) {
   243  		return;
   244  	}
   245  	compile();
   246  }
   247  
   248  function compile() {
   249  	var prog = document.getElementById("edit").value;
   250  	var req = new XMLHttpRequest();
   251  	xmlreq = req;
   252  	req.onreadystatechange = compileUpdate;
   253  	req.open("POST", "/compile", true);
   254  	req.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
   255  	req.send(prog);	
   256  }
   257  
   258  function compileUpdate() {
   259  	var req = xmlreq;
   260  	if(!req || req.readyState != 4) {
   261  		return;
   262  	}
   263  	if(req.status == 200) {
   264  		document.getElementById("output").innerHTML = req.responseText;
   265  		document.getElementById("errors").innerHTML = "";
   266  	} else {
   267  		document.getElementById("errors").innerHTML = req.responseText;
   268  		document.getElementById("output").innerHTML = "";
   269  	}
   270  }
   271  </script>
   272  </head>
   273  <body>
   274  <table width="100%"><tr><td width="60%" valign="top">
   275  <textarea autofocus="true" id="edit" spellcheck="false" onkeydown="keyHandler(event);" onkeyup="autocompile();">{{printf "%s" . |html}}</textarea>
   276  <div class="hints">
   277  (Shift-Enter to compile and run.)&nbsp;&nbsp;&nbsp;&nbsp;
   278  <input type="checkbox" id="autocompile" value="checked" /> Compile and run after each keystroke
   279  </div>
   280  <td width="3%">
   281  <td width="27%" align="right" valign="top">
   282  <div id="output"></div>
   283  </table>
   284  <div id="errors"></div>
   285  </body>
   286  </html>
   287  `
   288  
   289  var helloWorld = []byte(`package main
   290  
   291  import "fmt"
   292  
   293  func main() {
   294  	fmt.Println("hello, world")
   295  }
   296  `)