Commit 0bc18811 authored by Russ Cox's avatar Russ Cox

fmt, log: stop using unicode

$ go list -f '{{.ImportPath}} {{.Deps}}' fmt log
fmt [errors io math os reflect runtime strconv sync sync/atomic syscall time unicode/utf8 unsafe]
log [errors fmt io math os reflect runtime strconv sync sync/atomic syscall time unicode/utf8 unsafe]

R=bradfitz, rogpeppe, r, r, rsc
CC=golang-dev
https://golang.org/cl/5753055
parent 8f61631c
// Copyright 2012 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 fmt
var IsSpace = isSpace
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"unicode"
) )
type ( type (
...@@ -828,3 +829,13 @@ func TestBadVerbRecursion(t *testing.T) { ...@@ -828,3 +829,13 @@ func TestBadVerbRecursion(t *testing.T) {
t.Error("fail with value") t.Error("fail with value")
} }
} }
func TestIsSpace(t *testing.T) {
// This tests the internal isSpace function.
// IsSpace = isSpace is defined in export_test.go.
for i := rune(0); i <= unicode.MaxRune; i++ {
if IsSpace(i) != unicode.IsSpace(i) {
t.Errorf("isSpace(%U) = %v, want %v", IsSpace(i), unicode.IsSpace(i))
}
}
}
...@@ -5,9 +5,7 @@ ...@@ -5,9 +5,7 @@
package fmt package fmt
import ( import (
"bytes"
"strconv" "strconv"
"unicode"
"unicode/utf8" "unicode/utf8"
) )
...@@ -36,10 +34,10 @@ func init() { ...@@ -36,10 +34,10 @@ func init() {
} }
// A fmt is the raw formatter used by Printf etc. // A fmt is the raw formatter used by Printf etc.
// It prints into a bytes.Buffer that must be set up externally. // It prints into a buffer that must be set up separately.
type fmt struct { type fmt struct {
intbuf [nByte]byte intbuf [nByte]byte
buf *bytes.Buffer buf *buffer
// width, precision // width, precision
wid int wid int
prec int prec int
...@@ -69,7 +67,7 @@ func (f *fmt) clearflags() { ...@@ -69,7 +67,7 @@ func (f *fmt) clearflags() {
f.zero = false f.zero = false
} }
func (f *fmt) init(buf *bytes.Buffer) { func (f *fmt) init(buf *buffer) {
f.buf = buf f.buf = buf
f.clearflags() f.clearflags()
} }
...@@ -247,7 +245,7 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) { ...@@ -247,7 +245,7 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) {
} }
// If we want a quoted char for %#U, move the data up to make room. // If we want a quoted char for %#U, move the data up to make room.
if f.unicode && f.uniQuote && a >= 0 && a <= unicode.MaxRune && unicode.IsPrint(rune(a)) { if f.unicode && f.uniQuote && a >= 0 && a <= utf8.MaxRune && strconv.IsPrint(rune(a)) {
runeWidth := utf8.RuneLen(rune(a)) runeWidth := utf8.RuneLen(rune(a))
width := 1 + 1 + runeWidth + 1 // space, quote, rune, quote width := 1 + 1 + runeWidth + 1 // space, quote, rune, quote
copy(buf[i-width:], buf[i:]) // guaranteed to have enough room. copy(buf[i-width:], buf[i:]) // guaranteed to have enough room.
...@@ -290,16 +288,15 @@ func (f *fmt) fmt_s(s string) { ...@@ -290,16 +288,15 @@ func (f *fmt) fmt_s(s string) {
// fmt_sx formats a string as a hexadecimal encoding of its bytes. // fmt_sx formats a string as a hexadecimal encoding of its bytes.
func (f *fmt) fmt_sx(s, digits string) { func (f *fmt) fmt_sx(s, digits string) {
// TODO: Avoid buffer by pre-padding. // TODO: Avoid buffer by pre-padding.
var b bytes.Buffer var b []byte
for i := 0; i < len(s); i++ { for i := 0; i < len(s); i++ {
if i > 0 && f.space { if i > 0 && f.space {
b.WriteByte(' ') b = append(b, ' ')
} }
v := s[i] v := s[i]
b.WriteByte(digits[v>>4]) b = append(b, digits[v>>4], digits[v&0xF])
b.WriteByte(digits[v&0xF])
} }
f.pad(b.Bytes()) f.pad(b)
} }
// fmt_q formats a string as a double-quoted, escaped Go string constant. // fmt_q formats a string as a double-quoted, escaped Go string constant.
......
...@@ -5,13 +5,11 @@ ...@@ -5,13 +5,11 @@
package fmt package fmt
import ( import (
"bytes"
"errors" "errors"
"io" "io"
"os" "os"
"reflect" "reflect"
"sync" "sync"
"unicode"
"unicode/utf8" "unicode/utf8"
) )
...@@ -71,11 +69,45 @@ type GoStringer interface { ...@@ -71,11 +69,45 @@ type GoStringer interface {
GoString() string GoString() string
} }
// Use simple []byte instead of bytes.Buffer to avoid large dependency.
type buffer []byte
func (b *buffer) Write(p []byte) (n int, err error) {
*b = append(*b, p...)
return len(p), nil
}
func (b *buffer) WriteString(s string) (n int, err error) {
*b = append(*b, s...)
return len(s), nil
}
func (b *buffer) WriteByte(c byte) error {
*b = append(*b, c)
return nil
}
func (bp *buffer) WriteRune(r rune) error {
if r < utf8.RuneSelf {
*bp = append(*bp, byte(r))
return nil
}
b := *bp
n := len(b)
for n+utf8.UTFMax > cap(b) {
b = append(b, 0)
}
w := utf8.EncodeRune(b[n:n+utf8.UTFMax], r)
*bp = b[:n+w]
return nil
}
type pp struct { type pp struct {
n int n int
panicking bool panicking bool
erroring bool // printing an error condition erroring bool // printing an error condition
buf bytes.Buffer buf buffer
// field holds the current item, as an interface{}. // field holds the current item, as an interface{}.
field interface{} field interface{}
// value holds the current item, as a reflect.Value, and will be // value holds the current item, as a reflect.Value, and will be
...@@ -133,10 +165,10 @@ func newPrinter() *pp { ...@@ -133,10 +165,10 @@ func newPrinter() *pp {
// Save used pp structs in ppFree; avoids an allocation per invocation. // Save used pp structs in ppFree; avoids an allocation per invocation.
func (p *pp) free() { func (p *pp) free() {
// Don't hold on to pp structs with large buffers. // Don't hold on to pp structs with large buffers.
if cap(p.buf.Bytes()) > 1024 { if cap(p.buf) > 1024 {
return return
} }
p.buf.Reset() p.buf = p.buf[:0]
p.field = nil p.field = nil
p.value = reflect.Value{} p.value = reflect.Value{}
ppFree.put(p) ppFree.put(p)
...@@ -179,7 +211,7 @@ func (p *pp) Write(b []byte) (ret int, err error) { ...@@ -179,7 +211,7 @@ func (p *pp) Write(b []byte) (ret int, err error) {
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter() p := newPrinter()
p.doPrintf(format, a) p.doPrintf(format, a)
n64, err := p.buf.WriteTo(w) n64, err := w.Write(p.buf)
p.free() p.free()
return int(n64), err return int(n64), err
} }
...@@ -194,7 +226,7 @@ func Printf(format string, a ...interface{}) (n int, err error) { ...@@ -194,7 +226,7 @@ func Printf(format string, a ...interface{}) (n int, err error) {
func Sprintf(format string, a ...interface{}) string { func Sprintf(format string, a ...interface{}) string {
p := newPrinter() p := newPrinter()
p.doPrintf(format, a) p.doPrintf(format, a)
s := p.buf.String() s := string(p.buf)
p.free() p.free()
return s return s
} }
...@@ -213,7 +245,7 @@ func Errorf(format string, a ...interface{}) error { ...@@ -213,7 +245,7 @@ func Errorf(format string, a ...interface{}) error {
func Fprint(w io.Writer, a ...interface{}) (n int, err error) { func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter() p := newPrinter()
p.doPrint(a, false, false) p.doPrint(a, false, false)
n64, err := p.buf.WriteTo(w) n64, err := w.Write(p.buf)
p.free() p.free()
return int(n64), err return int(n64), err
} }
...@@ -230,7 +262,7 @@ func Print(a ...interface{}) (n int, err error) { ...@@ -230,7 +262,7 @@ func Print(a ...interface{}) (n int, err error) {
func Sprint(a ...interface{}) string { func Sprint(a ...interface{}) string {
p := newPrinter() p := newPrinter()
p.doPrint(a, false, false) p.doPrint(a, false, false)
s := p.buf.String() s := string(p.buf)
p.free() p.free()
return s return s
} }
...@@ -245,7 +277,7 @@ func Sprint(a ...interface{}) string { ...@@ -245,7 +277,7 @@ func Sprint(a ...interface{}) string {
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter() p := newPrinter()
p.doPrint(a, true, true) p.doPrint(a, true, true)
n64, err := p.buf.WriteTo(w) n64, err := w.Write(p.buf)
p.free() p.free()
return int(n64), err return int(n64), err
} }
...@@ -262,7 +294,7 @@ func Println(a ...interface{}) (n int, err error) { ...@@ -262,7 +294,7 @@ func Println(a ...interface{}) (n int, err error) {
func Sprintln(a ...interface{}) string { func Sprintln(a ...interface{}) string {
p := newPrinter() p := newPrinter()
p.doPrint(a, true, true) p.doPrint(a, true, true)
s := p.buf.String() s := string(p.buf)
p.free() p.free()
return s return s
} }
...@@ -352,7 +384,7 @@ func (p *pp) fmtInt64(v int64, verb rune) { ...@@ -352,7 +384,7 @@ func (p *pp) fmtInt64(v int64, verb rune) {
case 'o': case 'o':
p.fmt.integer(v, 8, signed, ldigits) p.fmt.integer(v, 8, signed, ldigits)
case 'q': case 'q':
if 0 <= v && v <= unicode.MaxRune { if 0 <= v && v <= utf8.MaxRune {
p.fmt.fmt_qc(v) p.fmt.fmt_qc(v)
} else { } else {
p.badVerb(verb) p.badVerb(verb)
...@@ -416,7 +448,7 @@ func (p *pp) fmtUint64(v uint64, verb rune, goSyntax bool) { ...@@ -416,7 +448,7 @@ func (p *pp) fmtUint64(v uint64, verb rune, goSyntax bool) {
case 'o': case 'o':
p.fmt.integer(int64(v), 8, unsigned, ldigits) p.fmt.integer(int64(v), 8, unsigned, ldigits)
case 'q': case 'q':
if 0 <= v && v <= unicode.MaxRune { if 0 <= v && v <= utf8.MaxRune {
p.fmt.fmt_qc(int64(v)) p.fmt.fmt_qc(int64(v))
} else { } else {
p.badVerb(verb) p.badVerb(verb)
......
...@@ -5,15 +5,12 @@ ...@@ -5,15 +5,12 @@
package fmt package fmt
import ( import (
"bytes"
"errors" "errors"
"io" "io"
"math" "math"
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"unicode"
"unicode/utf8" "unicode/utf8"
) )
...@@ -87,25 +84,36 @@ func Scanf(format string, a ...interface{}) (n int, err error) { ...@@ -87,25 +84,36 @@ func Scanf(format string, a ...interface{}) (n int, err error) {
return Fscanf(os.Stdin, format, a...) return Fscanf(os.Stdin, format, a...)
} }
type stringReader string
func (r *stringReader) Read(b []byte) (n int, err error) {
n = copy(b, *r)
*r = (*r)[n:]
if n == 0 {
err = io.EOF
}
return
}
// Sscan scans the argument string, storing successive space-separated // Sscan scans the argument string, storing successive space-separated
// values into successive arguments. Newlines count as space. It // values into successive arguments. Newlines count as space. It
// returns the number of items successfully scanned. If that is less // returns the number of items successfully scanned. If that is less
// than the number of arguments, err will report why. // than the number of arguments, err will report why.
func Sscan(str string, a ...interface{}) (n int, err error) { func Sscan(str string, a ...interface{}) (n int, err error) {
return Fscan(strings.NewReader(str), a...) return Fscan((*stringReader)(&str), a...)
} }
// Sscanln is similar to Sscan, but stops scanning at a newline and // Sscanln is similar to Sscan, but stops scanning at a newline and
// after the final item there must be a newline or EOF. // after the final item there must be a newline or EOF.
func Sscanln(str string, a ...interface{}) (n int, err error) { func Sscanln(str string, a ...interface{}) (n int, err error) {
return Fscanln(strings.NewReader(str), a...) return Fscanln((*stringReader)(&str), a...)
} }
// Sscanf scans the argument string, storing successive space-separated // Sscanf scans the argument string, storing successive space-separated
// values into successive arguments as determined by the format. It // values into successive arguments as determined by the format. It
// returns the number of items successfully parsed. // returns the number of items successfully parsed.
func Sscanf(str string, format string, a ...interface{}) (n int, err error) { func Sscanf(str string, format string, a ...interface{}) (n int, err error) {
return Fscanf(strings.NewReader(str), format, a...) return Fscanf((*stringReader)(&str), format, a...)
} }
// Fscan scans text read from r, storing successive space-separated // Fscan scans text read from r, storing successive space-separated
...@@ -149,7 +157,7 @@ const eof = -1 ...@@ -149,7 +157,7 @@ const eof = -1
// ss is the internal implementation of ScanState. // ss is the internal implementation of ScanState.
type ss struct { type ss struct {
rr io.RuneReader // where to read input rr io.RuneReader // where to read input
buf bytes.Buffer // token accumulator buf buffer // token accumulator
peekRune rune // one-rune lookahead peekRune rune // one-rune lookahead
prevRune rune // last rune returned by ReadRune prevRune rune // last rune returned by ReadRune
count int // runes consumed so far. count int // runes consumed so far.
...@@ -262,14 +270,46 @@ func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) { ...@@ -262,14 +270,46 @@ func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
if f == nil { if f == nil {
f = notSpace f = notSpace
} }
s.buf.Reset() s.buf = s.buf[:0]
tok = s.token(skipSpace, f) tok = s.token(skipSpace, f)
return return
} }
// space is a copy of the unicode.White_Space ranges,
// to avoid depending on package unicode.
var space = [][2]uint16{
{0x0009, 0x000d},
{0x0020, 0x0020},
{0x0085, 0x0085},
{0x00a0, 0x00a0},
{0x1680, 0x1680},
{0x180e, 0x180e},
{0x2000, 0x200a},
{0x2028, 0x2029},
{0x202f, 0x202f},
{0x205f, 0x205f},
{0x3000, 0x3000},
}
func isSpace(r rune) bool {
if r >= 1<<16 {
return false
}
rx := uint16(r)
for _, rng := range space {
if rx < rng[0] {
return false
}
if rx <= rng[1] {
return true
}
}
return false
}
// notSpace is the default scanning function used in Token. // notSpace is the default scanning function used in Token.
func notSpace(r rune) bool { func notSpace(r rune) bool {
return !unicode.IsSpace(r) return !isSpace(r)
} }
// skipSpace provides Scan() methods the ability to skip space and newline characters // skipSpace provides Scan() methods the ability to skip space and newline characters
...@@ -378,10 +418,10 @@ func (s *ss) free(old ssave) { ...@@ -378,10 +418,10 @@ func (s *ss) free(old ssave) {
return return
} }
// Don't hold on to ss structs with large buffers. // Don't hold on to ss structs with large buffers.
if cap(s.buf.Bytes()) > 1024 { if cap(s.buf) > 1024 {
return return
} }
s.buf.Reset() s.buf = s.buf[:0]
s.rr = nil s.rr = nil
ssFree.put(s) ssFree.put(s)
} }
...@@ -403,7 +443,7 @@ func (s *ss) skipSpace(stopAtNewline bool) { ...@@ -403,7 +443,7 @@ func (s *ss) skipSpace(stopAtNewline bool) {
s.errorString("unexpected newline") s.errorString("unexpected newline")
return return
} }
if !unicode.IsSpace(r) { if !isSpace(r) {
s.UnreadRune() s.UnreadRune()
break break
} }
...@@ -429,7 +469,7 @@ func (s *ss) token(skipSpace bool, f func(rune) bool) []byte { ...@@ -429,7 +469,7 @@ func (s *ss) token(skipSpace bool, f func(rune) bool) []byte {
} }
s.buf.WriteRune(r) s.buf.WriteRune(r)
} }
return s.buf.Bytes() return s.buf
} }
// typeError indicates that the type of the operand did not match the format // typeError indicates that the type of the operand did not match the format
...@@ -440,6 +480,15 @@ func (s *ss) typeError(field interface{}, expected string) { ...@@ -440,6 +480,15 @@ func (s *ss) typeError(field interface{}, expected string) {
var complexError = errors.New("syntax error scanning complex number") var complexError = errors.New("syntax error scanning complex number")
var boolError = errors.New("syntax error scanning boolean") var boolError = errors.New("syntax error scanning boolean")
func indexRune(s string, r rune) int {
for i, c := range s {
if c == r {
return i
}
}
return -1
}
// consume reads the next rune in the input and reports whether it is in the ok string. // consume reads the next rune in the input and reports whether it is in the ok string.
// If accept is true, it puts the character into the input token. // If accept is true, it puts the character into the input token.
func (s *ss) consume(ok string, accept bool) bool { func (s *ss) consume(ok string, accept bool) bool {
...@@ -447,7 +496,7 @@ func (s *ss) consume(ok string, accept bool) bool { ...@@ -447,7 +496,7 @@ func (s *ss) consume(ok string, accept bool) bool {
if r == eof { if r == eof {
return false return false
} }
if strings.IndexRune(ok, r) >= 0 { if indexRune(ok, r) >= 0 {
if accept { if accept {
s.buf.WriteRune(r) s.buf.WriteRune(r)
} }
...@@ -465,7 +514,7 @@ func (s *ss) peek(ok string) bool { ...@@ -465,7 +514,7 @@ func (s *ss) peek(ok string) bool {
if r != eof { if r != eof {
s.UnreadRune() s.UnreadRune()
} }
return strings.IndexRune(ok, r) >= 0 return indexRune(ok, r) >= 0
} }
func (s *ss) notEOF() { func (s *ss) notEOF() {
...@@ -560,7 +609,7 @@ func (s *ss) scanNumber(digits string, haveDigits bool) string { ...@@ -560,7 +609,7 @@ func (s *ss) scanNumber(digits string, haveDigits bool) string {
} }
for s.accept(digits) { for s.accept(digits) {
} }
return s.buf.String() return string(s.buf)
} }
// scanRune returns the next rune value in the input. // scanRune returns the next rune value in the input.
...@@ -660,16 +709,16 @@ func (s *ss) scanUint(verb rune, bitSize int) uint64 { ...@@ -660,16 +709,16 @@ func (s *ss) scanUint(verb rune, bitSize int) uint64 {
// if the width is specified. It's not rigorous about syntax because it doesn't check that // if the width is specified. It's not rigorous about syntax because it doesn't check that
// we have at least some digits, but Atof will do that. // we have at least some digits, but Atof will do that.
func (s *ss) floatToken() string { func (s *ss) floatToken() string {
s.buf.Reset() s.buf = s.buf[:0]
// NaN? // NaN?
if s.accept("nN") && s.accept("aA") && s.accept("nN") { if s.accept("nN") && s.accept("aA") && s.accept("nN") {
return s.buf.String() return string(s.buf)
} }
// leading sign? // leading sign?
s.accept(sign) s.accept(sign)
// Inf? // Inf?
if s.accept("iI") && s.accept("nN") && s.accept("fF") { if s.accept("iI") && s.accept("nN") && s.accept("fF") {
return s.buf.String() return string(s.buf)
} }
// digits? // digits?
for s.accept(decimalDigits) { for s.accept(decimalDigits) {
...@@ -688,7 +737,7 @@ func (s *ss) floatToken() string { ...@@ -688,7 +737,7 @@ func (s *ss) floatToken() string {
for s.accept(decimalDigits) { for s.accept(decimalDigits) {
} }
} }
return s.buf.String() return string(s.buf)
} }
// complexTokens returns the real and imaginary parts of the complex number starting here. // complexTokens returns the real and imaginary parts of the complex number starting here.
...@@ -698,13 +747,13 @@ func (s *ss) complexTokens() (real, imag string) { ...@@ -698,13 +747,13 @@ func (s *ss) complexTokens() (real, imag string) {
// TODO: accept N and Ni independently? // TODO: accept N and Ni independently?
parens := s.accept("(") parens := s.accept("(")
real = s.floatToken() real = s.floatToken()
s.buf.Reset() s.buf = s.buf[:0]
// Must now have a sign. // Must now have a sign.
if !s.accept("+-") { if !s.accept("+-") {
s.error(complexError) s.error(complexError)
} }
// Sign is now in buffer // Sign is now in buffer
imagSign := s.buf.String() imagSign := string(s.buf)
imag = s.floatToken() imag = s.floatToken()
if !s.accept("i") { if !s.accept("i") {
s.error(complexError) s.error(complexError)
...@@ -717,7 +766,7 @@ func (s *ss) complexTokens() (real, imag string) { ...@@ -717,7 +766,7 @@ func (s *ss) complexTokens() (real, imag string) {
// convertFloat converts the string to a float64value. // convertFloat converts the string to a float64value.
func (s *ss) convertFloat(str string, n int) float64 { func (s *ss) convertFloat(str string, n int) float64 {
if p := strings.Index(str, "p"); p >= 0 { if p := indexRune(str, 'p'); p >= 0 {
// Atof doesn't handle power-of-2 exponents, // Atof doesn't handle power-of-2 exponents,
// but they're easy to evaluate. // but they're easy to evaluate.
f, err := strconv.ParseFloat(str[:p], n) f, err := strconv.ParseFloat(str[:p], n)
...@@ -794,7 +843,7 @@ func (s *ss) quotedString() string { ...@@ -794,7 +843,7 @@ func (s *ss) quotedString() string {
} }
s.buf.WriteRune(r) s.buf.WriteRune(r)
} }
return s.buf.String() return string(s.buf)
case '"': case '"':
// Double-quoted: Include the quotes and let strconv.Unquote do the backslash escapes. // Double-quoted: Include the quotes and let strconv.Unquote do the backslash escapes.
s.buf.WriteRune(quote) s.buf.WriteRune(quote)
...@@ -811,7 +860,7 @@ func (s *ss) quotedString() string { ...@@ -811,7 +860,7 @@ func (s *ss) quotedString() string {
break break
} }
} }
result, err := strconv.Unquote(s.buf.String()) result, err := strconv.Unquote(string(s.buf))
if err != nil { if err != nil {
s.error(err) s.error(err)
} }
...@@ -844,7 +893,7 @@ func (s *ss) hexByte() (b byte, ok bool) { ...@@ -844,7 +893,7 @@ func (s *ss) hexByte() (b byte, ok bool) {
if rune1 == eof { if rune1 == eof {
return return
} }
if unicode.IsSpace(rune1) { if isSpace(rune1) {
s.UnreadRune() s.UnreadRune()
return return
} }
...@@ -862,11 +911,11 @@ func (s *ss) hexString() string { ...@@ -862,11 +911,11 @@ func (s *ss) hexString() string {
} }
s.buf.WriteByte(b) s.buf.WriteByte(b)
} }
if s.buf.Len() == 0 { if len(s.buf) == 0 {
s.errorString("Scan: no hex data for %x string") s.errorString("Scan: no hex data for %x string")
return "" return ""
} }
return s.buf.String() return string(s.buf)
} }
const floatVerbs = "beEfFgGv" const floatVerbs = "beEfFgGv"
...@@ -875,7 +924,7 @@ const hugeWid = 1 << 30 ...@@ -875,7 +924,7 @@ const hugeWid = 1 << 30
// scanOne scans a single value, deriving the scanner from the type of the argument. // scanOne scans a single value, deriving the scanner from the type of the argument.
func (s *ss) scanOne(verb rune, field interface{}) { func (s *ss) scanOne(verb rune, field interface{}) {
s.buf.Reset() s.buf = s.buf[:0]
var err error var err error
// If the parameter has its own Scan method, use that. // If the parameter has its own Scan method, use that.
if v, ok := field.(Scanner); ok { if v, ok := field.(Scanner); ok {
...@@ -1004,7 +1053,7 @@ func (s *ss) doScan(a []interface{}) (numProcessed int, err error) { ...@@ -1004,7 +1053,7 @@ func (s *ss) doScan(a []interface{}) (numProcessed int, err error) {
if r == '\n' || r == eof { if r == '\n' || r == eof {
break break
} }
if !unicode.IsSpace(r) { if !isSpace(r) {
s.errorString("Scan: expected newline") s.errorString("Scan: expected newline")
break break
} }
...@@ -1032,7 +1081,7 @@ func (s *ss) advance(format string) (i int) { ...@@ -1032,7 +1081,7 @@ func (s *ss) advance(format string) (i int) {
i += w // skip the first % i += w // skip the first %
} }
sawSpace := false sawSpace := false
for unicode.IsSpace(fmtc) && i < len(format) { for isSpace(fmtc) && i < len(format) {
sawSpace = true sawSpace = true
i += w i += w
fmtc, w = utf8.DecodeRuneInString(format[i:]) fmtc, w = utf8.DecodeRuneInString(format[i:])
...@@ -1044,7 +1093,7 @@ func (s *ss) advance(format string) (i int) { ...@@ -1044,7 +1093,7 @@ func (s *ss) advance(format string) (i int) {
if inputc == eof { if inputc == eof {
return return
} }
if !unicode.IsSpace(inputc) { if !isSpace(inputc) {
// Space in format but not in input: error // Space in format but not in input: error
s.errorString("expected space in input to match format") s.errorString("expected space in input to match format")
} }
......
...@@ -43,56 +43,62 @@ var pkgDeps = map[string][]string{ ...@@ -43,56 +43,62 @@ var pkgDeps = map[string][]string{
"unsafe", "unsafe",
}, },
// L1 adds simple data and functions, most notably // L1 adds simple functions and strings processing,
// Unicode and strings processing. // but not Unicode tables.
"bufio": {"L0", "unicode/utf8", "bytes"},
"bytes": {"L0", "unicode", "unicode/utf8"},
"math": {"unsafe"}, "math": {"unsafe"},
"math/cmplx": {"math"}, "math/cmplx": {"math"},
"math/rand": {"L0", "math"}, "math/rand": {"L0", "math"},
"path": {"L0", "unicode/utf8", "strings"},
"sort": {"math"}, "sort": {"math"},
"strconv": {"L0", "unicode/utf8", "math"}, "strconv": {"L0", "unicode/utf8", "math"},
"strings": {"L0", "unicode", "unicode/utf8"},
"unicode": {},
"unicode/utf16": {}, "unicode/utf16": {},
"unicode/utf8": {}, "unicode/utf8": {},
"L1": { "L1": {
"L0", "L0",
"bufio",
"bytes",
"math", "math",
"math/cmplx", "math/cmplx",
"math/rand", "math/rand",
"path",
"sort", "sort",
"strconv", "strconv",
"strings",
"unicode",
"unicode/utf16", "unicode/utf16",
"unicode/utf8", "unicode/utf8",
}, },
// L2 adds reflection and some basic utility packages // L2 adds Unicode and strings processing.
// and interface definitions, but nothing that makes "bufio": {"L0", "unicode/utf8", "bytes"},
// system calls. "bytes": {"L0", "unicode", "unicode/utf8"},
"crypto": {"L1", "hash"}, // interfaces "path": {"L0", "unicode/utf8", "strings"},
"crypto/cipher": {"L1"}, // interfaces "strings": {"L0", "unicode", "unicode/utf8"},
"encoding/base32": {"L1"}, "unicode": {},
"encoding/base64": {"L1"},
"encoding/binary": {"L1", "reflect"},
"hash": {"L1"}, // interfaces
"hash/adler32": {"L1", "hash"},
"hash/crc32": {"L1", "hash"},
"hash/crc64": {"L1", "hash"},
"hash/fnv": {"L1", "hash"},
"image": {"L1", "image/color"}, // interfaces
"image/color": {"L1"}, // interfaces
"reflect": {"L1"},
"L2": { "L2": {
"L1", "L1",
"bufio",
"bytes",
"path",
"strings",
"unicode",
},
// L3 adds reflection and some basic utility packages
// and interface definitions, but nothing that makes
// system calls.
"crypto": {"L2", "hash"}, // interfaces
"crypto/cipher": {"L2"}, // interfaces
"encoding/base32": {"L2"},
"encoding/base64": {"L2"},
"encoding/binary": {"L2", "reflect"},
"hash": {"L2"}, // interfaces
"hash/adler32": {"L2", "hash"},
"hash/crc32": {"L2", "hash"},
"hash/crc64": {"L2", "hash"},
"hash/fnv": {"L2", "hash"},
"image": {"L2", "image/color"}, // interfaces
"image/color": {"L2"}, // interfaces
"reflect": {"L2"},
"L3": {
"L2",
"crypto", "crypto",
"crypto/cipher", "crypto/cipher",
"encoding/base32", "encoding/base32",
...@@ -113,11 +119,11 @@ var pkgDeps = map[string][]string{ ...@@ -113,11 +119,11 @@ var pkgDeps = map[string][]string{
// Operating system access. // Operating system access.
"syscall": {"L0", "unicode/utf16"}, "syscall": {"L0", "unicode/utf16"},
"time": {"L0", "syscall"}, "time": {"L0", "syscall"},
"os": {"L0", "os", "syscall", "time", "unicode/utf16"}, "os": {"L1", "os", "syscall", "time"},
"path/filepath": {"L1", "os"}, "path/filepath": {"L2", "os"},
"io/ioutil": {"L1", "os", "path/filepath", "time"}, "io/ioutil": {"L2", "os", "path/filepath", "time"},
"os/exec": {"L1", "os", "syscall"}, "os/exec": {"L2", "os", "syscall"},
"os/signal": {"L1", "os", "syscall"}, "os/signal": {"L2", "os", "syscall"},
// OS enables basic operating system functionality, // OS enables basic operating system functionality,
// but not direct use of package syscall, nor os/signal. // but not direct use of package syscall, nor os/signal.
...@@ -129,37 +135,37 @@ var pkgDeps = map[string][]string{ ...@@ -129,37 +135,37 @@ var pkgDeps = map[string][]string{
"time", "time",
}, },
// Formatted I/O. // Formatted I/O: few dependencies (L1) but we must add reflect.
"fmt": {"L1", "OS", "reflect"}, "fmt": {"L1", "os", "reflect"},
"log": {"L1", "OS", "fmt"}, "log": {"L1", "os", "fmt", "time"},
// Packages used by testing must be low-level (L1+fmt). // Packages used by testing must be low-level (L2+fmt).
"regexp": {"L1", "regexp/syntax"}, "regexp": {"L2", "regexp/syntax"},
"regexp/syntax": {"L1"}, "regexp/syntax": {"L2"},
"runtime/debug": {"L1", "fmt", "io/ioutil", "os"}, "runtime/debug": {"L2", "fmt", "io/ioutil", "os"},
"runtime/pprof": {"L1", "fmt", "text/tabwriter"}, "runtime/pprof": {"L2", "fmt", "text/tabwriter"},
"text/tabwriter": {"L1"}, "text/tabwriter": {"L2"},
"testing": {"L1", "flag", "fmt", "os", "runtime/pprof", "time"}, "testing": {"L2", "flag", "fmt", "os", "runtime/pprof", "time"},
"testing/iotest": {"L1", "log"}, "testing/iotest": {"L2", "log"},
"testing/quick": {"L1", "flag", "fmt", "reflect"}, "testing/quick": {"L2", "flag", "fmt", "reflect"},
// L3 is defined as L2+fmt+log+time, because in general once // L4 is defined as L3+fmt+log+time, because in general once
// you're using L2 packages, use of fmt, log, or time is not a big deal. // you're using L3 packages, use of fmt, log, or time is not a big deal.
"L3": { "L4": {
"L2", "L3",
"fmt", "fmt",
"log", "log",
"time", "time",
}, },
// Go parser. // Go parser.
"go/ast": {"L3", "OS", "go/scanner", "go/token"}, "go/ast": {"L4", "OS", "go/scanner", "go/token"},
"go/doc": {"L3", "go/ast", "go/token", "regexp", "text/template"}, "go/doc": {"L4", "go/ast", "go/token", "regexp", "text/template"},
"go/parser": {"L3", "OS", "go/ast", "go/scanner", "go/token"}, "go/parser": {"L4", "OS", "go/ast", "go/scanner", "go/token"},
"go/printer": {"L3", "OS", "go/ast", "go/scanner", "go/token", "text/tabwriter"}, "go/printer": {"L4", "OS", "go/ast", "go/scanner", "go/token", "text/tabwriter"},
"go/scanner": {"L3", "OS", "go/token"}, "go/scanner": {"L4", "OS", "go/token"},
"go/token": {"L3"}, "go/token": {"L4"},
"GOPARSER": { "GOPARSER": {
"go/ast", "go/ast",
...@@ -171,48 +177,48 @@ var pkgDeps = map[string][]string{ ...@@ -171,48 +177,48 @@ var pkgDeps = map[string][]string{
}, },
// One of a kind. // One of a kind.
"archive/tar": {"L3", "OS"}, "archive/tar": {"L4", "OS"},
"archive/zip": {"L3", "OS", "compress/flate"}, "archive/zip": {"L4", "OS", "compress/flate"},
"compress/bzip2": {"L3"}, "compress/bzip2": {"L4"},
"compress/flate": {"L3"}, "compress/flate": {"L4"},
"compress/gzip": {"L3", "compress/flate"}, "compress/gzip": {"L4", "compress/flate"},
"compress/lzw": {"L3"}, "compress/lzw": {"L4"},
"compress/zlib": {"L3", "compress/flate"}, "compress/zlib": {"L4", "compress/flate"},
"database/sql": {"L3", "database/sql/driver"}, "database/sql": {"L4", "database/sql/driver"},
"database/sql/driver": {"L3", "time"}, "database/sql/driver": {"L4", "time"},
"debug/dwarf": {"L3"}, "debug/dwarf": {"L4"},
"debug/elf": {"L3", "OS", "debug/dwarf"}, "debug/elf": {"L4", "OS", "debug/dwarf"},
"debug/gosym": {"L3"}, "debug/gosym": {"L4"},
"debug/macho": {"L3", "OS", "debug/dwarf"}, "debug/macho": {"L4", "OS", "debug/dwarf"},
"debug/pe": {"L3", "OS", "debug/dwarf"}, "debug/pe": {"L4", "OS", "debug/dwarf"},
"encoding/ascii85": {"L3"}, "encoding/ascii85": {"L4"},
"encoding/asn1": {"L3", "math/big"}, "encoding/asn1": {"L4", "math/big"},
"encoding/csv": {"L3"}, "encoding/csv": {"L4"},
"encoding/gob": {"L3", "OS"}, "encoding/gob": {"L4", "OS"},
"encoding/hex": {"L3"}, "encoding/hex": {"L4"},
"encoding/json": {"L3"}, "encoding/json": {"L4"},
"encoding/pem": {"L3"}, "encoding/pem": {"L4"},
"encoding/xml": {"L3"}, "encoding/xml": {"L4"},
"flag": {"L3", "OS"}, "flag": {"L4", "OS"},
"go/build": {"L3", "OS", "GOPARSER"}, "go/build": {"L4", "OS", "GOPARSER"},
"html": {"L3"}, "html": {"L4"},
"image/draw": {"L3"}, "image/draw": {"L4"},
"image/gif": {"L3", "compress/lzw"}, "image/gif": {"L4", "compress/lzw"},
"image/jpeg": {"L3"}, "image/jpeg": {"L4"},
"image/png": {"L3", "compress/zlib"}, "image/png": {"L4", "compress/zlib"},
"index/suffixarray": {"L3", "regexp"}, "index/suffixarray": {"L4", "regexp"},
"math/big": {"L3"}, "math/big": {"L4"},
"mime": {"L3", "OS", "syscall"}, "mime": {"L4", "OS", "syscall"},
"net/url": {"L3"}, "net/url": {"L4"},
"text/scanner": {"L3", "OS"}, "text/scanner": {"L4", "OS"},
"text/template/parse": {"L3"}, "text/template/parse": {"L4"},
"html/template": { "html/template": {
"L3", "OS", "encoding/json", "html", "text/template", "L4", "OS", "encoding/json", "html", "text/template",
"text/template/parse", "text/template/parse",
}, },
"text/template": { "text/template": {
"L3", "OS", "net/url", "text/template/parse", "L4", "OS", "net/url", "text/template/parse",
}, },
// Cgo. // Cgo.
...@@ -223,11 +229,12 @@ var pkgDeps = map[string][]string{ ...@@ -223,11 +229,12 @@ var pkgDeps = map[string][]string{
// that shows up in programs that use cgo. // that shows up in programs that use cgo.
"C": {}, "C": {},
"os/user": {"L3", "CGO", "syscall"}, "os/user": {"L4", "CGO", "syscall"},
// Basic networking. // Basic networking.
// TODO: maybe remove math/rand. // Because net must be used by any package that wants to
"net": {"L0", "CGO", "math/rand", "os", "sort", "syscall", "time"}, // do networking portably, it must have a small dependency set: just L1+basic os.
"net": {"L1", "CGO", "os", "syscall", "time"},
// NET enables use of basic network-related packages. // NET enables use of basic network-related packages.
"NET": { "NET": {
...@@ -238,20 +245,20 @@ var pkgDeps = map[string][]string{ ...@@ -238,20 +245,20 @@ var pkgDeps = map[string][]string{
}, },
// Uses of networking. // Uses of networking.
"log/syslog": {"L3", "OS", "net"}, "log/syslog": {"L4", "OS", "net"},
"net/mail": {"L3", "NET", "OS"}, "net/mail": {"L4", "NET", "OS"},
"net/textproto": {"L3", "OS", "net"}, "net/textproto": {"L4", "OS", "net"},
// Core crypto. // Core crypto.
"crypto/aes": {"L2"}, "crypto/aes": {"L3"},
"crypto/des": {"L2"}, "crypto/des": {"L3"},
"crypto/hmac": {"L2"}, "crypto/hmac": {"L3"},
"crypto/md5": {"L2"}, "crypto/md5": {"L3"},
"crypto/rc4": {"L2"}, "crypto/rc4": {"L3"},
"crypto/sha1": {"L2"}, "crypto/sha1": {"L3"},
"crypto/sha256": {"L2"}, "crypto/sha256": {"L3"},
"crypto/sha512": {"L2"}, "crypto/sha512": {"L3"},
"crypto/subtle": {"L2"}, "crypto/subtle": {"L3"},
"CRYPTO": { "CRYPTO": {
"crypto/aes", "crypto/aes",
...@@ -268,14 +275,14 @@ var pkgDeps = map[string][]string{ ...@@ -268,14 +275,14 @@ var pkgDeps = map[string][]string{
// Random byte, number generation. // Random byte, number generation.
// This would be part of core crypto except that it imports // This would be part of core crypto except that it imports
// math/big, which imports fmt. // math/big, which imports fmt.
"crypto/rand": {"L3", "CRYPTO", "OS", "math/big", "syscall"}, "crypto/rand": {"L4", "CRYPTO", "OS", "math/big", "syscall"},
// Mathematical crypto: dependencies on fmt (L3) and math/big. // Mathematical crypto: dependencies on fmt (L4) and math/big.
// We could avoid some of the fmt, but math/big imports fmt anyway. // We could avoid some of the fmt, but math/big imports fmt anyway.
"crypto/dsa": {"L3", "CRYPTO", "math/big"}, "crypto/dsa": {"L4", "CRYPTO", "math/big"},
"crypto/ecdsa": {"L3", "CRYPTO", "crypto/elliptic", "math/big"}, "crypto/ecdsa": {"L4", "CRYPTO", "crypto/elliptic", "math/big"},
"crypto/elliptic": {"L3", "CRYPTO", "math/big"}, "crypto/elliptic": {"L4", "CRYPTO", "math/big"},
"crypto/rsa": {"L3", "CRYPTO", "crypto/rand", "math/big"}, "crypto/rsa": {"L4", "CRYPTO", "crypto/rand", "math/big"},
"CRYPTO-MATH": { "CRYPTO-MATH": {
"CRYPTO", "CRYPTO",
...@@ -290,31 +297,31 @@ var pkgDeps = map[string][]string{ ...@@ -290,31 +297,31 @@ var pkgDeps = map[string][]string{
// SSL/TLS. // SSL/TLS.
"crypto/tls": { "crypto/tls": {
"L3", "CRYPTO-MATH", "CGO", "OS", "L4", "CRYPTO-MATH", "CGO", "OS",
"crypto/x509", "encoding/pem", "net", "syscall", "crypto/x509", "encoding/pem", "net", "syscall",
}, },
"crypto/x509": {"L3", "CRYPTO-MATH", "OS", "CGO", "crypto/x509/pkix", "encoding/pem"}, "crypto/x509": {"L4", "CRYPTO-MATH", "OS", "CGO", "crypto/x509/pkix", "encoding/pem"},
"crypto/x509/pkix": {"L3", "CRYPTO-MATH"}, "crypto/x509/pkix": {"L4", "CRYPTO-MATH"},
// Simple net+crypto-aware packages. // Simple net+crypto-aware packages.
"mime/multipart": {"L3", "OS", "mime", "crypto/rand", "net/textproto"}, "mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto"},
"net/smtp": {"L3", "CRYPTO", "NET", "crypto/tls"}, "net/smtp": {"L4", "CRYPTO", "NET", "crypto/tls"},
// HTTP, kingpin of dependencies. // HTTP, kingpin of dependencies.
"net/http": { "net/http": {
"L3", "NET", "OS", "L4", "NET", "OS",
"compress/gzip", "crypto/tls", "mime/multipart", "runtime/debug", "compress/gzip", "crypto/tls", "mime/multipart", "runtime/debug",
}, },
// HTTP-using packages. // HTTP-using packages.
"expvar": {"L3", "OS", "encoding/json", "net/http"}, "expvar": {"L4", "OS", "encoding/json", "net/http"},
"net/http/cgi": {"L3", "NET", "OS", "crypto/tls", "net/http", "regexp"}, "net/http/cgi": {"L4", "NET", "OS", "crypto/tls", "net/http", "regexp"},
"net/http/fcgi": {"L3", "NET", "OS", "net/http", "net/http/cgi"}, "net/http/fcgi": {"L4", "NET", "OS", "net/http", "net/http/cgi"},
"net/http/httptest": {"L3", "NET", "OS", "crypto/tls", "flag", "net/http"}, "net/http/httptest": {"L4", "NET", "OS", "crypto/tls", "flag", "net/http"},
"net/http/httputil": {"L3", "NET", "OS", "net/http"}, "net/http/httputil": {"L4", "NET", "OS", "net/http"},
"net/http/pprof": {"L3", "OS", "html/template", "net/http", "runtime/pprof"}, "net/http/pprof": {"L4", "OS", "html/template", "net/http", "runtime/pprof"},
"net/rpc": {"L3", "NET", "encoding/gob", "net/http", "text/template"}, "net/rpc": {"L4", "NET", "encoding/gob", "net/http", "text/template"},
"net/rpc/jsonrpc": {"L3", "NET", "encoding/json", "net/rpc"}, "net/rpc/jsonrpc": {"L4", "NET", "encoding/json", "net/rpc"},
} }
// isMacro reports whether p is a package dependency macro // isMacro reports whether p is a package dependency macro
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
package log package log
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
...@@ -41,11 +40,11 @@ const ( ...@@ -41,11 +40,11 @@ const (
// the Writer's Write method. A Logger can be used simultaneously from // the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer. // multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct { type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix to write at beginning of each line prefix string // prefix to write at beginning of each line
flag int // properties flag int // properties
out io.Writer // destination for output out io.Writer // destination for output
buf bytes.Buffer // for accumulating text to write buf []byte // for accumulating text to write
} }
// New creates a new Logger. The out variable sets the // New creates a new Logger. The out variable sets the
...@@ -60,10 +59,10 @@ var std = New(os.Stderr, "", LstdFlags) ...@@ -60,10 +59,10 @@ var std = New(os.Stderr, "", LstdFlags)
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. // Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
// Knows the buffer has capacity. // Knows the buffer has capacity.
func itoa(buf *bytes.Buffer, i int, wid int) { func itoa(buf *[]byte, i int, wid int) {
var u uint = uint(i) var u uint = uint(i)
if u == 0 && wid <= 1 { if u == 0 && wid <= 1 {
buf.WriteByte('0') *buf = append(*buf, '0')
return return
} }
...@@ -75,38 +74,33 @@ func itoa(buf *bytes.Buffer, i int, wid int) { ...@@ -75,38 +74,33 @@ func itoa(buf *bytes.Buffer, i int, wid int) {
wid-- wid--
b[bp] = byte(u%10) + '0' b[bp] = byte(u%10) + '0'
} }
*buf = append(*buf, b[bp:]...)
// avoid slicing b to avoid an allocation.
for bp < len(b) {
buf.WriteByte(b[bp])
bp++
}
} }
func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time, file string, line int) { func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
buf.WriteString(l.prefix) *buf = append(*buf, l.prefix...)
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
if l.flag&Ldate != 0 { if l.flag&Ldate != 0 {
year, month, day := t.Date() year, month, day := t.Date()
itoa(buf, year, 4) itoa(buf, year, 4)
buf.WriteByte('/') *buf = append(*buf, '/')
itoa(buf, int(month), 2) itoa(buf, int(month), 2)
buf.WriteByte('/') *buf = append(*buf, '/')
itoa(buf, day, 2) itoa(buf, day, 2)
buf.WriteByte(' ') *buf = append(*buf, ' ')
} }
if l.flag&(Ltime|Lmicroseconds) != 0 { if l.flag&(Ltime|Lmicroseconds) != 0 {
hour, min, sec := t.Clock() hour, min, sec := t.Clock()
itoa(buf, hour, 2) itoa(buf, hour, 2)
buf.WriteByte(':') *buf = append(*buf, ':')
itoa(buf, min, 2) itoa(buf, min, 2)
buf.WriteByte(':') *buf = append(*buf, ':')
itoa(buf, sec, 2) itoa(buf, sec, 2)
if l.flag&Lmicroseconds != 0 { if l.flag&Lmicroseconds != 0 {
buf.WriteByte('.') *buf = append(*buf, '.')
itoa(buf, t.Nanosecond()/1e3, 6) itoa(buf, t.Nanosecond()/1e3, 6)
} }
buf.WriteByte(' ') *buf = append(*buf, ' ')
} }
} }
if l.flag&(Lshortfile|Llongfile) != 0 { if l.flag&(Lshortfile|Llongfile) != 0 {
...@@ -120,10 +114,10 @@ func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time, file string, line ...@@ -120,10 +114,10 @@ func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time, file string, line
} }
file = short file = short
} }
buf.WriteString(file) *buf = append(*buf, file...)
buf.WriteByte(':') *buf = append(*buf, ':')
itoa(buf, line, -1) itoa(buf, line, -1)
buf.WriteString(": ") *buf = append(*buf, ": "...)
} }
} }
...@@ -150,13 +144,13 @@ func (l *Logger) Output(calldepth int, s string) error { ...@@ -150,13 +144,13 @@ func (l *Logger) Output(calldepth int, s string) error {
} }
l.mu.Lock() l.mu.Lock()
} }
l.buf.Reset() l.buf = l.buf[:0]
l.formatHeader(&l.buf, now, file, line) l.formatHeader(&l.buf, now, file, line)
l.buf.WriteString(s) l.buf = append(l.buf, s...)
if len(s) > 0 && s[len(s)-1] != '\n' { if len(s) > 0 && s[len(s)-1] != '\n' {
l.buf.WriteByte('\n') l.buf = append(l.buf, '\n')
} }
_, err := l.out.Write(l.buf.Bytes()) _, err := l.out.Write(l.buf)
return err return err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment