go.talks/present: give presenter fields a more free format

R=r
CC=gobot, golang-dev
https://golang.org/cl/6674051
diff --git a/present/doc.go b/present/doc.go
index f3d9aca..4f368d7 100644
--- a/present/doc.go
+++ b/present/doc.go
@@ -29,9 +29,12 @@
 	<blank line>
 	Presenter Name
 	Job title, Company
-	Google+ profile link
-	@twitter_name
+	joe@example.com
 	http://url/
+	@twitter_name
+
+The presenter section may contain a mixture of text, twitter names, and links.
+Only the plain text lines will be displayed on the presentation front page.
 
 Multiple presenters may be specified, separated by a blank line.
 
diff --git a/present/link.go b/present/link.go
index 7fccab5..e5c685a 100644
--- a/present/link.go
+++ b/present/link.go
@@ -38,7 +38,11 @@
 	label := ""
 	switch len(arg) {
 	case 0:
-		label = strings.Replace(url.String(), url.Scheme+"://", "", 1)
+		scheme := url.Scheme + "://"
+		if url.Scheme == "mailto" {
+			scheme = "mailto:"
+		}
+		label = strings.Replace(url.String(), scheme, "", 1)
 	default:
 		label = strings.Join(arg, " ")
 	}
diff --git a/present/parse.go b/present/parse.go
index 26d7d0e..108b9b1 100644
--- a/present/parse.go
+++ b/present/parse.go
@@ -11,6 +11,8 @@
 	"fmt"
 	"html/template"
 	"io"
+	"log"
+	"net/url"
 	"path/filepath"
 	"strings"
 	"unicode"
@@ -75,11 +77,20 @@
 
 // Presenter represents the person who wrote and/or is giving the presentation.
 type Presenter struct {
-	Name    string `json:"name"`
-	Company string `json:"company"`
-	Gplus   string `json:"gplus"`
-	Twitter string `json:"twitter"`
-	WWW     string `json:"www"`
+	Elem []Elem
+}
+
+// TextElem returns the first text elements of the presenter details.
+// This is used to display the presenters' name, job title, and company
+// without the contact details.
+func (p *Presenter) TextElem() (elems []Elem) {
+	for _, el := range p.Elem {
+		if _, ok := el.(Text); !ok {
+			break
+		}
+		elems = append(elems, el)
+	}
+	return
 }
 
 // Slide represents a single presentation slide.
@@ -201,35 +212,7 @@
 		return pres, nil
 	}
 	// Presenters
-	for {
-		var text string
-		text, ok = lines.nextNonEmpty()
-		if !ok {
-			return nil, errors.New("unexpected EOF")
-		}
-		if strings.HasPrefix(text, "* ") {
-			lines.back()
-			break
-		}
-		p := Presenter{Name: text}
-		p.Company, ok = lines.next()
-		if !ok {
-			return nil, errors.New("no company")
-		}
-		p.Gplus, ok = lines.next()
-		if !ok {
-			return nil, errors.New("no google+ link")
-		}
-		p.Twitter, ok = lines.next()
-		if !ok {
-			return nil, errors.New("no twitter name")
-		}
-		p.WWW, ok = lines.next()
-		if !ok {
-			return nil, errors.New("no www link")
-		}
-		pres.Presenters = append(pres.Presenters, p)
-	}
+	pres.Presenters, err = parsePresenters(lines)
 	// Slides
 	for i := 0; ; i++ {
 		var slide Slide
@@ -317,3 +300,72 @@
 	}
 	return pres, nil
 }
+
+func parsePresenters(lines *Lines) (pres []Presenter, err error) {
+	// This grammar demarcates presenters with blanks.
+
+	// Skip blank lines.
+	if _, ok := lines.nextNonEmpty(); !ok {
+		return nil, errors.New("unexpected EOF")
+	}
+	lines.back()
+
+	var p *Presenter
+	for {
+		text, ok := lines.next()
+		if !ok {
+			return nil, errors.New("unexpected EOF")
+		}
+
+		// If we find a slide heading, we're done.
+		if strings.HasPrefix(text, "* ") {
+			lines.back()
+			break
+		}
+
+		// If we encounter a blank we're done with this presenter.
+		if p != nil && len(text) == 0 {
+			pres = append(pres, *p)
+			p = nil
+			continue
+		}
+		if p == nil {
+			p = new(Presenter)
+		}
+
+		// Parse the line. Those that
+		// - begin with @ are twitter names,
+		// - contain slashes are links, or
+		// - contain an @ symbol are an email address.
+		// The rest is just text.
+		var el Elem
+		switch {
+		case strings.HasPrefix(text, "@"):
+			el = parseURL("http://twitter.com/" + text[1:])
+			if l, ok := el.(Link); ok {
+				l.Args = []string{text}
+			}
+		case strings.Contains(text, ":"):
+			el = parseURL(text)
+		case strings.Contains(text, "@"):
+			el = parseURL("mailto:" + text)
+		}
+		if el == nil {
+			el = Text{Lines: []string{text}}
+		}
+		p.Elem = append(p.Elem, el)
+	}
+	if p != nil {
+		pres = append(pres, *p)
+	}
+	return pres, nil
+}
+
+func parseURL(text string) Elem {
+	u, err := url.Parse(text)
+	if err != nil {
+		log.Printf("Parse(%q): %v", text, err)
+		return nil
+	}
+	return Link{URL: u}
+}
diff --git a/present/slide.tmpl b/present/slide.tmpl
index e3ecdaa..62de725 100755
--- a/present/slide.tmpl
+++ b/present/slide.tmpl
@@ -23,10 +23,9 @@
         <h1>{{.Title}}</h1>
         {{with .Subtitle}}<h3>{{.}}</h3>{{end}}
         {{range .Presenters}}
-          <p>
-          	{{.Name}}
-            {{with .Company}}<br>{{.}}{{end}}
-          </p>
+          <div class="presenter">
+            {{range .TextElem}}{{.HTML $.Template}}{{end}}
+          </div>
         {{end}}
       </article>
       
@@ -46,13 +45,9 @@
       <article>
         <h3>Thank you</h1>
         {{range .Presenters}}
-          <p>
-          	{{.Name}}
-            {{with .Company}}<br>{{.}}{{end}}
-            {{with .Gplus}}<br><a href="{{.}}">{{.}}</a>{{end}}
-            {{with .Twitter}}<br><a href="http://twitter.com/{{.}}">{{.}}</a>{{end}}
-            {{with .WWW}}<br><a href="{{.}}">{{.}}</a>{{end}}
-          </p>
+          <div class="presenter">
+            {{range .Elem}}{{.HTML $.Template}}{{end}}
+          </div>
         {{end}}
       </article>
 
diff --git a/present/static/styles.css b/present/static/styles.css
index 1153118..08d9193 100755
--- a/present/static/styles.css
+++ b/present/static/styles.css
@@ -405,3 +405,14 @@
   right: 5px;
   bottom: 5px;
 }
+
+/* Presenter details */
+.presenter {
+	margin-top: 20px;
+}
+.presenter p,
+.presenter .link {
+	margin: 0;
+	font-size: 28px;
+	line-height: 1.2em;
+}