Import talksapp
diff --git a/talksapp/.gitignore b/talksapp/.gitignore
new file mode 100644
index 0000000..0026861
--- /dev/null
+++ b/talksapp/.gitignore
@@ -0,0 +1,22 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
diff --git a/talksapp/README.md b/talksapp/README.md
new file mode 100644
index 0000000..5e77c6f
--- /dev/null
+++ b/talksapp/README.md
@@ -0,0 +1,12 @@
+talksapp
+========
+
+This directory contains the source for [go-talks.appspot.com](http://go-talks.appspot.com).
+
+Development Environment Setup
+-----------------------------
+
+- Copy config.go.template to config.go and edit the file as described in the comments.
+- Install Go App Engine SDK
+- $ sh setup.sh
+- Run the server using the dev_appserver command.
diff --git a/talksapp/app.yaml b/talksapp/app.yaml
new file mode 100644
index 0000000..2e9b1d6
--- /dev/null
+++ b/talksapp/app.yaml
@@ -0,0 +1,19 @@
+application: go-talks
+version: 1
+runtime: go
+api_version: go1
+
+handlers:
+- url: /robots\.txt
+ static_files: assets/robots.txt
+ upload: assets/robots.txt
+- url: /favicon\.ico
+ static_files: present/static/favicon.ico
+ upload: present/static/favicon.ico
+- url: /static
+ static_dir: present/static
+- url: /play\.js
+ static_files: present/play.js
+ upload: present/play.js
+- url: /.*
+ script: _go_app
diff --git a/talksapp/assets/home.article b/talksapp/assets/home.article
new file mode 100644
index 0000000..21d5078
--- /dev/null
+++ b/talksapp/assets/home.article
@@ -0,0 +1,27 @@
+go-talks.appspot.org
+
+Francesc Campoy (maintainer)
+@francesc
+
+Gary Burd
+@gburd
+
+* Introduction
+
+This site plays slide presentations and articles stored on GitHub in the
+[[http://godoc.org/code.google.com/p/go.tools/present][present format]].
+
+The syntax for URLs is:
+
+ http://go-talks.appspot.com/github.com/owner/project/file.ext
+ http://go-talks.appspot.com/github.com/owner/project/sub/directory/file.ext
+
+The supported file extensions (.ext) are .slide and .article.
+
+The .html command is not supported.
+
+This page is [[/github.com/golang/gddo/talksapp/assets/home.article][an article]].
+
+* Feedback
+
+Report bugs and request features using the [[https://github.com/golang/gddo/issues/new][GitHub Issue Tracker]].
diff --git a/talksapp/assets/robots.txt b/talksapp/assets/robots.txt
new file mode 100644
index 0000000..1f53798
--- /dev/null
+++ b/talksapp/assets/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/talksapp/config.go.template b/talksapp/config.go.template
new file mode 100644
index 0000000..0dd1796
--- /dev/null
+++ b/talksapp/config.go.template
@@ -0,0 +1,10 @@
+package talksapp
+
+func init() {
+ // Register an application at https://github.com/settings/applications/new
+ // and enter the client ID and client secret here.
+ gitHubCredentials = "client_id=<id>&client_secret=<secret>"
+
+ // Set contact email for /bot.html
+ contactEmail = "example@example.com"
+}
diff --git a/talksapp/main.go b/talksapp/main.go
new file mode 100644
index 0000000..5ed7dbb
--- /dev/null
+++ b/talksapp/main.go
@@ -0,0 +1,249 @@
+// Copyright 2013 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 or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package talksapp implements the go-talks.appspot.com server.
+package talksapp
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "html/template"
+ "io"
+ "net/http"
+ "os"
+ "path"
+ "time"
+
+ "appengine"
+ "appengine/memcache"
+ "appengine/urlfetch"
+
+ "code.google.com/p/go.tools/present"
+ "github.com/golang/gddo/gosrc"
+)
+
+var (
+ presentTemplates = map[string]*template.Template{
+ ".article": parsePresentTemplate("article.tmpl"),
+ ".slide": parsePresentTemplate("slides.tmpl"),
+ }
+ homeArticle = loadHomeArticle()
+ contactEmail = "unknown@example.com"
+ gitHubCredentials = ""
+)
+
+func init() {
+ http.Handle("/", handlerFunc(serveRoot))
+ http.Handle("/compile", handlerFunc(serveCompile))
+ http.Handle("/bot.html", handlerFunc(serveBot))
+ present.PlayEnabled = true
+}
+
+func playable(c present.Code) bool {
+ return present.PlayEnabled && c.Play && c.Ext == ".go"
+}
+
+func parsePresentTemplate(name string) *template.Template {
+ t := present.Template()
+ t = t.Funcs(template.FuncMap{"playable": playable})
+ if _, err := t.ParseFiles("present/templates/"+name, "present/templates/action.tmpl"); err != nil {
+ panic(err)
+ }
+ t = t.Lookup("root")
+ if t == nil {
+ panic("root template not found for " + name)
+ }
+ return t
+}
+
+func loadHomeArticle() []byte {
+ const fname = "assets/home.article"
+ f, err := os.Open(fname)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ doc, err := present.Parse(f, fname, 0)
+ if err != nil {
+ panic(err)
+ }
+ var buf bytes.Buffer
+ if err := renderPresentation(&buf, fname, doc); err != nil {
+ panic(err)
+ }
+ return buf.Bytes()
+}
+
+func renderPresentation(w io.Writer, fname string, doc *present.Doc) error {
+ t := presentTemplates[path.Ext(fname)]
+ if t == nil {
+ return errors.New("unknown template extension")
+ }
+ data := struct {
+ *present.Doc
+ Template *template.Template
+ PlayEnabled bool
+ }{
+ doc,
+ t,
+ true,
+ }
+ return t.Execute(w, &data)
+}
+
+type presFileNotFoundError string
+
+func (s presFileNotFoundError) Error() string { return fmt.Sprintf("File %s not found.", string(s)) }
+
+func writeHTMLHeader(w http.ResponseWriter, status int) {
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(status)
+}
+
+func writeTextHeader(w http.ResponseWriter, status int) {
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ w.WriteHeader(status)
+}
+
+type transport struct {
+ rt http.RoundTripper
+ ua string
+}
+
+func (t transport) RoundTrip(r *http.Request) (*http.Response, error) {
+ r.Header.Set("User-Agent", t.ua)
+ if r.URL.Host == "api.github.com" && gitHubCredentials != "" {
+ if r.URL.RawQuery == "" {
+ r.URL.RawQuery = gitHubCredentials
+ } else {
+ r.URL.RawQuery += "&" + gitHubCredentials
+ }
+ }
+ return t.rt.RoundTrip(r)
+}
+
+func httpClient(r *http.Request) *http.Client {
+ c := appengine.NewContext(r)
+ return &http.Client{
+ Transport: &transport{
+ rt: &urlfetch.Transport{Context: c, Deadline: 10 * time.Second},
+ ua: fmt.Sprintf("%s (+http://%s/bot.html)", appengine.AppID(c), r.Host),
+ },
+ }
+}
+
+type handlerFunc func(http.ResponseWriter, *http.Request) error
+
+func (f handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ c := appengine.NewContext(r)
+ err := f(w, r)
+ if err == nil {
+ return
+ } else if gosrc.IsNotFound(err) {
+ writeTextHeader(w, 400)
+ io.WriteString(w, "Not Found.")
+ } else if e, ok := err.(*gosrc.RemoteError); ok {
+ writeTextHeader(w, 500)
+ fmt.Fprintf(w, "Error accessing %s.\n%v", e.Host, e)
+ c.Infof("Remote error %s: %v", e.Host, e)
+ } else if e, ok := err.(presFileNotFoundError); ok {
+ writeTextHeader(w, 200)
+ io.WriteString(w, e.Error())
+ } else if err != nil {
+ writeTextHeader(w, 500)
+ io.WriteString(w, "Internal server error.")
+ c.Errorf("Internal error %v", err)
+ }
+}
+
+func serveRoot(w http.ResponseWriter, r *http.Request) error {
+ switch {
+ case r.Method != "GET" && r.Method != "HEAD":
+ writeTextHeader(w, 405)
+ _, err := io.WriteString(w, "Method not supported.")
+ return err
+ case r.URL.Path == "/":
+ writeHTMLHeader(w, 200)
+ _, err := w.Write(homeArticle)
+ return err
+ default:
+ return servePresentation(w, r)
+ }
+}
+
+func servePresentation(w http.ResponseWriter, r *http.Request) error {
+ c := appengine.NewContext(r)
+ importPath := r.URL.Path[1:]
+
+ item, err := memcache.Get(c, importPath)
+ if err == nil {
+ writeHTMLHeader(w, 200)
+ w.Write(item.Value)
+ return nil
+ } else if err != memcache.ErrCacheMiss {
+ return err
+ }
+
+ c.Infof("Fetching presentation %s.", importPath)
+ pres, err := gosrc.GetPresentation(httpClient(r), importPath)
+ if err != nil {
+ return err
+ }
+
+ ctx := &present.Context{
+ ReadFile: func(name string) ([]byte, error) {
+ if p, ok := pres.Files[name]; ok {
+ return p, nil
+ }
+ return nil, presFileNotFoundError(name)
+ },
+ }
+
+ doc, err := ctx.Parse(bytes.NewReader(pres.Files[pres.Filename]), pres.Filename, 0)
+ if err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ if err := renderPresentation(&buf, importPath, doc); err != nil {
+ return err
+ }
+
+ if err := memcache.Add(c, &memcache.Item{
+ Key: importPath,
+ Value: buf.Bytes(),
+ Expiration: time.Hour,
+ }); err != nil {
+ return err
+ }
+
+ writeHTMLHeader(w, 200)
+ _, err = w.Write(buf.Bytes())
+ return err
+}
+
+func serveCompile(w http.ResponseWriter, r *http.Request) error {
+ client := urlfetch.Client(appengine.NewContext(r))
+ if err := r.ParseForm(); err != nil {
+ return err
+ }
+ resp, err := client.PostForm("http://play.golang.org/compile", r.Form)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
+ _, err = io.Copy(w, resp.Body)
+ return err
+}
+
+func serveBot(w http.ResponseWriter, r *http.Request) error {
+ c := appengine.NewContext(r)
+ writeTextHeader(w, 200)
+ _, err := fmt.Fprintf(w, "Contact %s for help with the %s bot.", contactEmail, appengine.AppID(c))
+ return err
+}
diff --git a/talksapp/setup.sh b/talksapp/setup.sh
new file mode 100755
index 0000000..bbac4a7
--- /dev/null
+++ b/talksapp/setup.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+go get golang.org/x/tools/cmd/present
+go get golang.org/x/tools/godoc
+present=`go list -f '{{.Dir}}' golang.org/x/tools/cmd/present`
+godoc=`go list -f '{{.Dir}}' golang.org/x/tools/godoc`
+mkdir -p present
+
+(cat $godoc/static/jquery.js $godoc/static/playground.js $godoc/static/play.js && echo "initPlayground(new HTTPTransport());") > present/play.js
+
+cd ./present
+for i in templates static
+do
+ ln -is $present/$i
+done