diff options
Diffstat (limited to 'src/pkg/go/doc')
-rw-r--r-- | src/pkg/go/doc/comment.go | 49 | ||||
-rw-r--r-- | src/pkg/go/doc/comment_test.go | 106 | ||||
-rw-r--r-- | src/pkg/go/doc/example.go | 15 |
3 files changed, 156 insertions, 14 deletions
diff --git a/src/pkg/go/doc/comment.go b/src/pkg/go/doc/comment.go index 5c8c43e0c..f414ca409 100644 --- a/src/pkg/go/doc/comment.go +++ b/src/pkg/go/doc/comment.go @@ -45,13 +45,13 @@ func commentEscape(w io.Writer, text string, nice bool) { const ( // Regexp for Go identifiers - identRx = `[a-zA-Z_][a-zA-Z_0-9]*` // TODO(gri) ASCII only for now - fix this + identRx = `[\pL_][\pL_0-9]*` // Regexp for URLs - protocol = `(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|prospero):` + protocol = `https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|prospero` hostPart = `[a-zA-Z0-9_@\-]+` - filePart = `[a-zA-Z0-9_?%#~&/\-+=]+` - urlRx = protocol + `//` + // http:// + filePart = `[a-zA-Z0-9_?%#~&/\-+=()]+` // parentheses may not be matching; see pairedParensPrefixLen + urlRx = `(` + protocol + `)://` + // http:// hostPart + `([.:]` + hostPart + `)*/?` + // //www.google.com:8080/ filePart + `([:.,]` + filePart + `)*` ) @@ -73,6 +73,29 @@ var ( html_endh = []byte("</h3>\n") ) +// pairedParensPrefixLen returns the length of the longest prefix of s containing paired parentheses. +func pairedParensPrefixLen(s string) int { + parens := 0 + l := len(s) + for i, ch := range s { + switch ch { + case '(': + if parens == 0 { + l = i + } + parens++ + case ')': + parens-- + if parens == 0 { + l = len(s) + } else if parens < 0 { + return i + } + } + } + return l +} + // Emphasize and escape a line of text for HTML. URLs are converted into links; // if the URL also appears in the words map, the link is taken from the map (if // the corresponding map value is the empty string, the URL is not converted @@ -92,18 +115,26 @@ func emphasize(w io.Writer, line string, words map[string]string, nice bool) { // write text before match commentEscape(w, line[0:m[0]], nice) - // analyze match + // adjust match if necessary match := line[m[0]:m[1]] + if n := pairedParensPrefixLen(match); n < len(match) { + // match contains unpaired parentheses (rare); + // redo matching with shortened line for correct indices + m = matchRx.FindStringSubmatchIndex(line[:m[0]+n]) + match = match[:n] + } + + // analyze match url := "" italics := false if words != nil { - url, italics = words[string(match)] + url, italics = words[match] } if m[2] >= 0 { // match against first parenthesized sub-regexp; must be match against urlRx if !italics { // no alternative URL in words list, use match instead - url = string(match) + url = match } italics = false // don't italicize URLs } @@ -392,7 +423,9 @@ func ToText(w io.Writer, text string, indent, preIndent string, width int) { case opPre: w.Write(nl) for _, line := range b.lines { - if !isBlank(line) { + if isBlank(line) { + w.Write([]byte("\n")) + } else { w.Write([]byte(preIndent)) w.Write([]byte(line)) } diff --git a/src/pkg/go/doc/comment_test.go b/src/pkg/go/doc/comment_test.go index aa21b8d1b..ad65c2a27 100644 --- a/src/pkg/go/doc/comment_test.go +++ b/src/pkg/go/doc/comment_test.go @@ -42,8 +42,9 @@ func TestIsHeading(t *testing.T) { } var blocksTests = []struct { - in string - out []block + in string + out []block + text string }{ { in: `Para 1. @@ -59,6 +60,22 @@ Para 3. pre1 Para 4. + + pre + pre1 + + pre2 + +Para 5. + + + pre + + + pre1 + pre2 + +Para 6. pre pre2 `, @@ -69,8 +86,44 @@ Para 4. {opPara, []string{"Para 3.\n"}}, {opPre, []string{"pre\n", "pre1\n"}}, {opPara, []string{"Para 4.\n"}}, + {opPre, []string{"pre\n", "pre1\n", "\n", "pre2\n"}}, + {opPara, []string{"Para 5.\n"}}, + {opPre, []string{"pre\n", "\n", "\n", "pre1\n", "pre2\n"}}, + {opPara, []string{"Para 6.\n"}}, {opPre, []string{"pre\n", "pre2\n"}}, }, + text: `. Para 1. Para 1 line 2. + +. Para 2. + + +. Section + +. Para 3. + +$ pre +$ pre1 + +. Para 4. + +$ pre +$ pre1 + +$ pre2 + +. Para 5. + +$ pre + + +$ pre1 +$ pre2 + +. Para 6. + +$ pre +$ pre2 +`, }, } @@ -83,14 +136,28 @@ func TestBlocks(t *testing.T) { } } +func TestToText(t *testing.T) { + var buf bytes.Buffer + for i, tt := range blocksTests { + ToText(&buf, tt.in, ". ", "$\t", 40) + if have := buf.String(); have != tt.text { + t.Errorf("#%d: mismatch\nhave: %s\nwant: %s\nhave vs want:\n%q\n%q", i, have, tt.text, have, tt.text) + } + buf.Reset() + } +} + var emphasizeTests = []struct { - in string - out string + in, out string }{ {"http://www.google.com/", `<a href="http://www.google.com/">http://www.google.com/</a>`}, {"https://www.google.com/", `<a href="https://www.google.com/">https://www.google.com/</a>`}, {"http://www.google.com/path.", `<a href="http://www.google.com/path">http://www.google.com/path</a>.`}, + {"http://en.wikipedia.org/wiki/Camellia_(cipher)", `<a href="http://en.wikipedia.org/wiki/Camellia_(cipher)">http://en.wikipedia.org/wiki/Camellia_(cipher)</a>`}, {"(http://www.google.com/)", `(<a href="http://www.google.com/">http://www.google.com/</a>)`}, + {"http://gmail.com)", `<a href="http://gmail.com">http://gmail.com</a>)`}, + {"((http://gmail.com))", `((<a href="http://gmail.com">http://gmail.com</a>))`}, + {"http://gmail.com ((http://gmail.com)) ()", `<a href="http://gmail.com">http://gmail.com</a> ((<a href="http://gmail.com">http://gmail.com</a>)) ()`}, {"Foo bar http://example.com/ quux!", `Foo bar <a href="http://example.com/">http://example.com/</a> quux!`}, {"Hello http://example.com/%2f/ /world.", `Hello <a href="http://example.com/%2f/">http://example.com/%2f/</a> /world.`}, {"Lorem http: ipsum //host/path", "Lorem http: ipsum //host/path"}, @@ -107,3 +174,34 @@ func TestEmphasize(t *testing.T) { } } } + +var pairedParensPrefixLenTests = []struct { + in, out string +}{ + {"", ""}, + {"foo", "foo"}, + {"()", "()"}, + {"foo()", "foo()"}, + {"foo()()()", "foo()()()"}, + {"foo()((()()))", "foo()((()()))"}, + {"foo()((()()))bar", "foo()((()()))bar"}, + {"foo)", "foo"}, + {"foo))", "foo"}, + {"foo)))))", "foo"}, + {"(foo", ""}, + {"((foo", ""}, + {"(((((foo", ""}, + {"(foo)", "(foo)"}, + {"((((foo))))", "((((foo))))"}, + {"foo()())", "foo()()"}, + {"foo((()())", "foo"}, + {"foo((()())) (() foo ", "foo((()())) "}, +} + +func TestPairedParensPrefixLen(t *testing.T) { + for i, tt := range pairedParensPrefixLenTests { + if out := tt.in[:pairedParensPrefixLen(tt.in)]; out != tt.out { + t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out) + } + } +} diff --git a/src/pkg/go/doc/example.go b/src/pkg/go/doc/example.go index 2358ed389..c414e548c 100644 --- a/src/pkg/go/doc/example.go +++ b/src/pkg/go/doc/example.go @@ -32,6 +32,17 @@ type Example struct { // Examples returns the examples found in the files, sorted by Name field. // The Order fields record the order in which the examples were encountered. +// +// Playable Examples must be in a package whose name ends in "_test". +// An Example is "playable" (the Play field is non-nil) in either of these +// circumstances: +// - The example function is self-contained: the function references only +// identifiers from other packages (or predeclared identifiers, such as +// "int") and the test file does not include a dot import. +// - The entire test file is the example: the file contains exactly one +// example function, zero test or benchmark functions, and at least one +// top-level function, type, variable, or constant declaration other +// than the example function. func Examples(files ...*ast.File) []*Example { var list []*Example for _, file := range files { @@ -244,7 +255,7 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { } } - // Strip "Output:" commment and adjust body end position. + // Strip "Output:" comment and adjust body end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. @@ -307,7 +318,7 @@ func playExampleFile(file *ast.File) *ast.File { return &f } -// stripOutputComment finds and removes an "Output:" commment from body +// stripOutputComment finds and removes an "Output:" comment from body // and comments, and adjusts the body block's end position. func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) { // Do nothing if no "Output:" comment found. |