go.talks/present: support fontifying markers
R=adg
CC=golang-dev
https://golang.org/cl/6625073
diff --git a/present/code.go b/present/code.go
index 448a593..c322f96 100644
--- a/present/code.go
+++ b/present/code.go
@@ -6,7 +6,6 @@
import (
"fmt"
- "html"
"html/template"
"io/ioutil"
"log"
@@ -292,15 +291,3 @@
func hide(text string) string {
return fmt.Sprintf(`<pre style="display: none">%s</pre>`, template.HTMLEscapeString(text))
}
-
-const codifyChar = "`"
-
-var codifyRE = regexp.MustCompile(fmt.Sprintf("%s[^%s]+%s", codifyChar, codifyChar, codifyChar))
-
-func codify(s string) template.HTML {
- s = html.EscapeString(s)
- repl := func(s string) string {
- return "<code>" + s[1:len(s)-1] + "</code>"
- }
- return template.HTML(codifyRE.ReplaceAllStringFunc(s, repl))
-}
diff --git a/present/doc.go b/present/doc.go
index 5b481d8..f3d9aca 100644
--- a/present/doc.go
+++ b/present/doc.go
@@ -40,7 +40,6 @@
* Title of slide or section (must have asterisk)
Some Text
- (Strings enclosed by `backquotes` will be presented as source code.)
- bullets
- more bullets
@@ -66,8 +65,22 @@
Lines starting with # in column 1 are commentary.
-Within the input for plain text or lists, text included in back
-quotes `xxx` will be presented in program font.
+Fonts:
+
+Within the input for plain text or lists, text bracketed by font
+markers will be presented in italic, bold, or program font.
+Marker characters are _ (italic), * (bold) and ` (program font).
+Unmatched markers appear as plain text.
+Within marked text, a single marker character becomes a space
+and a doubled single marker quotes the marker character.
+
+ _italic_
+ *bold*
+ `program`
+ _this_is_all_italic_
+ _Why_use_scoped__ptr_? Use plain ***ptr* instead.
+
+Functions:
A number of template functions are available through invocations
in the input text. Each such invocation contains a period as the
diff --git a/present/parse.go b/present/parse.go
index c294c10..26d7d0e 100644
--- a/present/parse.go
+++ b/present/parse.go
@@ -22,7 +22,7 @@
parsers = make(map[string]func(string, int, string) (Elem, error))
funcs = template.FuncMap{
- "codify": codify,
+ "style": style,
}
)
diff --git a/present/slide.tmpl b/present/slide.tmpl
index d4caba7..e3ecdaa 100755
--- a/present/slide.tmpl
+++ b/present/slide.tmpl
@@ -64,7 +64,7 @@
{{define "list"}}
<ul>
{{range .Bullet}}
- <li>{{codify .}}</li>
+ <li>{{style .}}</li>
{{end}}
</ul>
{{end}}
@@ -75,7 +75,7 @@
{{else}}
<p>
{{range $i, $l := .Lines}}{{if $i}}<br>
- {{end}}{{codify $l}}{{end}}
+ {{end}}{{style $l}}{{end}}
</p>
{{end}}
{{end}}
diff --git a/present/style.go b/present/style.go
new file mode 100644
index 0000000..3943af7
--- /dev/null
+++ b/present/style.go
@@ -0,0 +1,117 @@
+// 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 main
+
+import (
+ "bytes"
+ "html"
+ "html/template"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+/*
+ Fonts are demarcated by an initial and final char bracketing a
+ space-delimited word, plus possibly some terminal punctuation.
+ The chars are
+ _ for italic
+ * for bold
+ ` (back quote) for fixed width.
+ Inner appearances of the char become spaces. For instance,
+ _this_is_italic_!
+ becomes
+ <i>this is italic</i>!
+*/
+
+func style(s string) template.HTML {
+ return template.HTML(font(html.EscapeString(s)))
+}
+
+// font returns s with font indicators turned into HTML font tags.
+func font(s string) string {
+ if strings.IndexAny(s, "`_*") == -1 {
+ return s
+ }
+ words := split(s)
+ var b bytes.Buffer
+Word:
+ for w, word := range words {
+ if len(word) < 2 {
+ continue Word
+ }
+ char := word[0] // ASCII is OK.
+ open := ""
+ close := ""
+ switch char {
+ default:
+ continue Word
+ case '_':
+ open = "<i>"
+ close = "</i>"
+ case '*':
+ open = "<b>"
+ close = "</b>"
+ case '`':
+ open = "<code>"
+ close = "</code>"
+ }
+ // Terminal punctuation is OK but must be peeled off.
+ last := strings.LastIndex(word, word[:1])
+ if last == 0 {
+ continue Word
+ }
+ head, tail := word[:last+1], word[last+1:]
+ for _, r := range tail {
+ if !strings.ContainsRune(`.,;:()!?—–'"`, r) {
+ continue Word
+ }
+ }
+ b.Reset()
+ b.WriteString(open)
+ var wid int
+ for i := 1; i < len(head)-1; i += wid {
+ var r rune
+ r, wid = utf8.DecodeRuneInString(head[i:])
+ if r != rune(char) {
+ // Ordinary character.
+ b.WriteRune(r)
+ continue
+ }
+ if head[i+1] != char {
+ // Inner char becomes space.
+ b.WriteRune(' ')
+ continue
+ }
+ // Doubled char becomes real char.
+ // Not worth worrying about "_x__".
+ b.WriteByte(char)
+ wid++ // Consumed two chars, both ASCII.
+ }
+ b.WriteString(close) // Write closing tag.
+ b.WriteString(tail) // Restore trailing punctuation.
+ words[w] = b.String()
+ }
+ return strings.Join(words, "")
+}
+
+// split is like strings.Fields but also returns the runs of spaces.
+func split(s string) []string {
+ words := make([]string, 0, 10)
+ prevWasSpace := false
+ mark := 0
+ for i, r := range s {
+ isSpace := unicode.IsSpace(r)
+ if isSpace != prevWasSpace && i > mark {
+ words = append(words, s[mark:i])
+ mark = i
+ }
+ prevWasSpace = isSpace
+ }
+ if mark < len(s) {
+ words = append(words, s[mark:])
+ }
+ return words
+}
diff --git a/present/style_test.go b/present/style_test.go
new file mode 100644
index 0000000..3ea804b
--- /dev/null
+++ b/present/style_test.go
@@ -0,0 +1,80 @@
+// 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 main
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestSplit(t *testing.T) {
+ var tests = []struct {
+ in string
+ out []string
+ }{
+ {"", []string{}},
+ {" ", []string{" "}},
+ {"abc", []string{"abc"}},
+ {"abc def", []string{"abc", " ", "def"}},
+ {"abc def ", []string{"abc", " ", "def", " "}},
+ }
+ for _, test := range tests {
+ out := split(test.in)
+ if !reflect.DeepEqual(out, test.out) {
+ t.Errorf("split(%q):\ngot\t%q\nwant\t%q", test.in, out, test.out)
+ }
+ }
+}
+
+func TestFont(t *testing.T) {
+ var tests = []struct {
+ in string
+ out string
+ }{
+ {"", ""},
+ {" ", " "},
+ {"\tx", "\tx"},
+ {"_a_", "<i>a</i>"},
+ {"*a*", "<b>a</b>"},
+ {"`a`", "<code>a</code>"},
+ {"_a_b_", "<i>a b</i>"},
+ {"_a__b_", "<i>a_b</i>"},
+ {"_a___b_", "<i>a_ b</i>"},
+ {"*a**b*?", "<b>a*b</b>?"},
+ {"_a_<>_b_.", "<i>a <> b</i>."},
+ {"_Why_use_scoped__ptr_? Use plain ***ptr* instead.", "<i>Why use scoped_ptr</i>? Use plain <b>*ptr</b> instead."},
+ }
+ for _, test := range tests {
+ out := font(test.in)
+ if out != test.out {
+ t.Errorf("font(%q):\ngot\t%q\nwant\t%q", test.in, out, test.out)
+ }
+ }
+}
+
+func TestStyle(t *testing.T) {
+ var tests = []struct {
+ in string
+ out string
+ }{
+ {"", ""},
+ {" ", " "},
+ {"\tx", "\tx"},
+ {"_a_", "<i>a</i>"},
+ {"*a*", "<b>a</b>"},
+ {"`a`", "<code>a</code>"},
+ {"_a_b_", "<i>a b</i>"},
+ {"_a__b_", "<i>a_b</i>"},
+ {"_a___b_", "<i>a_ b</i>"},
+ {"*a**b*?", "<b>a*b</b>?"},
+ {"_a_<>_b_.", "<i>a <> b</i>."},
+ }
+ for _, test := range tests {
+ out := string(style(test.in))
+ if out != test.out {
+ t.Errorf("style(%q):\ngot\t%q\nwant\t%q", test.in, out, test.out)
+ }
+ }
+}