X-Tour-Commit: ab572597e342dc4e3a03dfd56449037d879e4e71
This commit is contained in:
Andrew Gerrand 2011-07-14 16:39:17 +10:00
Родитель 001739136e
Коммит 5d3839ca4d
7 изменённых файлов: 2678 добавлений и 0 удалений

206
tour/gotour/goplay.go Normal file
Просмотреть файл

@ -0,0 +1,206 @@
// 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"
"fmt"
"http"
"template"
)
func init() {
http.HandleFunc("/compile", Compile)
}
// 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) {
out, err := compile(req)
if err != nil {
w.WriteHeader(404)
var s string
if out != nil {
s = string(out)
} else {
s = err.String()
}
output.Execute(w, s)
return
}
// write the output of x as the http response
if *htmlOutput {
w.Write(out)
} else if url, ok := isImage(out); ok {
fmt.Fprintf(w, `<img src="%s">`, url)
} else {
output.Execute(w, out)
}
}
func isImage(out []byte) (string, bool) {
out = bytes.TrimSpace(out)
if !bytes.HasPrefix(out, []byte("IMAGE:")) {
return "", false
}
out = out[6:]
for _, c := range out {
switch {
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z', '0' <= c && c <= '9',
c == '+', c == '-', c == '/', c == '_', c == '=':
default:
println("bad", c)
return "", false
}
}
return "data:image/png;base64," + string(out), true
}
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: monospace; /* use the user's browser setting */
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(event) {
var e = window.event || 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 = function() { compileUpdate(req); }
req.open("POST", "/compile", true);
req.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
req.send(prog);
document.getElementById("output").innerHTML = "running...";
}
function compileUpdate(req) {
if(req != xmlreq || !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(event);" 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")
}
`)

161
tour/gotour/local.go Normal file
Просмотреть файл

@ -0,0 +1,161 @@
// Copyright 2011 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"
"fmt"
"go/build"
"http"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
// Imports so that goinstall automatically installs them.
_ "go-tour.googlecode.com/hg/pic"
_ "go-tour.googlecode.com/hg/wc"
)
const basePkg = "go-tour.googlecode.com/hg"
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
// where gc and ld should find the go-tour packages
pkgDir string
)
func main() {
flag.Parse()
// source of unique numbers
go func() {
for i := 0; ; i++ {
uniq <- i
}
}()
// set archChar
var err os.Error
archChar, err = build.ArchChar(runtime.GOARCH)
if err != nil {
log.Fatal(err)
}
// find and serve the go tour files
t, _, err := build.FindTree(basePkg)
if err != nil {
log.Fatalf("Couldn't find tour files: %v", err)
}
root := filepath.Join(t.SrcDir(), basePkg, "static")
log.Println("Serving content from", root)
http.Handle("/", http.FileServer(http.Dir(root)))
pkgDir = t.PkgDir()
log.Printf("Serving at http://%s/", *httpListen)
log.Fatal(http.ListenAndServe(*httpListen, nil))
}
var running struct {
sync.Mutex
cmd *exec.Cmd
}
func stopRun() {
running.Lock()
if running.cmd != nil {
running.cmd.Process.Kill()
running.cmd = nil
}
running.Unlock()
}
func compile(req *http.Request) (out []byte, err os.Error) {
stopRun()
// x is the base name for .go, .6, executable files
x := os.TempDir() + "/compile" + strconv.Itoa(<-uniq)
src := x + ".go"
obj := x + "." + archChar
bin := x
if runtime.GOOS == "windows" {
bin += ".exe"
}
// rewrite filename in error output
defer func() {
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
}
if err = ioutil.WriteFile(src, body.Bytes(), 0666); err != nil {
return
}
// build x.go, creating x.6
out, err = run(archChar+"g", "-I", pkgDir, "-o", obj, src)
defer os.Remove(obj)
if err != nil {
return
}
// link x.6, creating x (the program binary)
out, err = run(archChar+"l", "-L", pkgDir, "-o", bin, obj)
defer os.Remove(bin)
if err != nil {
return
}
// run x
return run(bin)
}
// run executes the specified command and returns its output and an error.
func run(args ...string) ([]byte, os.Error) {
var buf bytes.Buffer
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = &buf
cmd.Stderr = cmd.Stdout
// Start command and leave in 'running'.
running.Lock()
if running.cmd != nil {
defer running.Unlock()
return nil, fmt.Errorf("already running %s", running.cmd.Path)
}
if err := cmd.Start(); err != nil {
running.Unlock()
return nil, err
}
running.cmd = cmd
running.Unlock()
// Wait for the command. Clean up,
err := cmd.Wait()
running.Lock()
if running.cmd == cmd {
running.cmd = nil
}
running.Unlock()
return buf.Bytes(), err
}

37
tour/pic/pic.go Normal file
Просмотреть файл

@ -0,0 +1,37 @@
// 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 pic
import (
"bytes"
"encoding/base64"
"image"
"image/png"
"fmt"
)
func Show(f func(int, int)[][]uint8) {
const (
dx = 256
dy = 256
)
data := f(dx, dy)
m := image.NewNRGBA(dx, dy)
for y := 0; y < dy; y++ {
for x := 0; x < dx; x++ {
v := data[y][x]
m.Pix[y*dy+x] = image.NRGBAColor{v, v, 255, 255}
}
}
ShowImage(m)
}
func ShowImage(m image.Image) {
var buf bytes.Buffer
png.Encode(&buf, m)
enc := make([]byte, base64.StdEncoding.EncodedLen(buf.Len()))
base64.StdEncoding.Encode(enc, buf.Bytes())
fmt.Println("IMAGE:" + string(enc))
}

1711
tour/static/index.html Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

119
tour/static/tour.css Normal file
Просмотреть файл

@ -0,0 +1,119 @@
body {
font-family: 'Droid Serif', serif;
margin-left: 0px;
margin-right: 0px;
margin-top: 0px;
font-size: 120%;
}
.slides {
margin-left: 1em;
margin-right: 1em;
margin-top: 0px;
}
.slidenum {
float: right;
color: white;
padding: 5px;
margin: 0;
}
pre {
margin-top: 0px;
margin-bottom: 0px;
}
pre span {
font-family: 'Droid Serif', serif;
}
h1, h2, h3 {
font-family: 'Droid Sans', sans-serif;
margin-top: 20px;
}
h1 {
color: #fff;
background-color: #444;
margin: 0px;
padding: 5px;
}
code, pre, textarea {
font-family: 'Droid Sans Mono', monospace;
font-size: 100%;
}
div.code {
border: 1px solid black;
float: left;
width: 45%;
height: 80%;
margin: 0px 1em 0px 0px;
}
textarea {
border: 0px;
display: block;
padding: 5px;
width: 100%;
height: 95%;
margin-top: 0px;
}
textarea:focus {
border: 0px;
outline: 0px;
}
.programoutput {
color: #0000cc;
}
.compileerrors {
color: #cc0000;
}
hr {
border: 1px solid #ccc;
}
button {
display: inline;
font-size: 60%;
font-family: 'Droid Sans', sans-serif;
color: #000;
background-color: #eee;
border-color: #000;
border-width: 1px;
padding: 3px;
margin: 3px;
}
button.next {
margin-left: 1em;
}
button:hover {
background-color: #fff;
}
div.nav {
display: block;
text-align: right;
float: right;
}
div.clear {
clear: right;
}
h2.nocode {
text-align: center;
font-size: 300%;
margin-bottom: 1em;
}
div.toc {
display: none;
visibility: hidden;
}

283
tour/static/tour.js Normal file
Просмотреть файл

@ -0,0 +1,283 @@
window.onload = init;
var slides;
var slide = null;
var slidenum = 0;
var codebox = null;
var output = null;
var errors = null;
function findclass(el, name) {
var x = el.getElementsByClassName(name);
if (x.length == 0)
return null;
return x[0];
}
function initSlides() {
var $slides = $("div.slide");
$slides.each(function(i, slide) {
var $s = $(slide).hide();
var $code = null;
var $sdiv = $s.find("div");
if (!$s.hasClass("nocode") && $sdiv.length > 0) {
$code = $sdiv.last();
$code.remove();
}
var $h2 = $s.find("h2").first();
if ($h2.length > 0) {
$("<div/>").addClass("clear").insertAfter($h2);
var $nav = $("<div/>").addClass("nav")
if (i > 0) {
$nav.append($("<button>").click(function() {
show(i-1);
}).text("PREV").addClass("prev"));
}
if (i+1 < $slides.length) {
$nav.append($("<button>").click(function() {
show(i+1);
}).text("NEXT").addClass("next"));
}
$nav.insertBefore($h2);
}
if ($s.hasClass("nocode"))
$h2.addClass("nocode");
if ($code == null)
return;
var $codebox = $("<textarea/>").html($code.html().trim());
var $codenav = $("<div/>").addClass("nav");
$codenav.append($("<button>").click(function() {
compile($codebox[0]);
}).text("COMPILE").addClass("compile"));
$code.empty().addClass("code");
$code.append($codenav).append($codebox);
$s.prepend($code);
$s.append("<hr/>");
$s.append('<div class="compileerrors"/>')
$s.append('<div class="programoutput"/>')
});
return $slides;
}
function show(i) {
console.log("show", i);
if(i < 0 || i >= slides.length)
return;
if(slide != null) {
$(slide).hide();
}
document.onkeydown = null;
if(codebox != null) {
codebox.onkeydown = null;
codebox.onkeyup = null;
}
slidenum = i;
$("#num").text(i+1);
var url = location.href;
var j = url.indexOf("#");
if(j >= 0)
url = url.substr(0, j);
url += "#" + (slidenum+1).toString();
location.href = url;
slide = slides[i];
$(slide).show();
if ($(slide).hasClass("nocode")) {
setTimeout(function() {
document.onkeydown = pageUpDown;
}, 1);
return;
}
var $code = $("div.code", slide);
if ($code.length == 0)
return;
codebox = $code.find("textarea")[0];
if (codebox != null) {
codebox.spellcheck = false;
codebox.onkeydown = keyDown;
codebox.onkeyup = keyUp;
codebox.focus();
document.onclick = null;
}
output = $("div.programoutput", slide)[0];
errors = $("div.compileerrors", slide)[0];
document.onclick = function() { codebox.focus(); }
}
function urlSlideNumber(url) {
var i = url.indexOf("#");
if(i < 0)
return 0;
var frag = unescape(url.substr(i+1));
if(/^\d+$/.test(frag)) {
i = parseInt(frag);
if(i-1 < 0 || i-1 >= slides.length)
return 0;
return i-1;
}
return 0;
}
function insertTabs(cont, n) {
// find the selection start and end
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(el, tabs);
}, 1);
}
var keySeq = 0;
var keyWaiting = false;
function keyDown(event) {
var e = window.event || event;
if (e.keyCode == 9) { // tab
insertTabs(e.target, 1);
e.preventDefault();
return false;
}
if (e.keyCode == 13) { // enter
if (e.shiftKey) {
compile(e.target);
e.preventDefault();
return false;
}
autoindent(e.target);
}
if (e.keyCode == 33) { // page up
e.preventDefault();
show(slidenum-1);
return false;
}
if (e.keyCode == 34) { // page down
e.preventDefault();
show(slidenum+1);
return false;
}
return true;
}
function pageUpDown(event) {
var e = window.event || event;
if (e.keyCode == 33) { // page up
e.preventDefault();
show(slidenum-1);
return false;
}
if (e.keyCode == 34) { // page down
e.preventDefault();
show(slidenum+1);
return false;
}
return true;
}
var autocompile = false;
function keyUp(event) {
var e = window.event || event;
keySeq++;
if(!autocompile || codebox == null)
return;
if (!keyWaiting) {
var seq = keySeq;
keyWaiting = true;
setTimeout(function() { keyTimeout(seq, 50); }, 50)
}
}
var waitTime = 200; // wait 200 ms before compiling
function keyTimeout(seq, n) {
ks1 = seq;
ks2 = n;
if (keySeq != seq) {
seq = keySeq;
setTimeout(function() { keyTimeout(seq, 50); }, 50)
return;
}
if (n < waitTime) {
setTimeout(function() { keyTimeout(seq, n+50); }, 50)
return;
}
keyWaiting = false;
if (codebox != null)
compile(codebox);
}
var compileSeq = 0;
function compile(el) {
var prog = $(el).val();
var req = new XMLHttpRequest();
var seq = compileSeq++;
req.onreadystatechange = function() { compileUpdate(req, seq); }
req.open("POST", "/compile", true);
req.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
req.send(prog);
if (output) {
var seq = compileSeq;
if (errors)
errors.innerHTML = "";
output.innerHTML = "";
setTimeout(function() {
if (seq == compileSeq) {
output.innerHTML = "running...";
}
}, 1000);
}
}
function compileUpdate(req, seq) {
if(!req || req.readyState != 4 || compileSeq != seq)
return;
var out = req.responseText;
var err = "";
if(req.status != 200) {
err = out;
out = "";
}
if (output)
output.innerHTML = out;
if (errors)
errors.innerHTML = err;
compileSeq++;
}
function init() {
slides = initSlides();
show(urlSlideNumber(location.href));
}

161
tour/wc/wc.go Normal file
Просмотреть файл

@ -0,0 +1,161 @@
// 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 wc
import (
"bytes"
"fmt"
"http"
"io"
"log"
"sort"
"template"
"runtime/debug"
)
func frontPageHandler(c http.ResponseWriter, req *http.Request) {
frontPage.Execute(c, "a man a plan a canal panama")
}
var wcFunc func(string)map[string]int
func runwc(s string) (m map[string]int, err string) {
defer func() {
if v := recover(); v != nil {
err = fmt.Sprintln("panic: ", v)
err += "\nCall stack:\n" + string(debug.Stack())
}
}()
return wcFunc(s), ""
}
type wordCount struct {
Word string
Count int
}
type wordCounts []wordCount
func (wc wordCounts) Less(i, j int) bool {
return wc[i].Count > wc[j].Count ||
wc[i].Count == wc[j].Count && wc[i].Word < wc[j].Word
}
func (wc wordCounts) Swap(i, j int) {
wc[i], wc[j] = wc[j], wc[i]
}
func (wc wordCounts) Len() int {
return len(wc)
}
func wcHandler(c http.ResponseWriter, req *http.Request) {
var buf bytes.Buffer
io.Copy(&buf, req.Body)
m, err := runwc(buf.String())
if err != "" {
c.WriteHeader(404)
c.Write([]byte("<pre>"))
template.HTMLEscape(c, []byte(err))
c.Write([]byte("</pre>"))
return
}
w := make([]wordCount, len(m))
n := 0
for word, count := range m {
w[n] = wordCount{word, count}
n++
}
sort.Sort(wordCounts(w))
table.Execute(c, w)
}
// Serve runs a web server on port 4000 counting words using f.
func Serve(f func(string)map[string]int) {
wcFunc = f
http.HandleFunc("/", frontPageHandler)
http.HandleFunc("/wc", wcHandler)
err := http.ListenAndServe("127.0.0.1:4000", nil)
log.Fatal(err)
}
var frontPage, table *template.Template
func init() {
frontPage = template.New(nil)
frontPage.SetDelims("«", "»")
err := frontPage.Parse(frontPageText)
if err != nil {
panic(err)
}
table = template.New(nil)
table.SetDelims("«", "»")
err = table.Parse(tableText)
if err != nil {
panic(err)
}
}
var frontPageText = `<!doctype html>
<html>
<head>
<style>
h1 { font-family: monospace; }
</style>
<script>
var xmlreq;
function runwc() {
var prog = document.getElementById("edit").value;
var req = new XMLHttpRequest();
xmlreq = req;
req.onreadystatechange = wcUpdate;
req.open("POST", "/wc", true);
req.setRequestHeader("Content-Type", "text/plain; charset=utf-8");
req.send(prog);
}
function wcUpdate() {
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>
<h1>Interactive Word Count</h1>
<table width="100%"><tr><td width="60%" valign="top">
<textarea autofocus="true" id="edit" style="width: 100%; height: 200px; font-size: 100%;" spellcheck="false" contenteditable="true" onkeyup="runwc();">«@|html»
</textarea>
<br/>
<td width="3%">
<td width="27%" align="right" valign="top">
<div id="output" align="left" style="width: 100%; font-size: 100%;">
</div>
</table>
<div id="errors" align="left" style="width: 100%; font-family: monaco; font-size: 100%; color: #800;">
</div>
</body>
</html>
`
var tableText = `map [
<table>
«.repeated section @»
<tr><td width=20><td>«Word»<td>«Count»</tr>
«.end»
</table>
]
`