From 4b0c49c5328d4411eea85444bc1a8e3a8ad7e1eb Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Fri, 25 Jan 2013 10:24:33 +1100 Subject: [PATCH] go-tour: fix app engine version, restoring online-specific content R=minux.ma, campoy, campoy CC=golang-dev https://codereview.appspot.com/7195043 --- appengine/app.yaml => app.yaml | 7 ++- appengine/README | 16 ------ appengine/goplay/compile.go | 44 ---------------- appengine/goplay/fmt.go | 50 ------------------ gotour/appengine.go | 92 ++++++++++++++++++++++++++++++++++ gotour/goplay.go | 37 -------------- gotour/local.go | 53 +++++++++++++++----- gotour/tour.go | 2 +- tour.article | 39 ++++++++++++-- 9 files changed, 173 insertions(+), 167 deletions(-) rename appengine/app.yaml => app.yaml (72%) delete mode 100644 appengine/README delete mode 100644 appengine/goplay/compile.go delete mode 100644 appengine/goplay/fmt.go create mode 100644 gotour/appengine.go delete mode 100644 gotour/goplay.go diff --git a/appengine/app.yaml b/app.yaml similarity index 72% rename from appengine/app.yaml rename to app.yaml index 23b0346..83981aa 100644 --- a/appengine/app.yaml +++ b/app.yaml @@ -4,9 +4,6 @@ runtime: go api_version: go1 handlers: -- url: / - static_files: static/index.html - upload: static/index.html - url: /favicon.ico static_files: static/favicon.ico upload: static/favicon.ico @@ -14,5 +11,7 @@ handlers: static_dir: static - url: /talks static_dir: talks -- url: /(compile|fmt) +- url: /(|compile|fmt) script: _go_app + +nobuild_files: (solutions|prog)/.* diff --git a/appengine/README b/appengine/README deleted file mode 100644 index 5a8661d..0000000 --- a/appengine/README +++ /dev/null @@ -1,16 +0,0 @@ -This is the App Engine version of the Go Playground. - -To deploy: (instructions relative to the appengine directory) - -1. Make a copy of the static and template directories. - - cp -r ../{static,template} . - -2. Edit static/mode.js to set the tourMode variable to "appengine". - -3. Edit app.yaml to set the application name to something you have access to. - -4. Use appcfg.py to deploy it. - - /path/to/sdk/appcfg.py update . - diff --git a/appengine/goplay/compile.go b/appengine/goplay/compile.go deleted file mode 100644 index be1e3e7..0000000 --- a/appengine/goplay/compile.go +++ /dev/null @@ -1,44 +0,0 @@ -// 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 goplay - -import ( - "fmt" - "io" - "net/http" - - "appengine" - "appengine/urlfetch" -) - -const runUrl = "http://golang.org/compile?output=json" - -func init() { - http.HandleFunc("/compile", compile) -} - -func compile(w http.ResponseWriter, r *http.Request) { - if err := passThru(w, r); err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintln(w, "Compile server error.") - } -} - -func passThru(w io.Writer, req *http.Request) error { - c := appengine.NewContext(req) - client := urlfetch.Client(c) - defer req.Body.Close() - r, err := client.Post(runUrl, req.Header.Get("Content-type"), req.Body) - if err != nil { - c.Errorf("making POST request:", err) - return err - } - defer r.Body.Close() - if _, err := io.Copy(w, r.Body); err != nil { - c.Errorf("copying response Body:", err) - return err - } - return nil -} diff --git a/appengine/goplay/fmt.go b/appengine/goplay/fmt.go deleted file mode 100644 index e49bc72..0000000 --- a/appengine/goplay/fmt.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2012 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 goplay - -import ( - "bytes" - "encoding/json" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "net/http" -) - -func init() { - http.HandleFunc("/fmt", fmtHandler) -} - -type fmtResponse struct { - Body string - Error string -} - -func fmtHandler(w http.ResponseWriter, r *http.Request) { - resp := new(fmtResponse) - body, err := gofmt(r.FormValue("body")) - if err != nil { - resp.Error = err.Error() - } else { - resp.Body = body - } - json.NewEncoder(w).Encode(resp) -} - -func gofmt(body string) (string, error) { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "prog.go", body, parser.ParseComments) - if err != nil { - return "", err - } - ast.SortImports(fset, f) - var buf bytes.Buffer - err = printer.Fprint(&buf, fset, f) - if err != nil { - return "", err - } - return buf.String(), nil -} diff --git a/gotour/appengine.go b/gotour/appengine.go new file mode 100644 index 0000000..29ad33e --- /dev/null +++ b/gotour/appengine.go @@ -0,0 +1,92 @@ +// 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. + +// +build appengine + +package main + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net/http" + + "appengine" + "appengine/urlfetch" +) + +const runUrl = "http://golang.org/compile" + +func init() { + http.HandleFunc("/", rootHandler) + http.HandleFunc("/compile", compileHandler) +} + +func rootHandler(w http.ResponseWriter, r *http.Request) { + c := appengine.NewContext(r) + if err := renderTour(w, "."); err != nil { + c.Criticalf("template render: %v", err) + } +} + +func compileHandler(w http.ResponseWriter, r *http.Request) { + if err := passThru(w, r); err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, "Compile server error.") + } +} + +func passThru(w io.Writer, req *http.Request) error { + c := appengine.NewContext(req) + client := urlfetch.Client(c) + defer req.Body.Close() + r, err := client.Post(runUrl, req.Header.Get("Content-type"), req.Body) + if err != nil { + c.Errorf("making POST request:", err) + return err + } + defer r.Body.Close() + if _, err := io.Copy(w, r.Body); err != nil { + c.Errorf("copying response Body:", err) + return err + } + return nil +} + +// prepContent returns a Reader that produces the content from the given +// Reader, but strips the prefix "#appengine: " from each line. It also drops +// any non-blank like that follows a series of 1 or more lines with the prefix. +func prepContent(in io.Reader) io.Reader { + var prefix = []byte("#appengine: ") + out, w := io.Pipe() + go func() { + r := bufio.NewReader(in) + drop := false + for { + b, err := r.ReadBytes('\n') + if err != nil && err != io.EOF { + w.CloseWithError(err) + return + } + if bytes.HasPrefix(b, prefix) { + b = b[len(prefix):] + drop = true + } else if drop { + if len(b) > 1 { + b = nil + } + drop = false + } + if len(b) > 0 { + w.Write(b) + } + if err == io.EOF { + w.Close() + return + } + } + }() + return out +} diff --git a/gotour/goplay.go b/gotour/goplay.go deleted file mode 100644 index 8814def..0000000 --- a/gotour/goplay.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 ( - "encoding/json" - "log" - "net/http" -) - -func init() { - http.HandleFunc("/compile", Compile) -} - -type Response struct { - Output string `json:"output"` - Errors string `json:"compile_errors"` -} - -func Compile(w http.ResponseWriter, req *http.Request) { - resp := new(Response) - out, err := compile(req) - if err != nil { - if len(out) > 0 { - resp.Errors = string(out) + "\n" + err.Error() - } else { - resp.Errors = err.Error() - } - } else { - resp.Output = string(out) - } - if err := json.NewEncoder(w).Encode(resp); err != nil { - log.Println(err) - } -} diff --git a/gotour/local.go b/gotour/local.go index 6b97b40..b573d5d 100644 --- a/gotour/local.go +++ b/gotour/local.go @@ -2,13 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build !appengine + package main import ( "bytes" + "encoding/json" "flag" "fmt" "go/build" + "io" "io/ioutil" "log" "net" @@ -41,7 +45,7 @@ var ( // a source of numbers, for naming temporary files uniq = make(chan int) - // GOPATH containing the tour packages + // GOPATH containing the tour packages gopath = os.Getenv("GOPATH") ) @@ -83,23 +87,23 @@ func main() { } log.Println("Serving content from", root) + + fs := http.FileServer(http.Dir(root)) + http.Handle("/favicon.ico", fs) + http.Handle("/static/", fs) + http.Handle("/talks/", fs) + + http.HandleFunc("/compile", compileHandler) + http.HandleFunc("/kill", killHandler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/favicon.ico" { - fn := filepath.Join(root, "static", r.URL.Path[1:]) - http.ServeFile(w, r, fn) - return - } else if r.URL.Path == "/" { - err := renderTour(w, root) - if err != nil { + if r.URL.Path == "/" { + if err := renderTour(w, root); err != nil { log.Println(err) } return } http.Error(w, "not found", 404) }) - http.Handle("/static/", http.FileServer(http.Dir(root))) - http.Handle("/talks/", http.FileServer(http.Dir(root))) - http.HandleFunc("/kill", kill) host, port, err := net.SplitHostPort(*httpListen) if err != nil { @@ -136,6 +140,28 @@ If you don't understand this message, hit Control-C to terminate this process. WARNING! WARNING! WARNING! ` +type response struct { + Output string `json:"output"` + Errors string `json:"compile_errors"` +} + +func compileHandler(w http.ResponseWriter, req *http.Request) { + resp := new(response) + out, err := compile(req) + if err != nil { + if len(out) > 0 { + resp.Errors = string(out) + "\n" + err.Error() + } else { + resp.Errors = err.Error() + } + } else { + resp.Output = string(out) + } + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Println(err) + } +} + var running struct { sync.Mutex cmd *exec.Cmd @@ -150,7 +176,7 @@ func stopRun() { running.Unlock() } -func kill(w http.ResponseWriter, r *http.Request) { +func killHandler(w http.ResponseWriter, r *http.Request) { stopRun() } @@ -286,3 +312,6 @@ func startBrowser(url string) bool { cmd := exec.Command(args[0], append(args[1:], url)...) return cmd.Start() == nil } + +// prepContent for the local tour simply returns the content as-is. +func prepContent(r io.Reader) io.Reader { return r } diff --git a/gotour/tour.go b/gotour/tour.go index 5105c51..ce233e2 100644 --- a/gotour/tour.go +++ b/gotour/tour.go @@ -27,7 +27,7 @@ func renderTour(w io.Writer, root string) error { return err } defer f.Close() - doc, err := present.Parse(f, source, 0) + doc, err := present.Parse(prepContent(f), source, 0) if err != nil { return err } diff --git a/tour.article b/tour.article index c900a8a..ca17bbd 100644 --- a/tour.article +++ b/tour.article @@ -1,16 +1,32 @@ - A Tour of Go The Go Authors http://golang.org +# Throughout this file are a series of lines that begin with +# the string "#appengine: ". These lines are included in +# the tour content when deployed as an App Engine app. +# Furthermore, a single non-blank line that immediately follows +# a run of "#appengine: " lines will be hidden. This is so that +# App Engine-specific content may replace existing content. +# For example, this paragraph +# We are running +# #appengine: on App Engine. +# locally. +# Yay! +# reads as "We are running on App Engine. Yay!" on App Engine, +# and "We are running locally. Yay!" otherwise. + * Hello, 世界 Welcome to a tour of the [[http://golang.org/][Go programming language]]. The tour is divided into three sections. At the end of each section is a series of exercises for you to complete. -The tour is interactive. Click the Run button now (or type Shift-Enter) to compile and run the program on your computer. The result is displayed below the code. +The tour is interactive. Click the Run button now (or type Shift-Enter) to compile and run the program on +#appengine: a remote server. +your computer. +The result is displayed below the code. These example programs demonstrate different aspects of Go. The programs in the tour are meant to be starting points for your own experimentation. @@ -408,6 +424,10 @@ Switch cases evaluate cases from top to bottom, stopping when a case succeeds. does not call `f` if `i==0`.) +#appengine: *Note:* Time in the Go playground always appears to start at +#appengine: 2009-11-10 23:00:00 UTC, a value whose significance is left as an +#appengine: exercise for the reader. + .play prog/switch-evaluation-order.go * Switch with no condition @@ -553,6 +573,10 @@ In this example, the type `Hello` implements `http.Handler`. Visit [[http://localhost:4000/][http://localhost:4000/]] to see the greeting. +#appengine: *Note:* This example won't run through the web-based tour user +#appengine: interface. To try writing web servers you may want to +#appengine: [[http://golang.org/doc/install/][Install Go]]. + .play prog/web-servers.go * Images @@ -773,7 +797,16 @@ Modify the `Crawl` function to fetch URLs in parallel without fetching the same * Where to Go from here... -The [[http://golang.org/doc/][Go Documentation]] is a great place to start. It contains references, tutorials, videos, and more. +#appengine: You can get started by +#appengine: [[http://golang.org/doc/install/][installing Go]] or downloading the +#appengine: [[http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Go][Go App Engine SDK]]. + +#appengine: Once you have Go installed, the +The +[[http://golang.org/doc/][Go Documentation]] is a great place to +#appengine: continue. +start. +It contains references, tutorials, videos, and more. To learn how to organize and work with Go code, watch [[http://www.youtube.com/watch?v=XCsL89YtqCs][this screencast]] or read [[http://golang.org/doc/code.html][How to Write Go Code]].