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