From 621e49bb465f500cc46d47e39e828cf76d6381d7 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Tue, 24 Jul 2018 14:35:44 +0200 Subject: update vendor --- vendor/golang.org/x/text/message/catalog.go | 36 + .../golang.org/x/text/message/catalog/catalog.go | 369 ++++ .../x/text/message/catalog/catalog_test.go | 296 ++++ vendor/golang.org/x/text/message/catalog/dict.go | 129 ++ vendor/golang.org/x/text/message/catalog/go19.go | 15 + .../golang.org/x/text/message/catalog/gopre19.go | 23 + vendor/golang.org/x/text/message/catalog_test.go | 43 + vendor/golang.org/x/text/message/doc.go | 100 ++ vendor/golang.org/x/text/message/examples_test.go | 42 + vendor/golang.org/x/text/message/fmt_test.go | 1871 ++++++++++++++++++++ vendor/golang.org/x/text/message/format.go | 510 ++++++ vendor/golang.org/x/text/message/message.go | 186 ++ vendor/golang.org/x/text/message/message_test.go | 181 ++ .../golang.org/x/text/message/pipeline/extract.go | 305 ++++ .../golang.org/x/text/message/pipeline/generate.go | 251 +++ .../golang.org/x/text/message/pipeline/message.go | 241 +++ .../golang.org/x/text/message/pipeline/pipeline.go | 57 + .../golang.org/x/text/message/pipeline/rewrite.go | 268 +++ vendor/golang.org/x/text/message/print.go | 979 ++++++++++ 19 files changed, 5902 insertions(+) create mode 100644 vendor/golang.org/x/text/message/catalog.go create mode 100644 vendor/golang.org/x/text/message/catalog/catalog.go create mode 100644 vendor/golang.org/x/text/message/catalog/catalog_test.go create mode 100644 vendor/golang.org/x/text/message/catalog/dict.go create mode 100644 vendor/golang.org/x/text/message/catalog/go19.go create mode 100644 vendor/golang.org/x/text/message/catalog/gopre19.go create mode 100644 vendor/golang.org/x/text/message/catalog_test.go create mode 100644 vendor/golang.org/x/text/message/doc.go create mode 100644 vendor/golang.org/x/text/message/examples_test.go create mode 100644 vendor/golang.org/x/text/message/fmt_test.go create mode 100644 vendor/golang.org/x/text/message/format.go create mode 100644 vendor/golang.org/x/text/message/message.go create mode 100644 vendor/golang.org/x/text/message/message_test.go create mode 100644 vendor/golang.org/x/text/message/pipeline/extract.go create mode 100644 vendor/golang.org/x/text/message/pipeline/generate.go create mode 100644 vendor/golang.org/x/text/message/pipeline/message.go create mode 100644 vendor/golang.org/x/text/message/pipeline/pipeline.go create mode 100644 vendor/golang.org/x/text/message/pipeline/rewrite.go create mode 100644 vendor/golang.org/x/text/message/print.go (limited to 'vendor/golang.org/x/text/message') diff --git a/vendor/golang.org/x/text/message/catalog.go b/vendor/golang.org/x/text/message/catalog.go new file mode 100644 index 0000000..068271d --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog.go @@ -0,0 +1,36 @@ +// Copyright 2015 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 message + +// TODO: some types in this file will need to be made public at some time. +// Documentation and method names will reflect this by using the exported name. + +import ( + "golang.org/x/text/language" + "golang.org/x/text/message/catalog" +) + +// MatchLanguage reports the matched tag obtained from language.MatchStrings for +// the Matcher of the DefaultCatalog. +func MatchLanguage(preferred ...string) language.Tag { + c := DefaultCatalog + tag, _ := language.MatchStrings(c.Matcher(), preferred...) + return tag +} + +// DefaultCatalog is used by SetString. +var DefaultCatalog catalog.Catalog = defaultCatalog + +var defaultCatalog = catalog.NewBuilder() + +// SetString calls SetString on the initial default Catalog. +func SetString(tag language.Tag, key string, msg string) error { + return defaultCatalog.SetString(tag, key, msg) +} + +// Set calls Set on the initial default Catalog. +func Set(tag language.Tag, key string, msg ...catalog.Message) error { + return defaultCatalog.Set(tag, key, msg...) +} diff --git a/vendor/golang.org/x/text/message/catalog/catalog.go b/vendor/golang.org/x/text/message/catalog/catalog.go new file mode 100644 index 0000000..34a30d3 --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/catalog.go @@ -0,0 +1,369 @@ +// Copyright 2017 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 catalog defines collections of translated format strings. +// +// This package mostly defines types for populating catalogs with messages. The +// catmsg package contains further definitions for creating custom message and +// dictionary types as well as packages that use Catalogs. +// +// Package catalog defines various interfaces: Dictionary, Loader, and Message. +// A Dictionary maintains a set of translations of format strings for a single +// language. The Loader interface defines a source of dictionaries. A +// translation of a format string is represented by a Message. +// +// +// Catalogs +// +// A Catalog defines a programmatic interface for setting message translations. +// It maintains a set of per-language dictionaries with translations for a set +// of keys. For message translation to function properly, a translation should +// be defined for each key for each supported language. A dictionary may be +// underspecified, though, if there is a parent language that already defines +// the key. For example, a Dictionary for "en-GB" could leave out entries that +// are identical to those in a dictionary for "en". +// +// +// Messages +// +// A Message is a format string which varies on the value of substitution +// variables. For instance, to indicate the number of results one could want "no +// results" if there are none, "1 result" if there is 1, and "%d results" for +// any other number. Catalog is agnostic to the kind of format strings that are +// used: for instance, messages can follow either the printf-style substitution +// from package fmt or use templates. +// +// A Message does not substitute arguments in the format string. This job is +// reserved for packages that render strings, such as message, that use Catalogs +// to selected string. This separation of concerns allows Catalog to be used to +// store any kind of formatting strings. +// +// +// Selecting messages based on linguistic features of substitution arguments +// +// Messages may vary based on any linguistic features of the argument values. +// The most common one is plural form, but others exist. +// +// Selection messages are provided in packages that provide support for a +// specific linguistic feature. The following snippet uses plural.Select: +// +// catalog.Set(language.English, "You are %d minute(s) late.", +// plural.Select(1, +// "one", "You are 1 minute late.", +// "other", "You are %d minutes late.")) +// +// In this example, a message is stored in the Catalog where one of two messages +// is selected based on the first argument, a number. The first message is +// selected if the argument is singular (identified by the selector "one") and +// the second message is selected in all other cases. The selectors are defined +// by the plural rules defined in CLDR. The selector "other" is special and will +// always match. Each language always defines one of the linguistic categories +// to be "other." For English, singular is "one" and plural is "other". +// +// Selects can be nested. This allows selecting sentences based on features of +// multiple arguments or multiple linguistic properties of a single argument. +// +// +// String interpolation +// +// There is often a lot of commonality between the possible variants of a +// message. For instance, in the example above the word "minute" varies based on +// the plural catogory of the argument, but the rest of the sentence is +// identical. Using interpolation the above message can be rewritten as: +// +// catalog.Set(language.English, "You are %d minute(s) late.", +// catalog.Var("minutes", +// plural.Select(1, "one", "minute", "other", "minutes")), +// catalog.String("You are %[1]d ${minutes} late.")) +// +// Var is defined to return the variable name if the message does not yield a +// match. This allows us to further simplify this snippet to +// +// catalog.Set(language.English, "You are %d minute(s) late.", +// catalog.Var("minutes", plural.Select(1, "one", "minute")), +// catalog.String("You are %d ${minutes} late.")) +// +// Overall this is still only a minor improvement, but things can get a lot more +// unwieldy if more than one linguistic feature is used to determine a message +// variant. Consider the following example: +// +// // argument 1: list of hosts, argument 2: list of guests +// catalog.Set(language.English, "%[1]v invite(s) %[2]v to their party.", +// catalog.Var("their", +// plural.Select(1, +// "one", gender.Select(1, "female", "her", "other", "his"))), +// catalog.Var("invites", plural.Select(1, "one", "invite")) +// catalog.String("%[1]v ${invites} %[2]v to ${their} party.")), +// +// Without variable substitution, this would have to be written as +// +// // argument 1: list of hosts, argument 2: list of guests +// catalog.Set(language.English, "%[1]v invite(s) %[2]v to their party.", +// plural.Select(1, +// "one", gender.Select(1, +// "female", "%[1]v invites %[2]v to her party." +// "other", "%[1]v invites %[2]v to his party."), +// "other", "%[1]v invites %[2]v to their party.") +// +// Not necessarily shorter, but using variables there is less duplication and +// the messages are more maintenance friendly. Moreover, languages may have up +// to six plural forms. This makes the use of variables more welcome. +// +// Different messages using the same inflections can reuse variables by moving +// them to macros. Using macros we can rewrite the message as: +// +// // argument 1: list of hosts, argument 2: list of guests +// catalog.SetString(language.English, "%[1]v invite(s) %[2]v to their party.", +// "%[1]v ${invites(1)} %[2]v to ${their(1)} party.") +// +// Where the following macros were defined separately. +// +// catalog.SetMacro(language.English, "invites", plural.Select(1, "one", "invite")) +// catalog.SetMacro(language.English, "their", plural.Select(1, +// "one", gender.Select(1, "female", "her", "other", "his"))), +// +// Placeholders use parentheses and the arguments to invoke a macro. +// +// +// Looking up messages +// +// Message lookup using Catalogs is typically only done by specialized packages +// and is not something the user should be concerned with. For instance, to +// express the tardiness of a user using the related message we defined earlier, +// the user may use the package message like so: +// +// p := message.NewPrinter(language.English) +// p.Printf("You are %d minute(s) late.", 5) +// +// Which would print: +// You are 5 minutes late. +// +// +// This package is UNDER CONSTRUCTION and its API may change. +package catalog // import "golang.org/x/text/message/catalog" + +// TODO: +// Some way to freeze a catalog. +// - Locking on each lockup turns out to be about 50% of the total running time +// for some of the benchmarks in the message package. +// Consider these: +// - Sequence type to support sequences in user-defined messages. +// - Garbage collection: Remove dictionaries that can no longer be reached +// as other dictionaries have been added that cover all possible keys. + +import ( + "errors" + "fmt" + + "golang.org/x/text/internal" + + "golang.org/x/text/internal/catmsg" + "golang.org/x/text/language" +) + +// A Catalog allows lookup of translated messages. +type Catalog interface { + // Languages returns all languages for which the Catalog contains variants. + Languages() []language.Tag + + // Matcher returns a Matcher for languages from this Catalog. + Matcher() language.Matcher + + // A Context is used for evaluating Messages. + Context(tag language.Tag, r catmsg.Renderer) *Context + + // This method also makes Catalog a private interface. + lookup(tag language.Tag, key string) (data string, ok bool) +} + +// NewFromMap creates a Catalog from the given map. If a Dictionary is +// underspecified the entry is retrieved from a parent language. +func NewFromMap(dictionaries map[string]Dictionary, opts ...Option) (Catalog, error) { + options := options{} + for _, o := range opts { + o(&options) + } + c := &catalog{ + dicts: map[language.Tag]Dictionary{}, + } + _, hasFallback := dictionaries[options.fallback.String()] + if hasFallback { + // TODO: Should it be okay to not have a fallback language? + // Catalog generators could enforce there is always a fallback. + c.langs = append(c.langs, options.fallback) + } + for lang, dict := range dictionaries { + tag, err := language.Parse(lang) + if err != nil { + return nil, fmt.Errorf("catalog: invalid language tag %q", lang) + } + if _, ok := c.dicts[tag]; ok { + return nil, fmt.Errorf("catalog: duplicate entry for tag %q after normalization", tag) + } + c.dicts[tag] = dict + if !hasFallback || tag != options.fallback { + c.langs = append(c.langs, tag) + } + } + if hasFallback { + internal.SortTags(c.langs[1:]) + } else { + internal.SortTags(c.langs) + } + c.matcher = language.NewMatcher(c.langs) + return c, nil +} + +// A Dictionary is a source of translations for a single language. +type Dictionary interface { + // Lookup returns a message compiled with catmsg.Compile for the given key. + // It returns false for ok if such a message could not be found. + Lookup(key string) (data string, ok bool) +} + +type catalog struct { + langs []language.Tag + dicts map[language.Tag]Dictionary + macros store + matcher language.Matcher +} + +func (c *catalog) Languages() []language.Tag { return c.langs } +func (c *catalog) Matcher() language.Matcher { return c.matcher } + +func (c *catalog) lookup(tag language.Tag, key string) (data string, ok bool) { + for ; ; tag = tag.Parent() { + if dict, ok := c.dicts[tag]; ok { + if data, ok := dict.Lookup(key); ok { + return data, true + } + } + if tag == language.Und { + break + } + } + return "", false +} + +// Context returns a Context for formatting messages. +// Only one Message may be formatted per context at any given time. +func (c *catalog) Context(tag language.Tag, r catmsg.Renderer) *Context { + return &Context{ + cat: c, + tag: tag, + dec: catmsg.NewDecoder(tag, r, &dict{&c.macros, tag}), + } +} + +// A Builder allows building a Catalog programmatically. +type Builder struct { + options + matcher language.Matcher + + index store + macros store +} + +type options struct { + fallback language.Tag +} + +// An Option configures Catalog behavior. +type Option func(*options) + +// Fallback specifies the default fallback language. The default is Und. +func Fallback(tag language.Tag) Option { + return func(o *options) { o.fallback = tag } +} + +// TODO: +// // Catalogs specifies one or more sources for a Catalog. +// // Lookups are in order. +// // This can be changed inserting a Catalog used for setting, which implements +// // Loader, used for setting in the chain. +// func Catalogs(d ...Loader) Option { +// return nil +// } +// +// func Delims(start, end string) Option {} +// +// func Dict(tag language.Tag, d ...Dictionary) Option + +// NewBuilder returns an empty mutable Catalog. +func NewBuilder(opts ...Option) *Builder { + c := &Builder{} + for _, o := range opts { + o(&c.options) + } + return c +} + +// SetString is shorthand for Set(tag, key, String(msg)). +func (c *Builder) SetString(tag language.Tag, key string, msg string) error { + return c.set(tag, key, &c.index, String(msg)) +} + +// Set sets the translation for the given language and key. +// +// When evaluation this message, the first Message in the sequence to msgs to +// evaluate to a string will be the message returned. +func (c *Builder) Set(tag language.Tag, key string, msg ...Message) error { + return c.set(tag, key, &c.index, msg...) +} + +// SetMacro defines a Message that may be substituted in another message. +// The arguments to a macro Message are passed as arguments in the +// placeholder the form "${foo(arg1, arg2)}". +func (c *Builder) SetMacro(tag language.Tag, name string, msg ...Message) error { + return c.set(tag, name, &c.macros, msg...) +} + +// ErrNotFound indicates there was no message for the given key. +var ErrNotFound = errors.New("catalog: message not found") + +// String specifies a plain message string. It can be used as fallback if no +// other strings match or as a simple standalone message. +// +// It is an error to pass more than one String in a message sequence. +func String(name string) Message { + return catmsg.String(name) +} + +// Var sets a variable that may be substituted in formatting patterns using +// named substitution of the form "${name}". The name argument is used as a +// fallback if the statements do not produce a match. The statement sequence may +// not contain any Var calls. +// +// The name passed to a Var must be unique within message sequence. +func Var(name string, msg ...Message) Message { + return &catmsg.Var{Name: name, Message: firstInSequence(msg)} +} + +// Context returns a Context for formatting messages. +// Only one Message may be formatted per context at any given time. +func (b *Builder) Context(tag language.Tag, r catmsg.Renderer) *Context { + return &Context{ + cat: b, + tag: tag, + dec: catmsg.NewDecoder(tag, r, &dict{&b.macros, tag}), + } +} + +// A Context is used for evaluating Messages. +// Only one Message may be formatted per context at any given time. +type Context struct { + cat Catalog + tag language.Tag // TODO: use compact index. + dec *catmsg.Decoder +} + +// Execute looks up and executes the message with the given key. +// It returns ErrNotFound if no message could be found in the index. +func (c *Context) Execute(key string) error { + data, ok := c.cat.lookup(c.tag, key) + if !ok { + return ErrNotFound + } + return c.dec.Execute(data) +} diff --git a/vendor/golang.org/x/text/message/catalog/catalog_test.go b/vendor/golang.org/x/text/message/catalog/catalog_test.go new file mode 100644 index 0000000..08bfdc7 --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/catalog_test.go @@ -0,0 +1,296 @@ +// Copyright 2017 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 catalog + +import ( + "bytes" + "path" + "reflect" + "strings" + "testing" + + "golang.org/x/text/internal/catmsg" + "golang.org/x/text/language" +) + +type entry struct { + tag, key string + msg interface{} +} + +func langs(s string) []language.Tag { + t, _, _ := language.ParseAcceptLanguage(s) + return t +} + +type testCase struct { + desc string + cat []entry + lookup []entry + fallback string + match []string + tags []language.Tag +} + +var testCases = []testCase{{ + desc: "empty catalog", + lookup: []entry{ + {"en", "key", ""}, + {"en", "", ""}, + {"nl", "", ""}, + }, + match: []string{ + "gr -> und", + "en-US -> und", + "af -> und", + }, + tags: nil, // not an empty list. +}, { + desc: "one entry", + cat: []entry{ + {"en", "hello", "Hello!"}, + }, + lookup: []entry{ + {"und", "hello", ""}, + {"nl", "hello", ""}, + {"en", "hello", "Hello!"}, + {"en-US", "hello", "Hello!"}, + {"en-GB", "hello", "Hello!"}, + {"en-oxendict", "hello", "Hello!"}, + {"en-oxendict-u-ms-metric", "hello", "Hello!"}, + }, + match: []string{ + "gr -> en", + "en-US -> en", + }, + tags: langs("en"), +}, { + desc: "hierarchical languages", + cat: []entry{ + {"en", "hello", "Hello!"}, + {"en-GB", "hello", "Hellø!"}, + {"en-US", "hello", "Howdy!"}, + {"en", "greetings", "Greetings!"}, + {"gsw", "hello", "Grüetzi!"}, + }, + lookup: []entry{ + {"und", "hello", ""}, + {"nl", "hello", ""}, + {"en", "hello", "Hello!"}, + {"en-US", "hello", "Howdy!"}, + {"en-GB", "hello", "Hellø!"}, + {"en-oxendict", "hello", "Hello!"}, + {"en-US-oxendict-u-ms-metric", "hello", "Howdy!"}, + + {"und", "greetings", ""}, + {"nl", "greetings", ""}, + {"en", "greetings", "Greetings!"}, + {"en-US", "greetings", "Greetings!"}, + {"en-GB", "greetings", "Greetings!"}, + {"en-oxendict", "greetings", "Greetings!"}, + {"en-US-oxendict-u-ms-metric", "greetings", "Greetings!"}, + }, + fallback: "gsw", + match: []string{ + "gr -> gsw", + "en-US -> en-US", + }, + tags: langs("gsw, en, en-GB, en-US"), +}, { + desc: "variables", + cat: []entry{ + {"en", "hello %s", []Message{ + Var("person", String("Jane")), + String("Hello ${person}!"), + }}, + {"en", "hello error", []Message{ + Var("person", String("Jane")), + noMatchMessage{}, // trigger sequence path. + String("Hello ${person."), + }}, + {"en", "fallback to var value", []Message{ + Var("you", noMatchMessage{}, noMatchMessage{}), + String("Hello ${you}."), + }}, + {"en", "scopes", []Message{ + Var("person1", String("Mark")), + Var("person2", String("Jane")), + Var("couple", + Var("person1", String("Joe")), + String("${person1} and ${person2}")), + String("Hello ${couple}."), + }}, + {"en", "missing var", String("Hello ${missing}.")}, + }, + lookup: []entry{ + {"en", "hello %s", "Hello Jane!"}, + {"en", "hello error", "Hello $!(MISSINGBRACE)"}, + {"en", "fallback to var value", "Hello you."}, + {"en", "scopes", "Hello Joe and Jane."}, + {"en", "missing var", "Hello missing."}, + }, + tags: langs("en"), +}, { + desc: "macros", + cat: []entry{ + {"en", "macro1", String("Hello ${macro1(1)}.")}, + {"en", "macro2", String("Hello ${ macro1(2) }!")}, + {"en", "macroWS", String("Hello ${ macro1( 2 ) }!")}, + {"en", "missing", String("Hello ${ missing(1 }.")}, + {"en", "badnum", String("Hello ${ badnum(1b) }.")}, + {"en", "undefined", String("Hello ${ undefined(1) }.")}, + {"en", "macroU", String("Hello ${ macroU(2) }!")}, + }, + lookup: []entry{ + {"en", "macro1", "Hello Joe."}, + {"en", "macro2", "Hello Joe!"}, + {"en-US", "macroWS", "Hello Joe!"}, + {"en-NL", "missing", "Hello $!(MISSINGPAREN)."}, + {"en", "badnum", "Hello $!(BADNUM)."}, + {"en", "undefined", "Hello undefined."}, + {"en", "macroU", "Hello macroU!"}, + }, + tags: langs("en"), +}} + +func setMacros(b *Builder) { + b.SetMacro(language.English, "macro1", String("Joe")) + b.SetMacro(language.Und, "macro2", String("${macro1(1)}")) + b.SetMacro(language.English, "macroU", noMatchMessage{}) +} + +type buildFunc func(t *testing.T, tc testCase) Catalog + +func initBuilder(t *testing.T, tc testCase) Catalog { + options := []Option{} + if tc.fallback != "" { + options = append(options, Fallback(language.MustParse(tc.fallback))) + } + cat := NewBuilder(options...) + for _, e := range tc.cat { + tag := language.MustParse(e.tag) + switch msg := e.msg.(type) { + case string: + + cat.SetString(tag, e.key, msg) + case Message: + cat.Set(tag, e.key, msg) + case []Message: + cat.Set(tag, e.key, msg...) + } + } + setMacros(cat) + return cat +} + +type dictionary map[string]string + +func (d dictionary) Lookup(key string) (data string, ok bool) { + data, ok = d[key] + return data, ok +} + +func initCatalog(t *testing.T, tc testCase) Catalog { + m := map[string]Dictionary{} + for _, e := range tc.cat { + m[e.tag] = dictionary{} + } + for _, e := range tc.cat { + var msg Message + switch x := e.msg.(type) { + case string: + msg = String(x) + case Message: + msg = x + case []Message: + msg = firstInSequence(x) + } + data, _ := catmsg.Compile(language.MustParse(e.tag), nil, msg) + m[e.tag].(dictionary)[e.key] = data + } + options := []Option{} + if tc.fallback != "" { + options = append(options, Fallback(language.MustParse(tc.fallback))) + } + c, err := NewFromMap(m, options...) + if err != nil { + t.Fatal(err) + } + // TODO: implement macros for fixed catalogs. + b := NewBuilder() + setMacros(b) + c.(*catalog).macros.index = b.macros.index + return c +} + +func TestMatcher(t *testing.T) { + test := func(t *testing.T, init buildFunc) { + for _, tc := range testCases { + for _, s := range tc.match { + a := strings.Split(s, "->") + t.Run(path.Join(tc.desc, a[0]), func(t *testing.T) { + cat := init(t, tc) + got, _ := language.MatchStrings(cat.Matcher(), a[0]) + want := language.MustParse(strings.TrimSpace(a[1])) + if got != want { + t.Errorf("got %q; want %q", got, want) + } + }) + } + } + } + t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) + t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) +} + +func TestCatalog(t *testing.T) { + test := func(t *testing.T, init buildFunc) { + for _, tc := range testCases { + cat := init(t, tc) + wantTags := tc.tags + if got := cat.Languages(); !reflect.DeepEqual(got, wantTags) { + t.Errorf("%s:Languages: got %v; want %v", tc.desc, got, wantTags) + } + + for _, e := range tc.lookup { + t.Run(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) { + tag := language.MustParse(e.tag) + buf := testRenderer{} + ctx := cat.Context(tag, &buf) + want := e.msg.(string) + err := ctx.Execute(e.key) + gotFound := err != ErrNotFound + wantFound := want != "" + if gotFound != wantFound { + t.Fatalf("err: got %v (%v); want %v", gotFound, err, wantFound) + } + if got := buf.buf.String(); got != want { + t.Errorf("Lookup:\ngot %q\nwant %q", got, want) + } + }) + } + } + } + t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) + t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) +} + +type testRenderer struct { + buf bytes.Buffer +} + +func (f *testRenderer) Arg(i int) interface{} { return nil } +func (f *testRenderer) Render(s string) { f.buf.WriteString(s) } + +var msgNoMatch = catmsg.Register("no match", func(d *catmsg.Decoder) bool { + return false // no match +}) + +type noMatchMessage struct{} + +func (noMatchMessage) Compile(e *catmsg.Encoder) error { + e.EncodeMessageType(msgNoMatch) + return catmsg.ErrIncomplete +} diff --git a/vendor/golang.org/x/text/message/catalog/dict.go b/vendor/golang.org/x/text/message/catalog/dict.go new file mode 100644 index 0000000..a0eb818 --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/dict.go @@ -0,0 +1,129 @@ +// Copyright 2017 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 catalog + +import ( + "sync" + + "golang.org/x/text/internal" + "golang.org/x/text/internal/catmsg" + "golang.org/x/text/language" +) + +// TODO: +// Dictionary returns a Dictionary that returns the first Message, using the +// given language tag, that matches: +// 1. the last one registered by one of the Set methods +// 2. returned by one of the Loaders +// 3. repeat from 1. using the parent language +// This approach allows messages to be underspecified. +// func (c *Catalog) Dictionary(tag language.Tag) (Dictionary, error) { +// // TODO: verify dictionary exists. +// return &dict{&c.index, tag}, nil +// } + +type dict struct { + s *store + tag language.Tag // TODO: make compact tag. +} + +func (d *dict) Lookup(key string) (data string, ok bool) { + return d.s.lookup(d.tag, key) +} + +func (b *Builder) lookup(tag language.Tag, key string) (data string, ok bool) { + return b.index.lookup(tag, key) +} + +func (c *Builder) set(tag language.Tag, key string, s *store, msg ...Message) error { + data, err := catmsg.Compile(tag, &dict{&c.macros, tag}, firstInSequence(msg)) + + s.mutex.Lock() + defer s.mutex.Unlock() + + m := s.index[tag] + if m == nil { + m = msgMap{} + if s.index == nil { + s.index = map[language.Tag]msgMap{} + } + c.matcher = nil + s.index[tag] = m + } + + m[key] = data + return err +} + +func (c *Builder) Matcher() language.Matcher { + c.index.mutex.RLock() + m := c.matcher + c.index.mutex.RUnlock() + if m != nil { + return m + } + + c.index.mutex.Lock() + if c.matcher == nil { + c.matcher = language.NewMatcher(c.unlockedLanguages()) + } + m = c.matcher + c.index.mutex.Unlock() + return m +} + +type store struct { + mutex sync.RWMutex + index map[language.Tag]msgMap +} + +type msgMap map[string]string + +func (s *store) lookup(tag language.Tag, key string) (data string, ok bool) { + s.mutex.RLock() + defer s.mutex.RUnlock() + + for ; ; tag = tag.Parent() { + if msgs, ok := s.index[tag]; ok { + if msg, ok := msgs[key]; ok { + return msg, true + } + } + if tag == language.Und { + break + } + } + return "", false +} + +// Languages returns all languages for which the Catalog contains variants. +func (b *Builder) Languages() []language.Tag { + s := &b.index + s.mutex.RLock() + defer s.mutex.RUnlock() + + return b.unlockedLanguages() +} + +func (b *Builder) unlockedLanguages() []language.Tag { + s := &b.index + if len(s.index) == 0 { + return nil + } + tags := make([]language.Tag, 0, len(s.index)) + _, hasFallback := s.index[b.options.fallback] + offset := 0 + if hasFallback { + tags = append(tags, b.options.fallback) + offset = 1 + } + for t := range s.index { + if t != b.options.fallback { + tags = append(tags, t) + } + } + internal.SortTags(tags[offset:]) + return tags +} diff --git a/vendor/golang.org/x/text/message/catalog/go19.go b/vendor/golang.org/x/text/message/catalog/go19.go new file mode 100644 index 0000000..147fc7c --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/go19.go @@ -0,0 +1,15 @@ +// Copyright 2017 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. + +// +build go1.9 + +package catalog + +import "golang.org/x/text/internal/catmsg" + +// A Message holds a collection of translations for the same phrase that may +// vary based on the values of substitution arguments. +type Message = catmsg.Message + +type firstInSequence = catmsg.FirstOf diff --git a/vendor/golang.org/x/text/message/catalog/gopre19.go b/vendor/golang.org/x/text/message/catalog/gopre19.go new file mode 100644 index 0000000..a9753b9 --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/gopre19.go @@ -0,0 +1,23 @@ +// Copyright 2017 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. + +// +build !go1.9 + +package catalog + +import "golang.org/x/text/internal/catmsg" + +// A Message holds a collection of translations for the same phrase that may +// vary based on the values of substitution arguments. +type Message interface { + catmsg.Message +} + +func firstInSequence(m []Message) catmsg.Message { + a := []catmsg.Message{} + for _, m := range m { + a = append(a, m) + } + return catmsg.FirstOf(a) +} diff --git a/vendor/golang.org/x/text/message/catalog_test.go b/vendor/golang.org/x/text/message/catalog_test.go new file mode 100644 index 0000000..7a2301c --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog_test.go @@ -0,0 +1,43 @@ +// Copyright 2017 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 message + +import ( + "strings" + "testing" + + "golang.org/x/text/language" + "golang.org/x/text/message/catalog" +) + +func TestMatchLanguage(t *testing.T) { + c := catalog.NewBuilder(catalog.Fallback(language.English)) + c.SetString(language.Bengali, "", "") + c.SetString(language.English, "", "") + c.SetString(language.German, "", "") + + testCases := []struct { + args string // '|'-separated list + want string + }{{ + args: "de-CH", + want: "de", + }, { + args: "bn-u-nu-latn|en-US,en;q=0.9,de;q=0.8,nl;q=0.7", + want: "bn-u-nu-latn", + }, { + args: "gr", + want: "en", + }} + for _, tc := range testCases { + DefaultCatalog = c + t.Run(tc.args, func(t *testing.T) { + got := MatchLanguage(strings.Split(tc.args, "|")...) + if got != language.Make(tc.want) { + t.Errorf("got %q; want %q", got, tc.want) + } + }) + } +} diff --git a/vendor/golang.org/x/text/message/doc.go b/vendor/golang.org/x/text/message/doc.go new file mode 100644 index 0000000..2f7effd --- /dev/null +++ b/vendor/golang.org/x/text/message/doc.go @@ -0,0 +1,100 @@ +// Copyright 2017 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 message implements formatted I/O for localized strings with functions +// analogous to the fmt's print functions. It is a drop-in replacement for fmt. +// +// +// Localized Formatting +// +// A format string can be localized by replacing any of the print functions of +// fmt with an equivalent call to a Printer. +// +// p := message.NewPrinter(message.MatchLanguage("en")) +// p.Println(123456.78) // Prints 123,456.78 +// +// p.Printf("%d ducks in a row", 4331) // Prints 4,331 ducks in a row +// +// p := message.NewPrinter(message.MatchLanguage("nl")) +// p.Println("Hoogte: %f meter", 1244.9) // Prints Hoogte: 1.244,9 meter +// +// p := message.NewPrinter(message.MatchLanguage("bn")) +// p.Println(123456.78) // Prints ১,২৩,৪৫৬.৭৮ +// +// Printer currently supports numbers and specialized types for which packages +// exist in x/text. Other builtin types such as time.Time and slices are +// planned. +// +// Format strings largely have the same meaning as with fmt with the following +// notable exceptions: +// - flag # always resorts to fmt for printing +// - verb 'f', 'e', 'g', 'd' use localized formatting unless the '#' flag is +// specified. +// +// See package fmt for more options. +// +// +// Translation +// +// The format strings that are passed to Printf, Sprintf, Fprintf, or Errorf +// are used as keys to look up translations for the specified languages. +// More on how these need to be specified below. +// +// One can use arbitrary keys to distinguish between otherwise ambiguous +// strings: +// p := message.NewPrinter(language.English) +// p.Printf("archive(noun)") // Prints "archive" +// p.Printf("archive(verb)") // Prints "archive" +// +// p := message.NewPrinter(language.German) +// p.Printf("archive(noun)") // Prints "Archiv" +// p.Printf("archive(verb)") // Prints "archivieren" +// +// To retain the fallback functionality, use Key: +// p.Printf(message.Key("archive(noun)", "archive")) +// p.Printf(message.Key("archive(verb)", "archive")) +// +// +// Translation Pipeline +// +// Format strings that contain text need to be translated to support different +// locales. The first step is to extract strings that need to be translated. +// +// 1. Install gotext +// go get -u golang.org/x/text/cmd/gotext +// gotext -help +// +// 2. Mark strings in your source to be translated by using message.Printer, +// instead of the functions of the fmt package. +// +// 3. Extract the strings from your source +// +// gotext extract +// +// The output will be written to the textdata directory. +// +// 4. Send the files for translation +// +// It is planned to support multiple formats, but for now one will have to +// rewrite the JSON output to the desired format. +// +// 5. Inject translations into program +// +// 6. Repeat from 2 +// +// Right now this has to be done programmatically with calls to Set or +// SetString. These functions as well as the methods defined in +// see also package golang.org/x/text/message/catalog can be used to implement +// either dynamic or static loading of messages. +// +// +// Plural and Gender Forms +// +// Translated messages can vary based on the plural and gender forms of +// substitution values. In general, it is up to the translators to provide +// alternative translations for such forms. See the packages in +// golang.org/x/text/feature and golang.org/x/text/message/catalog for more +// information. +// +package message diff --git a/vendor/golang.org/x/text/message/examples_test.go b/vendor/golang.org/x/text/message/examples_test.go new file mode 100644 index 0000000..c73eaf9 --- /dev/null +++ b/vendor/golang.org/x/text/message/examples_test.go @@ -0,0 +1,42 @@ +// Copyright 2017 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 message_test + +import ( + "net/http" + + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +func Example_http() { + // languages supported by this service: + matcher := language.NewMatcher(message.DefaultCatalog.Languages()) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + lang, _ := r.Cookie("lang") + accept := r.Header.Get("Accept-Language") + fallback := "en" + tag, _ := language.MatchStrings(matcher, lang.String(), accept, fallback) + + p := message.NewPrinter(tag) + + p.Fprintln(w, "User language is", tag) + }) +} + +func ExamplePrinter_numbers() { + for _, lang := range []string{"en", "de", "de-CH", "fr", "bn"} { + p := message.NewPrinter(language.Make(lang)) + p.Printf("%-6s %g\n", lang, 123456.78) + } + + // Output: + // en 123,456.78 + // de 123.456,78 + // de-CH 123’456.78 + // fr 123 456,78 + // bn ১,২৩,৪৫৬.৭৮ +} diff --git a/vendor/golang.org/x/text/message/fmt_test.go b/vendor/golang.org/x/text/message/fmt_test.go new file mode 100644 index 0000000..2d6872b --- /dev/null +++ b/vendor/golang.org/x/text/message/fmt_test.go @@ -0,0 +1,1871 @@ +// Copyright 2017 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 message + +import ( + "bytes" + "fmt" + "io" + "math" + "reflect" + "runtime" + "strings" + "testing" + "time" + + "golang.org/x/text/language" +) + +type ( + renamedBool bool + renamedInt int + renamedInt8 int8 + renamedInt16 int16 + renamedInt32 int32 + renamedInt64 int64 + renamedUint uint + renamedUint8 uint8 + renamedUint16 uint16 + renamedUint32 uint32 + renamedUint64 uint64 + renamedUintptr uintptr + renamedString string + renamedBytes []byte + renamedFloat32 float32 + renamedFloat64 float64 + renamedComplex64 complex64 + renamedComplex128 complex128 +) + +func TestFmtInterface(t *testing.T) { + p := NewPrinter(language.Und) + var i1 interface{} + i1 = "abc" + s := p.Sprintf("%s", i1) + if s != "abc" { + t.Errorf(`Sprintf("%%s", empty("abc")) = %q want %q`, s, "abc") + } +} + +var ( + NaN = math.NaN() + posInf = math.Inf(1) + negInf = math.Inf(-1) + + intVar = 0 + + array = [5]int{1, 2, 3, 4, 5} + iarray = [4]interface{}{1, "hello", 2.5, nil} + slice = array[:] + islice = iarray[:] +) + +type A struct { + i int + j uint + s string + x []int +} + +type I int + +func (i I) String() string { + p := NewPrinter(language.Und) + return p.Sprintf("<%d>", int(i)) +} + +type B struct { + I I + j int +} + +type C struct { + i int + B +} + +type F int + +func (f F) Format(s fmt.State, c rune) { + p := NewPrinter(language.Und) + p.Fprintf(s, "<%c=F(%d)>", c, int(f)) +} + +type G int + +func (g G) GoString() string { + p := NewPrinter(language.Und) + return p.Sprintf("GoString(%d)", int(g)) +} + +type S struct { + F F // a struct field that Formats + G G // a struct field that GoStrings +} + +type SI struct { + I interface{} +} + +// P is a type with a String method with pointer receiver for testing %p. +type P int + +var pValue P + +func (p *P) String() string { + return "String(p)" +} + +var barray = [5]renamedUint8{1, 2, 3, 4, 5} +var bslice = barray[:] + +type byteStringer byte + +func (byteStringer) String() string { + return "X" +} + +var byteStringerSlice = []byteStringer{'h', 'e', 'l', 'l', 'o'} + +type byteFormatter byte + +func (byteFormatter) Format(f fmt.State, _ rune) { + p := NewPrinter(language.Und) + p.Fprint(f, "X") +} + +var byteFormatterSlice = []byteFormatter{'h', 'e', 'l', 'l', 'o'} + +var fmtTests = []struct { + fmt string + val interface{} + out string +}{ + // The behavior of the following tests differs from that of the fmt package. + + // Unlike with the fmt package, it is okay to have extra arguments for + // strings without format parameters. This is because it is impossible to + // distinguish between reordered or ordered format strings in this case. + // (For reordered format strings it is okay to not use arguments.) + {"", nil, ""}, + {"", 2, ""}, + {"no args", "hello", "no args"}, + + {"%017091901790959340919092959340919017929593813360", 0, "%!(NOVERB)"}, + {"%184467440737095516170v", 0, "%!(NOVERB)"}, + // Extra argument errors should format without flags set. + {"%010.2", "12345", "%!(NOVERB)"}, + + // Some key other differences, asides from localized values: + // - NaN values should not use affixes; so no signs (CLDR requirement) + // - Infinity uses patterns, so signs may be different (CLDR requirement) + // - The # flag is used to disable localization. + + // All following tests are analogous to those of the fmt package, but with + // localized numbers when appropriate. + {"%d", 12345, "12,345"}, + {"%v", 12345, "12,345"}, + {"%t", true, "true"}, + + // basic string + {"%s", "abc", "abc"}, + {"%q", "abc", `"abc"`}, + {"%x", "abc", "616263"}, + {"%x", "\xff\xf0\x0f\xff", "fff00fff"}, + {"%X", "\xff\xf0\x0f\xff", "FFF00FFF"}, + {"%x", "", ""}, + {"% x", "", ""}, + {"%#x", "", ""}, + {"%# x", "", ""}, + {"%x", "xyz", "78797a"}, + {"%X", "xyz", "78797A"}, + {"% x", "xyz", "78 79 7a"}, + {"% X", "xyz", "78 79 7A"}, + {"%#x", "xyz", "0x78797a"}, + {"%#X", "xyz", "0X78797A"}, + {"%# x", "xyz", "0x78 0x79 0x7a"}, + {"%# X", "xyz", "0X78 0X79 0X7A"}, + + // basic bytes + {"%s", []byte("abc"), "abc"}, + {"%s", [3]byte{'a', 'b', 'c'}, "abc"}, + {"%s", &[3]byte{'a', 'b', 'c'}, "&abc"}, + {"%q", []byte("abc"), `"abc"`}, + {"%x", []byte("abc"), "616263"}, + {"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"}, + {"%X", []byte("\xff\xf0\x0f\xff"), "FFF00FFF"}, + {"%x", []byte(""), ""}, + {"% x", []byte(""), ""}, + {"%#x", []byte(""), ""}, + {"%# x", []byte(""), ""}, + {"%x", []byte("xyz"), "78797a"}, + {"%X", []byte("xyz"), "78797A"}, + {"% x", []byte("xyz"), "78 79 7a"}, + {"% X", []byte("xyz"), "78 79 7A"}, + {"%#x", []byte("xyz"), "0x78797a"}, + {"%#X", []byte("xyz"), "0X78797A"}, + {"%# x", []byte("xyz"), "0x78 0x79 0x7a"}, + {"%# X", []byte("xyz"), "0X78 0X79 0X7A"}, + + // escaped strings + {"%q", "", `""`}, + {"%#q", "", "``"}, + {"%q", "\"", `"\""`}, + {"%#q", "\"", "`\"`"}, + {"%q", "`", `"` + "`" + `"`}, + {"%#q", "`", `"` + "`" + `"`}, + {"%q", "\n", `"\n"`}, + {"%#q", "\n", `"\n"`}, + {"%q", `\n`, `"\\n"`}, + {"%#q", `\n`, "`\\n`"}, + {"%q", "abc", `"abc"`}, + {"%#q", "abc", "`abc`"}, + {"%q", "日本語", `"日本語"`}, + {"%+q", "日本語", `"\u65e5\u672c\u8a9e"`}, + {"%#q", "日本語", "`日本語`"}, + {"%#+q", "日本語", "`日本語`"}, + {"%q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%+q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%#q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%#+q", "\a\b\f\n\r\t\v\"\\", `"\a\b\f\n\r\t\v\"\\"`}, + {"%q", "☺", `"☺"`}, + {"% q", "☺", `"☺"`}, // The space modifier should have no effect. + {"%+q", "☺", `"\u263a"`}, + {"%#q", "☺", "`☺`"}, + {"%#+q", "☺", "`☺`"}, + {"%10q", "⌘", ` "⌘"`}, + {"%+10q", "⌘", ` "\u2318"`}, + {"%-10q", "⌘", `"⌘" `}, + {"%+-10q", "⌘", `"\u2318" `}, + {"%010q", "⌘", `0000000"⌘"`}, + {"%+010q", "⌘", `00"\u2318"`}, + {"%-010q", "⌘", `"⌘" `}, // 0 has no effect when - is present. + {"%+-010q", "⌘", `"\u2318" `}, + {"%#8q", "\n", ` "\n"`}, + {"%#+8q", "\r", ` "\r"`}, + {"%#-8q", "\t", "` ` "}, + {"%#+-8q", "\b", `"\b" `}, + {"%q", "abc\xffdef", `"abc\xffdef"`}, + {"%+q", "abc\xffdef", `"abc\xffdef"`}, + {"%#q", "abc\xffdef", `"abc\xffdef"`}, + {"%#+q", "abc\xffdef", `"abc\xffdef"`}, + // Runes that are not printable. + {"%q", "\U0010ffff", `"\U0010ffff"`}, + {"%+q", "\U0010ffff", `"\U0010ffff"`}, + {"%#q", "\U0010ffff", "`􏿿`"}, + {"%#+q", "\U0010ffff", "`􏿿`"}, + // Runes that are not valid. + {"%q", string(0x110000), `"�"`}, + {"%+q", string(0x110000), `"\ufffd"`}, + {"%#q", string(0x110000), "`�`"}, + {"%#+q", string(0x110000), "`�`"}, + + // characters + {"%c", uint('x'), "x"}, + {"%c", 0xe4, "ä"}, + {"%c", 0x672c, "本"}, + {"%c", '日', "日"}, + {"%.0c", '⌘', "⌘"}, // Specifying precision should have no effect. + {"%3c", '⌘', " ⌘"}, + {"%-3c", '⌘', "⌘ "}, + // Runes that are not printable. + {"%c", '\U00000e00', "\u0e00"}, + {"%c", '\U0010ffff', "\U0010ffff"}, + // Runes that are not valid. + {"%c", -1, "�"}, + {"%c", 0xDC80, "�"}, + {"%c", rune(0x110000), "�"}, + {"%c", int64(0xFFFFFFFFF), "�"}, + {"%c", uint64(0xFFFFFFFFF), "�"}, + + // escaped characters + {"%q", uint(0), `'\x00'`}, + {"%+q", uint(0), `'\x00'`}, + {"%q", '"', `'"'`}, + {"%+q", '"', `'"'`}, + {"%q", '\'', `'\''`}, + {"%+q", '\'', `'\''`}, + {"%q", '`', "'`'"}, + {"%+q", '`', "'`'"}, + {"%q", 'x', `'x'`}, + {"%+q", 'x', `'x'`}, + {"%q", 'ÿ', `'ÿ'`}, + {"%+q", 'ÿ', `'\u00ff'`}, + {"%q", '\n', `'\n'`}, + {"%+q", '\n', `'\n'`}, + {"%q", '☺', `'☺'`}, + {"%+q", '☺', `'\u263a'`}, + {"% q", '☺', `'☺'`}, // The space modifier should have no effect. + {"%.0q", '☺', `'☺'`}, // Specifying precision should have no effect. + {"%10q", '⌘', ` '⌘'`}, + {"%+10q", '⌘', ` '\u2318'`}, + {"%-10q", '⌘', `'⌘' `}, + {"%+-10q", '⌘', `'\u2318' `}, + {"%010q", '⌘', `0000000'⌘'`}, + {"%+010q", '⌘', `00'\u2318'`}, + {"%-010q", '⌘', `'⌘' `}, // 0 has no effect when - is present. + {"%+-010q", '⌘', `'\u2318' `}, + // Runes that are not printable. + {"%q", '\U00000e00', `'\u0e00'`}, + {"%q", '\U0010ffff', `'\U0010ffff'`}, + // Runes that are not valid. + {"%q", int32(-1), "%!q(int32=-1)"}, + {"%q", 0xDC80, `'�'`}, + {"%q", rune(0x110000), "%!q(int32=1,114,112)"}, + {"%q", int64(0xFFFFFFFFF), "%!q(int64=68,719,476,735)"}, + {"%q", uint64(0xFFFFFFFFF), "%!q(uint64=68,719,476,735)"}, + + // width + {"%5s", "abc", " abc"}, + {"%2s", "\u263a", " ☺"}, + {"%-5s", "abc", "abc "}, + {"%-8q", "abc", `"abc" `}, + {"%05s", "abc", "00abc"}, + {"%08q", "abc", `000"abc"`}, + {"%5s", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"}, + {"%.5s", "abcdefghijklmnopqrstuvwxyz", "abcde"}, + {"%.0s", "日本語日本語", ""}, + {"%.5s", "日本語日本語", "日本語日本"}, + {"%.10s", "日本語日本語", "日本語日本語"}, + {"%.5s", []byte("日本語日本語"), "日本語日本"}, + {"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`}, + {"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"}, + {"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`}, + {"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), "6162636465"}, + {"%.3q", "日本語日本語", `"日本語"`}, + {"%.3q", []byte("日本語日本語"), `"日本語"`}, + {"%.1q", "日本語", `"日"`}, + {"%.1q", []byte("日本語"), `"日"`}, + {"%.1x", "日本語", "e6"}, + {"%.1X", []byte("日本語"), "E6"}, + {"%10.1q", "日本語日本語", ` "日"`}, + {"%10v", nil, " "}, + {"%-10v", nil, " "}, + + // integers + {"%d", uint(12345), "12,345"}, + {"%d", int(-12345), "-12,345"}, + {"%d", ^uint8(0), "255"}, + {"%d", ^uint16(0), "65,535"}, + {"%d", ^uint32(0), "4,294,967,295"}, + {"%d", ^uint64(0), "18,446,744,073,709,551,615"}, + {"%d", int8(-1 << 7), "-128"}, + {"%d", int16(-1 << 15), "-32,768"}, + {"%d", int32(-1 << 31), "-2,147,483,648"}, + {"%d", int64(-1 << 63), "-9,223,372,036,854,775,808"}, + {"%.d", 0, ""}, + {"%.0d", 0, ""}, + {"%6.0d", 0, " "}, + {"%06.0d", 0, " "}, + {"% d", 12345, " 12,345"}, + {"%+d", 12345, "+12,345"}, + {"%+d", -12345, "-12,345"}, + {"%b", 7, "111"}, + {"%b", -6, "-110"}, + {"%b", ^uint32(0), "11111111111111111111111111111111"}, + {"%b", ^uint64(0), "1111111111111111111111111111111111111111111111111111111111111111"}, + {"%b", int64(-1 << 63), zeroFill("-1", 63, "")}, + {"%o", 01234, "1234"}, + {"%#o", 01234, "01234"}, + {"%o", ^uint32(0), "37777777777"}, + {"%o", ^uint64(0), "1777777777777777777777"}, + {"%#X", 0, "0X0"}, + {"%x", 0x12abcdef, "12abcdef"}, + {"%X", 0x12abcdef, "12ABCDEF"}, + {"%x", ^uint32(0), "ffffffff"}, + {"%X", ^uint64(0), "FFFFFFFFFFFFFFFF"}, + {"%.20b", 7, "00000000000000000111"}, + {"%10d", 12345, " 12,345"}, + {"%10d", -12345, " -12,345"}, + {"%+10d", 12345, " +12,345"}, + {"%010d", 12345, "0,000,012,345"}, + {"%010d", -12345, "-0,000,012,345"}, + {"%20.8d", 1234, " 00,001,234"}, + {"%20.8d", -1234, " -00,001,234"}, + {"%020.8d", 1234, " 00,001,234"}, + {"%020.8d", -1234, " -00,001,234"}, + {"%-20.8d", 1234, "00,001,234 "}, + {"%-20.8d", -1234, "-00,001,234 "}, + {"%-#20.8x", 0x1234abc, "0x01234abc "}, + {"%-#20.8X", 0x1234abc, "0X01234ABC "}, + {"%-#20.8o", 01234, "00001234 "}, + + // Test correct f.intbuf overflow checks. + {"%068d", 1, "00," + strings.Repeat("000,", 21) + "001"}, + {"%068d", -1, "-00," + strings.Repeat("000,", 21) + "001"}, + {"%#.68x", 42, zeroFill("0x", 68, "2a")}, + {"%.68d", -42, "-00," + strings.Repeat("000,", 21) + "042"}, + {"%+.68d", 42, "+00," + strings.Repeat("000,", 21) + "042"}, + {"% .68d", 42, " 00," + strings.Repeat("000,", 21) + "042"}, + {"% +.68d", 42, "+00," + strings.Repeat("000,", 21) + "042"}, + + // unicode format + {"%U", 0, "U+0000"}, + {"%U", -1, "U+FFFFFFFFFFFFFFFF"}, + {"%U", '\n', `U+000A`}, + {"%#U", '\n', `U+000A`}, + {"%+U", 'x', `U+0078`}, // Plus flag should have no effect. + {"%# U", 'x', `U+0078 'x'`}, // Space flag should have no effect. + {"%#.2U", 'x', `U+0078 'x'`}, // Precisions below 4 should print 4 digits. + {"%U", '\u263a', `U+263A`}, + {"%#U", '\u263a', `U+263A '☺'`}, + {"%U", '\U0001D6C2', `U+1D6C2`}, + {"%#U", '\U0001D6C2', `U+1D6C2 '𝛂'`}, + {"%#14.6U", '⌘', " U+002318 '⌘'"}, + {"%#-14.6U", '⌘', "U+002318 '⌘' "}, + {"%#014.6U", '⌘', " U+002318 '⌘'"}, + {"%#-014.6U", '⌘', "U+002318 '⌘' "}, + {"%.68U", uint(42), zeroFill("U+", 68, "2A")}, + {"%#.68U", '日', zeroFill("U+", 68, "65E5") + " '日'"}, + + // floats + {"%+.3e", 0.0, "+0.000\u202f×\u202f10⁰⁰"}, + {"%+.3e", 1.0, "+1.000\u202f×\u202f10⁰⁰"}, + {"%+.3f", -1.0, "-1.000"}, + {"%+.3F", -1.0, "-1.000"}, + {"%+.3F", float32(-1.0), "-1.000"}, + {"%+07.2f", 1.0, "+001.00"}, + {"%+07.2f", -1.0, "-001.00"}, + {"%-07.2f", 1.0, "1.00 "}, + {"%-07.2f", -1.0, "-1.00 "}, + {"%+-07.2f", 1.0, "+1.00 "}, + {"%+-07.2f", -1.0, "-1.00 "}, + {"%-+07.2f", 1.0, "+1.00 "}, + {"%-+07.2f", -1.0, "-1.00 "}, + {"%+10.2f", +1.0, " +1.00"}, + {"%+10.2f", -1.0, " -1.00"}, + {"% .3E", -1.0, "-1.000\u202f×\u202f10⁰⁰"}, + {"% .3e", 1.0, " 1.000\u202f×\u202f10⁰⁰"}, + {"%+.3g", 0.0, "+0"}, + {"%+.3g", 1.0, "+1"}, + {"%+.3g", -1.0, "-1"}, + {"% .3g", -1.0, "-1"}, + {"% .3g", 1.0, " 1"}, + {"%b", float32(1.0), "8388608p-23"}, + {"%b", 1.0, "4503599627370496p-52"}, + // Test sharp flag used with floats. + {"%#g", 1e-323, "1.00000e-323"}, + {"%#g", -1.0, "-1.00000"}, + {"%#g", 1.1, "1.10000"}, + {"%#g", 123456.0, "123456."}, + {"%#g", 1234567.0, "1.234567e+06"}, + {"%#g", 1230000.0, "1.23000e+06"}, + {"%#g", 1000000.0, "1.00000e+06"}, + {"%#.0f", 1.0, "1."}, + {"%#.0e", 1.0, "1.e+00"}, + {"%#.0g", 1.0, "1."}, + {"%#.0g", 1100000.0, "1.e+06"}, + {"%#.4f", 1.0, "1.0000"}, + {"%#.4e", 1.0, "1.0000e+00"}, + {"%#.4g", 1.0, "1.000"}, + {"%#.4g", 100000.0, "1.000e+05"}, + {"%#.0f", 123.0, "123."}, + {"%#.0e", 123.0, "1.e+02"}, + {"%#.0g", 123.0, "1.e+02"}, + {"%#.4f", 123.0, "123.0000"}, + {"%#.4e", 123.0, "1.2300e+02"}, + {"%#.4g", 123.0, "123.0"}, + {"%#.4g", 123000.0, "1.230e+05"}, + {"%#9.4g", 1.0, " 1.000"}, + // The sharp flag has no effect for binary float format. + {"%#b", 1.0, "4503599627370496p-52"}, + // Precision has no effect for binary float format. + {"%.4b", float32(1.0), "8388608p-23"}, + {"%.4b", -1.0, "-4503599627370496p-52"}, + // Test correct f.intbuf boundary checks. + {"%.68f", 1.0, zeroFill("1.", 68, "")}, + {"%.68f", -1.0, zeroFill("-1.", 68, "")}, + // float infinites and NaNs + {"%f", posInf, "∞"}, + {"%.1f", negInf, "-∞"}, + {"% f", NaN, "NaN"}, + {"%20f", posInf, " ∞"}, + {"% 20F", posInf, " ∞"}, + {"% 20e", negInf, " -∞"}, + {"%+20E", negInf, " -∞"}, + {"% +20g", negInf, " -∞"}, + {"%+-20G", posInf, "+∞ "}, + {"%20e", NaN, " NaN"}, + {"% +20E", NaN, " NaN"}, + {"% -20g", NaN, "NaN "}, + {"%+-20G", NaN, "NaN "}, + // Zero padding does not apply to infinities and NaN. + {"%+020e", posInf, " +∞"}, + {"%-020f", negInf, "-∞ "}, + {"%-020E", NaN, "NaN "}, + + // complex values + {"%.f", 0i, "(0+0i)"}, + {"% .f", 0i, "( 0+0i)"}, + {"%+.f", 0i, "(+0+0i)"}, + {"% +.f", 0i, "(+0+0i)"}, + {"%+.3e", 0i, "(+0.000\u202f×\u202f10⁰⁰+0.000\u202f×\u202f10⁰⁰i)"}, + {"%+.3f", 0i, "(+0.000+0.000i)"}, + {"%+.3g", 0i, "(+0+0i)"}, + {"%+.3e", 1 + 2i, "(+1.000\u202f×\u202f10⁰⁰+2.000\u202f×\u202f10⁰⁰i)"}, + {"%+.3f", 1 + 2i, "(+1.000+2.000i)"}, + {"%+.3g", 1 + 2i, "(+1+2i)"}, + {"%.3e", 0i, "(0.000\u202f×\u202f10⁰⁰+0.000\u202f×\u202f10⁰⁰i)"}, + {"%.3f", 0i, "(0.000+0.000i)"}, + {"%.3F", 0i, "(0.000+0.000i)"}, + {"%.3F", complex64(0i), "(0.000+0.000i)"}, + {"%.3g", 0i, "(0+0i)"}, + {"%.3e", 1 + 2i, "(1.000\u202f×\u202f10⁰⁰+2.000\u202f×\u202f10⁰⁰i)"}, + {"%.3f", 1 + 2i, "(1.000+2.000i)"}, + {"%.3g", 1 + 2i, "(1+2i)"}, + {"%.3e", -1 - 2i, "(-1.000\u202f×\u202f10⁰⁰-2.000\u202f×\u202f10⁰⁰i)"}, + {"%.3f", -1 - 2i, "(-1.000-2.000i)"}, + {"%.3g", -1 - 2i, "(-1-2i)"}, + {"% .3E", -1 - 2i, "(-1.000\u202f×\u202f10⁰⁰-2.000\u202f×\u202f10⁰⁰i)"}, + {"%+.3g", 1 + 2i, "(+1+2i)"}, + {"%+.3g", complex64(1 + 2i), "(+1+2i)"}, + {"%#g", 1 + 2i, "(1.00000+2.00000i)"}, + {"%#g", 123456 + 789012i, "(123456.+789012.i)"}, + {"%#g", 1e-10i, "(0.00000+1.00000e-10i)"}, + {"%#g", -1e10 - 1.11e100i, "(-1.00000e+10-1.11000e+100i)"}, + {"%#.0f", 1.23 + 1.0i, "(1.+1.i)"}, + {"%#.0e", 1.23 + 1.0i, "(1.e+00+1.e+00i)"}, + {"%#.0g", 1.23 + 1.0i, "(1.+1.i)"}, + {"%#.0g", 0 + 100000i, "(0.+1.e+05i)"}, + {"%#.0g", 1230000 + 0i, "(1.e+06+0.i)"}, + {"%#.4f", 1 + 1.23i, "(1.0000+1.2300i)"}, + {"%#.4e", 123 + 1i, "(1.2300e+02+1.0000e+00i)"}, + {"%#.4g", 123 + 1.23i, "(123.0+1.230i)"}, + {"%#12.5g", 0 + 100000i, "( 0.0000 +1.0000e+05i)"}, + {"%#12.5g", 1230000 - 0i, "( 1.2300e+06 +0.0000i)"}, + {"%b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"}, + {"%b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"}, + // The sharp flag has no effect for binary complex format. + {"%#b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"}, + // Precision has no effect for binary complex format. + {"%.4b", 1 + 2i, "(4503599627370496p-52+4503599627370496p-51i)"}, + {"%.4b", complex64(1 + 2i), "(8388608p-23+8388608p-22i)"}, + // complex infinites and NaNs + {"%f", complex(posInf, posInf), "(∞+∞i)"}, + {"%f", complex(negInf, negInf), "(-∞-∞i)"}, + {"%f", complex(NaN, NaN), "(NaN+NaNi)"}, + {"%.1f", complex(posInf, posInf), "(∞+∞i)"}, + {"% f", complex(posInf, posInf), "( ∞+∞i)"}, + {"% f", complex(negInf, negInf), "(-∞-∞i)"}, + {"% f", complex(NaN, NaN), "(NaN+NaNi)"}, + {"%8e", complex(posInf, posInf), "( ∞ +∞i)"}, + {"% 8E", complex(posInf, posInf), "( ∞ +∞i)"}, + {"%+8f", complex(negInf, negInf), "( -∞ -∞i)"}, + {"% +8g", complex(negInf, negInf), "( -∞ -∞i)"}, // TODO(g) + {"% -8G", complex(NaN, NaN), "(NaN +NaN i)"}, + {"%+-8b", complex(NaN, NaN), "(+NaN +NaN i)"}, + // Zero padding does not apply to infinities and NaN. + {"%08f", complex(posInf, posInf), "( ∞ +∞i)"}, + {"%-08g", complex(negInf, negInf), "(-∞ -∞ i)"}, + {"%-08G", complex(NaN, NaN), "(NaN +NaN i)"}, + + // old test/fmt_test.go + {"%e", 1.0, "1.000000\u202f×\u202f10⁰⁰"}, + {"%e", 1234.5678e3, "1.234568\u202f×\u202f10⁰⁶"}, + {"%e", 1234.5678e-8, "1.234568\u202f×\u202f10⁻⁰⁵"}, + {"%e", -7.0, "-7.000000\u202f×\u202f10⁰⁰"}, + {"%e", -1e-9, "-1.000000\u202f×\u202f10⁻⁰⁹"}, + {"%f", 1234.5678e3, "1,234,567.800000"}, + {"%f", 1234.5678e-8, "0.000012"}, + {"%f", -7.0, "-7.000000"}, + {"%f", -1e-9, "-0.000000"}, + {"%g", 1234.5678e3, "1.2345678\u202f×\u202f10⁰⁶"}, + {"%g", float32(1234.5678e3), "1.2345678\u202f×\u202f10⁰⁶"}, + {"%g", 1234.5678e-8, "1.2345678\u202f×\u202f10⁻⁰⁵"}, + {"%g", -7.0, "-7"}, + {"%g", -1e-9, "-1\u202f×\u202f10⁻⁰⁹"}, + {"%g", float32(-1e-9), "-1\u202f×\u202f10⁻⁰⁹"}, + {"%E", 1.0, "1.000000\u202f×\u202f10⁰⁰"}, + {"%E", 1234.5678e3, "1.234568\u202f×\u202f10⁰⁶"}, + {"%E", 1234.5678e-8, "1.234568\u202f×\u202f10⁻⁰⁵"}, + {"%E", -7.0, "-7.000000\u202f×\u202f10⁰⁰"}, + {"%E", -1e-9, "-1.000000\u202f×\u202f10⁻⁰⁹"}, + {"%G", 1234.5678e3, "1.2345678\u202f×\u202f10⁰⁶"}, + {"%G", float32(1234.5678e3), "1.2345678\u202f×\u202f10⁰⁶"}, + {"%G", 1234.5678e-8, "1.2345678\u202f×\u202f10⁻⁰⁵"}, + {"%G", -7.0, "-7"}, + {"%G", -1e-9, "-1\u202f×\u202f10⁻⁰⁹"}, + {"%G", float32(-1e-9), "-1\u202f×\u202f10⁻⁰⁹"}, + {"%20.5s", "qwertyuiop", " qwert"}, + {"%.5s", "qwertyuiop", "qwert"}, + {"%-20.5s", "qwertyuiop", "qwert "}, + {"%20c", 'x', " x"}, + {"%-20c", 'x', "x "}, + {"%20.6e", 1.2345e3, " 1.234500\u202f×\u202f10⁰³"}, + {"%20.6e", 1.2345e-3, " 1.234500\u202f×\u202f10⁻⁰³"}, + {"%20e", 1.2345e3, " 1.234500\u202f×\u202f10⁰³"}, + {"%20e", 1.2345e-3, " 1.234500\u202f×\u202f10⁻⁰³"}, + {"%20.8e", 1.2345e3, " 1.23450000\u202f×\u202f10⁰³"}, + {"%20f", 1.23456789e3, " 1,234.567890"}, + {"%20f", 1.23456789e-3, " 0.001235"}, + {"%20f", 12345678901.23456789, "12,345,678,901.234568"}, + {"%-20f", 1.23456789e3, "1,234.567890 "}, + {"%20.8f", 1.23456789e3, " 1,234.56789000"}, + {"%20.8f", 1.23456789e-3, " 0.00123457"}, + {"%g", 1.23456789e3, "1,234.56789"}, + {"%g", 1.23456789e-3, "0.00123456789"}, + {"%g", 1.23456789e20, "1.23456789\u202f×\u202f10²⁰"}, + + // arrays + {"%v", array, "[1 2 3 4 5]"}, + {"%v", iarray, "[1 hello 2.5 ]"}, + {"%v", barray, "[1 2 3 4 5]"}, + {"%v", &array, "&[1 2 3 4 5]"}, + {"%v", &iarray, "&[1 hello 2.5 ]"}, + {"%v", &barray, "&[1 2 3 4 5]"}, + + // slices + {"%v", slice, "[1 2 3 4 5]"}, + {"%v", islice, "[1 hello 2.5 ]"}, + {"%v", bslice, "[1 2 3 4 5]"}, + {"%v", &slice, "&[1 2 3 4 5]"}, + {"%v", &islice, "&[1 hello 2.5 ]"}, + {"%v", &bslice, "&[1 2 3 4 5]"}, + + // byte arrays and slices with %b,%c,%d,%o,%U and %v + {"%b", [3]byte{65, 66, 67}, "[1000001 1000010 1000011]"}, + {"%c", [3]byte{65, 66, 67}, "[A B C]"}, + {"%d", [3]byte{65, 66, 67}, "[65 66 67]"}, + {"%o", [3]byte{65, 66, 67}, "[101 102 103]"}, + {"%U", [3]byte{65, 66, 67}, "[U+0041 U+0042 U+0043]"}, + {"%v", [3]byte{65, 66, 67}, "[65 66 67]"}, + {"%v", [1]byte{123}, "[123]"}, + {"%012v", []byte{}, "[]"}, + {"%#012v", []byte{}, "[]byte{}"}, + {"%6v", []byte{1, 11, 111}, "[ 1 11 111]"}, + {"%06v", []byte{1, 11, 111}, "[000001 000011 000111]"}, + {"%-6v", []byte{1, 11, 111}, "[1 11 111 ]"}, + {"%-06v", []byte{1, 11, 111}, "[1 11 111 ]"}, + {"%#v", []byte{1, 11, 111}, "[]byte{0x1, 0xb, 0x6f}"}, + {"%#6v", []byte{1, 11, 111}, "[]byte{ 0x1, 0xb, 0x6f}"}, + {"%#06v", []byte{1, 11, 111}, "[]byte{0x000001, 0x00000b, 0x00006f}"}, + {"%#-6v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"}, + {"%#-06v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"}, + // f.space should and f.plus should not have an effect with %v. + {"% v", []byte{1, 11, 111}, "[ 1 11 111]"}, + {"%+v", [3]byte{1, 11, 111}, "[1 11 111]"}, + {"%# -6v", []byte{1, 11, 111}, "[]byte{ 0x1 , 0xb , 0x6f }"}, + {"%#+-6v", [3]byte{1, 11, 111}, "[3]uint8{0x1 , 0xb , 0x6f }"}, + // f.space and f.plus should have an effect with %d. + {"% d", []byte{1, 11, 111}, "[ 1 11 111]"}, + {"%+d", [3]byte{1, 11, 111}, "[+1 +11 +111]"}, + {"%# -6d", []byte{1, 11, 111}, "[ 1 11 111 ]"}, + {"%#+-6d", [3]byte{1, 11, 111}, "[+1 +11 +111 ]"}, + + // floates with %v + {"%v", 1.2345678, "1.2345678"}, + {"%v", float32(1.2345678), "1.2345678"}, + + // complexes with %v + {"%v", 1 + 2i, "(1+2i)"}, + {"%v", complex64(1 + 2i), "(1+2i)"}, + + // structs + {"%v", A{1, 2, "a", []int{1, 2}}, `{1 2 a [1 2]}`}, + {"%+v", A{1, 2, "a", []int{1, 2}}, `{i:1 j:2 s:a x:[1 2]}`}, + + // +v on structs with Stringable items + {"%+v", B{1, 2}, `{I:<1> j:2}`}, + {"%+v", C{1, B{2, 3}}, `{i:1 B:{I:<2> j:3}}`}, + + // other formats on Stringable items + {"%s", I(23), `<23>`}, + {"%q", I(23), `"<23>"`}, + {"%x", I(23), `3c32333e`}, + {"%#x", I(23), `0x3c32333e`}, + {"%# x", I(23), `0x3c 0x32 0x33 0x3e`}, + // Stringer applies only to string formats. + {"%d", I(23), `23`}, + // Stringer applies to the extracted value. + {"%s", reflect.ValueOf(I(23)), `<23>`}, + + // go syntax + {"%#v", A{1, 2, "a", []int{1, 2}}, `message.A{i:1, j:0x2, s:"a", x:[]int{1, 2}}`}, + {"%#v", new(byte), "(*uint8)(0xPTR)"}, + {"%#v", TestFmtInterface, "(func(*testing.T))(0xPTR)"}, + {"%#v", make(chan int), "(chan int)(0xPTR)"}, + {"%#v", uint64(1<<64 - 1), "0xffffffffffffffff"}, + {"%#v", 1000000000, "1000000000"}, + {"%#v", map[string]int{"a": 1}, `map[string]int{"a":1}`}, + {"%#v", map[string]B{"a": {1, 2}}, `map[string]message.B{"a":message.B{I:1, j:2}}`}, + {"%#v", []string{"a", "b"}, `[]string{"a", "b"}`}, + {"%#v", SI{}, `message.SI{I:interface {}(nil)}`}, + {"%#v", []int(nil), `[]int(nil)`}, + {"%#v", []int{}, `[]int{}`}, + {"%#v", array, `[5]int{1, 2, 3, 4, 5}`}, + {"%#v", &array, `&[5]int{1, 2, 3, 4, 5}`}, + {"%#v", iarray, `[4]interface {}{1, "hello", 2.5, interface {}(nil)}`}, + {"%#v", &iarray, `&[4]interface {}{1, "hello", 2.5, interface {}(nil)}`}, + {"%#v", map[int]byte(nil), `map[int]uint8(nil)`}, + {"%#v", map[int]byte{}, `map[int]uint8{}`}, + {"%#v", "foo", `"foo"`}, + {"%#v", barray, `[5]message.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`}, + {"%#v", bslice, `[]message.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`}, + {"%#v", []int32(nil), "[]int32(nil)"}, + {"%#v", 1.2345678, "1.2345678"}, + {"%#v", float32(1.2345678), "1.2345678"}, + // Only print []byte and []uint8 as type []byte if they appear at the top level. + {"%#v", []byte(nil), "[]byte(nil)"}, + {"%#v", []uint8(nil), "[]byte(nil)"}, + {"%#v", []byte{}, "[]byte{}"}, + {"%#v", []uint8{}, "[]byte{}"}, + {"%#v", reflect.ValueOf([]byte{}), "[]uint8{}"}, + {"%#v", reflect.ValueOf([]uint8{}), "[]uint8{}"}, + {"%#v", &[]byte{}, "&[]uint8{}"}, + {"%#v", &[]byte{}, "&[]uint8{}"}, + {"%#v", [3]byte{}, "[3]uint8{0x0, 0x0, 0x0}"}, + {"%#v", [3]uint8{}, "[3]uint8{0x0, 0x0, 0x0}"}, + + // slices with other formats + {"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`}, + {"%x", []int{1, 2, 15}, `[1 2 f]`}, + {"%d", []int{1, 2, 15}, `[1 2 15]`}, + {"%d", []byte{1, 2, 15}, `[1 2 15]`}, + {"%q", []string{"a", "b"}, `["a" "b"]`}, + {"% 02x", []byte{1}, "01"}, + {"% 02x", []byte{1, 2, 3}, "01 02 03"}, + + // Padding with byte slices. + {"%2x", []byte{}, " "}, + {"%#2x", []byte{}, " "}, + {"% 02x", []byte{}, "00"}, + {"%# 02x", []byte{}, "00"}, + {"%-2x", []byte{}, " "}, + {"%-02x", []byte{}, " "}, + {"%8x", []byte{0xab}, " ab"}, + {"% 8x", []byte{0xab}, " ab"}, + {"%#8x", []byte{0xab}, " 0xab"}, + {"%# 8x", []byte{0xab}, " 0xab"}, + {"%08x", []byte{0xab}, "000000ab"}, + {"% 08x", []byte{0xab}, "000000ab"}, + {"%#08x", []byte{0xab}, "00000xab"}, + {"%# 08x", []byte{0xab}, "00000xab"}, + {"%10x", []byte{0xab, 0xcd}, " abcd"}, + {"% 10x", []byte{0xab, 0xcd}, " ab cd"}, + {"%#10x", []byte{0xab, 0xcd}, " 0xabcd"}, + {"%# 10x", []byte{0xab, 0xcd}, " 0xab 0xcd"}, + {"%010x", []byte{0xab, 0xcd}, "000000abcd"}, + {"% 010x", []byte{0xab, 0xcd}, "00000ab cd"}, + {"%#010x", []byte{0xab, 0xcd}, "00000xabcd"}, + {"%# 010x", []byte{0xab, 0xcd}, "00xab 0xcd"}, + {"%-10X", []byte{0xab}, "AB "}, + {"% -010X", []byte{0xab}, "AB "}, + {"%#-10X", []byte{0xab, 0xcd}, "0XABCD "}, + {"%# -010X", []byte{0xab, 0xcd}, "0XAB 0XCD "}, + // Same for strings + {"%2x", "", " "}, + {"%#2x", "", " "}, + {"% 02x", "", "00"}, + {"%# 02x", "", "00"}, + {"%-2x", "", " "}, + {"%-02x", "", " "}, + {"%8x", "\xab", " ab"}, + {"% 8x", "\xab", " ab"}, + {"%#8x", "\xab", " 0xab"}, + {"%# 8x", "\xab", " 0xab"}, + {"%08x", "\xab", "000000ab"}, + {"% 08x", "\xab", "000000ab"}, + {"%#08x", "\xab", "00000xab"}, + {"%# 08x", "\xab", "00000xab"}, + {"%10x", "\xab\xcd", " abcd"}, + {"% 10x", "\xab\xcd", " ab cd"}, + {"%#10x", "\xab\xcd", " 0xabcd"}, + {"%# 10x", "\xab\xcd", " 0xab 0xcd"}, + {"%010x", "\xab\xcd", "000000abcd"}, + {"% 010x", "\xab\xcd", "00000ab cd"}, + {"%#010x", "\xab\xcd", "00000xabcd"}, + {"%# 010x", "\xab\xcd", "00xab 0xcd"}, + {"%-10X", "\xab", "AB "}, + {"% -010X", "\xab", "AB "}, + {"%#-10X", "\xab\xcd", "0XABCD "}, + {"%# -010X", "\xab\xcd", "0XAB 0XCD "}, + + // renamings + {"%v", renamedBool(true), "true"}, + {"%d", renamedBool(true), "%!d(message.renamedBool=true)"}, + {"%o", renamedInt(8), "10"}, + {"%d", renamedInt8(-9), "-9"}, + {"%v", renamedInt16(10), "10"}, + {"%v", renamedInt32(-11), "-11"}, + {"%X", renamedInt64(255), "FF"}, + {"%v", renamedUint(13), "13"}, + {"%o", renamedUint8(14), "16"}, + {"%X", renamedUint16(15), "F"}, + {"%d", renamedUint32(16), "16"}, + {"%X", renamedUint64(17), "11"}, + {"%o", renamedUintptr(18), "22"}, + {"%x", renamedString("thing"), "7468696e67"}, + {"%d", renamedBytes([]byte{1, 2, 15}), `[1 2 15]`}, + {"%q", renamedBytes([]byte("hello")), `"hello"`}, + {"%x", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "68656c6c6f"}, + {"%X", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "68656C6C6F"}, + {"%s", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, "hello"}, + {"%q", []renamedUint8{'h', 'e', 'l', 'l', 'o'}, `"hello"`}, + {"%v", renamedFloat32(22), "22"}, + {"%v", renamedFloat64(33), "33"}, + {"%v", renamedComplex64(3 + 4i), "(3+4i)"}, + {"%v", renamedComplex128(4 - 3i), "(4-3i)"}, + + // Formatter + {"%x", F(1), ""}, + {"%x", G(2), "2"}, + {"%+v", S{F(4), G(5)}, "{F: G:5}"}, + + // GoStringer + {"%#v", G(6), "GoString(6)"}, + {"%#v", S{F(7), G(8)}, "message.S{F:, G:GoString(8)}"}, + + // %T + {"%T", byte(0), "uint8"}, + {"%T", reflect.ValueOf(nil), "reflect.Value"}, + {"%T", (4 - 3i), "complex128"}, + {"%T", renamedComplex128(4 - 3i), "message.renamedComplex128"}, + {"%T", intVar, "int"}, + {"%6T", &intVar, " *int"}, + {"%10T", nil, " "}, + {"%-10T", nil, " "}, + + // %p with pointers + {"%p", (*int)(nil), "0x0"}, + {"%#p", (*int)(nil), "0"}, + {"%p", &intVar, "0xPTR"}, + {"%#p", &intVar, "PTR"}, + {"%p", &array, "0xPTR"}, + {"%p", &slice, "0xPTR"}, + {"%8.2p", (*int)(nil), " 0x00"}, + {"%-20.16p", &intVar, "0xPTR "}, + // %p on non-pointers + {"%p", make(chan int), "0xPTR"}, + {"%p", make(map[int]int), "0xPTR"}, + {"%p", func() {}, "0xPTR"}, + {"%p", 27, "%!p(int=27)"}, // not a pointer at all + {"%p", nil, "%!p()"}, // nil on its own has no type ... + {"%#p", nil, "%!p()"}, // ... and hence is not a pointer type. + // pointers with specified base + {"%b", &intVar, "PTR_b"}, + {"%d", &intVar, "PTR_d"}, + {"%o", &intVar, "PTR_o"}, + {"%x", &intVar, "PTR_x"}, + {"%X", &intVar, "PTR_X"}, + // %v on pointers + {"%v", nil, ""}, + {"%#v", nil, ""}, + {"%v", (*int)(nil), ""}, + {"%#v", (*int)(nil), "(*int)(nil)"}, + {"%v", &intVar, "0xPTR"}, + {"%#v", &intVar, "(*int)(0xPTR)"}, + {"%8.2v", (*int)(nil), " "}, + {"%-20.16v", &intVar, "0xPTR "}, + // string method on pointer + {"%s", &pValue, "String(p)"}, // String method... + {"%p", &pValue, "0xPTR"}, // ... is not called with %p. + + // %d on Stringer should give integer if possible + {"%s", time.Time{}.Month(), "January"}, + {"%d", time.Time{}.Month(), "1"}, + + // erroneous things + {"%s %", "hello", "hello %!(NOVERB)"}, + {"%s %.2", "hello", "hello %!(NOVERB)"}, + + // The "" show up because maps are printed by + // first obtaining a list of keys and then looking up + // each key. Since NaNs can be map keys but cannot + // be fetched directly, the lookup fails and returns a + // zero reflect.Value, which formats as . + // This test is just to check that it shows the two NaNs at all. + {"%v", map[float64]int{NaN: 1, NaN: 2}, "map[NaN: NaN:]"}, + + // Comparison of padding rules with C printf. + /* + C program: + #include + + char *format[] = { + "[%.2f]", + "[% .2f]", + "[%+.2f]", + "[%7.2f]", + "[% 7.2f]", + "[%+7.2f]", + "[% +7.2f]", + "[%07.2f]", + "[% 07.2f]", + "[%+07.2f]", + "[% +07.2f]" + }; + + int main(void) { + int i; + for(i = 0; i < 11; i++) { + printf("%s: ", format[i]); + printf(format[i], 1.0); + printf(" "); + printf(format[i], -1.0); + printf("\n"); + } + } + + Output: + [%.2f]: [1.00] [-1.00] + [% .2f]: [ 1.00] [-1.00] + [%+.2f]: [+1.00] [-1.00] + [%7.2f]: [ 1.00] [ -1.00] + [% 7.2f]: [ 1.00] [ -1.00] + [%+7.2f]: [ +1.00] [ -1.00] + [% +7.2f]: [ +1.00] [ -1.00] + [%07.2f]: [0001.00] [-001.00] + [% 07.2f]: [ 001.00] [-001.00] + [%+07.2f]: [+001.00] [-001.00] + [% +07.2f]: [+001.00] [-001.00] + + */ + {"%.2f", 1.0, "1.00"}, + {"%.2f", -1.0, "-1.00"}, + {"% .2f", 1.0, " 1.00"}, + {"% .2f", -1.0, "-1.00"}, + {"%+.2f", 1.0, "+1.00"}, + {"%+.2f", -1.0, "-1.00"}, + {"%7.2f", 1.0, " 1.00"}, + {"%7.2f", -1.0, " -1.00"}, + {"% 7.2f", 1.0, " 1.00"}, + {"% 7.2f", -1.0, " -1.00"}, + {"%+7.2f", 1.0, " +1.00"}, + {"%+7.2f", -1.0, " -1.00"}, + {"% +7.2f", 1.0, " +1.00"}, + {"% +7.2f", -1.0, " -1.00"}, + // Padding with 0's indicates minimum number of integer digits minus the + // period, if present, and minus the sign if it is fixed. + // TODO: consider making this number the number of significant digits. + {"%07.2f", 1.0, "0,001.00"}, + {"%07.2f", -1.0, "-0,001.00"}, + {"% 07.2f", 1.0, " 001.00"}, + {"% 07.2f", -1.0, "-001.00"}, + {"%+07.2f", 1.0, "+001.00"}, + {"%+07.2f", -1.0, "-001.00"}, + {"% +07.2f", 1.0, "+001.00"}, + {"% +07.2f", -1.0, "-001.00"}, + + // Complex numbers: exhaustively tested in TestComplexFormatting. + {"%7.2f", 1 + 2i, "( 1.00 +2.00i)"}, + {"%+07.2f", -1 - 2i, "(-001.00-002.00i)"}, + + // Use spaces instead of zero if padding to the right. + {"%0-5s", "abc", "abc "}, + {"%-05.1f", 1.0, "1.0 "}, + + // float and complex formatting should not change the padding width + // for other elements. See issue 14642. + {"%06v", []interface{}{+10.0, 10}, "[000,010 000,010]"}, + {"%06v", []interface{}{-10.0, 10}, "[-000,010 000,010]"}, + {"%06v", []interface{}{+10.0 + 10i, 10}, "[(000,010+00,010i) 000,010]"}, + {"%06v", []interface{}{-10.0 + 10i, 10}, "[(-000,010+00,010i) 000,010]"}, + + // integer formatting should not alter padding for other elements. + {"%03.6v", []interface{}{1, 2.0, "x"}, "[000,001 002 00x]"}, + {"%03.0v", []interface{}{0, 2.0, "x"}, "[ 002 000]"}, + + // Complex fmt used to leave the plus flag set for future entries in the array + // causing +2+0i and +3+0i instead of 2+0i and 3+0i. + {"%v", []complex64{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"}, + {"%v", []complex128{1, 2, 3}, "[(1+0i) (2+0i) (3+0i)]"}, + + // Incomplete format specification caused crash. + {"%.", 3, "%!.(int=3)"}, + + // Padding for complex numbers. Has been bad, then fixed, then bad again. + {"%+10.2f", +104.66 + 440.51i, "( +104.66 +440.51i)"}, + {"%+10.2f", -104.66 + 440.51i, "( -104.66 +440.51i)"}, + {"%+10.2f", +104.66 - 440.51i, "( +104.66 -440.51i)"}, + {"%+10.2f", -104.66 - 440.51i, "( -104.66 -440.51i)"}, + {"%010.2f", +104.66 + 440.51i, "(0,000,104.66+000,440.51i)"}, + {"%+010.2f", +104.66 + 440.51i, "(+000,104.66+000,440.51i)"}, + {"%+010.2f", -104.66 + 440.51i, "(-000,104.66+000,440.51i)"}, + {"%+010.2f", +104.66 - 440.51i, "(+000,104.66-000,440.51i)"}, + {"%+010.2f", -104.66 - 440.51i, "(-000,104.66-000,440.51i)"}, + + // []T where type T is a byte with a Stringer method. + {"%v", byteStringerSlice, "[X X X X X]"}, + {"%s", byteStringerSlice, "hello"}, + {"%q", byteStringerSlice, "\"hello\""}, + {"%x", byteStringerSlice, "68656c6c6f"}, + {"%X", byteStringerSlice, "68656C6C6F"}, + {"%#v", byteStringerSlice, "[]message.byteStringer{0x68, 0x65, 0x6c, 0x6c, 0x6f}"}, + + // And the same for Formatter. + {"%v", byteFormatterSlice, "[X X X X X]"}, + {"%s", byteFormatterSlice, "hello"}, + {"%q", byteFormatterSlice, "\"hello\""}, + {"%x", byteFormatterSlice, "68656c6c6f"}, + {"%X", byteFormatterSlice, "68656C6C6F"}, + // This next case seems wrong, but the docs say the Formatter wins here. + {"%#v", byteFormatterSlice, "[]message.byteFormatter{X, X, X, X, X}"}, + + // reflect.Value handled specially in Go 1.5, making it possible to + // see inside non-exported fields (which cannot be accessed with Interface()). + // Issue 8965. + {"%v", reflect.ValueOf(A{}).Field(0).String(), ""}, // Equivalent to the old way. + {"%v", reflect.ValueOf(A{}).Field(0), "0"}, // Sees inside the field. + + // verbs apply to the extracted value too. + {"%s", reflect.ValueOf("hello"), "hello"}, + {"%q", reflect.ValueOf("hello"), `"hello"`}, + {"%#04x", reflect.ValueOf(256), "0x0100"}, + + // invalid reflect.Value doesn't crash. + {"%v", reflect.Value{}, ""}, + {"%v", &reflect.Value{}, ""}, + {"%v", SI{reflect.Value{}}, "{}"}, + + // Tests to check that not supported verbs generate an error string. + {"%☠", nil, "%!☠()"}, + {"%☠", interface{}(nil), "%!☠()"}, + {"%☠", int(0), "%!☠(int=0)"}, + {"%☠", uint(0), "%!☠(uint=0)"}, + {"%☠", []byte{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"}, + {"%☠", []uint8{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"}, + {"%☠", [1]byte{0}, "[%!☠(uint8=0)]"}, + {"%☠", [1]uint8{0}, "[%!☠(uint8=0)]"}, + {"%☠", "hello", "%!☠(string=hello)"}, + {"%☠", 1.2345678, "%!☠(float64=1.2345678)"}, + {"%☠", float32(1.2345678), "%!☠(float32=1.2345678)"}, + {"%☠", 1.2345678 + 1.2345678i, "%!☠(complex128=(1.2345678+1.2345678i))"}, + {"%☠", complex64(1.2345678 + 1.2345678i), "%!☠(complex64=(1.2345678+1.2345678i))"}, + {"%☠", &intVar, "%!☠(*int=0xPTR)"}, + {"%☠", make(chan int), "%!☠(chan int=0xPTR)"}, + {"%☠", func() {}, "%!☠(func()=0xPTR)"}, + {"%☠", reflect.ValueOf(renamedInt(0)), "%!☠(message.renamedInt=0)"}, + {"%☠", SI{renamedInt(0)}, "{%!☠(message.renamedInt=0)}"}, + {"%☠", &[]interface{}{I(1), G(2)}, "&[%!☠(message.I=1) %!☠(message.G=2)]"}, + {"%☠", SI{&[]interface{}{I(1), G(2)}}, "{%!☠(*[]interface {}=&[1 2])}"}, + {"%☠", reflect.Value{}, ""}, + {"%☠", map[float64]int{NaN: 1}, "map[%!☠(float64=NaN):%!☠()]"}, +} + +// zeroFill generates zero-filled strings of the specified width. The length +// of the suffix (but not the prefix) is compensated for in the width calculation. +func zeroFill(prefix string, width int, suffix string) string { + return prefix + strings.Repeat("0", width-len(suffix)) + suffix +} + +func TestSprintf(t *testing.T) { + p := NewPrinter(language.Und) + for _, tt := range fmtTests { + t.Run(fmt.Sprint(tt.fmt, "/", tt.val), func(t *testing.T) { + s := p.Sprintf(tt.fmt, tt.val) + i := strings.Index(tt.out, "PTR") + if i >= 0 && i < len(s) { + var pattern, chars string + switch { + case strings.HasPrefix(tt.out[i:], "PTR_b"): + pattern = "PTR_b" + chars = "01" + case strings.HasPrefix(tt.out[i:], "PTR_o"): + pattern = "PTR_o" + chars = "01234567" + case strings.HasPrefix(tt.out[i:], "PTR_d"): + pattern = "PTR_d" + chars = "0123456789" + case strings.HasPrefix(tt.out[i:], "PTR_x"): + pattern = "PTR_x" + chars = "0123456789abcdef" + case strings.HasPrefix(tt.out[i:], "PTR_X"): + pattern = "PTR_X" + chars = "0123456789ABCDEF" + default: + pattern = "PTR" + chars = "0123456789abcdefABCDEF" + } + p := s[:i] + pattern + for j := i; j < len(s); j++ { + if !strings.ContainsRune(chars, rune(s[j])) { + p += s[j:] + break + } + } + s = p + } + if s != tt.out { + if _, ok := tt.val.(string); ok { + // Don't requote the already-quoted strings. + // It's too confusing to read the errors. + t.Errorf("Sprintf(%q, %q) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out) + } else { + t.Errorf("Sprintf(%q, %v) = %q want %q", tt.fmt, tt.val, s, tt.out) + } + } + }) + } +} + +var f float64 + +// TestComplexFormatting checks that a complex always formats to the same +// thing as if done by hand with two singleton prints. +func TestComplexFormatting(t *testing.T) { + var yesNo = []bool{true, false} + var values = []float64{1, 0, -1, posInf, negInf, NaN} + p := NewPrinter(language.Und) + for _, plus := range yesNo { + for _, zero := range yesNo { + for _, space := range yesNo { + for _, char := range "fFeEgG" { + realFmt := "%" + if zero { + realFmt += "0" + } + if space { + realFmt += " " + } + if plus { + realFmt += "+" + } + realFmt += "10.2" + realFmt += string(char) + // Imaginary part always has a sign, so force + and ignore space. + imagFmt := "%" + if zero { + imagFmt += "0" + } + imagFmt += "+" + imagFmt += "10.2" + imagFmt += string(char) + for _, realValue := range values { + for _, imagValue := range values { + one := p.Sprintf(realFmt, complex(realValue, imagValue)) + two := p.Sprintf("("+realFmt+imagFmt+"i)", realValue, imagValue) + if math.IsNaN(imagValue) { + p := len(two) - len("NaNi)") - 1 + if two[p] == ' ' { + two = two[:p] + "+" + two[p+1:] + } else { + two = two[:p+1] + "+" + two[p+1:] + } + } + if one != two { + t.Error(f, one, two) + } + } + } + } + } + } + } +} + +type SE []interface{} // slice of empty; notational compactness. + +var reorderTests = []struct { + format string + args SE + out string +}{ + {"%[1]d", SE{1}, "1"}, + {"%[2]d", SE{2, 1}, "1"}, + {"%[2]d %[1]d", SE{1, 2}, "2 1"}, + {"%[2]*[1]d", SE{2, 5}, " 2"}, + {"%6.2f", SE{12.0}, " 12.00"}, // Explicit version of next line. + {"%[3]*.[2]*[1]f", SE{12.0, 2, 6}, " 12.00"}, + {"%[1]*.[2]*[3]f", SE{6, 2, 12.0}, " 12.00"}, + {"%10f", SE{12.0}, " 12.000000"}, + {"%[1]*[3]f", SE{10, 99, 12.0}, " 12.000000"}, + {"%.6f", SE{12.0}, "12.000000"}, // Explicit version of next line. + {"%.[1]*[3]f", SE{6, 99, 12.0}, "12.000000"}, + {"%6.f", SE{12.0}, " 12"}, // // Explicit version of next line; empty precision means zero. + {"%[1]*.[3]f", SE{6, 3, 12.0}, " 12"}, + // An actual use! Print the same arguments twice. + {"%d %d %d %#[1]o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015"}, + + // Erroneous cases. + {"%[d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%]d", SE{2, 1}, "%!](int=2)d%!(EXTRA int=1)"}, + {"%[]d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%[-3]d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%[99]d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%[3]", SE{2, 1}, "%!(NOVERB)"}, + {"%[1].2d", SE{5, 6}, "%!d(BADINDEX)"}, + {"%[1]2d", SE{2, 1}, "%!d(BADINDEX)"}, + {"%3.[2]d", SE{7}, "%!d(BADINDEX)"}, + {"%.[2]d", SE{7}, "%!d(BADINDEX)"}, + {"%d %d %d %#[1]o %#o %#o %#o", SE{11, 12, 13}, "11 12 13 013 014 015 %!o(MISSING)"}, + {"%[5]d %[2]d %d", SE{1, 2, 3}, "%!d(BADINDEX) 2 3"}, + {"%d %[3]d %d", SE{1, 2}, "1 %!d(BADINDEX) 2"}, // Erroneous index does not affect sequence. + {"%.[]", SE{}, "%!](BADINDEX)"}, // Issue 10675 + {"%.-3d", SE{42}, "%!-(int=42)3d"}, // TODO: Should this set return better error messages? + // The following messages are interpreted as if there is no substitution, + // in which case it is okay to have extra arguments. This is different + // semantics from the fmt package. + {"%2147483648d", SE{42}, "%!(NOVERB)"}, + {"%-2147483648d", SE{42}, "%!(NOVERB)"}, + {"%.2147483648d", SE{42}, "%!(NOVERB)"}, +} + +func TestReorder(t *testing.T) { + p := NewPrinter(language.Und) + for _, tc := range reorderTests { + t.Run(fmt.Sprint(tc.format, "/", tc.args), func(t *testing.T) { + s := p.Sprintf(tc.format, tc.args...) + if s != tc.out { + t.Errorf("Sprintf(%q, %v) = %q want %q", tc.format, tc.args, s, tc.out) + } + }) + } +} + +func BenchmarkSprintfPadding(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%16f", 1.0) + } + }) +} + +func BenchmarkSprintfEmpty(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("") + } + }) +} + +func BenchmarkSprintfString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%s", "hello") + } + }) +} + +func BenchmarkSprintfTruncateString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%.3s", "日本語日本語日本語") + } + }) +} + +func BenchmarkSprintfQuoteString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%q", "日本語日本語日本語") + } + }) +} + +func BenchmarkSprintfInt(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%d", 5) + } + }) +} + +func BenchmarkSprintfIntInt(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%d %d", 5, 6) + } + }) +} + +func BenchmarkSprintfPrefixedInt(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("This is some meaningless prefix text that needs to be scanned %d", 6) + } + }) +} + +func BenchmarkSprintfFloat(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%g", 5.23184) + } + }) +} + +func BenchmarkSprintfComplex(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%f", 5.23184+5.23184i) + } + }) +} + +func BenchmarkSprintfBoolean(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%t", true) + } + }) +} + +func BenchmarkSprintfHexString(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("% #x", "0123456789abcdef") + } + }) +} + +func BenchmarkSprintfHexBytes(b *testing.B) { + data := []byte("0123456789abcdef") + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("% #x", data) + } + }) +} + +func BenchmarkSprintfBytes(b *testing.B) { + data := []byte("0123456789abcdef") + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%v", data) + } + }) +} + +func BenchmarkSprintfStringer(b *testing.B) { + stringer := I(12345) + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%v", stringer) + } + }) +} + +func BenchmarkSprintfStructure(b *testing.B) { + s := &[]interface{}{SI{12345}, map[int]string{0: "hello"}} + b.RunParallel(func(pb *testing.PB) { + p := NewPrinter(language.English) + for pb.Next() { + p.Sprintf("%#v", s) + } + }) +} + +func BenchmarkManyArgs(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + var buf bytes.Buffer + p := NewPrinter(language.English) + for pb.Next() { + buf.Reset() + p.Fprintf(&buf, "%2d/%2d/%2d %d:%d:%d %s %s\n", 3, 4, 5, 11, 12, 13, "hello", "world") + } + }) +} + +func BenchmarkFprintInt(b *testing.B) { + var buf bytes.Buffer + p := NewPrinter(language.English) + for i := 0; i < b.N; i++ { + buf.Reset() + p.Fprint(&buf, 123456) + } +} + +func BenchmarkFprintfBytes(b *testing.B) { + data := []byte(string("0123456789")) + var buf bytes.Buffer + p := NewPrinter(language.English) + for i := 0; i < b.N; i++ { + buf.Reset() + p.Fprintf(&buf, "%s", data) + } +} + +func BenchmarkFprintIntNoAlloc(b *testing.B) { + var x interface{} = 123456 + var buf bytes.Buffer + p := NewPrinter(language.English) + for i := 0; i < b.N; i++ { + buf.Reset() + p.Fprint(&buf, x) + } +} + +var mallocBuf bytes.Buffer +var mallocPointer *int // A pointer so we know the interface value won't allocate. + +var mallocTest = []struct { + count int + desc string + fn func(p *Printer) +}{ + {0, `Sprintf("")`, func(p *Printer) { p.Sprintf("") }}, + {1, `Sprintf("xxx")`, func(p *Printer) { p.Sprintf("xxx") }}, + {2, `Sprintf("%x")`, func(p *Printer) { p.Sprintf("%x", 7) }}, + {2, `Sprintf("%s")`, func(p *Printer) { p.Sprintf("%s", "hello") }}, + {3, `Sprintf("%x %x")`, func(p *Printer) { p.Sprintf("%x %x", 7, 112) }}, + {2, `Sprintf("%g")`, func(p *Printer) { p.Sprintf("%g", float32(3.14159)) }}, // TODO: Can this be 1? + {1, `Fprintf(buf, "%s")`, func(p *Printer) { mallocBuf.Reset(); p.Fprintf(&mallocBuf, "%s", "hello") }}, + // If the interface value doesn't need to allocate, amortized allocation overhead should be zero. + {0, `Fprintf(buf, "%x %x %x")`, func(p *Printer) { + mallocBuf.Reset() + p.Fprintf(&mallocBuf, "%x %x %x", mallocPointer, mallocPointer, mallocPointer) + }}, +} + +var _ bytes.Buffer + +func TestCountMallocs(t *testing.T) { + switch { + case testing.Short(): + t.Skip("skipping malloc count in short mode") + case runtime.GOMAXPROCS(0) > 1: + t.Skip("skipping; GOMAXPROCS>1") + // TODO: detect race detecter enabled. + // case race.Enabled: + // t.Skip("skipping malloc count under race detector") + } + p := NewPrinter(language.English) + for _, mt := range mallocTest { + mallocs := testing.AllocsPerRun(100, func() { mt.fn(p) }) + if got, max := mallocs, float64(mt.count); got > max { + t.Errorf("%s: got %v allocs, want <=%v", mt.desc, got, max) + } + } +} + +type flagPrinter struct{} + +func (flagPrinter) Format(f fmt.State, c rune) { + s := "%" + for i := 0; i < 128; i++ { + if f.Flag(i) { + s += string(i) + } + } + if w, ok := f.Width(); ok { + s += fmt.Sprintf("%d", w) + } + if p, ok := f.Precision(); ok { + s += fmt.Sprintf(".%d", p) + } + s += string(c) + io.WriteString(f, "["+s+"]") +} + +var flagtests = []struct { + in string + out string +}{ + {"%a", "[%a]"}, + {"%-a", "[%-a]"}, + {"%+a", "[%+a]"}, + {"%#a", "[%#a]"}, + {"% a", "[% a]"}, + {"%0a", "[%0a]"}, + {"%1.2a", "[%1.2a]"}, + {"%-1.2a", "[%-1.2a]"}, + {"%+1.2a", "[%+1.2a]"}, + {"%-+1.2a", "[%+-1.2a]"}, + {"%-+1.2abc", "[%+-1.2a]bc"}, + {"%-1.2abc", "[%-1.2a]bc"}, +} + +func TestFlagParser(t *testing.T) { + var flagprinter flagPrinter + for _, tt := range flagtests { + s := NewPrinter(language.Und).Sprintf(tt.in, &flagprinter) + if s != tt.out { + t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out) + } + } +} + +func TestStructPrinter(t *testing.T) { + type T struct { + a string + b string + c int + } + var s T + s.a = "abc" + s.b = "def" + s.c = 123 + var tests = []struct { + fmt string + out string + }{ + {"%v", "{abc def 123}"}, + {"%+v", "{a:abc b:def c:123}"}, + {"%#v", `message.T{a:"abc", b:"def", c:123}`}, + } + p := NewPrinter(language.Und) + for _, tt := range tests { + out := p.Sprintf(tt.fmt, s) + if out != tt.out { + t.Errorf("Sprintf(%q, s) = %#q, want %#q", tt.fmt, out, tt.out) + } + // The same but with a pointer. + out = p.Sprintf(tt.fmt, &s) + if out != "&"+tt.out { + t.Errorf("Sprintf(%q, &s) = %#q, want %#q", tt.fmt, out, "&"+tt.out) + } + } +} + +func TestSlicePrinter(t *testing.T) { + p := NewPrinter(language.Und) + slice := []int{} + s := p.Sprint(slice) + if s != "[]" { + t.Errorf("empty slice printed as %q not %q", s, "[]") + } + slice = []int{1, 2, 3} + s = p.Sprint(slice) + if s != "[1 2 3]" { + t.Errorf("slice: got %q expected %q", s, "[1 2 3]") + } + s = p.Sprint(&slice) + if s != "&[1 2 3]" { + t.Errorf("&slice: got %q expected %q", s, "&[1 2 3]") + } +} + +// presentInMap checks map printing using substrings so we don't depend on the +// print order. +func presentInMap(s string, a []string, t *testing.T) { + for i := 0; i < len(a); i++ { + loc := strings.Index(s, a[i]) + if loc < 0 { + t.Errorf("map print: expected to find %q in %q", a[i], s) + } + // make sure the match ends here + loc += len(a[i]) + if loc >= len(s) || (s[loc] != ' ' && s[loc] != ']') { + t.Errorf("map print: %q not properly terminated in %q", a[i], s) + } + } +} + +func TestMapPrinter(t *testing.T) { + p := NewPrinter(language.Und) + m0 := make(map[int]string) + s := p.Sprint(m0) + if s != "map[]" { + t.Errorf("empty map printed as %q not %q", s, "map[]") + } + m1 := map[int]string{1: "one", 2: "two", 3: "three"} + a := []string{"1:one", "2:two", "3:three"} + presentInMap(p.Sprintf("%v", m1), a, t) + presentInMap(p.Sprint(m1), a, t) + // Pointer to map prints the same but with initial &. + if !strings.HasPrefix(p.Sprint(&m1), "&") { + t.Errorf("no initial & for address of map") + } + presentInMap(p.Sprintf("%v", &m1), a, t) + presentInMap(p.Sprint(&m1), a, t) +} + +func TestEmptyMap(t *testing.T) { + const emptyMapStr = "map[]" + var m map[string]int + p := NewPrinter(language.Und) + s := p.Sprint(m) + if s != emptyMapStr { + t.Errorf("nil map printed as %q not %q", s, emptyMapStr) + } + m = make(map[string]int) + s = p.Sprint(m) + if s != emptyMapStr { + t.Errorf("empty map printed as %q not %q", s, emptyMapStr) + } +} + +// TestBlank checks that Sprint (and hence Print, Fprint) puts spaces in the +// right places, that is, between arg pairs in which neither is a string. +func TestBlank(t *testing.T) { + p := NewPrinter(language.Und) + got := p.Sprint("<", 1, ">:", 1, 2, 3, "!") + expect := "<1>:1 2 3!" + if got != expect { + t.Errorf("got %q expected %q", got, expect) + } +} + +// TestBlankln checks that Sprintln (and hence Println, Fprintln) puts spaces in +// the right places, that is, between all arg pairs. +func TestBlankln(t *testing.T) { + p := NewPrinter(language.Und) + got := p.Sprintln("<", 1, ">:", 1, 2, 3, "!") + expect := "< 1 >: 1 2 3 !\n" + if got != expect { + t.Errorf("got %q expected %q", got, expect) + } +} + +// TestFormatterPrintln checks Formatter with Sprint, Sprintln, Sprintf. +func TestFormatterPrintln(t *testing.T) { + p := NewPrinter(language.Und) + f := F(1) + expect := "\n" + s := p.Sprint(f, "\n") + if s != expect { + t.Errorf("Sprint wrong with Formatter: expected %q got %q", expect, s) + } + s = p.Sprintln(f) + if s != expect { + t.Errorf("Sprintln wrong with Formatter: expected %q got %q", expect, s) + } + s = p.Sprintf("%v\n", f) + if s != expect { + t.Errorf("Sprintf wrong with Formatter: expected %q got %q", expect, s) + } +} + +func args(a ...interface{}) []interface{} { return a } + +var startests = []struct { + fmt string + in []interface{} + out string +}{ + {"%*d", args(4, 42), " 42"}, + {"%-*d", args(4, 42), "42 "}, + {"%*d", args(-4, 42), "42 "}, + {"%-*d", args(-4, 42), "42 "}, + {"%.*d", args(4, 42), "0,042"}, + {"%*.*d", args(8, 4, 42), " 0,042"}, + {"%0*d", args(4, 42), "0,042"}, + // Some non-int types for width. (Issue 10732). + {"%0*d", args(uint(4), 42), "0,042"}, + {"%0*d", args(uint64(4), 42), "0,042"}, + {"%0*d", args('\x04', 42), "0,042"}, + {"%0*d", args(uintptr(4), 42), "0,042"}, + + // erroneous + {"%*d", args(nil, 42), "%!(BADWIDTH)42"}, + {"%*d", args(int(1e7), 42), "%!(BADWIDTH)42"}, + {"%*d", args(int(-1e7), 42), "%!(BADWIDTH)42"}, + {"%.*d", args(nil, 42), "%!(BADPREC)42"}, + {"%.*d", args(-1, 42), "%!(BADPREC)42"}, + {"%.*d", args(int(1e7), 42), "%!(BADPREC)42"}, + {"%.*d", args(uint(1e7), 42), "%!(BADPREC)42"}, + {"%.*d", args(uint64(1<<63), 42), "%!(BADPREC)42"}, // Huge negative (-inf). + {"%.*d", args(uint64(1<<64-1), 42), "%!(BADPREC)42"}, // Small negative (-1). + {"%*d", args(5, "foo"), "%!d(string= foo)"}, + {"%*% %d", args(20, 5), "% 5"}, + {"%*", args(4), "%!(NOVERB)"}, +} + +func TestWidthAndPrecision(t *testing.T) { + p := NewPrinter(language.Und) + for i, tt := range startests { + t.Run(fmt.Sprint(tt.fmt, tt.in), func(t *testing.T) { + s := p.Sprintf(tt.fmt, tt.in...) + if s != tt.out { + t.Errorf("#%d: %q: got %q expected %q", i, tt.fmt, s, tt.out) + } + }) + } +} + +// PanicS is a type that panics in String. +type PanicS struct { + message interface{} +} + +// Value receiver. +func (p PanicS) String() string { + panic(p.message) +} + +// PanicGo is a type that panics in GoString. +type PanicGo struct { + message interface{} +} + +// Value receiver. +func (p PanicGo) GoString() string { + panic(p.message) +} + +// PanicF is a type that panics in Format. +type PanicF struct { + message interface{} +} + +// Value receiver. +func (p PanicF) Format(f fmt.State, c rune) { + panic(p.message) +} + +var panictests = []struct { + desc string + fmt string + in interface{} + out string +}{ + // String + {"String", "%s", (*PanicS)(nil), ""}, // nil pointer special case + {"String", "%s", PanicS{io.ErrUnexpectedEOF}, "%!s(PANIC=unexpected EOF)"}, + {"String", "%s", PanicS{3}, "%!s(PANIC=3)"}, + // GoString + {"GoString", "%#v", (*PanicGo)(nil), ""}, // nil pointer special case + {"GoString", "%#v", PanicGo{io.ErrUnexpectedEOF}, "%!v(PANIC=unexpected EOF)"}, + {"GoString", "%#v", PanicGo{3}, "%!v(PANIC=3)"}, + // Issue 18282. catchPanic should not clear fmtFlags permanently. + {"Issue 18282", "%#v", []interface{}{PanicGo{3}, PanicGo{3}}, "[]interface {}{%!v(PANIC=3), %!v(PANIC=3)}"}, + // Format + {"Format", "%s", (*PanicF)(nil), ""}, // nil pointer special case + {"Format", "%s", PanicF{io.ErrUnexpectedEOF}, "%!s(PANIC=unexpected EOF)"}, + {"Format", "%s", PanicF{3}, "%!s(PANIC=3)"}, +} + +func TestPanics(t *testing.T) { + p := NewPrinter(language.Und) + for i, tt := range panictests { + t.Run(fmt.Sprint(tt.desc, "/", tt.fmt, "/", tt.in), func(t *testing.T) { + s := p.Sprintf(tt.fmt, tt.in) + if s != tt.out { + t.Errorf("%d: %q: got %q expected %q", i, tt.fmt, s, tt.out) + } + }) + } +} + +// recurCount tests that erroneous String routine doesn't cause fatal recursion. +var recurCount = 0 + +type Recur struct { + i int + failed *bool +} + +func (r *Recur) String() string { + p := NewPrinter(language.Und) + if recurCount++; recurCount > 10 { + *r.failed = true + return "FAIL" + } + // This will call badVerb. Before the fix, that would cause us to recur into + // this routine to print %!p(value). Now we don't call the user's method + // during an error. + return p.Sprintf("recur@%p value: %d", r, r.i) +} + +func TestBadVerbRecursion(t *testing.T) { + p := NewPrinter(language.Und) + failed := false + r := &Recur{3, &failed} + p.Sprintf("recur@%p value: %d\n", &r, r.i) + if failed { + t.Error("fail with pointer") + } + failed = false + r = &Recur{4, &failed} + p.Sprintf("recur@%p, value: %d\n", r, r.i) + if failed { + t.Error("fail with value") + } +} + +func TestNilDoesNotBecomeTyped(t *testing.T) { + p := NewPrinter(language.Und) + type A struct{} + type B struct{} + var a *A = nil + var b B = B{} + + // indirect the Sprintf call through this noVetWarn variable to avoid + // "go test" failing vet checks in Go 1.10+. + noVetWarn := p.Sprintf + got := noVetWarn("%s %s %s %s %s", nil, a, nil, b, nil) + + const expect = "%!s() %!s(*message.A=) %!s() {} %!s()" + if got != expect { + t.Errorf("expected:\n\t%q\ngot:\n\t%q", expect, got) + } +} + +var formatterFlagTests = []struct { + in string + val interface{} + out string +}{ + // scalar values with the (unused by fmt) 'a' verb. + {"%a", flagPrinter{}, "[%a]"}, + {"%-a", flagPrinter{}, "[%-a]"}, + {"%+a", flagPrinter{}, "[%+a]"}, + {"%#a", flagPrinter{}, "[%#a]"}, + {"% a", flagPrinter{}, "[% a]"}, + {"%0a", flagPrinter{}, "[%0a]"}, + {"%1.2a", flagPrinter{}, "[%1.2a]"}, + {"%-1.2a", flagPrinter{}, "[%-1.2a]"}, + {"%+1.2a", flagPrinter{}, "[%+1.2a]"}, + {"%-+1.2a", flagPrinter{}, "[%+-1.2a]"}, + {"%-+1.2abc", flagPrinter{}, "[%+-1.2a]bc"}, + {"%-1.2abc", flagPrinter{}, "[%-1.2a]bc"}, + + // composite values with the 'a' verb + {"%a", [1]flagPrinter{}, "[[%a]]"}, + {"%-a", [1]flagPrinter{}, "[[%-a]]"}, + {"%+a", [1]flagPrinter{}, "[[%+a]]"}, + {"%#a", [1]flagPrinter{}, "[[%#a]]"}, + {"% a", [1]flagPrinter{}, "[[% a]]"}, + {"%0a", [1]flagPrinter{}, "[[%0a]]"}, + {"%1.2a", [1]flagPrinter{}, "[[%1.2a]]"}, + {"%-1.2a", [1]flagPrinter{}, "[[%-1.2a]]"}, + {"%+1.2a", [1]flagPrinter{}, "[[%+1.2a]]"}, + {"%-+1.2a", [1]flagPrinter{}, "[[%+-1.2a]]"}, + {"%-+1.2abc", [1]flagPrinter{}, "[[%+-1.2a]]bc"}, + {"%-1.2abc", [1]flagPrinter{}, "[[%-1.2a]]bc"}, + + // simple values with the 'v' verb + {"%v", flagPrinter{}, "[%v]"}, + {"%-v", flagPrinter{}, "[%-v]"}, + {"%+v", flagPrinter{}, "[%+v]"}, + {"%#v", flagPrinter{}, "[%#v]"}, + {"% v", flagPrinter{}, "[% v]"}, + {"%0v", flagPrinter{}, "[%0v]"}, + {"%1.2v", flagPrinter{}, "[%1.2v]"}, + {"%-1.2v", flagPrinter{}, "[%-1.2v]"}, + {"%+1.2v", flagPrinter{}, "[%+1.2v]"}, + {"%-+1.2v", flagPrinter{}, "[%+-1.2v]"}, + {"%-+1.2vbc", flagPrinter{}, "[%+-1.2v]bc"}, + {"%-1.2vbc", flagPrinter{}, "[%-1.2v]bc"}, + + // composite values with the 'v' verb. + {"%v", [1]flagPrinter{}, "[[%v]]"}, + {"%-v", [1]flagPrinter{}, "[[%-v]]"}, + {"%+v", [1]flagPrinter{}, "[[%+v]]"}, + {"%#v", [1]flagPrinter{}, "[1]message.flagPrinter{[%#v]}"}, + {"% v", [1]flagPrinter{}, "[[% v]]"}, + {"%0v", [1]flagPrinter{}, "[[%0v]]"}, + {"%1.2v", [1]flagPrinter{}, "[[%1.2v]]"}, + {"%-1.2v", [1]flagPrinter{}, "[[%-1.2v]]"}, + {"%+1.2v", [1]flagPrinter{}, "[[%+1.2v]]"}, + {"%-+1.2v", [1]flagPrinter{}, "[[%+-1.2v]]"}, + {"%-+1.2vbc", [1]flagPrinter{}, "[[%+-1.2v]]bc"}, + {"%-1.2vbc", [1]flagPrinter{}, "[[%-1.2v]]bc"}, +} + +func TestFormatterFlags(t *testing.T) { + p := NewPrinter(language.Und) + for _, tt := range formatterFlagTests { + s := p.Sprintf(tt.in, tt.val) + if s != tt.out { + t.Errorf("Sprintf(%q, %T) = %q, want %q", tt.in, tt.val, s, tt.out) + } + } +} diff --git a/vendor/golang.org/x/text/message/format.go b/vendor/golang.org/x/text/message/format.go new file mode 100644 index 0000000..a47d17d --- /dev/null +++ b/vendor/golang.org/x/text/message/format.go @@ -0,0 +1,510 @@ +// Copyright 2017 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 message + +import ( + "bytes" + "strconv" + "unicode/utf8" + + "golang.org/x/text/internal/format" +) + +const ( + ldigits = "0123456789abcdefx" + udigits = "0123456789ABCDEFX" +) + +const ( + signed = true + unsigned = false +) + +// A formatInfo is the raw formatter used by Printf etc. +// It prints into a buffer that must be set up separately. +type formatInfo struct { + buf *bytes.Buffer + + format.Parser + + // intbuf is large enough to store %b of an int64 with a sign and + // avoids padding at the end of the struct on 32 bit architectures. + intbuf [68]byte +} + +func (f *formatInfo) init(buf *bytes.Buffer) { + f.ClearFlags() + f.buf = buf +} + +// writePadding generates n bytes of padding. +func (f *formatInfo) writePadding(n int) { + if n <= 0 { // No padding bytes needed. + return + } + f.buf.Grow(n) + // Decide which byte the padding should be filled with. + padByte := byte(' ') + if f.Zero { + padByte = byte('0') + } + // Fill padding with padByte. + for i := 0; i < n; i++ { + f.buf.WriteByte(padByte) // TODO: make more efficient. + } +} + +// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *formatInfo) pad(b []byte) { + if !f.WidthPresent || f.Width == 0 { + f.buf.Write(b) + return + } + width := f.Width - utf8.RuneCount(b) + if !f.Minus { + // left padding + f.writePadding(width) + f.buf.Write(b) + } else { + // right padding + f.buf.Write(b) + f.writePadding(width) + } +} + +// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *formatInfo) padString(s string) { + if !f.WidthPresent || f.Width == 0 { + f.buf.WriteString(s) + return + } + width := f.Width - utf8.RuneCountInString(s) + if !f.Minus { + // left padding + f.writePadding(width) + f.buf.WriteString(s) + } else { + // right padding + f.buf.WriteString(s) + f.writePadding(width) + } +} + +// fmt_boolean formats a boolean. +func (f *formatInfo) fmt_boolean(v bool) { + if v { + f.padString("true") + } else { + f.padString("false") + } +} + +// fmt_unicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'". +func (f *formatInfo) fmt_unicode(u uint64) { + buf := f.intbuf[0:] + + // With default precision set the maximum needed buf length is 18 + // for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits + // into the already allocated intbuf with a capacity of 68 bytes. + prec := 4 + if f.PrecPresent && f.Prec > 4 { + prec = f.Prec + // Compute space needed for "U+" , number, " '", character, "'". + width := 2 + prec + 2 + utf8.UTFMax + 1 + if width > len(buf) { + buf = make([]byte, width) + } + } + + // Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left. + i := len(buf) + + // For %#U we want to add a space and a quoted character at the end of the buffer. + if f.Sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { + i-- + buf[i] = '\'' + i -= utf8.RuneLen(rune(u)) + utf8.EncodeRune(buf[i:], rune(u)) + i-- + buf[i] = '\'' + i-- + buf[i] = ' ' + } + // Format the Unicode code point u as a hexadecimal number. + for u >= 16 { + i-- + buf[i] = udigits[u&0xF] + prec-- + u >>= 4 + } + i-- + buf[i] = udigits[u] + prec-- + // Add zeros in front of the number until requested precision is reached. + for prec > 0 { + i-- + buf[i] = '0' + prec-- + } + // Add a leading "U+". + i-- + buf[i] = '+' + i-- + buf[i] = 'U' + + oldZero := f.Zero + f.Zero = false + f.pad(buf[i:]) + f.Zero = oldZero +} + +// fmt_integer formats signed and unsigned integers. +func (f *formatInfo) fmt_integer(u uint64, base int, isSigned bool, digits string) { + negative := isSigned && int64(u) < 0 + if negative { + u = -u + } + + buf := f.intbuf[0:] + // The already allocated f.intbuf with a capacity of 68 bytes + // is large enough for integer formatting when no precision or width is set. + if f.WidthPresent || f.PrecPresent { + // Account 3 extra bytes for possible addition of a sign and "0x". + width := 3 + f.Width + f.Prec // wid and prec are always positive. + if width > len(buf) { + // We're going to need a bigger boat. + buf = make([]byte, width) + } + } + + // Two ways to ask for extra leading zero digits: %.3d or %03d. + // If both are specified the f.zero flag is ignored and + // padding with spaces is used instead. + prec := 0 + if f.PrecPresent { + prec = f.Prec + // Precision of 0 and value of 0 means "print nothing" but padding. + if prec == 0 && u == 0 { + oldZero := f.Zero + f.Zero = false + f.writePadding(f.Width) + f.Zero = oldZero + return + } + } else if f.Zero && f.WidthPresent { + prec = f.Width + if negative || f.Plus || f.Space { + prec-- // leave room for sign + } + } + + // Because printing is easier right-to-left: format u into buf, ending at buf[i]. + // We could make things marginally faster by splitting the 32-bit case out + // into a separate block but it's not worth the duplication, so u has 64 bits. + i := len(buf) + // Use constants for the division and modulo for more efficient code. + // Switch cases ordered by popularity. + switch base { + case 10: + for u >= 10 { + i-- + next := u / 10 + buf[i] = byte('0' + u - next*10) + u = next + } + case 16: + for u >= 16 { + i-- + buf[i] = digits[u&0xF] + u >>= 4 + } + case 8: + for u >= 8 { + i-- + buf[i] = byte('0' + u&7) + u >>= 3 + } + case 2: + for u >= 2 { + i-- + buf[i] = byte('0' + u&1) + u >>= 1 + } + default: + panic("fmt: unknown base; can't happen") + } + i-- + buf[i] = digits[u] + for i > 0 && prec > len(buf)-i { + i-- + buf[i] = '0' + } + + // Various prefixes: 0x, -, etc. + if f.Sharp { + switch base { + case 8: + if buf[i] != '0' { + i-- + buf[i] = '0' + } + case 16: + // Add a leading 0x or 0X. + i-- + buf[i] = digits[16] + i-- + buf[i] = '0' + } + } + + if negative { + i-- + buf[i] = '-' + } else if f.Plus { + i-- + buf[i] = '+' + } else if f.Space { + i-- + buf[i] = ' ' + } + + // Left padding with zeros has already been handled like precision earlier + // or the f.zero flag is ignored due to an explicitly set precision. + oldZero := f.Zero + f.Zero = false + f.pad(buf[i:]) + f.Zero = oldZero +} + +// truncate truncates the string to the specified precision, if present. +func (f *formatInfo) truncate(s string) string { + if f.PrecPresent { + n := f.Prec + for i := range s { + n-- + if n < 0 { + return s[:i] + } + } + } + return s +} + +// fmt_s formats a string. +func (f *formatInfo) fmt_s(s string) { + s = f.truncate(s) + f.padString(s) +} + +// fmt_sbx formats a string or byte slice as a hexadecimal encoding of its bytes. +func (f *formatInfo) fmt_sbx(s string, b []byte, digits string) { + length := len(b) + if b == nil { + // No byte slice present. Assume string s should be encoded. + length = len(s) + } + // Set length to not process more bytes than the precision demands. + if f.PrecPresent && f.Prec < length { + length = f.Prec + } + // Compute width of the encoding taking into account the f.sharp and f.space flag. + width := 2 * length + if width > 0 { + if f.Space { + // Each element encoded by two hexadecimals will get a leading 0x or 0X. + if f.Sharp { + width *= 2 + } + // Elements will be separated by a space. + width += length - 1 + } else if f.Sharp { + // Only a leading 0x or 0X will be added for the whole string. + width += 2 + } + } else { // The byte slice or string that should be encoded is empty. + if f.WidthPresent { + f.writePadding(f.Width) + } + return + } + // Handle padding to the left. + if f.WidthPresent && f.Width > width && !f.Minus { + f.writePadding(f.Width - width) + } + // Write the encoding directly into the output buffer. + buf := f.buf + if f.Sharp { + // Add leading 0x or 0X. + buf.WriteByte('0') + buf.WriteByte(digits[16]) + } + var c byte + for i := 0; i < length; i++ { + if f.Space && i > 0 { + // Separate elements with a space. + buf.WriteByte(' ') + if f.Sharp { + // Add leading 0x or 0X for each element. + buf.WriteByte('0') + buf.WriteByte(digits[16]) + } + } + if b != nil { + c = b[i] // Take a byte from the input byte slice. + } else { + c = s[i] // Take a byte from the input string. + } + // Encode each byte as two hexadecimal digits. + buf.WriteByte(digits[c>>4]) + buf.WriteByte(digits[c&0xF]) + } + // Handle padding to the right. + if f.WidthPresent && f.Width > width && f.Minus { + f.writePadding(f.Width - width) + } +} + +// fmt_sx formats a string as a hexadecimal encoding of its bytes. +func (f *formatInfo) fmt_sx(s, digits string) { + f.fmt_sbx(s, nil, digits) +} + +// fmt_bx formats a byte slice as a hexadecimal encoding of its bytes. +func (f *formatInfo) fmt_bx(b []byte, digits string) { + f.fmt_sbx("", b, digits) +} + +// fmt_q formats a string as a double-quoted, escaped Go string constant. +// If f.sharp is set a raw (backquoted) string may be returned instead +// if the string does not contain any control characters other than tab. +func (f *formatInfo) fmt_q(s string) { + s = f.truncate(s) + if f.Sharp && strconv.CanBackquote(s) { + f.padString("`" + s + "`") + return + } + buf := f.intbuf[:0] + if f.Plus { + f.pad(strconv.AppendQuoteToASCII(buf, s)) + } else { + f.pad(strconv.AppendQuote(buf, s)) + } +} + +// fmt_c formats an integer as a Unicode character. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *formatInfo) fmt_c(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + w := utf8.EncodeRune(buf[:utf8.UTFMax], r) + f.pad(buf[:w]) +} + +// fmt_qc formats an integer as a single-quoted, escaped Go character constant. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *formatInfo) fmt_qc(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + if f.Plus { + f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) + } else { + f.pad(strconv.AppendQuoteRune(buf, r)) + } +} + +// fmt_float formats a float64. It assumes that verb is a valid format specifier +// for strconv.AppendFloat and therefore fits into a byte. +func (f *formatInfo) fmt_float(v float64, size int, verb rune, prec int) { + // Explicit precision in format specifier overrules default precision. + if f.PrecPresent { + prec = f.Prec + } + // Format number, reserving space for leading + sign if needed. + num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size) + if num[1] == '-' || num[1] == '+' { + num = num[1:] + } else { + num[0] = '+' + } + // f.space means to add a leading space instead of a "+" sign unless + // the sign is explicitly asked for by f.plus. + if f.Space && num[0] == '+' && !f.Plus { + num[0] = ' ' + } + // Special handling for infinities and NaN, + // which don't look like a number so shouldn't be padded with zeros. + if num[1] == 'I' || num[1] == 'N' { + oldZero := f.Zero + f.Zero = false + // Remove sign before NaN if not asked for. + if num[1] == 'N' && !f.Space && !f.Plus { + num = num[1:] + } + f.pad(num) + f.Zero = oldZero + return + } + // The sharp flag forces printing a decimal point for non-binary formats + // and retains trailing zeros, which we may need to restore. + if f.Sharp && verb != 'b' { + digits := 0 + switch verb { + case 'v', 'g', 'G': + digits = prec + // If no precision is set explicitly use a precision of 6. + if digits == -1 { + digits = 6 + } + } + + // Buffer pre-allocated with enough room for + // exponent notations of the form "e+123". + var tailBuf [5]byte + tail := tailBuf[:0] + + hasDecimalPoint := false + // Starting from i = 1 to skip sign at num[0]. + for i := 1; i < len(num); i++ { + switch num[i] { + case '.': + hasDecimalPoint = true + case 'e', 'E': + tail = append(tail, num[i:]...) + num = num[:i] + default: + digits-- + } + } + if !hasDecimalPoint { + num = append(num, '.') + } + for digits > 0 { + num = append(num, '0') + digits-- + } + num = append(num, tail...) + } + // We want a sign if asked for and if the sign is not positive. + if f.Plus || num[0] != '+' { + // If we're zero padding to the left we want the sign before the leading zeros. + // Achieve this by writing the sign out and then padding the unsigned number. + if f.Zero && f.WidthPresent && f.Width > len(num) { + f.buf.WriteByte(num[0]) + f.writePadding(f.Width - len(num)) + f.buf.Write(num[1:]) + return + } + f.pad(num) + return + } + // No sign to show and the number is positive; just print the unsigned number. + f.pad(num[1:]) +} diff --git a/vendor/golang.org/x/text/message/message.go b/vendor/golang.org/x/text/message/message.go new file mode 100644 index 0000000..a3473bf --- /dev/null +++ b/vendor/golang.org/x/text/message/message.go @@ -0,0 +1,186 @@ +// Copyright 2015 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 message // import "golang.org/x/text/message" + +import ( + "io" + "os" + + // Include features to facilitate generated catalogs. + _ "golang.org/x/text/feature/plural" + + "golang.org/x/text/internal/number" + "golang.org/x/text/language" + "golang.org/x/text/message/catalog" +) + +// A Printer implements language-specific formatted I/O analogous to the fmt +// package. +type Printer struct { + // the language + tag language.Tag + + toDecimal number.Formatter + toScientific number.Formatter + + cat catalog.Catalog +} + +type options struct { + cat catalog.Catalog + // TODO: + // - allow %s to print integers in written form (tables are likely too large + // to enable this by default). + // - list behavior + // +} + +// An Option defines an option of a Printer. +type Option func(o *options) + +// Catalog defines the catalog to be used. +func Catalog(c catalog.Catalog) Option { + return func(o *options) { o.cat = c } +} + +// NewPrinter returns a Printer that formats messages tailored to language t. +func NewPrinter(t language.Tag, opts ...Option) *Printer { + options := &options{ + cat: DefaultCatalog, + } + for _, o := range opts { + o(options) + } + p := &Printer{ + tag: t, + cat: options.cat, + } + p.toDecimal.InitDecimal(t) + p.toScientific.InitScientific(t) + return p +} + +// Sprint is like fmt.Sprint, but using language-specific formatting. +func (p *Printer) Sprint(a ...interface{}) string { + pp := newPrinter(p) + pp.doPrint(a) + s := pp.String() + pp.free() + return s +} + +// Fprint is like fmt.Fprint, but using language-specific formatting. +func (p *Printer) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + pp := newPrinter(p) + pp.doPrint(a) + n64, err := io.Copy(w, &pp.Buffer) + pp.free() + return int(n64), err +} + +// Print is like fmt.Print, but using language-specific formatting. +func (p *Printer) Print(a ...interface{}) (n int, err error) { + return p.Fprint(os.Stdout, a...) +} + +// Sprintln is like fmt.Sprintln, but using language-specific formatting. +func (p *Printer) Sprintln(a ...interface{}) string { + pp := newPrinter(p) + pp.doPrintln(a) + s := pp.String() + pp.free() + return s +} + +// Fprintln is like fmt.Fprintln, but using language-specific formatting. +func (p *Printer) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + pp := newPrinter(p) + pp.doPrintln(a) + n64, err := io.Copy(w, &pp.Buffer) + pp.free() + return int(n64), err +} + +// Println is like fmt.Println, but using language-specific formatting. +func (p *Printer) Println(a ...interface{}) (n int, err error) { + return p.Fprintln(os.Stdout, a...) +} + +// Sprintf is like fmt.Sprintf, but using language-specific formatting. +func (p *Printer) Sprintf(key Reference, a ...interface{}) string { + pp := newPrinter(p) + lookupAndFormat(pp, key, a) + s := pp.String() + pp.free() + return s +} + +// Fprintf is like fmt.Fprintf, but using language-specific formatting. +func (p *Printer) Fprintf(w io.Writer, key Reference, a ...interface{}) (n int, err error) { + pp := newPrinter(p) + lookupAndFormat(pp, key, a) + n, err = w.Write(pp.Bytes()) + pp.free() + return n, err + +} + +// Printf is like fmt.Printf, but using language-specific formatting. +func (p *Printer) Printf(key Reference, a ...interface{}) (n int, err error) { + pp := newPrinter(p) + lookupAndFormat(pp, key, a) + n, err = os.Stdout.Write(pp.Bytes()) + pp.free() + return n, err +} + +func lookupAndFormat(p *printer, r Reference, a []interface{}) { + p.fmt.Reset(a) + var id, msg string + switch v := r.(type) { + case string: + id, msg = v, v + case key: + id, msg = v.id, v.fallback + default: + panic("key argument is not a Reference") + } + + if p.catContext.Execute(id) == catalog.ErrNotFound { + if p.catContext.Execute(msg) == catalog.ErrNotFound { + p.Render(msg) + return + } + } +} + +// Arg implements catmsg.Renderer. +func (p *printer) Arg(i int) interface{} { // TODO, also return "ok" bool + i-- + if uint(i) < uint(len(p.fmt.Args)) { + return p.fmt.Args[i] + } + return nil +} + +// Render implements catmsg.Renderer. +func (p *printer) Render(msg string) { + p.doPrintf(msg) +} + +// A Reference is a string or a message reference. +type Reference interface { + // TODO: also allow []string +} + +// Key creates a message Reference for a message where the given id is used for +// message lookup and the fallback is returned when no matches are found. +func Key(id string, fallback string) Reference { + return key{id, fallback} +} + +type key struct { + id, fallback string +} diff --git a/vendor/golang.org/x/text/message/message_test.go b/vendor/golang.org/x/text/message/message_test.go new file mode 100644 index 0000000..326f716 --- /dev/null +++ b/vendor/golang.org/x/text/message/message_test.go @@ -0,0 +1,181 @@ +// Copyright 2015 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 message + +import ( + "bytes" + "fmt" + "io" + "testing" + + "golang.org/x/text/internal" + "golang.org/x/text/internal/format" + "golang.org/x/text/language" + "golang.org/x/text/message/catalog" +) + +type formatFunc func(s fmt.State, v rune) + +func (f formatFunc) Format(s fmt.State, v rune) { f(s, v) } + +func TestBinding(t *testing.T) { + testCases := []struct { + tag string + value interface{} + want string + }{ + {"en", 1, "1"}, + {"en", "2", "2"}, + { // Language is passed. + "en", + formatFunc(func(fs fmt.State, v rune) { + s := fs.(format.State) + io.WriteString(s, s.Language().String()) + }), + "en", + }, + } + for i, tc := range testCases { + p := NewPrinter(language.MustParse(tc.tag)) + if got := p.Sprint(tc.value); got != tc.want { + t.Errorf("%d:%s:Sprint(%v) = %q; want %q", i, tc.tag, tc.value, got, tc.want) + } + var buf bytes.Buffer + p.Fprint(&buf, tc.value) + if got := buf.String(); got != tc.want { + t.Errorf("%d:%s:Fprint(%v) = %q; want %q", i, tc.tag, tc.value, got, tc.want) + } + } +} + +func TestLocalization(t *testing.T) { + type test struct { + tag string + key Reference + args []interface{} + want string + } + args := func(x ...interface{}) []interface{} { return x } + empty := []interface{}{} + joe := []interface{}{"Joe"} + joeAndMary := []interface{}{"Joe", "Mary"} + + testCases := []struct { + desc string + cat []entry + test []test + }{{ + desc: "empty", + test: []test{ + {"en", "key", empty, "key"}, + {"en", "", empty, ""}, + {"nl", "", empty, ""}, + }, + }, { + desc: "hierarchical languages", + cat: []entry{ + {"en", "hello %s", "Hello %s!"}, + {"en-GB", "hello %s", "Hellø %s!"}, + {"en-US", "hello %s", "Howdy %s!"}, + {"en", "greetings %s and %s", "Greetings %s and %s!"}, + }, + test: []test{ + {"und", "hello %s", joe, "hello Joe"}, + {"nl", "hello %s", joe, "hello Joe"}, + {"en", "hello %s", joe, "Hello Joe!"}, + {"en-US", "hello %s", joe, "Howdy Joe!"}, + {"en-GB", "hello %s", joe, "Hellø Joe!"}, + {"en-oxendict", "hello %s", joe, "Hello Joe!"}, + {"en-US-oxendict-u-ms-metric", "hello %s", joe, "Howdy Joe!"}, + + {"und", "greetings %s and %s", joeAndMary, "greetings Joe and Mary"}, + {"nl", "greetings %s and %s", joeAndMary, "greetings Joe and Mary"}, + {"en", "greetings %s and %s", joeAndMary, "Greetings Joe and Mary!"}, + {"en-US", "greetings %s and %s", joeAndMary, "Greetings Joe and Mary!"}, + {"en-GB", "greetings %s and %s", joeAndMary, "Greetings Joe and Mary!"}, + {"en-oxendict", "greetings %s and %s", joeAndMary, "Greetings Joe and Mary!"}, + {"en-US-oxendict-u-ms-metric", "greetings %s and %s", joeAndMary, "Greetings Joe and Mary!"}, + }, + }, { + desc: "references", + cat: []entry{ + {"en", "hello", "Hello!"}, + }, + test: []test{ + {"en", "hello", empty, "Hello!"}, + {"en", Key("hello", "fallback"), empty, "Hello!"}, + {"en", Key("xxx", "fallback"), empty, "fallback"}, + {"und", Key("hello", "fallback"), empty, "fallback"}, + }, + }, { + desc: "zero substitution", // work around limitation of fmt + cat: []entry{ + {"en", "hello %s", "Hello!"}, + {"en", "hi %s and %s", "Hello %[2]s!"}, + }, + test: []test{ + {"en", "hello %s", joe, "Hello!"}, + {"en", "hello %s", joeAndMary, "Hello!"}, + {"en", "hi %s and %s", joeAndMary, "Hello Mary!"}, + // The following tests resolve to the fallback string. + {"und", "hello", joeAndMary, "hello"}, + {"und", "hello %%%%", joeAndMary, "hello %%"}, + {"und", "hello %#%%4.2% ", joeAndMary, "hello %% "}, + {"und", "hello %s", joeAndMary, "hello Joe%!(EXTRA string=Mary)"}, + {"und", "hello %+%%s", joeAndMary, "hello %Joe%!(EXTRA string=Mary)"}, + {"und", "hello %-42%%s ", joeAndMary, "hello %Joe %!(EXTRA string=Mary)"}, + }, + }, { + desc: "number formatting", // work around limitation of fmt + cat: []entry{ + {"und", "files", "%d files left"}, + {"und", "meters", "%.2f meters"}, + {"de", "files", "%d Dateien übrig"}, + }, + test: []test{ + {"en", "meters", args(3000.2), "3,000.20 meters"}, + {"en-u-nu-gujr", "files", args(123456), "૧૨૩,૪૫૬ files left"}, + {"de", "files", args(1234), "1.234 Dateien übrig"}, + {"de-CH", "files", args(1234), "1’234 Dateien übrig"}, + {"de-CH-u-nu-mong", "files", args(1234), "᠑’᠒᠓᠔ Dateien übrig"}, + }, + }} + + for _, tc := range testCases { + cat, _ := initCat(tc.cat) + + for i, pt := range tc.test { + t.Run(fmt.Sprintf("%s:%d", tc.desc, i), func(t *testing.T) { + p := NewPrinter(language.MustParse(pt.tag), Catalog(cat)) + + if got := p.Sprintf(pt.key, pt.args...); got != pt.want { + t.Errorf("Sprintf(%q, %v) = %s; want %s", + pt.key, pt.args, got, pt.want) + return // Next error will likely be the same. + } + + w := &bytes.Buffer{} + p.Fprintf(w, pt.key, pt.args...) + if got := w.String(); got != pt.want { + t.Errorf("Fprintf(%q, %v) = %s; want %s", + pt.key, pt.args, got, pt.want) + } + }) + } + } +} + +type entry struct{ tag, key, msg string } + +func initCat(entries []entry) (*catalog.Builder, []language.Tag) { + tags := []language.Tag{} + cat := catalog.NewBuilder() + for _, e := range entries { + tag := language.MustParse(e.tag) + tags = append(tags, tag) + cat.SetString(tag, e.key, e.msg) + } + return cat, internal.UniqueTags(tags) +} diff --git a/vendor/golang.org/x/text/message/pipeline/extract.go b/vendor/golang.org/x/text/message/pipeline/extract.go new file mode 100644 index 0000000..20ad914 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/extract.go @@ -0,0 +1,305 @@ +// Copyright 2016 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 pipeline + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/token" + "go/types" + "path" + "path/filepath" + "strings" + "unicode" + "unicode/utf8" + + fmtparser "golang.org/x/text/internal/format" + "golang.org/x/tools/go/loader" +) + +// TODO: +// - merge information into existing files +// - handle different file formats (PO, XLIFF) +// - handle features (gender, plural) +// - message rewriting + +// Extract extracts all strings form the package defined in Config. +func Extract(c *Config) (*Locale, error) { + conf := loader.Config{} + prog, err := loadPackages(&conf, c.Packages) + if err != nil { + return nil, wrap(err, "") + } + + // print returns Go syntax for the specified node. + print := func(n ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, conf.Fset, n) + return buf.String() + } + + var messages []Message + + for _, info := range prog.AllPackages { + for _, f := range info.Files { + // Associate comments with nodes. + cmap := ast.NewCommentMap(prog.Fset, f, f.Comments) + getComment := func(n ast.Node) string { + cs := cmap.Filter(n).Comments() + if len(cs) > 0 { + return strings.TrimSpace(cs[0].Text()) + } + return "" + } + + // Find function calls. + ast.Inspect(f, func(n ast.Node) bool { + call, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + // Skip calls of functions other than + // (*message.Printer).{Sp,Fp,P}rintf. + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + meth := info.Selections[sel] + if meth == nil || meth.Kind() != types.MethodVal { + return true + } + // TODO: remove cheap hack and check if the type either + // implements some interface or is specifically of type + // "golang.org/x/text/message".Printer. + m, ok := extractFuncs[path.Base(meth.Recv().String())] + if !ok { + return true + } + + fmtType, ok := m[meth.Obj().Name()] + if !ok { + return true + } + // argn is the index of the format string. + argn := fmtType.arg + if argn >= len(call.Args) { + return true + } + + args := call.Args[fmtType.arg:] + + fmtMsg, ok := msgStr(info, args[0]) + if !ok { + // TODO: identify the type of the format argument. If it + // is not a string, multiple keys may be defined. + return true + } + comment := "" + key := []string{} + if ident, ok := args[0].(*ast.Ident); ok { + key = append(key, ident.Name) + if v, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && v.Comment != nil { + // TODO: get comment above ValueSpec as well + comment = v.Comment.Text() + } + } + + arguments := []argument{} + args = args[1:] + simArgs := make([]interface{}, len(args)) + for i, arg := range args { + expr := print(arg) + val := "" + if v := info.Types[arg].Value; v != nil { + val = v.ExactString() + simArgs[i] = val + switch arg.(type) { + case *ast.BinaryExpr, *ast.UnaryExpr: + expr = val + } + } + arguments = append(arguments, argument{ + ArgNum: i + 1, + Type: info.Types[arg].Type.String(), + UnderlyingType: info.Types[arg].Type.Underlying().String(), + Expr: expr, + Value: val, + Comment: getComment(arg), + Position: posString(conf, info, arg.Pos()), + // TODO report whether it implements + // interfaces plural.Interface, + // gender.Interface. + }) + } + msg := "" + + ph := placeholders{index: map[string]string{}} + + p := fmtparser.Parser{} + p.Reset(simArgs) + for p.SetFormat(fmtMsg); p.Scan(); { + switch p.Status { + case fmtparser.StatusText: + msg += p.Text() + case fmtparser.StatusSubstitution, + fmtparser.StatusBadWidthSubstitution, + fmtparser.StatusBadPrecSubstitution: + arguments[p.ArgNum-1].used = true + arg := arguments[p.ArgNum-1] + sub := p.Text() + if !p.HasIndex { + r, sz := utf8.DecodeLastRuneInString(sub) + sub = fmt.Sprintf("%s[%d]%c", sub[:len(sub)-sz], p.ArgNum, r) + } + msg += fmt.Sprintf("{%s}", ph.addArg(&arg, sub)) + } + } + key = append(key, msg) + + // Add additional Placeholders that can be used in translations + // that are not present in the string. + for _, arg := range arguments { + if arg.used { + continue + } + ph.addArg(&arg, fmt.Sprintf("%%[%d]v", arg.ArgNum)) + } + + if c := getComment(call.Args[0]); c != "" { + comment = c + } + + messages = append(messages, Message{ + ID: key, + Key: fmtMsg, + Message: Text{Msg: msg}, + // TODO(fix): this doesn't get the before comment. + Comment: comment, + Placeholders: ph.slice, + Position: posString(conf, info, call.Lparen), + }) + return true + }) + } + } + + out := &Locale{ + Language: c.SourceLanguage, + Messages: messages, + } + return out, nil +} + +func posString(conf loader.Config, info *loader.PackageInfo, pos token.Pos) string { + p := conf.Fset.Position(pos) + file := fmt.Sprintf("%s:%d:%d", filepath.Base(p.Filename), p.Line, p.Column) + return filepath.Join(info.Pkg.Path(), file) +} + +// extractFuncs indicates the types and methods for which to extract strings, +// and which argument to extract. +// TODO: use the types in conf.Import("golang.org/x/text/message") to extract +// the correct instances. +var extractFuncs = map[string]map[string]extractType{ + // TODO: Printer -> *golang.org/x/text/message.Printer + "message.Printer": { + "Printf": extractType{arg: 0, format: true}, + "Sprintf": extractType{arg: 0, format: true}, + "Fprintf": extractType{arg: 1, format: true}, + + "Lookup": extractType{arg: 0}, + }, +} + +type extractType struct { + // format indicates if the next arg is a formatted string or whether to + // concatenate all arguments + format bool + // arg indicates the position of the argument to extract. + arg int +} + +func getID(arg *argument) string { + s := getLastComponent(arg.Expr) + s = strip(s) + s = strings.Replace(s, " ", "", -1) + // For small variable names, use user-defined types for more info. + if len(s) <= 2 && arg.UnderlyingType != arg.Type { + s = getLastComponent(arg.Type) + } + return strings.Title(s) +} + +// strip is a dirty hack to convert function calls to placeholder IDs. +func strip(s string) string { + s = strings.Map(func(r rune) rune { + if unicode.IsSpace(r) || r == '-' { + return '_' + } + if !unicode.In(r, unicode.Letter, unicode.Mark) { + return -1 + } + return r + }, s) + // Strip "Get" from getter functions. + if strings.HasPrefix(s, "Get") || strings.HasPrefix(s, "get") { + if len(s) > len("get") { + r, _ := utf8.DecodeRuneInString(s) + if !unicode.In(r, unicode.Ll, unicode.M) { // not lower or mark + s = s[len("get"):] + } + } + } + return s +} + +type placeholders struct { + index map[string]string + slice []Placeholder +} + +func (p *placeholders) addArg(arg *argument, sub string) (id string) { + id = getID(arg) + id1 := id + alt, ok := p.index[id1] + for i := 1; ok && alt != sub; i++ { + id1 = fmt.Sprintf("%s_%d", id, i) + alt, ok = p.index[id1] + } + p.index[id1] = sub + p.slice = append(p.slice, Placeholder{ + ID: id1, + String: sub, + Type: arg.Type, + UnderlyingType: arg.UnderlyingType, + ArgNum: arg.ArgNum, + Expr: arg.Expr, + Comment: arg.Comment, + }) + return id1 +} + +func getLastComponent(s string) string { + return s[1+strings.LastIndexByte(s, '.'):] +} + +func msgStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) { + v := info.Types[e].Value + if v == nil || v.Kind() != constant.String { + return "", false + } + s = constant.StringVal(v) + // Only record strings with letters. + for _, r := range s { + if unicode.In(r, unicode.L) { + return s, true + } + } + return "", false +} diff --git a/vendor/golang.org/x/text/message/pipeline/generate.go b/vendor/golang.org/x/text/message/pipeline/generate.go new file mode 100644 index 0000000..c5e51bd --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/generate.go @@ -0,0 +1,251 @@ +// Copyright 2017 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 pipeline + +import ( + "fmt" + "io" + "regexp" + "sort" + "strings" + "text/template" + + "golang.org/x/text/collate" + "golang.org/x/text/feature/plural" + "golang.org/x/text/internal" + "golang.org/x/text/internal/catmsg" + "golang.org/x/text/internal/gen" + "golang.org/x/text/language" +) + +var transRe = regexp.MustCompile(`messages\.(.*)\.json`) + +// Generate writes a Go file with the given package name to w, which defines a +// Catalog with translated messages. +func Generate(w io.Writer, pkg string, extracted *Locale, trans ...*Locale) (n int, err error) { + // TODO: add in external input. Right now we assume that all files are + // manually created and stored in the textdata directory. + + // Build up index of translations and original messages. + translations := map[language.Tag]map[string]Message{} + languages := []language.Tag{} + langVars := []string{} + usedKeys := map[string]int{} + + for _, loc := range trans { + tag := loc.Language + if _, ok := translations[tag]; !ok { + translations[tag] = map[string]Message{} + languages = append(languages, tag) + } + for _, m := range loc.Messages { + if !m.Translation.IsEmpty() { + for _, id := range m.ID { + if _, ok := translations[tag][id]; ok { + logf("Duplicate translation in locale %q for message %q", tag, id) + } + translations[tag][id] = m + } + } + } + } + + // Verify completeness and register keys. + internal.SortTags(languages) + + for _, tag := range languages { + langVars = append(langVars, strings.Replace(tag.String(), "-", "_", -1)) + dict := translations[tag] + for _, msg := range extracted.Messages { + for _, id := range msg.ID { + if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() { + if _, ok := usedKeys[msg.Key]; !ok { + usedKeys[msg.Key] = len(usedKeys) + } + break + } + // TODO: log missing entry. + logf("%s: Missing entry for %q.", tag, id) + } + } + } + + cw := gen.NewCodeWriter() + + x := &struct { + Fallback language.Tag + Languages []string + }{ + Fallback: extracted.Language, + Languages: langVars, + } + + if err := lookup.Execute(cw, x); err != nil { + return 0, wrap(err, "error") + } + + keyToIndex := []string{} + for k := range usedKeys { + keyToIndex = append(keyToIndex, k) + } + sort.Strings(keyToIndex) + fmt.Fprint(cw, "var messageKeyToIndex = map[string]int{\n") + for _, k := range keyToIndex { + fmt.Fprintf(cw, "%q: %d,\n", k, usedKeys[k]) + } + fmt.Fprint(cw, "}\n\n") + + for i, tag := range languages { + dict := translations[tag] + a := make([]string, len(usedKeys)) + for _, msg := range extracted.Messages { + for _, id := range msg.ID { + if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() { + m, err := assemble(&msg, &trans.Translation) + if err != nil { + return 0, wrap(err, "error") + } + // TODO: support macros. + data, err := catmsg.Compile(tag, nil, m) + if err != nil { + return 0, wrap(err, "error") + } + key := usedKeys[msg.Key] + if d := a[key]; d != "" && d != data { + logf("Duplicate non-consistent translation for key %q, picking the one for message %q", msg.Key, id) + } + a[key] = string(data) + break + } + } + } + index := []uint32{0} + p := 0 + for _, s := range a { + p += len(s) + index = append(index, uint32(p)) + } + + cw.WriteVar(langVars[i]+"Index", index) + cw.WriteConst(langVars[i]+"Data", strings.Join(a, "")) + } + return cw.WriteGo(w, pkg, "") +} + +func assemble(m *Message, t *Text) (msg catmsg.Message, err error) { + keys := []string{} + for k := range t.Var { + keys = append(keys, k) + } + sort.Strings(keys) + var a []catmsg.Message + for _, k := range keys { + t := t.Var[k] + m, err := assemble(m, &t) + if err != nil { + return nil, err + } + a = append(a, &catmsg.Var{Name: k, Message: m}) + } + if t.Select != nil { + s, err := assembleSelect(m, t.Select) + if err != nil { + return nil, err + } + a = append(a, s) + } + if t.Msg != "" { + sub, err := m.Substitute(t.Msg) + if err != nil { + return nil, err + } + a = append(a, catmsg.String(sub)) + } + switch len(a) { + case 0: + return nil, errorf("generate: empty message") + case 1: + return a[0], nil + default: + return catmsg.FirstOf(a), nil + + } +} + +func assembleSelect(m *Message, s *Select) (msg catmsg.Message, err error) { + cases := []string{} + for c := range s.Cases { + cases = append(cases, c) + } + sortCases(cases) + + caseMsg := []interface{}{} + for _, c := range cases { + cm := s.Cases[c] + m, err := assemble(m, &cm) + if err != nil { + return nil, err + } + caseMsg = append(caseMsg, c, m) + } + + ph := m.Placeholder(s.Arg) + + switch s.Feature { + case "plural": + // TODO: only printf-style selects are supported as of yet. + return plural.Selectf(ph.ArgNum, ph.String, caseMsg...), nil + } + return nil, errorf("unknown feature type %q", s.Feature) +} + +func sortCases(cases []string) { + // TODO: implement full interface. + sort.Slice(cases, func(i, j int) bool { + if cases[j] == "other" && cases[i] != "other" { + return true + } + // the following code relies on '<' < '=' < any letter. + return cmpNumeric(cases[i], cases[j]) == -1 + }) +} + +var cmpNumeric = collate.New(language.Und, collate.Numeric).CompareString + +var lookup = template.Must(template.New("gen").Parse(` +import ( + "golang.org/x/text/language" + "golang.org/x/text/message" + "golang.org/x/text/message/catalog" +) + +type dictionary struct { + index []uint32 + data string +} + +func (d *dictionary) Lookup(key string) (data string, ok bool) { + p := messageKeyToIndex[key] + start, end := d.index[p], d.index[p+1] + if start == end { + return "", false + } + return d.data[start:end], true +} + +func init() { + dict := map[string]catalog.Dictionary{ + {{range .Languages}}"{{.}}": &dictionary{index: {{.}}Index, data: {{.}}Data }, + {{end}} + } + fallback := language.MustParse("{{.Fallback}}") + cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback)) + if err != nil { + panic(err) + } + message.DefaultCatalog = cat +} + +`)) diff --git a/vendor/golang.org/x/text/message/pipeline/message.go b/vendor/golang.org/x/text/message/pipeline/message.go new file mode 100644 index 0000000..8e54700 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/message.go @@ -0,0 +1,241 @@ +// Copyright 2017 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 pipeline + +import ( + "encoding/json" + "strings" + + "golang.org/x/text/language" +) + +// TODO: these definitions should be moved to a package so that the can be used +// by other tools. + +// The file contains the structures used to define translations of a certain +// messages. +// +// A translation may have multiple translations strings, or messages, depending +// on the feature values of the various arguments. For instance, consider +// a hypothetical translation from English to English, where the source defines +// the format string "%d file(s) remaining". +// See the examples directory for examples of extracted messages. + +// Config contains configuration for the translation pipeline. +type Config struct { + SourceLanguage language.Tag + + // Supported indicates the languages for which data should be generated. + // If unspecified, it will attempt to derive the set of supported languages + // from the context. + Supported []language.Tag + + Packages []string + + // TODO: + // - Printf-style configuration + // - Template-style configuration + // - Extraction options + // - Rewrite options + // - Generation options +} + +// A Locale is used to store all information for a single locale. This type is +// used both for extraction and injection. +type Locale struct { + Language language.Tag `json:"language"` + Messages []Message `json:"messages"` + Macros map[string]Text `json:"macros,omitempty"` +} + +// A Message describes a message to be translated. +type Message struct { + // ID contains a list of identifiers for the message. + ID IDList `json:"id"` + // Key is the string that is used to look up the message at runtime. + Key string `json:"key"` + Meaning string `json:"meaning,omitempty"` + Message Text `json:"message"` + Translation Text `json:"translation"` + + Comment string `json:"comment,omitempty"` + TranslatorComment string `json:"translatorComment,omitempty"` + + Placeholders []Placeholder `json:"placeholders,omitempty"` + + // TODO: default placeholder syntax is {foo}. Allow alternative escaping + // like `foo`. + + // Extraction information. + Position string `json:"position,omitempty"` // filePosition:line +} + +// Placeholder reports the placeholder for the given ID if it is defined or nil +// otherwise. +func (m *Message) Placeholder(id string) *Placeholder { + for _, p := range m.Placeholders { + if p.ID == id { + return &p + } + } + return nil +} + +// Substitute replaces placeholders in msg with their original value. +func (m *Message) Substitute(msg string) (sub string, err error) { + last := 0 + for i := 0; i < len(msg); { + pLeft := strings.IndexByte(msg[i:], '{') + if pLeft == -1 { + break + } + pLeft += i + pRight := strings.IndexByte(msg[pLeft:], '}') + if pRight == -1 { + return "", errorf("unmatched '}'") + } + pRight += pLeft + id := strings.TrimSpace(msg[pLeft+1 : pRight]) + i = pRight + 1 + if id != "" && id[0] == '$' { + continue + } + sub += msg[last:pLeft] + last = i + ph := m.Placeholder(id) + if ph == nil { + return "", errorf("unknown placeholder %q in message %q", id, msg) + } + sub += ph.String + } + sub += msg[last:] + return sub, err +} + +// A Placeholder is a part of the message that should not be changed by a +// translator. It can be used to hide or prettify format strings (e.g. %d or +// {{.Count}}), hide HTML, or mark common names that should not be translated. +type Placeholder struct { + // ID is the placeholder identifier without the curly braces. + ID string `json:"id"` + + // String is the string with which to replace the placeholder. This may be a + // formatting string (for instance "%d" or "{{.Count}}") or a literal string + // (
). + String string `json:"string"` + + Type string `json:"type"` + UnderlyingType string `json:"underlyingType"` + // ArgNum and Expr are set if the placeholder is a substitution of an + // argument. + ArgNum int `json:"argNum,omitempty"` + Expr string `json:"expr,omitempty"` + + Comment string `json:"comment,omitempty"` + Example string `json:"example,omitempty"` + + // Features contains the features that are available for the implementation + // of this argument. + Features []Feature `json:"features,omitempty"` +} + +// An argument contains information about the arguments passed to a message. +type argument struct { + // ArgNum corresponds to the number that should be used for explicit argument indexes (e.g. + // "%[1]d"). + ArgNum int `json:"argNum,omitempty"` + + used bool // Used by Placeholder + Type string `json:"type"` + UnderlyingType string `json:"underlyingType"` + Expr string `json:"expr"` + Value string `json:"value,omitempty"` + Comment string `json:"comment,omitempty"` + Position string `json:"position,omitempty"` +} + +// Feature holds information about a feature that can be implemented by +// an Argument. +type Feature struct { + Type string `json:"type"` // Right now this is only gender and plural. + + // TODO: possible values and examples for the language under consideration. + +} + +// Text defines a message to be displayed. +type Text struct { + // Msg and Select contains the message to be displayed. Msg may be used as + // a fallback value if none of the select cases match. + Msg string `json:"msg,omitempty"` + Select *Select `json:"select,omitempty"` + + // Var defines a map of variables that may be substituted in the selected + // message. + Var map[string]Text `json:"var,omitempty"` + + // Example contains an example message formatted with default values. + Example string `json:"example,omitempty"` +} + +// IsEmpty reports whether this Text can generate anything. +func (t *Text) IsEmpty() bool { + return t.Msg == "" && t.Select == nil && t.Var == nil +} + +// rawText erases the UnmarshalJSON method. +type rawText Text + +// UnmarshalJSON implements json.Unmarshaler. +func (t *Text) UnmarshalJSON(b []byte) error { + if b[0] == '"' { + return json.Unmarshal(b, &t.Msg) + } + return json.Unmarshal(b, (*rawText)(t)) +} + +// MarshalJSON implements json.Marshaler. +func (t *Text) MarshalJSON() ([]byte, error) { + if t.Select == nil && t.Var == nil && t.Example == "" { + return json.Marshal(t.Msg) + } + return json.Marshal((*rawText)(t)) +} + +// IDList is a set identifiers that each may refer to possibly different +// versions of the same message. When looking up a messages, the first +// identifier in the list takes precedence. +type IDList []string + +// UnmarshalJSON implements json.Unmarshaler. +func (id *IDList) UnmarshalJSON(b []byte) error { + if b[0] == '"' { + *id = []string{""} + return json.Unmarshal(b, &((*id)[0])) + } + return json.Unmarshal(b, (*[]string)(id)) +} + +// MarshalJSON implements json.Marshaler. +func (id *IDList) MarshalJSON() ([]byte, error) { + if len(*id) == 1 { + return json.Marshal((*id)[0]) + } + return json.Marshal((*[]string)(id)) +} + +// Select selects a Text based on the feature value associated with a feature of +// a certain argument. +type Select struct { + Feature string `json:"feature"` // Name of Feature type (e.g plural) + Arg string `json:"arg"` // The placeholder ID + Cases map[string]Text `json:"cases"` +} + +// TODO: order matters, but can we derive the ordering from the case keys? +// type Case struct { +// Key string `json:"key"` +// Value Text `json:"value"` +// } diff --git a/vendor/golang.org/x/text/message/pipeline/pipeline.go b/vendor/golang.org/x/text/message/pipeline/pipeline.go new file mode 100644 index 0000000..733a50c --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/pipeline.go @@ -0,0 +1,57 @@ +// Copyright 2017 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 pipeline provides tools for creating translation pipelines. +// +// NOTE: UNDER DEVELOPMENT. API MAY CHANGE. +package pipeline + +import ( + "fmt" + "go/build" + "go/parser" + "log" + + "golang.org/x/tools/go/loader" +) + +const ( + extractFile = "extracted.gotext.json" + outFile = "out.gotext.json" + gotextSuffix = ".gotext.json" +) + +// NOTE: The command line tool already prefixes with "gotext:". +var ( + wrap = func(err error, msg string) error { + return fmt.Errorf("%s: %v", msg, err) + } + wrapf = func(err error, msg string, args ...interface{}) error { + return wrap(err, fmt.Sprintf(msg, args...)) + } + errorf = fmt.Errorf +) + +// TODO: don't log. +func logf(format string, args ...interface{}) { + log.Printf(format, args...) +} + +func loadPackages(conf *loader.Config, args []string) (*loader.Program, error) { + if len(args) == 0 { + args = []string{"."} + } + + conf.Build = &build.Default + conf.ParserMode = parser.ParseComments + + // Use the initial packages from the command line. + args, err := conf.FromArgs(args, false) + if err != nil { + return nil, wrap(err, "loading packages failed") + } + + // Load, parse and type-check the whole program. + return conf.Load() +} diff --git a/vendor/golang.org/x/text/message/pipeline/rewrite.go b/vendor/golang.org/x/text/message/pipeline/rewrite.go new file mode 100644 index 0000000..fa78324 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/rewrite.go @@ -0,0 +1,268 @@ +// Copyright 2017 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 pipeline + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/token" + "io" + "os" + "strings" + + "golang.org/x/tools/go/loader" +) + +const printerType = "golang.org/x/text/message.Printer" + +// Rewrite rewrites the Go files in a single package to use the localization +// machinery and rewrites strings to adopt best practices when possible. +// If w is not nil the generated files are written to it, each files with a +// "--- " header. Otherwise the files are overwritten. +func Rewrite(w io.Writer, goPackage string) error { + conf := &loader.Config{ + AllowErrors: true, // Allow unused instances of message.Printer. + } + prog, err := loadPackages(conf, []string{goPackage}) + if err != nil { + return wrap(err, "") + } + + for _, info := range prog.InitialPackages() { + for _, f := range info.Files { + // Associate comments with nodes. + + // Pick up initialized Printers at the package level. + r := rewriter{info: info, conf: conf} + for _, n := range info.InitOrder { + if t := r.info.Types[n.Rhs].Type.String(); strings.HasSuffix(t, printerType) { + r.printerVar = n.Lhs[0].Name() + } + } + + ast.Walk(&r, f) + + w := w + if w == nil { + var err error + if w, err = os.Create(conf.Fset.File(f.Pos()).Name()); err != nil { + return wrap(err, "open failed") + } + } else { + fmt.Fprintln(w, "---", conf.Fset.File(f.Pos()).Name()) + } + + if err := format.Node(w, conf.Fset, f); err != nil { + return wrap(err, "go format failed") + } + } + } + + return nil +} + +type rewriter struct { + info *loader.PackageInfo + conf *loader.Config + printerVar string +} + +// print returns Go syntax for the specified node. +func (r *rewriter) print(n ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, r.conf.Fset, n) + return buf.String() +} + +func (r *rewriter) Visit(n ast.Node) ast.Visitor { + // Save the state by scope. + if _, ok := n.(*ast.BlockStmt); ok { + r := *r + return &r + } + // Find Printers created by assignment. + stmt, ok := n.(*ast.AssignStmt) + if ok { + for _, v := range stmt.Lhs { + if r.printerVar == r.print(v) { + r.printerVar = "" + } + } + for i, v := range stmt.Rhs { + if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) { + r.printerVar = r.print(stmt.Lhs[i]) + return r + } + } + } + // Find Printers created by variable declaration. + spec, ok := n.(*ast.ValueSpec) + if ok { + for _, v := range spec.Names { + if r.printerVar == r.print(v) { + r.printerVar = "" + } + } + for i, v := range spec.Values { + if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) { + r.printerVar = r.print(spec.Names[i]) + return r + } + } + } + if r.printerVar == "" { + return r + } + call, ok := n.(*ast.CallExpr) + if !ok { + return r + } + + // TODO: Handle literal values? + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return r + } + meth := r.info.Selections[sel] + + source := r.print(sel.X) + fun := r.print(sel.Sel) + if meth != nil { + source = meth.Recv().String() + fun = meth.Obj().Name() + } + + // TODO: remove cheap hack and check if the type either + // implements some interface or is specifically of type + // "golang.org/x/text/message".Printer. + m, ok := rewriteFuncs[source] + if !ok { + return r + } + + rewriteType, ok := m[fun] + if !ok { + return r + } + ident := ast.NewIdent(r.printerVar) + ident.NamePos = sel.X.Pos() + sel.X = ident + if rewriteType.method != "" { + sel.Sel.Name = rewriteType.method + } + + // Analyze arguments. + argn := rewriteType.arg + if rewriteType.format || argn >= len(call.Args) { + return r + } + hasConst := false + for _, a := range call.Args[argn:] { + if v := r.info.Types[a].Value; v != nil && v.Kind() == constant.String { + hasConst = true + break + } + } + if !hasConst { + return r + } + sel.Sel.Name = rewriteType.methodf + + // We are done if there is only a single string that does not need to be + // escaped. + if len(call.Args) == 1 { + s, ok := constStr(r.info, call.Args[0]) + if ok && !strings.Contains(s, "%") && !rewriteType.newLine { + return r + } + } + + // Rewrite arguments as format string. + expr := &ast.BasicLit{ + ValuePos: call.Lparen, + Kind: token.STRING, + } + newArgs := append(call.Args[:argn:argn], expr) + newStr := []string{} + for i, a := range call.Args[argn:] { + if s, ok := constStr(r.info, a); ok { + newStr = append(newStr, strings.Replace(s, "%", "%%", -1)) + } else { + newStr = append(newStr, "%v") + newArgs = append(newArgs, call.Args[argn+i]) + } + } + s := strings.Join(newStr, rewriteType.sep) + if rewriteType.newLine { + s += "\n" + } + expr.Value = fmt.Sprintf("%q", s) + + call.Args = newArgs + + // TODO: consider creating an expression instead of a constant string and + // then wrapping it in an escape function or so: + // call.Args[argn+i] = &ast.CallExpr{ + // Fun: &ast.SelectorExpr{ + // X: ast.NewIdent("message"), + // Sel: ast.NewIdent("Lookup"), + // }, + // Args: []ast.Expr{a}, + // } + // } + + return r +} + +type rewriteType struct { + // method is the name of the equivalent method on a printer, or "" if it is + // the same. + method string + + // methodf is the method to use if the arguments can be rewritten as a + // arguments to a printf-style call. + methodf string + + // format is true if the method takes a formatting string followed by + // substitution arguments. + format bool + + // arg indicates the position of the argument to extract. If all is + // positive, all arguments from this argument onwards needs to be extracted. + arg int + + sep string + newLine bool +} + +// rewriteFuncs list functions that can be directly mapped to the printer +// functions of the message package. +var rewriteFuncs = map[string]map[string]rewriteType{ + // TODO: Printer -> *golang.org/x/text/message.Printer + "fmt": { + "Print": rewriteType{methodf: "Printf"}, + "Sprint": rewriteType{methodf: "Sprintf"}, + "Fprint": rewriteType{methodf: "Fprintf"}, + + "Println": rewriteType{methodf: "Printf", sep: " ", newLine: true}, + "Sprintln": rewriteType{methodf: "Sprintf", sep: " ", newLine: true}, + "Fprintln": rewriteType{methodf: "Fprintf", sep: " ", newLine: true}, + + "Printf": rewriteType{method: "Printf", format: true}, + "Sprintf": rewriteType{method: "Sprintf", format: true}, + "Fprintf": rewriteType{method: "Fprintf", format: true}, + }, +} + +func constStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) { + v := info.Types[e].Value + if v == nil || v.Kind() != constant.String { + return "", false + } + return constant.StringVal(v), true +} diff --git a/vendor/golang.org/x/text/message/print.go b/vendor/golang.org/x/text/message/print.go new file mode 100644 index 0000000..777e172 --- /dev/null +++ b/vendor/golang.org/x/text/message/print.go @@ -0,0 +1,979 @@ +// Copyright 2017 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 message + +import ( + "bytes" + "fmt" // TODO: consider copying interfaces from package fmt to avoid dependency. + "math" + "reflect" + "sync" + "unicode/utf8" + + "golang.org/x/text/internal/format" + "golang.org/x/text/internal/number" + "golang.org/x/text/language" + "golang.org/x/text/message/catalog" +) + +// Strings for use with buffer.WriteString. +// This is less overhead than using buffer.Write with byte arrays. +const ( + commaSpaceString = ", " + nilAngleString = "" + nilParenString = "(nil)" + nilString = "nil" + mapString = "map[" + percentBangString = "%!" + missingString = "(MISSING)" + badIndexString = "(BADINDEX)" + panicString = "(PANIC=" + extraString = "%!(EXTRA " + badWidthString = "%!(BADWIDTH)" + badPrecString = "%!(BADPREC)" + noVerbString = "%!(NOVERB)" + + invReflectString = "" +) + +var printerPool = sync.Pool{ + New: func() interface{} { return new(printer) }, +} + +// newPrinter allocates a new printer struct or grabs a cached one. +func newPrinter(pp *Printer) *printer { + p := printerPool.Get().(*printer) + p.Printer = *pp + // TODO: cache most of the following call. + p.catContext = pp.cat.Context(pp.tag, p) + + p.panicking = false + p.erroring = false + p.fmt.init(&p.Buffer) + return p +} + +// free saves used printer structs in printerFree; avoids an allocation per invocation. +func (p *printer) free() { + p.Buffer.Reset() + p.arg = nil + p.value = reflect.Value{} + printerPool.Put(p) +} + +// printer is used to store a printer's state. +// It implements "golang.org/x/text/internal/format".State. +type printer struct { + Printer + + // the context for looking up message translations + catContext *catalog.Context + + // buffer for accumulating output. + bytes.Buffer + + // arg holds the current item, as an interface{}. + arg interface{} + // value is used instead of arg for reflect values. + value reflect.Value + + // fmt is used to format basic items such as integers or strings. + fmt formatInfo + + // panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion. + panicking bool + // erroring is set when printing an error string to guard against calling handleMethods. + erroring bool +} + +// Language implements "golang.org/x/text/internal/format".State. +func (p *printer) Language() language.Tag { return p.tag } + +func (p *printer) Width() (wid int, ok bool) { return p.fmt.Width, p.fmt.WidthPresent } + +func (p *printer) Precision() (prec int, ok bool) { return p.fmt.Prec, p.fmt.PrecPresent } + +func (p *printer) Flag(b int) bool { + switch b { + case '-': + return p.fmt.Minus + case '+': + return p.fmt.Plus || p.fmt.PlusV + case '#': + return p.fmt.Sharp || p.fmt.SharpV + case ' ': + return p.fmt.Space + case '0': + return p.fmt.Zero + } + return false +} + +// getField gets the i'th field of the struct value. +// If the field is itself is an interface, return a value for +// the thing inside the interface, not the interface itself. +func getField(v reflect.Value, i int) reflect.Value { + val := v.Field(i) + if val.Kind() == reflect.Interface && !val.IsNil() { + val = val.Elem() + } + return val +} + +func (p *printer) unknownType(v reflect.Value) { + if !v.IsValid() { + p.WriteString(nilAngleString) + return + } + p.WriteByte('?') + p.WriteString(v.Type().String()) + p.WriteByte('?') +} + +func (p *printer) badVerb(verb rune) { + p.erroring = true + p.WriteString(percentBangString) + p.WriteRune(verb) + p.WriteByte('(') + switch { + case p.arg != nil: + p.WriteString(reflect.TypeOf(p.arg).String()) + p.WriteByte('=') + p.printArg(p.arg, 'v') + case p.value.IsValid(): + p.WriteString(p.value.Type().String()) + p.WriteByte('=') + p.printValue(p.value, 'v', 0) + default: + p.WriteString(nilAngleString) + } + p.WriteByte(')') + p.erroring = false +} + +func (p *printer) fmtBool(v bool, verb rune) { + switch verb { + case 't', 'v': + p.fmt.fmt_boolean(v) + default: + p.badVerb(verb) + } +} + +// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or +// not, as requested, by temporarily setting the sharp flag. +func (p *printer) fmt0x64(v uint64, leading0x bool) { + sharp := p.fmt.Sharp + p.fmt.Sharp = leading0x + p.fmt.fmt_integer(v, 16, unsigned, ldigits) + p.fmt.Sharp = sharp +} + +// fmtInteger formats a signed or unsigned integer. +func (p *printer) fmtInteger(v uint64, isSigned bool, verb rune) { + switch verb { + case 'v': + if p.fmt.SharpV && !isSigned { + p.fmt0x64(v, true) + return + } + fallthrough + case 'd': + if p.fmt.Sharp || p.fmt.SharpV { + p.fmt.fmt_integer(v, 10, isSigned, ldigits) + } else { + p.fmtDecimalInt(v, isSigned) + } + case 'b': + p.fmt.fmt_integer(v, 2, isSigned, ldigits) + case 'o': + p.fmt.fmt_integer(v, 8, isSigned, ldigits) + case 'x': + p.fmt.fmt_integer(v, 16, isSigned, ldigits) + case 'X': + p.fmt.fmt_integer(v, 16, isSigned, udigits) + case 'c': + p.fmt.fmt_c(v) + case 'q': + if v <= utf8.MaxRune { + p.fmt.fmt_qc(v) + } else { + p.badVerb(verb) + } + case 'U': + p.fmt.fmt_unicode(v) + default: + p.badVerb(verb) + } +} + +// fmtFloat formats a float. The default precision for each verb +// is specified as last argument in the call to fmt_float. +func (p *printer) fmtFloat(v float64, size int, verb rune) { + switch verb { + case 'b': + p.fmt.fmt_float(v, size, verb, -1) + case 'v': + verb = 'g' + fallthrough + case 'g', 'G': + if p.fmt.Sharp || p.fmt.SharpV { + p.fmt.fmt_float(v, size, verb, -1) + } else { + p.fmtVariableFloat(v, size) + } + case 'e', 'E': + if p.fmt.Sharp || p.fmt.SharpV { + p.fmt.fmt_float(v, size, verb, 6) + } else { + p.fmtScientific(v, size, 6) + } + case 'f', 'F': + if p.fmt.Sharp || p.fmt.SharpV { + p.fmt.fmt_float(v, size, verb, 6) + } else { + p.fmtDecimalFloat(v, size, 6) + } + default: + p.badVerb(verb) + } +} + +func (p *printer) setFlags(f *number.Formatter) { + f.Flags &^= number.ElideSign + if p.fmt.Plus || p.fmt.Space { + f.Flags |= number.AlwaysSign + if !p.fmt.Plus { + f.Flags |= number.ElideSign + } + } else { + f.Flags &^= number.AlwaysSign + } +} + +func (p *printer) updatePadding(f *number.Formatter) { + f.Flags &^= number.PadMask + if p.fmt.Minus { + f.Flags |= number.PadAfterSuffix + } else { + f.Flags |= number.PadBeforePrefix + } + f.PadRune = ' ' + f.FormatWidth = uint16(p.fmt.Width) +} + +func (p *printer) initDecimal(minFrac, maxFrac int) { + f := &p.toDecimal + f.MinIntegerDigits = 1 + f.MaxIntegerDigits = 0 + f.MinFractionDigits = uint8(minFrac) + f.MaxFractionDigits = int16(maxFrac) + p.setFlags(f) + f.PadRune = 0 + if p.fmt.WidthPresent { + if p.fmt.Zero { + wid := p.fmt.Width + // Use significant integers for this. + // TODO: this is not the same as width, but so be it. + if f.MinFractionDigits > 0 { + wid -= 1 + int(f.MinFractionDigits) + } + if p.fmt.Plus || p.fmt.Space { + wid-- + } + if wid > 0 && wid > int(f.MinIntegerDigits) { + f.MinIntegerDigits = uint8(wid) + } + } + p.updatePadding(f) + } +} + +func (p *printer) initScientific(minFrac, maxFrac int) { + f := &p.toScientific + if maxFrac < 0 { + f.SetPrecision(maxFrac) + } else { + f.SetPrecision(maxFrac + 1) + f.MinFractionDigits = uint8(minFrac) + f.MaxFractionDigits = int16(maxFrac) + } + f.MinExponentDigits = 2 + p.setFlags(f) + f.PadRune = 0 + if p.fmt.WidthPresent { + f.Flags &^= number.PadMask + if p.fmt.Zero { + f.PadRune = f.Digit(0) + f.Flags |= number.PadAfterPrefix + } else { + f.PadRune = ' ' + f.Flags |= number.PadBeforePrefix + } + p.updatePadding(f) + } +} + +func (p *printer) fmtDecimalInt(v uint64, isSigned bool) { + var d number.Decimal + + f := &p.toDecimal + if p.fmt.PrecPresent { + p.setFlags(f) + f.MinIntegerDigits = uint8(p.fmt.Prec) + f.MaxIntegerDigits = 0 + f.MinFractionDigits = 0 + f.MaxFractionDigits = 0 + if p.fmt.WidthPresent { + p.updatePadding(f) + } + } else { + p.initDecimal(0, 0) + } + d.ConvertInt(p.toDecimal.RoundingContext, isSigned, v) + + out := p.toDecimal.Format([]byte(nil), &d) + p.Buffer.Write(out) +} + +func (p *printer) fmtDecimalFloat(v float64, size, prec int) { + var d number.Decimal + if p.fmt.PrecPresent { + prec = p.fmt.Prec + } + p.initDecimal(prec, prec) + d.ConvertFloat(p.toDecimal.RoundingContext, v, size) + + out := p.toDecimal.Format([]byte(nil), &d) + p.Buffer.Write(out) +} + +func (p *printer) fmtVariableFloat(v float64, size int) { + prec := -1 + if p.fmt.PrecPresent { + prec = p.fmt.Prec + } + var d number.Decimal + p.initScientific(0, prec) + d.ConvertFloat(p.toScientific.RoundingContext, v, size) + + // Copy logic of 'g' formatting from strconv. It is simplified a bit as + // we don't have to mind having prec > len(d.Digits). + shortest := prec < 0 + ePrec := prec + if shortest { + prec = len(d.Digits) + ePrec = 6 + } else if prec == 0 { + prec = 1 + ePrec = 1 + } + exp := int(d.Exp) - 1 + if exp < -4 || exp >= ePrec { + p.initScientific(0, prec) + + out := p.toScientific.Format([]byte(nil), &d) + p.Buffer.Write(out) + } else { + if prec > int(d.Exp) { + prec = len(d.Digits) + } + if prec -= int(d.Exp); prec < 0 { + prec = 0 + } + p.initDecimal(0, prec) + + out := p.toDecimal.Format([]byte(nil), &d) + p.Buffer.Write(out) + } +} + +func (p *printer) fmtScientific(v float64, size, prec int) { + var d number.Decimal + if p.fmt.PrecPresent { + prec = p.fmt.Prec + } + p.initScientific(prec, prec) + rc := p.toScientific.RoundingContext + d.ConvertFloat(rc, v, size) + + out := p.toScientific.Format([]byte(nil), &d) + p.Buffer.Write(out) + +} + +// fmtComplex formats a complex number v with +// r = real(v) and j = imag(v) as (r+ji) using +// fmtFloat for r and j formatting. +func (p *printer) fmtComplex(v complex128, size int, verb rune) { + // Make sure any unsupported verbs are found before the + // calls to fmtFloat to not generate an incorrect error string. + switch verb { + case 'v', 'b', 'g', 'G', 'f', 'F', 'e', 'E': + p.WriteByte('(') + p.fmtFloat(real(v), size/2, verb) + // Imaginary part always has a sign. + if math.IsNaN(imag(v)) { + // By CLDR's rules, NaNs do not use patterns or signs. As this code + // relies on AlwaysSign working for imaginary parts, we need to + // manually handle NaNs. + f := &p.toScientific + p.setFlags(f) + p.updatePadding(f) + p.setFlags(f) + nan := f.Symbol(number.SymNan) + extra := 0 + if w, ok := p.Width(); ok { + extra = w - utf8.RuneCountInString(nan) - 1 + } + if f.Flags&number.PadAfterNumber == 0 { + for ; extra > 0; extra-- { + p.WriteRune(f.PadRune) + } + } + p.WriteString(f.Symbol(number.SymPlusSign)) + p.WriteString(nan) + for ; extra > 0; extra-- { + p.WriteRune(f.PadRune) + } + p.WriteString("i)") + return + } + oldPlus := p.fmt.Plus + p.fmt.Plus = true + p.fmtFloat(imag(v), size/2, verb) + p.WriteString("i)") // TODO: use symbol? + p.fmt.Plus = oldPlus + default: + p.badVerb(verb) + } +} + +func (p *printer) fmtString(v string, verb rune) { + switch verb { + case 'v': + if p.fmt.SharpV { + p.fmt.fmt_q(v) + } else { + p.fmt.fmt_s(v) + } + case 's': + p.fmt.fmt_s(v) + case 'x': + p.fmt.fmt_sx(v, ldigits) + case 'X': + p.fmt.fmt_sx(v, udigits) + case 'q': + p.fmt.fmt_q(v) + default: + p.badVerb(verb) + } +} + +func (p *printer) fmtBytes(v []byte, verb rune, typeString string) { + switch verb { + case 'v', 'd': + if p.fmt.SharpV { + p.WriteString(typeString) + if v == nil { + p.WriteString(nilParenString) + return + } + p.WriteByte('{') + for i, c := range v { + if i > 0 { + p.WriteString(commaSpaceString) + } + p.fmt0x64(uint64(c), true) + } + p.WriteByte('}') + } else { + p.WriteByte('[') + for i, c := range v { + if i > 0 { + p.WriteByte(' ') + } + p.fmt.fmt_integer(uint64(c), 10, unsigned, ldigits) + } + p.WriteByte(']') + } + case 's': + p.fmt.fmt_s(string(v)) + case 'x': + p.fmt.fmt_bx(v, ldigits) + case 'X': + p.fmt.fmt_bx(v, udigits) + case 'q': + p.fmt.fmt_q(string(v)) + default: + p.printValue(reflect.ValueOf(v), verb, 0) + } +} + +func (p *printer) fmtPointer(value reflect.Value, verb rune) { + var u uintptr + switch value.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: + u = value.Pointer() + default: + p.badVerb(verb) + return + } + + switch verb { + case 'v': + if p.fmt.SharpV { + p.WriteByte('(') + p.WriteString(value.Type().String()) + p.WriteString(")(") + if u == 0 { + p.WriteString(nilString) + } else { + p.fmt0x64(uint64(u), true) + } + p.WriteByte(')') + } else { + if u == 0 { + p.fmt.padString(nilAngleString) + } else { + p.fmt0x64(uint64(u), !p.fmt.Sharp) + } + } + case 'p': + p.fmt0x64(uint64(u), !p.fmt.Sharp) + case 'b', 'o', 'd', 'x', 'X': + if verb == 'd' { + p.fmt.Sharp = true // Print as standard go. TODO: does this make sense? + } + p.fmtInteger(uint64(u), unsigned, verb) + default: + p.badVerb(verb) + } +} + +func (p *printer) catchPanic(arg interface{}, verb rune) { + if err := recover(); err != nil { + // If it's a nil pointer, just say "". The likeliest causes are a + // Stringer that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "" is a nice result. + if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() { + p.WriteString(nilAngleString) + return + } + // Otherwise print a concise panic message. Most of the time the panic + // value will print itself nicely. + if p.panicking { + // Nested panics; the recursion in printArg cannot succeed. + panic(err) + } + + oldFlags := p.fmt.Parser + // For this output we want default behavior. + p.fmt.ClearFlags() + + p.WriteString(percentBangString) + p.WriteRune(verb) + p.WriteString(panicString) + p.panicking = true + p.printArg(err, 'v') + p.panicking = false + p.WriteByte(')') + + p.fmt.Parser = oldFlags + } +} + +func (p *printer) handleMethods(verb rune) (handled bool) { + if p.erroring { + return + } + // Is it a Formatter? + if formatter, ok := p.arg.(format.Formatter); ok { + handled = true + defer p.catchPanic(p.arg, verb) + formatter.Format(p, verb) + return + } + if formatter, ok := p.arg.(fmt.Formatter); ok { + handled = true + defer p.catchPanic(p.arg, verb) + formatter.Format(p, verb) + return + } + + // If we're doing Go syntax and the argument knows how to supply it, take care of it now. + if p.fmt.SharpV { + if stringer, ok := p.arg.(fmt.GoStringer); ok { + handled = true + defer p.catchPanic(p.arg, verb) + // Print the result of GoString unadorned. + p.fmt.fmt_s(stringer.GoString()) + return + } + } else { + // If a string is acceptable according to the format, see if + // the value satisfies one of the string-valued interfaces. + // Println etc. set verb to %v, which is "stringable". + switch verb { + case 'v', 's', 'x', 'X', 'q': + // Is it an error or Stringer? + // The duplication in the bodies is necessary: + // setting handled and deferring catchPanic + // must happen before calling the method. + switch v := p.arg.(type) { + case error: + handled = true + defer p.catchPanic(p.arg, verb) + p.fmtString(v.Error(), verb) + return + + case fmt.Stringer: + handled = true + defer p.catchPanic(p.arg, verb) + p.fmtString(v.String(), verb) + return + } + } + } + return false +} + +func (p *printer) printArg(arg interface{}, verb rune) { + p.arg = arg + p.value = reflect.Value{} + + if arg == nil { + switch verb { + case 'T', 'v': + p.fmt.padString(nilAngleString) + default: + p.badVerb(verb) + } + return + } + + // Special processing considerations. + // %T (the value's type) and %p (its address) are special; we always do them first. + switch verb { + case 'T': + p.fmt.fmt_s(reflect.TypeOf(arg).String()) + return + case 'p': + p.fmtPointer(reflect.ValueOf(arg), 'p') + return + } + + // Some types can be done without reflection. + switch f := arg.(type) { + case bool: + p.fmtBool(f, verb) + case float32: + p.fmtFloat(float64(f), 32, verb) + case float64: + p.fmtFloat(f, 64, verb) + case complex64: + p.fmtComplex(complex128(f), 64, verb) + case complex128: + p.fmtComplex(f, 128, verb) + case int: + p.fmtInteger(uint64(f), signed, verb) + case int8: + p.fmtInteger(uint64(f), signed, verb) + case int16: + p.fmtInteger(uint64(f), signed, verb) + case int32: + p.fmtInteger(uint64(f), signed, verb) + case int64: + p.fmtInteger(uint64(f), signed, verb) + case uint: + p.fmtInteger(uint64(f), unsigned, verb) + case uint8: + p.fmtInteger(uint64(f), unsigned, verb) + case uint16: + p.fmtInteger(uint64(f), unsigned, verb) + case uint32: + p.fmtInteger(uint64(f), unsigned, verb) + case uint64: + p.fmtInteger(f, unsigned, verb) + case uintptr: + p.fmtInteger(uint64(f), unsigned, verb) + case string: + p.fmtString(f, verb) + case []byte: + p.fmtBytes(f, verb, "[]byte") + case reflect.Value: + // Handle extractable values with special methods + // since printValue does not handle them at depth 0. + if f.IsValid() && f.CanInterface() { + p.arg = f.Interface() + if p.handleMethods(verb) { + return + } + } + p.printValue(f, verb, 0) + default: + // If the type is not simple, it might have methods. + if !p.handleMethods(verb) { + // Need to use reflection, since the type had no + // interface methods that could be used for formatting. + p.printValue(reflect.ValueOf(f), verb, 0) + } + } +} + +// printValue is similar to printArg but starts with a reflect value, not an interface{} value. +// It does not handle 'p' and 'T' verbs because these should have been already handled by printArg. +func (p *printer) printValue(value reflect.Value, verb rune, depth int) { + // Handle values with special methods if not already handled by printArg (depth == 0). + if depth > 0 && value.IsValid() && value.CanInterface() { + p.arg = value.Interface() + if p.handleMethods(verb) { + return + } + } + p.arg = nil + p.value = value + + switch f := value; value.Kind() { + case reflect.Invalid: + if depth == 0 { + p.WriteString(invReflectString) + } else { + switch verb { + case 'v': + p.WriteString(nilAngleString) + default: + p.badVerb(verb) + } + } + case reflect.Bool: + p.fmtBool(f.Bool(), verb) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p.fmtInteger(uint64(f.Int()), signed, verb) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p.fmtInteger(f.Uint(), unsigned, verb) + case reflect.Float32: + p.fmtFloat(f.Float(), 32, verb) + case reflect.Float64: + p.fmtFloat(f.Float(), 64, verb) + case reflect.Complex64: + p.fmtComplex(f.Complex(), 64, verb) + case reflect.Complex128: + p.fmtComplex(f.Complex(), 128, verb) + case reflect.String: + p.fmtString(f.String(), verb) + case reflect.Map: + if p.fmt.SharpV { + p.WriteString(f.Type().String()) + if f.IsNil() { + p.WriteString(nilParenString) + return + } + p.WriteByte('{') + } else { + p.WriteString(mapString) + } + keys := f.MapKeys() + for i, key := range keys { + if i > 0 { + if p.fmt.SharpV { + p.WriteString(commaSpaceString) + } else { + p.WriteByte(' ') + } + } + p.printValue(key, verb, depth+1) + p.WriteByte(':') + p.printValue(f.MapIndex(key), verb, depth+1) + } + if p.fmt.SharpV { + p.WriteByte('}') + } else { + p.WriteByte(']') + } + case reflect.Struct: + if p.fmt.SharpV { + p.WriteString(f.Type().String()) + } + p.WriteByte('{') + for i := 0; i < f.NumField(); i++ { + if i > 0 { + if p.fmt.SharpV { + p.WriteString(commaSpaceString) + } else { + p.WriteByte(' ') + } + } + if p.fmt.PlusV || p.fmt.SharpV { + if name := f.Type().Field(i).Name; name != "" { + p.WriteString(name) + p.WriteByte(':') + } + } + p.printValue(getField(f, i), verb, depth+1) + } + p.WriteByte('}') + case reflect.Interface: + value := f.Elem() + if !value.IsValid() { + if p.fmt.SharpV { + p.WriteString(f.Type().String()) + p.WriteString(nilParenString) + } else { + p.WriteString(nilAngleString) + } + } else { + p.printValue(value, verb, depth+1) + } + case reflect.Array, reflect.Slice: + switch verb { + case 's', 'q', 'x', 'X': + // Handle byte and uint8 slices and arrays special for the above verbs. + t := f.Type() + if t.Elem().Kind() == reflect.Uint8 { + var bytes []byte + if f.Kind() == reflect.Slice { + bytes = f.Bytes() + } else if f.CanAddr() { + bytes = f.Slice(0, f.Len()).Bytes() + } else { + // We have an array, but we cannot Slice() a non-addressable array, + // so we build a slice by hand. This is a rare case but it would be nice + // if reflection could help a little more. + bytes = make([]byte, f.Len()) + for i := range bytes { + bytes[i] = byte(f.Index(i).Uint()) + } + } + p.fmtBytes(bytes, verb, t.String()) + return + } + } + if p.fmt.SharpV { + p.WriteString(f.Type().String()) + if f.Kind() == reflect.Slice && f.IsNil() { + p.WriteString(nilParenString) + return + } + p.WriteByte('{') + for i := 0; i < f.Len(); i++ { + if i > 0 { + p.WriteString(commaSpaceString) + } + p.printValue(f.Index(i), verb, depth+1) + } + p.WriteByte('}') + } else { + p.WriteByte('[') + for i := 0; i < f.Len(); i++ { + if i > 0 { + p.WriteByte(' ') + } + p.printValue(f.Index(i), verb, depth+1) + } + p.WriteByte(']') + } + case reflect.Ptr: + // pointer to array or slice or struct? ok at top level + // but not embedded (avoid loops) + if depth == 0 && f.Pointer() != 0 { + switch a := f.Elem(); a.Kind() { + case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: + p.WriteByte('&') + p.printValue(a, verb, depth+1) + return + } + } + fallthrough + case reflect.Chan, reflect.Func, reflect.UnsafePointer: + p.fmtPointer(f, verb) + default: + p.unknownType(f) + } +} + +func (p *printer) badArgNum(verb rune) { + p.WriteString(percentBangString) + p.WriteRune(verb) + p.WriteString(badIndexString) +} + +func (p *printer) missingArg(verb rune) { + p.WriteString(percentBangString) + p.WriteRune(verb) + p.WriteString(missingString) +} + +func (p *printer) doPrintf(fmt string) { + for p.fmt.Parser.SetFormat(fmt); p.fmt.Scan(); { + switch p.fmt.Status { + case format.StatusText: + p.WriteString(p.fmt.Text()) + case format.StatusSubstitution: + p.printArg(p.Arg(p.fmt.ArgNum), p.fmt.Verb) + case format.StatusBadWidthSubstitution: + p.WriteString(badWidthString) + p.printArg(p.Arg(p.fmt.ArgNum), p.fmt.Verb) + case format.StatusBadPrecSubstitution: + p.WriteString(badPrecString) + p.printArg(p.Arg(p.fmt.ArgNum), p.fmt.Verb) + case format.StatusNoVerb: + p.WriteString(noVerbString) + case format.StatusBadArgNum: + p.badArgNum(p.fmt.Verb) + case format.StatusMissingArg: + p.missingArg(p.fmt.Verb) + default: + panic("unreachable") + } + } + + // Check for extra arguments, but only if there was at least one ordered + // argument. Note that this behavior is necessarily different from fmt: + // different variants of messages may opt to drop some or all of the + // arguments. + if !p.fmt.Reordered && p.fmt.ArgNum < len(p.fmt.Args) && p.fmt.ArgNum != 0 { + p.fmt.ClearFlags() + p.WriteString(extraString) + for i, arg := range p.fmt.Args[p.fmt.ArgNum:] { + if i > 0 { + p.WriteString(commaSpaceString) + } + if arg == nil { + p.WriteString(nilAngleString) + } else { + p.WriteString(reflect.TypeOf(arg).String()) + p.WriteString("=") + p.printArg(arg, 'v') + } + } + p.WriteByte(')') + } +} + +func (p *printer) doPrint(a []interface{}) { + prevString := false + for argNum, arg := range a { + isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String + // Add a space between two non-string arguments. + if argNum > 0 && !isString && !prevString { + p.WriteByte(' ') + } + p.printArg(arg, 'v') + prevString = isString + } +} + +// doPrintln is like doPrint but always adds a space between arguments +// and a newline after the last argument. +func (p *printer) doPrintln(a []interface{}) { + for argNum, arg := range a { + if argNum > 0 { + p.WriteByte(' ') + } + p.printArg(arg, 'v') + } + p.WriteByte('\n') +} -- cgit v1.2.3