-
Notifications
You must be signed in to change notification settings - Fork 17.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
go/doc/comment: add Printer and basic comment printing
[This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement printing of plain text doc paragraphs. For #51082. Change-Id: Ieff0af64a900f566bfc833c3b5706488f1444798 Reviewed-on: https://go-review.googlesource.com/c/go/+/397279 Run-TryBot: Russ Cox <rsc@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org>
- Loading branch information
Showing
9 changed files
with
500 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Copyright 2022 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 comment | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
) | ||
|
||
// An htmlPrinter holds the state needed for printing a Doc as HTML. | ||
type htmlPrinter struct { | ||
*Printer | ||
} | ||
|
||
// HTML returns an HTML formatting of the Doc. | ||
// See the [Printer] documentation for ways to customize the HTML output. | ||
func (p *Printer) HTML(d *Doc) []byte { | ||
hp := &htmlPrinter{Printer: p} | ||
var out bytes.Buffer | ||
for _, x := range d.Content { | ||
hp.block(&out, x) | ||
} | ||
return out.Bytes() | ||
} | ||
|
||
// block prints the block x to out. | ||
func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { | ||
switch x := x.(type) { | ||
default: | ||
fmt.Fprintf(out, "?%T", x) | ||
|
||
case *Paragraph: | ||
out.WriteString("<p>") | ||
p.text(out, x.Text) | ||
out.WriteString("\n") | ||
} | ||
} | ||
|
||
// text prints the text sequence x to out. | ||
func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { | ||
for _, t := range x { | ||
switch t := t.(type) { | ||
case Plain: | ||
p.escape(out, string(t)) | ||
} | ||
} | ||
} | ||
|
||
// escape prints s to out as plain text, | ||
// escaping < & " ' and > to avoid being misinterpreted | ||
// in larger HTML constructs. | ||
func (p *htmlPrinter) escape(out *bytes.Buffer, s string) { | ||
start := 0 | ||
for i := 0; i < len(s); i++ { | ||
switch s[i] { | ||
case '<': | ||
out.WriteString(s[start:i]) | ||
out.WriteString("<") | ||
start = i + 1 | ||
case '&': | ||
out.WriteString(s[start:i]) | ||
out.WriteString("&") | ||
start = i + 1 | ||
case '"': | ||
out.WriteString(s[start:i]) | ||
out.WriteString(""") | ||
start = i + 1 | ||
case '\'': | ||
out.WriteString(s[start:i]) | ||
out.WriteString("'") | ||
start = i + 1 | ||
case '>': | ||
out.WriteString(s[start:i]) | ||
out.WriteString(">") | ||
start = i + 1 | ||
} | ||
} | ||
out.WriteString(s[start:]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright 2022 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 comment | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
) | ||
|
||
// An mdPrinter holds the state needed for printing a Doc as Markdown. | ||
type mdPrinter struct { | ||
*Printer | ||
raw bytes.Buffer | ||
} | ||
|
||
// Markdown returns a Markdown formatting of the Doc. | ||
// See the [Printer] documentation for ways to customize the Markdown output. | ||
func (p *Printer) Markdown(d *Doc) []byte { | ||
mp := &mdPrinter{Printer: p} | ||
|
||
var out bytes.Buffer | ||
for i, x := range d.Content { | ||
if i > 0 { | ||
out.WriteByte('\n') | ||
} | ||
mp.block(&out, x) | ||
} | ||
return out.Bytes() | ||
} | ||
|
||
// block prints the block x to out. | ||
func (p *mdPrinter) block(out *bytes.Buffer, x Block) { | ||
switch x := x.(type) { | ||
default: | ||
fmt.Fprintf(out, "?%T", x) | ||
|
||
case *Paragraph: | ||
p.text(out, x.Text) | ||
out.WriteString("\n") | ||
} | ||
} | ||
|
||
// text prints the text sequence x to out. | ||
func (p *mdPrinter) text(out *bytes.Buffer, x []Text) { | ||
p.raw.Reset() | ||
p.rawText(&p.raw, x) | ||
line := bytes.TrimSpace(p.raw.Bytes()) | ||
if len(line) == 0 { | ||
return | ||
} | ||
switch line[0] { | ||
case '+', '-', '*', '#': | ||
// Escape what would be the start of an unordered list or heading. | ||
out.WriteByte('\\') | ||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | ||
i := 1 | ||
for i < len(line) && '0' <= line[i] && line[i] <= '9' { | ||
i++ | ||
} | ||
if i < len(line) && (line[i] == '.' || line[i] == ')') { | ||
// Escape what would be the start of an ordered list. | ||
out.Write(line[:i]) | ||
out.WriteByte('\\') | ||
line = line[i:] | ||
} | ||
} | ||
out.Write(line) | ||
} | ||
|
||
// rawText prints the text sequence x to out, | ||
// without worrying about escaping characters | ||
// that have special meaning at the start of a Markdown line. | ||
func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) { | ||
for _, t := range x { | ||
switch t := t.(type) { | ||
case Plain: | ||
p.escape(out, string(t)) | ||
} | ||
} | ||
} | ||
|
||
// escape prints s to out as plain text, | ||
// escaping special characters to avoid being misinterpreted | ||
// as Markdown markup sequences. | ||
func (p *mdPrinter) escape(out *bytes.Buffer, s string) { | ||
start := 0 | ||
for i := 0; i < len(s); i++ { | ||
switch s[i] { | ||
case '\n': | ||
// Turn all \n into spaces, for a few reasons: | ||
// - Avoid introducing paragraph breaks accidentally. | ||
// - Avoid the need to reindent after the newline. | ||
// - Avoid problems with Markdown renderers treating | ||
// every mid-paragraph newline as a <br>. | ||
out.WriteString(s[start:i]) | ||
out.WriteByte(' ') | ||
start = i + 1 | ||
continue | ||
case '`', '_', '*', '[', '<', '\\': | ||
// Not all of these need to be escaped all the time, | ||
// but is valid and easy to do so. | ||
// We assume the Markdown is being passed to a | ||
// Markdown renderer, not edited by a person, | ||
// so it's fine to have escapes that are not strictly | ||
// necessary in some cases. | ||
out.WriteString(s[start:i]) | ||
out.WriteByte('\\') | ||
out.WriteByte(s[i]) | ||
start = i + 1 | ||
} | ||
} | ||
out.WriteString(s[start:]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright 2022 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 comment | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// A Printer is a doc comment printer. | ||
// The fields in the struct can be filled in before calling | ||
// any of the printing methods | ||
// in order to customize the details of the printing process. | ||
type Printer struct { | ||
// HeadingLevel is the nesting level used for | ||
// HTML and Markdown headings. | ||
// If HeadingLevel is zero, it defaults to level 3, | ||
// meaning to use <h3> and ###. | ||
HeadingLevel int | ||
|
||
// HeadingID is a function that computes the heading ID | ||
// (anchor tag) to use for the heading h when generating | ||
// HTML and Markdown. If HeadingID returns an empty string, | ||
// then the heading ID is omitted. | ||
// If HeadingID is nil, h.DefaultID is used. | ||
HeadingID func(h *Heading) string | ||
|
||
// DocLinkURL is a function that computes the URL for the given DocLink. | ||
// If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used. | ||
DocLinkURL func(link *DocLink) string | ||
|
||
// DocLinkBaseURL is used when DocLinkURL is nil, | ||
// passed to [DocLink.DefaultURL] to construct a DocLink's URL. | ||
// See that method's documentation for details. | ||
DocLinkBaseURL string | ||
|
||
// TextPrefix is a prefix to print at the start of every line | ||
// when generating text output using the Text method. | ||
TextPrefix string | ||
|
||
// TextCodePrefix is the prefix to print at the start of each | ||
// preformatted (code block) line when generating text output, | ||
// instead of (not in addition to) TextPrefix. | ||
// If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t". | ||
TextCodePrefix string | ||
|
||
// TextWidth is the maximum width text line to generate, | ||
// measured in Unicode code points, | ||
// excluding TextPrefix and the newline character. | ||
// If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix. | ||
// If TextWidth is negative, there is no limit. | ||
TextWidth int | ||
} | ||
|
||
type commentPrinter struct { | ||
*Printer | ||
headingPrefix string | ||
needDoc map[string]bool | ||
} | ||
|
||
// Comment returns the standard Go formatting of the Doc, | ||
// without any comment markers. | ||
func (p *Printer) Comment(d *Doc) []byte { | ||
cp := &commentPrinter{Printer: p} | ||
var out bytes.Buffer | ||
for i, x := range d.Content { | ||
if i > 0 && blankBefore(x) { | ||
out.WriteString("\n") | ||
} | ||
cp.block(&out, x) | ||
} | ||
|
||
return out.Bytes() | ||
} | ||
|
||
// blankBefore reports whether the block x requires a blank line before it. | ||
// All blocks do, except for Lists that return false from x.BlankBefore(). | ||
func blankBefore(x Block) bool { | ||
if x, ok := x.(*List); ok { | ||
return x.BlankBefore() | ||
} | ||
return true | ||
} | ||
|
||
// block prints the block x to out. | ||
func (p *commentPrinter) block(out *bytes.Buffer, x Block) { | ||
switch x := x.(type) { | ||
default: | ||
fmt.Fprintf(out, "?%T", x) | ||
|
||
case *Paragraph: | ||
p.text(out, "", x.Text) | ||
out.WriteString("\n") | ||
} | ||
} | ||
|
||
// text prints the text sequence x to out. | ||
func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { | ||
for _, t := range x { | ||
switch t := t.(type) { | ||
case Plain: | ||
p.indent(out, indent, string(t)) | ||
} | ||
} | ||
} | ||
|
||
// indent prints s to out, indenting with the indent string | ||
// after each newline in s. | ||
func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) { | ||
for s != "" { | ||
line, rest, ok := strings.Cut(s, "\n") | ||
out.WriteString(line) | ||
if ok { | ||
out.WriteString("\n") | ||
out.WriteString(indent) | ||
} | ||
s = rest | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
-- input -- | ||
$ | ||
Blank line at start and end. | ||
$ | ||
-- gofmt -- | ||
Blank line at start and end. | ||
-- text -- | ||
Blank line at start and end. | ||
-- markdown -- | ||
Blank line at start and end. | ||
-- html -- | ||
<p>Blank line at start and end. |
Oops, something went wrong.