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.) 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 `)