Skip to content

Commit ae3d890

Browse files
committed
go/doc/comment: parse and print identifiers, automatic links
[This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement parsing and printing of unmarked identifiers and automatic URL links in plain text. For #51082. Change-Id: Ib83ad482937501a6fc14fa788eab289533a68e3a Reviewed-on: https://go-review.googlesource.com/c/go/+/397280 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 6130b88 commit ae3d890

File tree

11 files changed

+185
-7
lines changed

11 files changed

+185
-7
lines changed

src/go/doc/comment/html.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) {
4444
switch t := t.(type) {
4545
case Plain:
4646
p.escape(out, string(t))
47+
case Italic:
48+
out.WriteString("<i>")
49+
p.escape(out, string(t))
50+
out.WriteString("</i>")
51+
case *Link:
52+
out.WriteString(`<a href="`)
53+
p.escape(out, t.URL)
54+
out.WriteString(`">`)
55+
p.text(out, t.Text)
56+
out.WriteString("</a>")
4757
}
4858
}
4959
}

src/go/doc/comment/markdown.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) {
7777
switch t := t.(type) {
7878
case Plain:
7979
p.escape(out, string(t))
80+
case Italic:
81+
out.WriteString("*")
82+
p.escape(out, string(t))
83+
out.WriteString("*")
84+
case *Link:
85+
out.WriteString("[")
86+
p.rawText(out, t.Text)
87+
out.WriteString("](")
88+
out.WriteString(t.URL)
89+
out.WriteString(")")
8090
}
8191
}
8292
}

src/go/doc/comment/parse.go

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,12 @@ func (p *Parser) Parse(text string) *Doc {
261261
}
262262

263263
// Second pass: interpret all the Plain text now that we know the links.
264-
// TODO: Actually interpret the plain text.
264+
for _, b := range d.Content {
265+
switch b := b.(type) {
266+
case *Paragraph:
267+
b.Text = d.parseText(string(b.Text[0].(Plain)))
268+
}
269+
}
265270

266271
return d.Doc
267272
}
@@ -401,6 +406,74 @@ func (d *parseDoc) paragraph(lines []string) (b Block, rest []string) {
401406
return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}, rest
402407
}
403408

409+
// parseText parses s as text and returns the parsed Text elements.
410+
func (d *parseDoc) parseText(s string) []Text {
411+
var out []Text
412+
var w strings.Builder
413+
wrote := 0
414+
writeUntil := func(i int) {
415+
w.WriteString(s[wrote:i])
416+
wrote = i
417+
}
418+
flush := func(i int) {
419+
writeUntil(i)
420+
if w.Len() > 0 {
421+
out = append(out, Plain(w.String()))
422+
w.Reset()
423+
}
424+
}
425+
for i := 0; i < len(s); {
426+
t := s[i:]
427+
const autoLink = true
428+
if autoLink {
429+
if url, ok := autoURL(t); ok {
430+
flush(i)
431+
// Note: The old comment parser would look up the URL in words
432+
// and replace the target with words[URL] if it was non-empty.
433+
// That would allow creating links that display as one URL but
434+
// when clicked go to a different URL. Not sure what the point
435+
// of that is, so we're not doing that lookup here.
436+
out = append(out, &Link{Auto: true, Text: []Text{Plain(url)}, URL: url})
437+
i += len(url)
438+
wrote = i
439+
continue
440+
}
441+
if id, ok := ident(t); ok {
442+
url, italics := d.Words[id]
443+
if !italics {
444+
i += len(id)
445+
continue
446+
}
447+
flush(i)
448+
if url == "" {
449+
out = append(out, Italic(id))
450+
} else {
451+
out = append(out, &Link{Auto: true, Text: []Text{Italic(id)}, URL: url})
452+
}
453+
i += len(id)
454+
wrote = i
455+
continue
456+
}
457+
}
458+
switch {
459+
case strings.HasPrefix(t, "``"):
460+
writeUntil(i)
461+
w.WriteRune('“')
462+
i += 2
463+
wrote = i
464+
case strings.HasPrefix(t, "''"):
465+
writeUntil(i)
466+
w.WriteRune('”')
467+
i += 2
468+
wrote = i
469+
default:
470+
i++
471+
}
472+
}
473+
flush(len(s))
474+
return out
475+
}
476+
404477
// autoURL checks whether s begins with a URL that should be hyperlinked.
405478
// If so, it returns the URL, which is a prefix of s, and ok == true.
406479
// Otherwise it returns "", false.

src/go/doc/comment/print.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) {
103103
switch t := t.(type) {
104104
case Plain:
105105
p.indent(out, indent, string(t))
106+
case Italic:
107+
p.indent(out, indent, string(t))
108+
case *Link:
109+
p.text(out, indent, t.Text)
106110
}
107111
}
108112
}

src/go/doc/comment/testdata/hello.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ Hello, world
3030

3131
This is a test.
3232
-- text --
33-
Hello,
34-
world
33+
Hello, world
3534

36-
This is
37-
a test.
35+
This is a test.

