package doc
import (
+ "bytes"
"io"
"strings"
"text/template" // for HTMLEscape
"unicode/utf8"
)
+const (
+ ldquo = "“"
+ rdquo = "”"
+ ulquo = "“"
+ urquo = "”"
+)
+
var (
- ldquo = []byte("“")
- rdquo = []byte("”")
+ htmlQuoteReplacer = strings.NewReplacer(ulquo, ldquo, urquo, rdquo)
+ unicodeQuoteReplacer = strings.NewReplacer("``", ulquo, "''", urquo)
)
// Escape comment text for HTML. If nice is set,
// also turn `` into “ and '' into ”.
func commentEscape(w io.Writer, text string, nice bool) {
- last := 0
if nice {
- for i := 0; i < len(text)-1; i++ {
- ch := text[i]
- if ch == text[i+1] && (ch == '`' || ch == '\'') {
- template.HTMLEscape(w, []byte(text[last:i]))
- last = i + 2
- switch ch {
- case '`':
- w.Write(ldquo)
- case '\'':
- w.Write(rdquo)
- }
- i++ // loop will add one more
- }
- }
+ // In the first pass, we convert `` and '' into their unicode equivalents.
+ // This prevents them from being escaped in HTMLEscape.
+ text = convertQuotes(text)
+ var buf bytes.Buffer
+ template.HTMLEscape(&buf, []byte(text))
+ // Now we convert the unicode quotes to their HTML escaped entities to maintain old behavior.
+ // We need to use a temp buffer to read the string back and do the conversion,
+ // otherwise HTMLEscape will escape & to &
+ htmlQuoteReplacer.WriteString(w, buf.String())
+ return
}
- template.HTMLEscape(w, []byte(text[last:]))
+ template.HTMLEscape(w, []byte(text))
+}
+
+func convertQuotes(text string) string {
+ return unicodeQuoteReplacer.Replace(text)
}
const (
}
// allow "." when followed by non-space
- for b := line;; {
+ for b := line; ; {
i := strings.IndexRune(b, '.')
if i < 0 {
break
case opPara:
// l.write will add leading newline if required
for _, line := range b.lines {
+ line = convertQuotes(line)
l.write(line)
}
l.flush()
case opHead:
w.Write(nl)
for _, line := range b.lines {
+ line = convertQuotes(line)
l.write(line + "\n")
}
l.flush()
w.Write([]byte("\n"))
} else {
w.Write([]byte(preIndent))
+ line = convertQuotes(line)
w.Write([]byte(line))
}
}
import (
"bytes"
"reflect"
+ "strings"
"testing"
)
}
}
}
+
+func TestCommentEscape(t *testing.T) {
+ commentTests := []struct {
+ in, out string
+ }{
+ {"typically invoked as ``go tool asm'',", "typically invoked as " + ldquo + "go tool asm" + rdquo + ","},
+ {"For more detail, run ``go help test'' and ``go help testflag''", "For more detail, run " + ldquo + "go help test" + rdquo + " and " + ldquo + "go help testflag" + rdquo},
+ }
+ for i, tt := range commentTests {
+ var buf strings.Builder
+ commentEscape(&buf, tt.in, true)
+ out := buf.String()
+ if out != tt.out {
+ t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
+ }
+ }
+}
{"All Rights reserved. Package foo does bar.", 20, ""},
{"All rights reserved. Package foo does bar.", 20, ""},
{"Authors: foo@bar.com. Package foo does bar.", 21, ""},
+ {"typically invoked as ``go tool asm'',", 37, "typically invoked as " + ulquo + "go tool asm" + urquo + ","},
}
func TestSynopsis(t *testing.T) {