mirror of
https://github.com/golang/go
synced 2024-11-11 23:50:22 -07:00
godoc: text wrapping
Example: PACKAGE package utf8 import "unicode/utf8" Package utf8 implements functions and constants to support text encoded in UTF-8. This package calls a Unicode character a rune for brevity. CONSTANTS const ( RuneError = unicode.ReplacementChar // the "error" Rune or "replacement character". RuneSelf = 0x80 // characters below Runeself are represented as themselves in a single byte. UTFMax = 4 // maximum number of bytes of a UTF-8 encoded Unicode character. ) Numbers fundamental to the encoding. FUNCTIONS func DecodeLastRune(p []byte) (r rune, size int) DecodeLastRune unpacks the last UTF-8 encoding in p and returns the rune and its width in bytes. func DecodeLastRuneInString(s string) (r rune, size int) DecodeLastRuneInString is like DecodeLastRune but its input is a string. func DecodeRune(p []byte) (r rune, size int) DecodeRune unpacks the first UTF-8 encoding in p and returns the rune and its width in bytes. func DecodeRuneInString(s string) (r rune, size int) DecodeRuneInString is like DecodeRune but its input is a string. func EncodeRune(p []byte, r rune) int EncodeRune writes into p (which must be large enough) the UTF-8 encoding of the rune. It returns the number of bytes written. func FullRune(p []byte) bool FullRune reports whether the bytes in p begin with a full UTF-8 encoding of a rune. An invalid encoding is considered a full Rune since it will convert as a width-1 error rune. func FullRuneInString(s string) bool FullRuneInString is like FullRune but its input is a string. func RuneCount(p []byte) int RuneCount returns the number of runes in p. Erroneous and short encodings are treated as single runes of width 1 byte. func RuneCountInString(s string) (n int) RuneCountInString is like RuneCount but its input is a string. func RuneLen(r rune) int RuneLen returns the number of bytes required to encode the rune. func RuneStart(b byte) bool RuneStart reports whether the byte could be the first byte of an encoded rune. Second and subsequent bytes always have the top two bits set to 10. func Valid(p []byte) bool Valid reports whether p consists entirely of valid UTF-8-encoded runes. func ValidString(s string) bool ValidString reports whether s consists entirely of valid UTF-8-encoded runes. TYPES type String struct { // contains filtered or unexported fields } String wraps a regular string with a small structure that provides more efficient indexing by code point index, as opposed to byte index. Scanning incrementally forwards or backwards is O(1) per index operation (although not as fast a range clause going forwards). Random access is O(N) in the length of the string, but the overhead is less than always scanning from the beginning. If the string is ASCII, random access is O(1). Unlike the built-in string type, String has internal mutable state and is not thread-safe. func NewString(contents string) *String NewString returns a new UTF-8 string with the provided contents. func (s *String) At(i int) rune At returns the rune with index i in the String. The sequence of runes is the same as iterating over the contents with a "for range" clause. func (s *String) Init(contents string) *String Init initializes an existing String to hold the provided contents. It returns a pointer to the initialized String. func (s *String) IsASCII() bool IsASCII returns a boolean indicating whether the String contains only ASCII bytes. func (s *String) RuneCount() int RuneCount returns the number of runes (Unicode code points) in the String. func (s *String) Slice(i, j int) string Slice returns the string sliced at rune positions [i:j]. func (s *String) String() string String returns the contents of the String. This method also means the String is directly printable by fmt.Print. Fixes #2479. R=golang-dev, dsymonds, mattn.jp, r, gri, r CC=golang-dev https://golang.org/cl/5472051
This commit is contained in:
parent
61655bc513
commit
6bf84214c1
@ -5,11 +5,11 @@
|
||||
*/}}{{with .PDoc}}{{if $.IsPkg}}PACKAGE
|
||||
|
||||
package {{.PackageName}}
|
||||
import "{{.ImportPath}}"
|
||||
import "{{.ImportPath}}"
|
||||
|
||||
{{else}}COMMAND DOCUMENTATION
|
||||
|
||||
{{end}}{{.Doc}}{{/*
|
||||
{{end}}{{comment_text .Doc " " "\t"}}{{/*
|
||||
|
||||
---------------------------------------
|
||||
|
||||
@ -17,7 +17,7 @@ import "{{.ImportPath}}"
|
||||
CONSTANTS
|
||||
|
||||
{{range .}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}{{end}}
|
||||
{{comment_text .Doc " " "\t"}}{{end}}
|
||||
{{end}}{{/*
|
||||
|
||||
---------------------------------------
|
||||
@ -26,7 +26,7 @@ CONSTANTS
|
||||
VARIABLES
|
||||
|
||||
{{range .}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}{{end}}
|
||||
{{comment_text .Doc " " "\t"}}{{end}}
|
||||
{{end}}{{/*
|
||||
|
||||
---------------------------------------
|
||||
@ -35,7 +35,7 @@ VARIABLES
|
||||
FUNCTIONS
|
||||
|
||||
{{range .}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}
|
||||
{{comment_text .Doc " " "\t"}}
|
||||
{{end}}{{end}}{{/*
|
||||
|
||||
---------------------------------------
|
||||
@ -44,15 +44,15 @@ FUNCTIONS
|
||||
TYPES
|
||||
|
||||
{{range .}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}
|
||||
{{comment_text .Doc " " "\t"}}
|
||||
{{range .Consts}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}
|
||||
{{comment_text .Doc " " "\t"}}
|
||||
{{end}}{{range .Vars}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}
|
||||
{{comment_text .Doc " " "\t"}}
|
||||
{{end}}{{range .Factories}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}
|
||||
{{comment_text .Doc " " "\t"}}
|
||||
{{end}}{{range .Methods}}{{node .Decl $.FSet}}
|
||||
{{.Doc}}
|
||||
{{comment_text .Doc " " "\t"}}
|
||||
{{end}}{{end}}{{end}}{{/*
|
||||
|
||||
---------------------------------------
|
||||
@ -60,7 +60,7 @@ TYPES
|
||||
*/}}{{with .Bugs}}
|
||||
BUGS
|
||||
|
||||
{{range .}}{{.}}
|
||||
{{range .}}{{comment_text . " " "\t"}}
|
||||
{{end}}{{end}}{{end}}{{/*
|
||||
|
||||
---------------------------------------
|
||||
|
@ -461,6 +461,27 @@ func comment_htmlFunc(comment string) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// punchCardWidth is the number of columns of fixed-width
|
||||
// characters to assume when wrapping text. Very few people
|
||||
// use terminals or cards smaller than 80 characters, so 80 it is.
|
||||
// We do not try to sniff the environment or the tty to adapt to
|
||||
// the situation; instead, by using a constant we make sure that
|
||||
// godoc always produces the same output regardless of context,
|
||||
// a consistency that is lost otherwise. For example, if we sniffed
|
||||
// the environment or tty, then http://golang.org/pkg/math/?m=text
|
||||
// would depend on the width of the terminal where godoc started,
|
||||
// which is clearly bogus. More generally, the Unix tools that behave
|
||||
// differently when writing to a tty than when writing to a file have
|
||||
// a history of causing confusion (compare `ls` and `ls | cat`), and we
|
||||
// want to avoid that mistake here.
|
||||
const punchCardWidth = 80
|
||||
|
||||
func comment_textFunc(comment, indent, preIndent string) string {
|
||||
var buf bytes.Buffer
|
||||
doc.ToText(&buf, comment, indent, preIndent, punchCardWidth-2*len(indent))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string {
|
||||
var buf bytes.Buffer
|
||||
for _, eg := range examples {
|
||||
@ -556,6 +577,7 @@ var fmap = template.FuncMap{
|
||||
"node": nodeFunc,
|
||||
"node_html": node_htmlFunc,
|
||||
"comment_html": comment_htmlFunc,
|
||||
"comment_text": comment_textFunc,
|
||||
|
||||
// support for URL attributes
|
||||
"pkgLink": pkgLinkFunc,
|
||||
|
@ -281,7 +281,20 @@ func heading(line string) string {
|
||||
return line
|
||||
}
|
||||
|
||||
// Convert comment text to formatted HTML.
|
||||
type op int
|
||||
|
||||
const (
|
||||
opPara op = iota
|
||||
opHead
|
||||
opPre
|
||||
)
|
||||
|
||||
type block struct {
|
||||
op op
|
||||
lines []string
|
||||
}
|
||||
|
||||
// ToHTML converts comment text to formatted HTML.
|
||||
// The comment was prepared by DocReader,
|
||||
// so it is known not to have leading, trailing blank lines
|
||||
// nor to have trailing spaces at the end of lines.
|
||||
@ -299,20 +312,43 @@ func heading(line string) string {
|
||||
// map value is not the empty string, it is considered a URL and the word is converted
|
||||
// into a link.
|
||||
func ToHTML(w io.Writer, text string, words map[string]string) {
|
||||
inpara := false
|
||||
lastWasBlank := false
|
||||
lastWasHeading := false
|
||||
|
||||
close := func() {
|
||||
if inpara {
|
||||
for _, b := range blocks(text) {
|
||||
switch b.op {
|
||||
case opPara:
|
||||
w.Write(html_p)
|
||||
for _, line := range b.lines {
|
||||
emphasize(w, line, words, true)
|
||||
}
|
||||
w.Write(html_endp)
|
||||
inpara = false
|
||||
case opHead:
|
||||
w.Write(html_h)
|
||||
for _, line := range b.lines {
|
||||
commentEscape(w, line, true)
|
||||
}
|
||||
w.Write(html_endh)
|
||||
case opPre:
|
||||
w.Write(html_pre)
|
||||
for _, line := range b.lines {
|
||||
emphasize(w, line, nil, false)
|
||||
}
|
||||
w.Write(html_endpre)
|
||||
}
|
||||
}
|
||||
open := func() {
|
||||
if !inpara {
|
||||
w.Write(html_p)
|
||||
inpara = true
|
||||
}
|
||||
|
||||
func blocks(text string) []block {
|
||||
var (
|
||||
out []block
|
||||
para []string
|
||||
|
||||
lastWasBlank = false
|
||||
lastWasHeading = false
|
||||
)
|
||||
|
||||
close := func() {
|
||||
if para != nil {
|
||||
out = append(out, block{opPara, para})
|
||||
para = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,17 +376,13 @@ func ToHTML(w io.Writer, text string, words map[string]string) {
|
||||
for j > i && isBlank(lines[j-1]) {
|
||||
j--
|
||||
}
|
||||
block := lines[i:j]
|
||||
pre := lines[i:j]
|
||||
i = j
|
||||
|
||||
unindent(block)
|
||||
unindent(pre)
|
||||
|
||||
// put those lines in a pre block
|
||||
w.Write(html_pre)
|
||||
for _, line := range block {
|
||||
emphasize(w, line, nil, false) // no nice text formatting
|
||||
}
|
||||
w.Write(html_endpre)
|
||||
out = append(out, block{opPre, pre})
|
||||
lastWasHeading = false
|
||||
continue
|
||||
}
|
||||
@ -362,9 +394,7 @@ func ToHTML(w io.Writer, text string, words map[string]string) {
|
||||
// might be a heading.
|
||||
if head := heading(line); head != "" {
|
||||
close()
|
||||
w.Write(html_h)
|
||||
commentEscape(w, head, true) // nice text formatting
|
||||
w.Write(html_endh)
|
||||
out = append(out, block{opHead, []string{head}})
|
||||
i += 2
|
||||
lastWasHeading = true
|
||||
continue
|
||||
@ -372,11 +402,95 @@ func ToHTML(w io.Writer, text string, words map[string]string) {
|
||||
}
|
||||
|
||||
// open paragraph
|
||||
open()
|
||||
lastWasBlank = false
|
||||
lastWasHeading = false
|
||||
emphasize(w, lines[i], words, true) // nice text formatting
|
||||
para = append(para, lines[i])
|
||||
i++
|
||||
}
|
||||
close()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ToText prepares comment text for presentation in textual output.
|
||||
// It wraps paragraphs of text to width or fewer Unicode code points
|
||||
// and then prefixes each line with the indent. In preformatted sections
|
||||
// (such as program text), it prefixes each non-blank line with preIndent.
|
||||
func ToText(w io.Writer, text string, indent, preIndent string, width int) {
|
||||
l := lineWrapper{
|
||||
out: w,
|
||||
width: width,
|
||||
indent: indent,
|
||||
}
|
||||
for i, b := range blocks(text) {
|
||||
switch b.op {
|
||||
case opPara:
|
||||
if i > 0 {
|
||||
w.Write(nl)
|
||||
}
|
||||
for _, line := range b.lines {
|
||||
l.write(line)
|
||||
}
|
||||
l.flush()
|
||||
case opHead:
|
||||
w.Write(nl)
|
||||
for _, line := range b.lines {
|
||||
l.write(line + "\n")
|
||||
}
|
||||
l.flush()
|
||||
case opPre:
|
||||
w.Write(nl)
|
||||
for _, line := range b.lines {
|
||||
if !isBlank(line) {
|
||||
w.Write([]byte(preIndent))
|
||||
w.Write([]byte(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type lineWrapper struct {
|
||||
out io.Writer
|
||||
printed bool
|
||||
width int
|
||||
indent string
|
||||
n int
|
||||
pendSpace int
|
||||
}
|
||||
|
||||
var nl = []byte("\n")
|
||||
var space = []byte(" ")
|
||||
|
||||
func (l *lineWrapper) write(text string) {
|
||||
if l.n == 0 && l.printed {
|
||||
l.out.Write(nl) // blank line before new paragraph
|
||||
}
|
||||
l.printed = true
|
||||
|
||||
for _, f := range strings.Fields(text) {
|
||||
w := utf8.RuneCountInString(f)
|
||||
// wrap if line is too long
|
||||
if l.n > 0 && l.n+l.pendSpace+w > l.width {
|
||||
l.out.Write(nl)
|
||||
l.n = 0
|
||||
l.pendSpace = 0
|
||||
}
|
||||
if l.n == 0 {
|
||||
l.out.Write([]byte(l.indent))
|
||||
}
|
||||
l.out.Write(space[:l.pendSpace])
|
||||
l.out.Write([]byte(f))
|
||||
l.n += l.pendSpace + w
|
||||
l.pendSpace = 1
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lineWrapper) flush() {
|
||||
if l.n == 0 {
|
||||
return
|
||||
}
|
||||
l.out.Write(nl)
|
||||
l.pendSpace = 0
|
||||
l.n = 0
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package doc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -38,3 +39,45 @@ func TestIsHeading(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var blocksTests = []struct {
|
||||
in string
|
||||
out []block
|
||||
}{
|
||||
{
|
||||
in: `Para 1.
|
||||
Para 1 line 2.
|
||||
|
||||
Para 2.
|
||||
|
||||
Section
|
||||
|
||||
Para 3.
|
||||
|
||||
pre
|
||||
pre1
|
||||
|
||||
Para 4.
|
||||
pre
|
||||
pre2
|
||||
`,
|
||||
out: []block{
|
||||
{opPara, []string{"Para 1.\n", "Para 1 line 2.\n"}},
|
||||
{opPara, []string{"Para 2.\n"}},
|
||||
{opHead, []string{"Section"}},
|
||||
{opPara, []string{"Para 3.\n"}},
|
||||
{opPre, []string{"pre\n", "pre1\n"}},
|
||||
{opPara, []string{"Para 4.\n"}},
|
||||
{opPre, []string{"pre\n", "pre2\n"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestBlocks(t *testing.T) {
|
||||
for i, tt := range blocksTests {
|
||||
b := blocks(tt.in)
|
||||
if !reflect.DeepEqual(b, tt.out) {
|
||||
t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, b, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user