src/go/doc/comment/testdata/link.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- input --
2+
The Go home page is https://go.dev/.
3+
It used to be https://golang.org.
4+
5+
-- gofmt --
6+
The Go home page is https://go.dev/.
7+
It used to be https://golang.org.
8+
9+
-- text --
10+
The Go home page is https://go.dev/. It used to be https://golang.org.
11+
12+
-- markdown --
13+
The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org).
14+
15+
-- html --
16+
<p>The Go home page is <a href="https://go.dev/">https://go.dev/</a>.
17+
It used to be <a href="https://golang.org">https://golang.org</a>.

src/go/doc/comment/testdata/para.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- input --
2+
Hello, world.
3+
This is a paragraph.
4+
5+
-- gofmt --
6+
Hello, world.
7+
This is a paragraph.
8+
9+
-- text --
10+
Hello, world. This is a paragraph.
11+
12+
-- markdown --
13+
Hello, world. This is a paragraph.
14+
15+
-- html --
16+
<p>Hello, world.
17+
This is a paragraph.

src/go/doc/comment/testdata/text2.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{"TextWidth": -1}
2+
-- input --
3+
Package gob manages streams of gobs - binary values exchanged between an
4+
Encoder (transmitter) and a Decoder (receiver). A typical use is
5+
transporting arguments and results of remote procedure calls (RPCs) such as
6+
those provided by package "net/rpc".
7+
8+
The implementation compiles a custom codec for each data type in the stream
9+
and is most efficient when a single Encoder is used to transmit a stream of
10+
values, amortizing the cost of compilation.
11+
-- text --
12+
Package gob manages streams of gobs - binary values exchanged between an Encoder (transmitter) and a Decoder (receiver). A typical use is transporting arguments and results of remote procedure calls (RPCs) such as those provided by package "net/rpc".
13+
14+
The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation.

src/go/doc/comment/testdata/words.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- input --
2+
This is an italicword and a linkedword and Unicöde.
3+
-- gofmt --
4+
This is an italicword and a linkedword and Unicöde.
5+
-- text --
6+
This is an italicword and a linkedword and Unicöde.
7+
-- markdown --
8+
This is an *italicword* and a [*linkedword*](https://example.com/linkedword) and Unicöde.
9+
-- html --
10+
<p>This is an <i>italicword</i> and a <a href="https://example.com/linkedword"><i>linkedword</i></a> and Unicöde.

src/go/doc/comment/testdata_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package comment
66

77
import (
88
"bytes"
9+
"encoding/json"
910
"fmt"
1011
"internal/diff"
1112
"internal/txtar"
@@ -20,6 +21,10 @@ func TestTestdata(t *testing.T) {
2021
t.Fatalf("no testdata")
2122
}
2223
var p Parser
24+
p.Words = map[string]string{
25+
"italicword": "",
26+
"linkedword": "https://example.com/linkedword",
27+
}
2328

2429
stripDollars := func(b []byte) []byte {
2530
// Remove trailing $ on lines.
@@ -30,10 +35,17 @@ func TestTestdata(t *testing.T) {
3035
}
3136
for _, file := range files {
3237
t.Run(filepath.Base(file), func(t *testing.T) {
38+
var pr Printer
3339
a, err := txtar.ParseFile(file)
3440
if err != nil {
3541
t.Fatal(err)
3642
}
43+
if len(a.Comment) > 0 {
44+
err := json.Unmarshal(a.Comment, &pr)
45+
if err != nil {
46+
t.Fatalf("unmarshalling top json: %v", err)
47+
}
48+
}
3749
if len(a.Files) < 1 || a.Files[0].Name != "input" {
3850
t.Fatalf("first file is not %q", "input")
3951
}
@@ -44,7 +56,6 @@ func TestTestdata(t *testing.T) {
4456
want = want[:len(want)-1]
4557
}
4658
var out []byte
47-
var pr Printer
4859
switch f.Name {
4960
default:
5061
t.Fatalf("unknown output file %q", f.Name)

src/go/doc/comment/text.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ package comment
77
import (
88
"bytes"
99
"fmt"
10+
"strings"
1011
)
1112

1213
// A textPrinter holds the state needed for printing a Doc as plain text.
1314
type textPrinter struct {
1415
*Printer
16+
long bytes.Buffer
1517
}
1618

1719
// Text returns a textual formatting of the Doc.
@@ -59,11 +61,23 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) {
5961
// text prints the text sequence x to out.
6062
// TODO: Wrap lines.
6163
func (p *textPrinter) text(out *bytes.Buffer, x []Text) {
64+
p.oneLongLine(&p.long, x)
65+
out.WriteString(strings.ReplaceAll(p.long.String(), "\n", " "))
66+
p.long.Reset()
67+
writeNL(out)
68+
}
69+
70+
// oneLongLine prints the text sequence x to out as one long line,
71+
// without worrying about line wrapping.
72+
func (p *textPrinter) oneLongLine(out *bytes.Buffer, x []Text) {
6273
for _, t := range x {
6374
switch t := t.(type) {
6475
case Plain:
6576
out.WriteString(string(t))
77+
case Italic:
78+
out.WriteString(string(t))
79+
case *Link:
80+
p.oneLongLine(out, t.Text)
6681
}
6782
}
68-
writeNL(out)
6983
}

0 commit comments

Comments
 (0)