From 2fa7c464f019f67c5c0494aaf5ac0f5ecc1ee7a7 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 16 Jan 2018 12:03:31 -0500 Subject: Updated dependencies and added avct/uasurfer (#8089) * Updated dependencies and added avct/uasurfer * Added uasurfer to NOTICE.txt --- vendor/golang.org/x/text/message/catalog.go | 12 +- .../golang.org/x/text/message/catalog/catalog.go | 161 ++++++-- .../x/text/message/catalog/catalog_test.go | 148 +++++-- vendor/golang.org/x/text/message/catalog/dict.go | 49 ++- 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 | 6 +- vendor/golang.org/x/text/message/fmt_test.go | 30 +- vendor/golang.org/x/text/message/format.go | 150 +++----- vendor/golang.org/x/text/message/message.go | 103 ++--- vendor/golang.org/x/text/message/message_test.go | 4 +- .../golang.org/x/text/message/pipeline/extract.go | 314 +++++++++++++++ .../golang.org/x/text/message/pipeline/generate.go | 314 +++++++++++++++ .../x/text/message/pipeline/go19_test.go | 13 + .../golang.org/x/text/message/pipeline/message.go | 241 ++++++++++++ .../golang.org/x/text/message/pipeline/pipeline.go | 422 ++++++++++++++++++++ .../x/text/message/pipeline/pipeline_test.go | 126 ++++++ .../golang.org/x/text/message/pipeline/rewrite.go | 268 +++++++++++++ .../message/pipeline/testdata/test1/catalog_gen.go | 85 ++++ .../pipeline/testdata/test1/catalog_gen.go.want | 85 ++++ .../pipeline/testdata/test1/catalog_test.go | 49 +++ .../pipeline/testdata/test1/extracted.gotext.json | 188 +++++++++ .../testdata/test1/extracted.gotext.json.want | 188 +++++++++ .../testdata/test1/locales/de/messages.gotext.json | 123 ++++++ .../testdata/test1/locales/de/out.gotext.json | 137 +++++++ .../testdata/test1/locales/de/out.gotext.json.want | 137 +++++++ .../test1/locales/en-US/messages.gotext.json | 91 +++++ .../testdata/test1/locales/en-US/out.gotext.json | 154 ++++++++ .../test1/locales/en-US/out.gotext.json.want | 154 ++++++++ .../testdata/test1/locales/zh/messages.gotext.json | 135 +++++++ .../testdata/test1/locales/zh/out.gotext.json | 137 +++++++ .../testdata/test1/locales/zh/out.gotext.json.want | 137 +++++++ .../text/message/pipeline/testdata/test1/test1.go | 75 ++++ vendor/golang.org/x/text/message/print.go | 427 +++++---------------- 35 files changed, 4193 insertions(+), 551 deletions(-) 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 mode change 100755 => 100644 vendor/golang.org/x/text/message/fmt_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/go19_test.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/pipeline_test.go create mode 100644 vendor/golang.org/x/text/message/pipeline/rewrite.go create mode 100644 vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go create mode 100644 vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go.want create mode 100644 vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_test.go create mode 100644 vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json create mode 100644 vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json.want create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/messages.gotext.json create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json.want create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/messages.gotext.json create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/messages.gotext.json create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json create mode 100755 vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want create mode 100644 vendor/golang.org/x/text/message/pipeline/testdata/test1/test1.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 index 2f65b4bbc..068271def 100644 --- a/vendor/golang.org/x/text/message/catalog.go +++ b/vendor/golang.org/x/text/message/catalog.go @@ -12,10 +12,18 @@ import ( "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.Catalog = defaultCatalog -var defaultCatalog = catalog.New() +var defaultCatalog = catalog.NewBuilder() // SetString calls SetString on the initial default Catalog. func SetString(tag language.Tag, key string, msg string) error { diff --git a/vendor/golang.org/x/text/message/catalog/catalog.go b/vendor/golang.org/x/text/message/catalog/catalog.go index 957444c10..34a30d3c8 100644 --- a/vendor/golang.org/x/text/message/catalog/catalog.go +++ b/vendor/golang.org/x/text/message/catalog/catalog.go @@ -156,23 +156,127 @@ import ( "errors" "fmt" + "golang.org/x/text/internal" + "golang.org/x/text/internal/catmsg" "golang.org/x/text/language" ) -// A Catalog holds translations for messages for supported languages. -type Catalog struct { +// 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{} +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. @@ -186,22 +290,17 @@ type Option func(*options) // // func Dict(tag language.Tag, d ...Dictionary) Option -// New returns a new Catalog. -func New(opts ...Option) *Catalog { - c := &Catalog{} +// NewBuilder returns an empty mutable Catalog. +func NewBuilder(opts ...Option) *Builder { + c := &Builder{} for _, o := range opts { o(&c.options) } return c } -// Languages returns all languages for which the Catalog contains variants. -func (c *Catalog) Languages() []language.Tag { - return c.index.languages() -} - // SetString is shorthand for Set(tag, key, String(msg)). -func (c *Catalog) SetString(tag language.Tag, key string, msg string) error { +func (c *Builder) SetString(tag language.Tag, key string, msg string) error { return c.set(tag, key, &c.index, String(msg)) } @@ -209,26 +308,20 @@ func (c *Catalog) SetString(tag language.Tag, key string, msg string) error { // // When evaluation this message, the first Message in the sequence to msgs to // evaluate to a string will be the message returned. -func (c *Catalog) Set(tag language.Tag, key string, msg ...Message) error { +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 *Catalog) SetMacro(tag language.Tag, name string, msg ...Message) error { +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") -// 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 -} - // String specifies a plain message string. It can be used as fallback if no // other strings match or as a simple standalone message. // @@ -247,44 +340,28 @@ func Var(name string, msg ...Message) Message { return &catmsg.Var{Name: name, Message: firstInSequence(msg)} } -// firstInSequence is a message type that prints the first message in the -// sequence that resolves to a match for the given substitution arguments. -type firstInSequence []Message - -func (s firstInSequence) Compile(e *catmsg.Encoder) error { - e.EncodeMessageType(catmsg.First) - err := catmsg.ErrIncomplete - for i, m := range s { - if err == nil { - return fmt.Errorf("catalog: message argument %d is complete and blocks subsequent messages", i-1) - } - err = e.EncodeMessage(m) - } - return err -} - // 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 { +func (b *Builder) Context(tag language.Tag, r catmsg.Renderer) *Context { return &Context{ - cat: c, + cat: b, tag: tag, - dec: catmsg.NewDecoder(tag, r, &dict{&c.macros, 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 + 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.index.lookup(c.tag, key) + data, ok := c.cat.lookup(c.tag, key) if !ok { return ErrNotFound } diff --git a/vendor/golang.org/x/text/message/catalog/catalog_test.go b/vendor/golang.org/x/text/message/catalog/catalog_test.go index 97ab4d88a..08bfdc7ce 100644 --- a/vendor/golang.org/x/text/message/catalog/catalog_test.go +++ b/vendor/golang.org/x/text/message/catalog/catalog_test.go @@ -6,11 +6,11 @@ package catalog import ( "bytes" - "fmt" + "path" "reflect" + "strings" "testing" - "golang.org/x/text/internal" "golang.org/x/text/internal/catmsg" "golang.org/x/text/language" ) @@ -20,17 +20,33 @@ type entry struct { msg interface{} } -var testCases = []struct { - desc string - cat []entry - lookup []entry -}{{ +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{ @@ -45,6 +61,11 @@ var testCases = []struct { {"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{ @@ -52,6 +73,7 @@ var testCases = []struct { {"en-GB", "hello", "Hellø!"}, {"en-US", "hello", "Howdy!"}, {"en", "greetings", "Greetings!"}, + {"gsw", "hello", "Grüetzi!"}, }, lookup: []entry{ {"und", "hello", ""}, @@ -70,6 +92,12 @@ var testCases = []struct { {"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{ @@ -103,6 +131,7 @@ var testCases = []struct { {"en", "scopes", "Hello Joe and Jane."}, {"en", "missing var", "Hello missing."}, }, + tags: langs("en"), }, { desc: "macros", cat: []entry{ @@ -122,16 +151,29 @@ var testCases = []struct { {"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 initCat(entries []entry) (*Catalog, []language.Tag) { - tags := []language.Tag{} - cat := New() - for _, e := range entries { +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) - tags = append(tags, tag) switch msg := e.msg.(type) { case string: + cat.SetString(tag, e.key, msg) case Message: cat.Set(tag, e.key, msg) @@ -139,23 +181,81 @@ func initCat(entries []entry) (*Catalog, []language.Tag) { cat.Set(tag, e.key, msg...) } } - return cat, internal.UniqueTags(tags) + setMacros(cat) + return cat } -func TestCatalog(t *testing.T) { - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s", tc.desc), func(t *testing.T) { - cat, wantTags := initCat(tc.cat) - cat.SetMacro(language.English, "macro1", String("Joe")) - cat.SetMacro(language.Und, "macro2", String("${macro1(1)}")) - cat.SetMacro(language.English, "macroU", noMatchMessage{}) +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(fmt.Sprintf("%s/%s", e.tag, e.key), func(t *testing.T) { + 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) @@ -171,8 +271,10 @@ func TestCatalog(t *testing.T) { } }) } - }) + } } + t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) + t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) } type testRenderer struct { diff --git a/vendor/golang.org/x/text/message/catalog/dict.go b/vendor/golang.org/x/text/message/catalog/dict.go index 1810fabc6..a0eb81810 100644 --- a/vendor/golang.org/x/text/message/catalog/dict.go +++ b/vendor/golang.org/x/text/message/catalog/dict.go @@ -33,7 +33,11 @@ func (d *dict) Lookup(key string) (data string, ok bool) { return d.s.lookup(d.tag, key) } -func (c *Catalog) set(tag language.Tag, key string, s *store, msg ...Message) error { +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() @@ -45,6 +49,7 @@ func (c *Catalog) set(tag language.Tag, key string, s *store, msg ...Message) er if s.index == nil { s.index = map[language.Tag]msgMap{} } + c.matcher = nil s.index[tag] = m } @@ -52,6 +57,23 @@ func (c *Catalog) set(tag language.Tag, key string, s *store, msg ...Message) er 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 @@ -76,15 +98,32 @@ func (s *store) lookup(tag language.Tag, key string) (data string, ok bool) { return "", false } -// Languages returns all languages for which the store contains variants. -func (s *store) languages() []language.Tag { +// 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 { - tags = append(tags, t) + if t != b.options.fallback { + tags = append(tags, t) + } } - internal.SortTags(tags) + 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 000000000..147fc7cf5 --- /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 000000000..a9753b905 --- /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 000000000..7a2301c0e --- /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 index 89c1592a4..2f7effd25 100644 --- a/vendor/golang.org/x/text/message/doc.go +++ b/vendor/golang.org/x/text/message/doc.go @@ -11,15 +11,15 @@ // 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(language.English) +// 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(language.Dutch) +// p := message.NewPrinter(message.MatchLanguage("nl")) // p.Println("Hoogte: %f meter", 1244.9) // Prints Hoogte: 1.244,9 meter // -// p := message.NewPrinter(language.Bengali) +// p := message.NewPrinter(message.MatchLanguage("bn")) // p.Println(123456.78) // Prints ১,২৩,৪৫৬.৭৮ // // Printer currently supports numbers and specialized types for which packages diff --git a/vendor/golang.org/x/text/message/fmt_test.go b/vendor/golang.org/x/text/message/fmt_test.go old mode 100755 new mode 100644 index 2110bb532..2d6872bf8 --- a/vendor/golang.org/x/text/message/fmt_test.go +++ b/vendor/golang.org/x/text/message/fmt_test.go @@ -1786,7 +1786,12 @@ func TestNilDoesNotBecomeTyped(t *testing.T) { type B struct{} var a *A = nil var b B = B{} - got := p.Sprintf("%s %s %s %s %s", nil, a, nil, b, nil) // go vet should complain about this line. + + // 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) @@ -1864,26 +1869,3 @@ func TestFormatterFlags(t *testing.T) { } } } - -func TestParsenum(t *testing.T) { - testCases := []struct { - s string - start, end int - num int - isnum bool - newi int - }{ - {"a123", 0, 4, 0, false, 0}, - {"1234", 1, 1, 0, false, 1}, - {"123a", 0, 4, 123, true, 3}, - {"12a3", 0, 4, 12, true, 2}, - {"1234", 0, 4, 1234, true, 4}, - {"1a234", 1, 3, 0, false, 1}, - } - for _, tt := range testCases { - num, isnum, newi := parsenum(tt.s, tt.start, tt.end) - if num != tt.num || isnum != tt.isnum || newi != tt.newi { - t.Errorf("parsenum(%q, %d, %d) = %d, %v, %d, want %d, %v, %d", tt.s, tt.start, tt.end, num, isnum, newi, tt.num, tt.isnum, tt.newi) - } - } -} diff --git a/vendor/golang.org/x/text/message/format.go b/vendor/golang.org/x/text/message/format.go index d3340d194..a47d17dd4 100644 --- a/vendor/golang.org/x/text/message/format.go +++ b/vendor/golang.org/x/text/message/format.go @@ -8,6 +8,8 @@ import ( "bytes" "strconv" "unicode/utf8" + + "golang.org/x/text/internal/format" ) const ( @@ -20,45 +22,21 @@ const ( unsigned = false ) -// flags placed in a separate struct for easy clearing. -type fmtFlags struct { - widPresent bool - precPresent bool - minus bool - plus bool - sharp bool - space bool - zero bool - - // For the formats %+v %#v, we set the plusV/sharpV flags - // and clear the plus/sharp flags since %+v and %#v are in effect - // different, flagless formats set at the top level. - plusV bool - sharpV bool -} - // 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 - fmtFlags - - wid int // width - prec int // precision + 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) clearflags() { - f.fmtFlags = fmtFlags{} -} - func (f *formatInfo) init(buf *bytes.Buffer) { + f.ClearFlags() f.buf = buf - f.clearflags() } // writePadding generates n bytes of padding. @@ -69,7 +47,7 @@ func (f *formatInfo) writePadding(n int) { f.buf.Grow(n) // Decide which byte the padding should be filled with. padByte := byte(' ') - if f.zero { + if f.Zero { padByte = byte('0') } // Fill padding with padByte. @@ -80,12 +58,12 @@ func (f *formatInfo) writePadding(n int) { // pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). func (f *formatInfo) pad(b []byte) { - if !f.widPresent || f.wid == 0 { + if !f.WidthPresent || f.Width == 0 { f.buf.Write(b) return } - width := f.wid - utf8.RuneCount(b) - if !f.minus { + width := f.Width - utf8.RuneCount(b) + if !f.Minus { // left padding f.writePadding(width) f.buf.Write(b) @@ -98,12 +76,12 @@ func (f *formatInfo) pad(b []byte) { // padString appends s to f.buf, padded on left (!f.minus) or right (f.minus). func (f *formatInfo) padString(s string) { - if !f.widPresent || f.wid == 0 { + if !f.WidthPresent || f.Width == 0 { f.buf.WriteString(s) return } - width := f.wid - utf8.RuneCountInString(s) - if !f.minus { + width := f.Width - utf8.RuneCountInString(s) + if !f.Minus { // left padding f.writePadding(width) f.buf.WriteString(s) @@ -131,8 +109,8 @@ func (f *formatInfo) fmt_unicode(u uint64) { // 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 + 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) { @@ -144,7 +122,7 @@ func (f *formatInfo) fmt_unicode(u uint64) { 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)) { + if f.Sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { i-- buf[i] = '\'' i -= utf8.RuneLen(rune(u)) @@ -176,10 +154,10 @@ func (f *formatInfo) fmt_unicode(u uint64) { i-- buf[i] = 'U' - oldZero := f.zero - f.zero = false + oldZero := f.Zero + f.Zero = false f.pad(buf[i:]) - f.zero = oldZero + f.Zero = oldZero } // fmt_integer formats signed and unsigned integers. @@ -192,9 +170,9 @@ func (f *formatInfo) fmt_integer(u uint64, base int, isSigned bool, digits strin 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.widPresent || f.precPresent { + if f.WidthPresent || f.PrecPresent { // Account 3 extra bytes for possible addition of a sign and "0x". - width := 3 + f.wid + f.prec // wid and prec are always positive. + 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) @@ -205,19 +183,19 @@ func (f *formatInfo) fmt_integer(u uint64, base int, isSigned bool, digits strin // 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 + 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.wid) - f.zero = oldZero + oldZero := f.Zero + f.Zero = false + f.writePadding(f.Width) + f.Zero = oldZero return } - } else if f.zero && f.widPresent { - prec = f.wid - if negative || f.plus || f.space { + } else if f.Zero && f.WidthPresent { + prec = f.Width + if negative || f.Plus || f.Space { prec-- // leave room for sign } } @@ -265,7 +243,7 @@ func (f *formatInfo) fmt_integer(u uint64, base int, isSigned bool, digits strin } // Various prefixes: 0x, -, etc. - if f.sharp { + if f.Sharp { switch base { case 8: if buf[i] != '0' { @@ -284,26 +262,26 @@ func (f *formatInfo) fmt_integer(u uint64, base int, isSigned bool, digits strin if negative { i-- buf[i] = '-' - } else if f.plus { + } else if f.Plus { i-- buf[i] = '+' - } else if f.space { + } 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 + oldZero := f.Zero + f.Zero = false f.pad(buf[i:]) - f.zero = oldZero + 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 + if f.PrecPresent { + n := f.Prec for i := range s { n-- if n < 0 { @@ -328,46 +306,46 @@ func (f *formatInfo) fmt_sbx(s string, b []byte, digits string) { length = len(s) } // Set length to not process more bytes than the precision demands. - if f.precPresent && f.prec < length { - length = f.prec + 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 { + if f.Space { // Each element encoded by two hexadecimals will get a leading 0x or 0X. - if f.sharp { + if f.Sharp { width *= 2 } // Elements will be separated by a space. width += length - 1 - } else if f.sharp { + } 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.widPresent { - f.writePadding(f.wid) + if f.WidthPresent { + f.writePadding(f.Width) } return } // Handle padding to the left. - if f.widPresent && f.wid > width && !f.minus { - f.writePadding(f.wid - width) + 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 { + 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 { + if f.Space && i > 0 { // Separate elements with a space. buf.WriteByte(' ') - if f.sharp { + if f.Sharp { // Add leading 0x or 0X for each element. buf.WriteByte('0') buf.WriteByte(digits[16]) @@ -383,8 +361,8 @@ func (f *formatInfo) fmt_sbx(s string, b []byte, digits string) { buf.WriteByte(digits[c&0xF]) } // Handle padding to the right. - if f.widPresent && f.wid > width && f.minus { - f.writePadding(f.wid - width) + if f.WidthPresent && f.Width > width && f.Minus { + f.writePadding(f.Width - width) } } @@ -403,12 +381,12 @@ func (f *formatInfo) fmt_bx(b []byte, digits string) { // 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) { + if f.Sharp && strconv.CanBackquote(s) { f.padString("`" + s + "`") return } buf := f.intbuf[:0] - if f.plus { + if f.Plus { f.pad(strconv.AppendQuoteToASCII(buf, s)) } else { f.pad(strconv.AppendQuote(buf, s)) @@ -435,7 +413,7 @@ func (f *formatInfo) fmt_qc(c uint64) { r = utf8.RuneError } buf := f.intbuf[:0] - if f.plus { + if f.Plus { f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) } else { f.pad(strconv.AppendQuoteRune(buf, r)) @@ -446,8 +424,8 @@ func (f *formatInfo) fmt_qc(c uint64) { // 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 + 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) @@ -458,25 +436,25 @@ func (f *formatInfo) fmt_float(v float64, size int, verb rune, prec int) { } // 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 { + 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 + oldZero := f.Zero + f.Zero = false // Remove sign before NaN if not asked for. - if num[1] == 'N' && !f.space && !f.plus { + if num[1] == 'N' && !f.Space && !f.Plus { num = num[1:] } f.pad(num) - f.zero = oldZero + 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' { + if f.Sharp && verb != 'b' { digits := 0 switch verb { case 'v', 'g', 'G': @@ -515,12 +493,12 @@ func (f *formatInfo) fmt_float(v float64, size int, verb rune, prec int) { num = append(num, tail...) } // We want a sign if asked for and if the sign is not positive. - if f.plus || num[0] != '+' { + 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.widPresent && f.wid > len(num) { + if f.Zero && f.WidthPresent && f.Width > len(num) { f.buf.WriteByte(num[0]) - f.writePadding(f.wid - len(num)) + f.writePadding(f.Width - len(num)) f.buf.Write(num[1:]) return } diff --git a/vendor/golang.org/x/text/message/message.go b/vendor/golang.org/x/text/message/message.go index ba4f95ab0..a3473bffd 100644 --- a/vendor/golang.org/x/text/message/message.go +++ b/vendor/golang.org/x/text/message/message.go @@ -8,26 +8,28 @@ 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" ) -// TODO: allow more than one goroutine per printer. This will allow porting from -// fmt much less error prone. - // A Printer implements language-specific formatted I/O analogous to the fmt -// package. Only one goroutine may use a Printer at the same time. +// package. type Printer struct { - // Wrap the fields in a hidden type to hide some of the implemented methods. - printer printer + // the language + tag language.Tag + + toDecimal number.Formatter + toScientific number.Formatter - // NOTE: limiting one goroutine per Printer allows for many optimizations - // and simplifications. We can consider removing this restriction down the - // road if it the benefits do not seem to outweigh the disadvantages. + cat catalog.Catalog } type options struct { - cat *catalog.Catalog + cat catalog.Catalog // TODO: // - allow %s to print integers in written form (tables are likely too large // to enable this by default). @@ -39,39 +41,42 @@ type options struct { type Option func(o *options) // Catalog defines the catalog to be used. -func Catalog(c *catalog.Catalog) Option { +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, + cat: DefaultCatalog, } for _, o := range opts { o(options) } - p := &Printer{printer{ + p := &Printer{ tag: t, - }} - p.printer.toDecimal.InitDecimal(t) - p.printer.toScientific.InitScientific(t) - p.printer.catContext = options.cat.Context(t, &p.printer) + 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 { - p.printer.reset() - p.printer.doPrint(a) - return p.printer.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) { - p.printer.reset() - p.printer.doPrint(a) - n64, err := io.Copy(w, &p.printer.Buffer) + pp := newPrinter(p) + pp.doPrint(a) + n64, err := io.Copy(w, &pp.Buffer) + pp.free() return int(n64), err } @@ -82,16 +87,19 @@ func (p *Printer) Print(a ...interface{}) (n int, err error) { // Sprintln is like fmt.Sprintln, but using language-specific formatting. func (p *Printer) Sprintln(a ...interface{}) string { - p.printer.reset() - p.printer.doPrintln(a) - return p.printer.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) { - p.printer.reset() - p.printer.doPrintln(a) - n64, err := io.Copy(w, &p.printer.Buffer) + pp := newPrinter(p) + pp.doPrintln(a) + n64, err := io.Copy(w, &pp.Buffer) + pp.free() return int(n64), err } @@ -102,25 +110,34 @@ func (p *Printer) Println(a ...interface{}) (n int, err error) { // Sprintf is like fmt.Sprintf, but using language-specific formatting. func (p *Printer) Sprintf(key Reference, a ...interface{}) string { - lookupAndFormat(p, key, a) - return p.printer.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) { - lookupAndFormat(p, key, a) - return w.Write(p.printer.Bytes()) + 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) { - lookupAndFormat(p, key, a) - return os.Stdout.Write(p.printer.Bytes()) + 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.printer.reset() - p.printer.args = a +func lookupAndFormat(p *printer, r Reference, a []interface{}) { + p.fmt.Reset(a) var id, msg string switch v := r.(type) { case string: @@ -131,9 +148,9 @@ func lookupAndFormat(p *Printer, r Reference, a []interface{}) { panic("key argument is not a Reference") } - if p.printer.catContext.Execute(id) == catalog.ErrNotFound { - if p.printer.catContext.Execute(msg) == catalog.ErrNotFound { - p.printer.Render(msg) + if p.catContext.Execute(id) == catalog.ErrNotFound { + if p.catContext.Execute(msg) == catalog.ErrNotFound { + p.Render(msg) return } } @@ -142,8 +159,8 @@ func lookupAndFormat(p *Printer, r Reference, a []interface{}) { // Arg implements catmsg.Renderer. func (p *printer) Arg(i int) interface{} { // TODO, also return "ok" bool i-- - if uint(i) < uint(len(p.args)) { - return p.args[i] + if uint(i) < uint(len(p.fmt.Args)) { + return p.fmt.Args[i] } return nil } diff --git a/vendor/golang.org/x/text/message/message_test.go b/vendor/golang.org/x/text/message/message_test.go index 091ed3bcb..326f716fb 100644 --- a/vendor/golang.org/x/text/message/message_test.go +++ b/vendor/golang.org/x/text/message/message_test.go @@ -169,9 +169,9 @@ func TestLocalization(t *testing.T) { type entry struct{ tag, key, msg string } -func initCat(entries []entry) (*catalog.Catalog, []language.Tag) { +func initCat(entries []entry) (*catalog.Builder, []language.Tag) { tags := []language.Tag{} - cat := catalog.New() + cat := catalog.NewBuilder() for _, e := range entries { tag := language.MustParse(e.tag) tags = append(tags, tag) 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 000000000..379cc6d86 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/extract.go @@ -0,0 +1,314 @@ +// 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 + +// - %m substitutions +// - `msg:"etc"` tags +// - msg/Msg top-level vars and strings. + +// Extract extracts all strings form the package defined in Config. +func Extract(c *Config) (*State, 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{}} + + trimmed, _, _ := trimWS(fmtMsg) + + p := fmtparser.Parser{} + p.Reset(simArgs) + for p.SetFormat(trimmed); 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 + }) + } + } + + return &State{ + Config: *c, + program: prog, + Extracted: Messages{ + Language: c.SourceLanguage, + Messages: messages, + }, + }, 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, unicode.Number) { + 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 000000000..5d329b2f4 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/generate.go @@ -0,0 +1,314 @@ +// 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" + "go/build" + "io" + "path/filepath" + "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" + "golang.org/x/tools/go/loader" +) + +var transRe = regexp.MustCompile(`messages\.(.*)\.json`) + +// Generate writes a Go file that defines a Catalog with translated messages. +// Translations are retrieved from s.Messages, not s.Translations, so it +// is assumed Merge has been called. +func (s *State) Generate() error { + path := s.Config.GenPackage + if path == "" { + path = "." + } + isDir := path[0] == '.' + prog, err := loadPackages(&loader.Config{}, []string{path}) + if err != nil { + return wrap(err, "could not load package") + } + pkgs := prog.InitialPackages() + if len(pkgs) != 1 { + return errorf("more than one package selected: %v", pkgs) + } + pkg := pkgs[0].Pkg.Name() + + cw, err := s.generate() + if err != nil { + return err + } + if !isDir { + gopath := build.Default.GOPATH + path = filepath.Join(gopath, filepath.FromSlash(pkgs[0].Pkg.Path())) + } + path = filepath.Join(path, s.Config.GenFile) + cw.WriteGoFile(path, pkg) // TODO: WriteGoFile should return error. + return err +} + +// WriteGen writes a Go file with the given package name to w that defines a +// Catalog with translated messages. Translations are retrieved from s.Messages, +// not s.Translations, so it is assumed Merge has been called. +func (s *State) WriteGen(w io.Writer, pkg string) error { + cw, err := s.generate() + if err != nil { + return err + } + _, err = cw.WriteGo(w, pkg, "") + return err +} + +// Generate is deprecated; use (*State).Generate(). +func Generate(w io.Writer, pkg string, extracted *Messages, trans ...Messages) (n int, err error) { + s := State{ + Extracted: *extracted, + Translations: trans, + } + cw, err := s.generate() + if err != nil { + return 0, err + } + return cw.WriteGo(w, pkg, "") +} + +func (s *State) generate() (*gen.CodeWriter, error) { + // Build up index of translations and original messages. + translations := map[language.Tag]map[string]Message{} + languages := []language.Tag{} + usedKeys := map[string]int{} + + for _, loc := range s.Messages { + 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 { + warnf("Duplicate translation in locale %q for message %q", tag, id) + } + translations[tag][id] = m + } + } + } + } + + // Verify completeness and register keys. + internal.SortTags(languages) + + langVars := []string{} + for _, tag := range languages { + langVars = append(langVars, strings.Replace(tag.String(), "-", "_", -1)) + dict := translations[tag] + for _, msg := range s.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. + warnf("%s: Missing entry for %q.", tag, id) + } + } + } + + cw := gen.NewCodeWriter() + + x := &struct { + Fallback language.Tag + Languages []string + }{ + Fallback: s.Extracted.Language, + Languages: langVars, + } + + if err := lookup.Execute(cw, x); err != nil { + return nil, 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 s.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 nil, wrap(err, "error") + } + _, leadWS, trailWS := trimWS(msg.Key) + if leadWS != "" || trailWS != "" { + m = catmsg.Affix{ + Message: m, + Prefix: leadWS, + Suffix: trailWS, + } + } + // TODO: support macros. + data, err := catmsg.Compile(tag, nil, m) + if err != nil { + return nil, wrap(err, "error") + } + key := usedKeys[msg.Key] + if d := a[key]; d != "" && d != data { + warnf("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, nil +} + +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/go19_test.go b/vendor/golang.org/x/text/message/pipeline/go19_test.go new file mode 100644 index 000000000..c9517c130 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/go19_test.go @@ -0,0 +1,13 @@ +// 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 pipeline + +import "testing" + +func init() { + setHelper = (*testing.T).Helper +} 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 000000000..c83a8fd87 --- /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" + "errors" + "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. + +// Messages is used to store translations for a single language. +type Messages 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,omitempty"` + 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"` + + // Fuzzy indicates that the provide translation needs review by a + // translator, for instance because it was derived from automated + // translation. + Fuzzy bool `json:"fuzzy,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 +} + +var errIncompatibleMessage = errors.New("messages incompatible") + +func checkEquivalence(a, b *Message) error { + for _, v := range a.ID { + for _, w := range b.ID { + if v == w { + return nil + } + } + } + // TODO: canonicalize placeholders and check for type equivalence. + return errIncompatibleMessage +} + +// 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 000000000..cafd6f29b --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/pipeline.go @@ -0,0 +1,422 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" + "go/build" + "go/parser" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + "unicode" + + "golang.org/x/text/internal" + "golang.org/x/text/language" + "golang.org/x/text/runes" + "golang.org/x/tools/go/loader" +) + +const ( + extractFile = "extracted.gotext.json" + outFile = "out.gotext.json" + gotextSuffix = "gotext.json" +) + +// Config contains configuration for the translation pipeline. +type Config struct { + // Supported indicates the languages for which data should be generated. + // The default is to support all locales for which there are matching + // translation files. + Supported []language.Tag + + // --- Extraction + + SourceLanguage language.Tag + + Packages []string + + // --- File structure + + // Dir is the root dir for all operations. + Dir string + + // TranslationsPattern is a regular expression to match incoming translation + // files. These files may appear in any directory rooted at Dir. + // language for the translation files is determined as follows: + // 1. From the Language field in the file. + // 2. If not present, from a valid language tag in the filename, separated + // by dots (e.g. "en-US.json" or "incoming.pt_PT.xmb"). + // 3. If not present, from a the closest subdirectory in which the file + // is contained that parses as a valid language tag. + TranslationsPattern string + + // OutPattern defines the location for translation files for a certain + // language. The default is "{{.Dir}}/{{.Language}}/out.{{.Ext}}" + OutPattern string + + // Format defines the file format for generated translation files. + // The default is XMB. Alternatives are GetText, XLIFF, L20n, GoText. + Format string + + Ext string + + // TODO: + // Actions are additional actions to be performed after the initial extract + // and merge. + // Actions []struct { + // Name string + // Options map[string]string + // } + + // --- Generation + + // GenFile may be in a different package. It is not defined, it will + // be written to stdout. + GenFile string + + // GenPackage is the package or relative path into which to generate the + // file. If not specified it is relative to the current directory. + GenPackage string + + // DeclareVar defines a variable to which to assing the generated Catalog. + DeclareVar string + + // SetDefault determines whether to assign the generated Catalog to + // message.DefaultCatalog. The default for this is true if DeclareVar is + // not defined, false otherwise. + SetDefault bool + + // TODO: + // - Printf-style configuration + // - Template-style configuration + // - Extraction options + // - Rewrite options + // - Generation options +} + +// Operations: +// - extract: get the strings +// - disambiguate: find messages with the same key, but possible different meaning. +// - create out: create a list of messages that need translations +// - load trans: load the list of current translations +// - merge: assign list of translations as done +// - (action)expand: analyze features and create example sentences for each version. +// - (action)googletrans: pre-populate messages with automatic translations. +// - (action)export: send out messages somewhere non-standard +// - (action)import: load messages from somewhere non-standard +// - vet program: don't pass "foo" + var + "bar" strings. Not using funcs for translated strings. +// - vet trans: coverage: all translations/ all features. +// - generate: generate Go code + +// State holds all accumulated information on translations during processing. +type State struct { + Config Config + + Package string + program *loader.Program + + Extracted Messages `json:"messages"` + + // Messages includes all messages for which there need to be translations. + // Duplicates may be eliminated. Generation will be done from these messages + // (usually after merging). + Messages []Messages + + // Translations are incoming translations for the application messages. + Translations []Messages +} + +func (s *State) dir() string { + if d := s.Config.Dir; d != "" { + return d + } + return "./locales" +} + +func outPattern(s *State) (string, error) { + c := s.Config + pat := c.OutPattern + if pat == "" { + pat = "{{.Dir}}/{{.Language}}/out.{{.Ext}}" + } + + ext := c.Ext + if ext == "" { + ext = c.Format + } + if ext == "" { + ext = gotextSuffix + } + t, err := template.New("").Parse(pat) + if err != nil { + return "", wrap(err, "error parsing template") + } + buf := bytes.Buffer{} + err = t.Execute(&buf, map[string]string{ + "Dir": s.dir(), + "Language": "%s", + "Ext": ext, + }) + return filepath.FromSlash(buf.String()), wrap(err, "incorrect OutPattern") +} + +var transRE = regexp.MustCompile(`.*\.` + gotextSuffix) + +// Import loads existing translation files. +func (s *State) Import() error { + outPattern, err := outPattern(s) + if err != nil { + return err + } + re := transRE + if pat := s.Config.TranslationsPattern; pat != "" { + if re, err = regexp.Compile(pat); err != nil { + return wrapf(err, "error parsing regexp %q", s.Config.TranslationsPattern) + } + } + x := importer{s, outPattern, re} + return x.walkImport(s.dir(), s.Config.SourceLanguage) +} + +type importer struct { + state *State + outPattern string + transFile *regexp.Regexp +} + +func (i *importer) walkImport(path string, tag language.Tag) error { + files, err := ioutil.ReadDir(path) + if err != nil { + return nil + } + for _, f := range files { + name := f.Name() + tag := tag + if f.IsDir() { + if t, err := language.Parse(name); err == nil { + tag = t + } + // We ignore errors + if err := i.walkImport(filepath.Join(path, name), tag); err != nil { + return err + } + continue + } + for _, l := range strings.Split(name, ".") { + if t, err := language.Parse(l); err == nil { + tag = t + } + } + file := filepath.Join(path, name) + // TODO: Should we skip files that match output files? + if fmt.Sprintf(i.outPattern, tag) == file { + continue + } + // TODO: handle different file formats. + if !i.transFile.MatchString(name) { + continue + } + b, err := ioutil.ReadFile(file) + if err != nil { + return wrap(err, "read file failed") + } + var translations Messages + if err := json.Unmarshal(b, &translations); err != nil { + return wrap(err, "parsing translation file failed") + } + i.state.Translations = append(i.state.Translations, translations) + } + return nil +} + +// Merge merges the extracted messages with the existing translations. +func (s *State) Merge() error { + if s.Messages != nil { + panic("already merged") + } + // Create an index for each unique message. + // Duplicates are okay as long as the substitution arguments are okay as + // well. + // Top-level messages are okay to appear in multiple substitution points. + + // Collect key equivalence. + msgs := []*Message{} + keyToIDs := map[string]*Message{} + for _, m := range s.Extracted.Messages { + m := m + if prev, ok := keyToIDs[m.Key]; ok { + if err := checkEquivalence(&m, prev); err != nil { + warnf("Key %q matches conflicting messages: %v and %v", m.Key, prev.ID, m.ID) + // TODO: track enough information so that the rewriter can + // suggest/disambiguate messages. + } + // TODO: add position to message. + continue + } + i := len(msgs) + msgs = append(msgs, &m) + keyToIDs[m.Key] = msgs[i] + } + + // Messages with different keys may still refer to the same translated + // message (e.g. different whitespace). Filter these. + idMap := map[string]bool{} + filtered := []*Message{} + for _, m := range msgs { + found := false + for _, id := range m.ID { + found = found || idMap[id] + } + if !found { + filtered = append(filtered, m) + } + for _, id := range m.ID { + idMap[id] = true + } + } + + // Build index of translations. + translations := map[language.Tag]map[string]Message{} + languages := append([]language.Tag{}, s.Config.Supported...) + + for _, t := range s.Translations { + tag := t.Language + if _, ok := translations[tag]; !ok { + translations[tag] = map[string]Message{} + languages = append(languages, tag) + } + for _, m := range t.Messages { + if !m.Translation.IsEmpty() { + for _, id := range m.ID { + if _, ok := translations[tag][id]; ok { + warnf("Duplicate translation in locale %q for message %q", tag, id) + } + translations[tag][id] = m + } + } + } + } + languages = internal.UniqueTags(languages) + + for _, tag := range languages { + ms := Messages{Language: tag} + for _, orig := range filtered { + m := *orig + m.Key = "" + m.Position = "" + + for _, id := range m.ID { + if t, ok := translations[tag][id]; ok { + m.Translation = t.Translation + if t.TranslatorComment != "" { + m.TranslatorComment = t.TranslatorComment + m.Fuzzy = t.Fuzzy + } + break + } + } + if tag == s.Config.SourceLanguage && m.Translation.IsEmpty() { + m.Translation = m.Message + if m.TranslatorComment == "" { + m.TranslatorComment = "Copied from source." + m.Fuzzy = true + } + } + // TODO: if translation is empty: pre-expand based on available + // linguistic features. This may also be done as a plugin. + ms.Messages = append(ms.Messages, m) + } + s.Messages = append(s.Messages, ms) + } + return nil +} + +// Export writes out the messages to translation out files. +func (s *State) Export() error { + path, err := outPattern(s) + if err != nil { + return wrap(err, "export failed") + } + for _, out := range s.Messages { + // TODO: inject translations from existing files to avoid retranslation. + data, err := json.MarshalIndent(out, "", " ") + if err != nil { + return wrap(err, "JSON marshal failed") + } + file := fmt.Sprintf(path, out.Language) + if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + return wrap(err, "dir create failed") + } + if err := ioutil.WriteFile(file, data, 0644); err != nil { + return wrap(err, "write failed") + } + } + return nil +} + +var ( + ws = runes.In(unicode.White_Space).Contains + notWS = runes.NotIn(unicode.White_Space).Contains +) + +func trimWS(s string) (trimmed, leadWS, trailWS string) { + trimmed = strings.TrimRightFunc(s, ws) + trailWS = s[len(trimmed):] + if i := strings.IndexFunc(trimmed, notWS); i > 0 { + leadWS = trimmed[:i] + trimmed = trimmed[i:] + } + return trimmed, leadWS, trailWS +} + +// NOTE: The command line tool already prefixes with "gotext:". +var ( + wrap = func(err error, msg string) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %v", msg, err) + } + wrapf = func(err error, msg string, args ...interface{}) error { + if err == nil { + return nil + } + return wrap(err, fmt.Sprintf(msg, args...)) + } + errorf = fmt.Errorf +) + +func warnf(format string, args ...interface{}) { + // TODO: don't log. + 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/pipeline_test.go b/vendor/golang.org/x/text/message/pipeline/pipeline_test.go new file mode 100644 index 000000000..293101b25 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/pipeline_test.go @@ -0,0 +1,126 @@ +// 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 ( + "bufio" + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "golang.org/x/text/language" +) + +var genFiles = flag.Bool("gen", false, "generate output files instead of comparing") + +// setHelper is testing.T.Helper on Go 1.9+, overridden by go19_test.go. +var setHelper = func(t *testing.T) {} + +func TestFullCycle(t *testing.T) { + const path = "./testdata" + dirs, err := ioutil.ReadDir(path) + if err != nil { + t.Fatal(err) + } + for _, f := range dirs { + t.Run(f.Name(), func(t *testing.T) { + chk := func(t *testing.T, err error) { + setHelper(t) + if err != nil { + t.Fatal(err) + } + } + dir := filepath.Join(path, f.Name()) + pkgPath := fmt.Sprintf("%s/%s", path, f.Name()) + config := Config{ + SourceLanguage: language.AmericanEnglish, + Packages: []string{pkgPath}, + Dir: filepath.Join(dir, "locales"), + GenFile: "catalog_gen.go", + GenPackage: pkgPath, + } + // TODO: load config if available. + s, err := Extract(&config) + chk(t, err) + chk(t, s.Import()) + chk(t, s.Merge()) + // TODO: + // for range s.Config.Actions { + // // TODO: do the actions. + // } + chk(t, s.Export()) + chk(t, s.Generate()) + + writeJSON(t, filepath.Join(dir, "extracted.gotext.json"), s.Extracted) + checkOutput(t, dir) + }) + } +} + +func checkOutput(t *testing.T, p string) { + filepath.Walk(p, func(p string, f os.FileInfo, err error) error { + if f.IsDir() { + return nil + } + if filepath.Ext(p) != ".want" { + return nil + } + gotFile := p[:len(p)-len(".want")] + got, err := ioutil.ReadFile(gotFile) + if err != nil { + t.Errorf("failed to read %q", p) + return nil + } + if *genFiles { + if err := ioutil.WriteFile(p, got, 0644); err != nil { + t.Fatal(err) + } + } + want, err := ioutil.ReadFile(p) + if err != nil { + t.Errorf("failed to read %q", p) + } else { + scanGot := bufio.NewScanner(bytes.NewReader(got)) + scanWant := bufio.NewScanner(bytes.NewReader(want)) + line := 0 + clean := func(s string) string { + if i := strings.LastIndex(s, "//"); i != -1 { + s = s[:i] + } + return path.Clean(filepath.ToSlash(s)) + } + for scanGot.Scan() && scanWant.Scan() { + got := clean(scanGot.Text()) + want := clean(scanWant.Text()) + if got != want { + t.Errorf("file %q differs from .want file at line %d:\n\t%s\n\t%s", gotFile, line, got, want) + break + } + line++ + } + if scanGot.Scan() || scanWant.Scan() { + t.Errorf("file %q differs from .want file at line %d.", gotFile, line) + } + } + return nil + }) +} + +func writeJSON(t *testing.T, path string, x interface{}) { + data, err := json.MarshalIndent(x, "", " ") + if err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path, data, 0644); err != nil { + t.Fatal(err) + } +} 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 000000000..cf1511f56 --- /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, args ...string) error { + conf := &loader.Config{ + AllowErrors: true, // Allow unused instances of message.Printer. + } + prog, err := loadPackages(conf, args) + 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/pipeline/testdata/test1/catalog_gen.go b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go new file mode 100644 index 000000000..7d93f4868 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go @@ -0,0 +1,85 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +package main + +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{ + "de": &dictionary{index: deIndex, data: deData}, + "en_US": &dictionary{index: en_USIndex, data: en_USData}, + "zh": &dictionary{index: zhIndex, data: zhData}, + } + fallback := language.MustParse("en-US") + cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback)) + if err != nil { + panic(err) + } + message.DefaultCatalog = cat +} + +var messageKeyToIndex = map[string]int{ + "%.2[1]f miles traveled (%[1]f)": 8, + "%[1]s is visiting %[3]s!\n": 3, + "%d files remaining!": 4, + "%d more files remaining!": 5, + "%s is out of order!": 7, + "%s is visiting %s!\n": 2, + "Hello %s!\n": 1, + "Hello world!\n": 0, + "Use the following code for your discount: %d\n": 6, +} + +var deIndex = []uint32{ // 10 elements + 0x00000000, 0x00000011, 0x00000023, 0x0000003d, + 0x00000057, 0x00000075, 0x00000094, 0x00000094, + 0x00000094, 0x00000094, +} // Size: 64 bytes + +const deData string = "" + // Size: 148 bytes + "\x04\x00\x01\x0a\x0c\x02Hallo Welt!\x04\x00\x01\x0a\x0d\x02Hallo %[1]s!" + + "\x04\x00\x01\x0a\x15\x02%[1]s besucht %[2]s!\x04\x00\x01\x0a\x15\x02%[1]" + + "s besucht %[3]s!\x02Noch zwei Bestände zu gehen!\x02Noch %[1]d Bestände " + + "zu gehen!" + +var en_USIndex = []uint32{ // 10 elements + 0x00000000, 0x00000012, 0x00000024, 0x00000042, + 0x00000060, 0x00000077, 0x000000ba, 0x000000ef, + 0x00000106, 0x00000125, +} // Size: 64 bytes + +const en_USData string = "" + // Size: 293 bytes + "\x04\x00\x01\x0a\x0d\x02Hello world!\x04\x00\x01\x0a\x0d\x02Hello %[1]s!" + + "\x04\x00\x01\x0a\x19\x02%[1]s is visiting %[2]s!\x04\x00\x01\x0a\x19\x02" + + "%[1]s is visiting %[3]s!\x02%[1]d files remaining!\x14\x01\x81\x01\x00" + + "\x02\x14\x02One file remaining!\x00&\x02There are %[1]d more files remai" + + "ning!\x04\x00\x01\x0a0\x02Use the following code for your discount: %[1]" + + "d\x02%[1]s is out of order!\x02%.2[1]f miles traveled (%[1]f)" + +var zhIndex = []uint32{ // 10 elements + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, +} // Size: 64 bytes + +const zhData string = "" + +// Total table size 633 bytes (0KiB); checksum: 74B32E70 diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go.want b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go.want new file mode 100644 index 000000000..7d93f4868 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go.want @@ -0,0 +1,85 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +package main + +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{ + "de": &dictionary{index: deIndex, data: deData}, + "en_US": &dictionary{index: en_USIndex, data: en_USData}, + "zh": &dictionary{index: zhIndex, data: zhData}, + } + fallback := language.MustParse("en-US") + cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback)) + if err != nil { + panic(err) + } + message.DefaultCatalog = cat +} + +var messageKeyToIndex = map[string]int{ + "%.2[1]f miles traveled (%[1]f)": 8, + "%[1]s is visiting %[3]s!\n": 3, + "%d files remaining!": 4, + "%d more files remaining!": 5, + "%s is out of order!": 7, + "%s is visiting %s!\n": 2, + "Hello %s!\n": 1, + "Hello world!\n": 0, + "Use the following code for your discount: %d\n": 6, +} + +var deIndex = []uint32{ // 10 elements + 0x00000000, 0x00000011, 0x00000023, 0x0000003d, + 0x00000057, 0x00000075, 0x00000094, 0x00000094, + 0x00000094, 0x00000094, +} // Size: 64 bytes + +const deData string = "" + // Size: 148 bytes + "\x04\x00\x01\x0a\x0c\x02Hallo Welt!\x04\x00\x01\x0a\x0d\x02Hallo %[1]s!" + + "\x04\x00\x01\x0a\x15\x02%[1]s besucht %[2]s!\x04\x00\x01\x0a\x15\x02%[1]" + + "s besucht %[3]s!\x02Noch zwei Bestände zu gehen!\x02Noch %[1]d Bestände " + + "zu gehen!" + +var en_USIndex = []uint32{ // 10 elements + 0x00000000, 0x00000012, 0x00000024, 0x00000042, + 0x00000060, 0x00000077, 0x000000ba, 0x000000ef, + 0x00000106, 0x00000125, +} // Size: 64 bytes + +const en_USData string = "" + // Size: 293 bytes + "\x04\x00\x01\x0a\x0d\x02Hello world!\x04\x00\x01\x0a\x0d\x02Hello %[1]s!" + + "\x04\x00\x01\x0a\x19\x02%[1]s is visiting %[2]s!\x04\x00\x01\x0a\x19\x02" + + "%[1]s is visiting %[3]s!\x02%[1]d files remaining!\x14\x01\x81\x01\x00" + + "\x02\x14\x02One file remaining!\x00&\x02There are %[1]d more files remai" + + "ning!\x04\x00\x01\x0a0\x02Use the following code for your discount: %[1]" + + "d\x02%[1]s is out of order!\x02%.2[1]f miles traveled (%[1]f)" + +var zhIndex = []uint32{ // 10 elements + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, +} // Size: 64 bytes + +const zhData string = "" + +// Total table size 633 bytes (0KiB); checksum: 74B32E70 diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_test.go b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_test.go new file mode 100644 index 000000000..eeb7c25f4 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_test.go @@ -0,0 +1,49 @@ +// 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 main + +import ( + "path" + "testing" + + "golang.org/x/text/message" +) + +func TestCatalog(t *testing.T) { + args := func(a ...interface{}) []interface{} { return a } + testCases := []struct { + lang string + key string + args []interface{} + want string + }{{ + lang: "en", + key: "Hello world!\n", + want: "Hello world!\n", + }, { + lang: "de", + key: "Hello world!\n", + want: "Hallo Welt!\n", + }, { + lang: "en", + key: "%d more files remaining!", + args: args(1), + want: "One file remaining!", + }, { + lang: "en-u-nu-fullwide", + key: "%d more files remaining!", + args: args(5), + want: "There are 5 more files remaining!", + }} + for _, tc := range testCases { + t.Run(path.Join(tc.lang, tc.key), func(t *testing.T) { + p := message.NewPrinter(message.MatchLanguage(tc.lang)) + got := p.Sprintf(tc.key, tc.args...) + if got != tc.want { + t.Errorf("got %q; want %q", got, tc.want) + } + }) + } +} diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json new file mode 100644 index 000000000..4d317af59 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json @@ -0,0 +1,188 @@ +{ + "language": "en-US", + "messages": [ + { + "id": "Hello world!", + "key": "Hello world!\n", + "message": "Hello world!", + "translation": "", + "position": "testdata/test1/test1.go:19:10" + }, + { + "id": "Hello {City}!", + "key": "Hello %s!\n", + "message": "Hello {City}!", + "translation": "", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ], + "position": "testdata/test1/test1.go:24:10" + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%s is visiting %s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ], + "position": "testdata/test1/test1.go:30:10" + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%[1]s is visiting %[3]s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "", + "comment": "Field names are placeholders.", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "pp.Person" + }, + { + "id": "Place", + "string": "%[3]s", + "type": "string", + "underlyingType": "string", + "argNum": 3, + "expr": "pp.Place", + "comment": "Place the person is visiting." + }, + { + "id": "Extra", + "string": "%[2]v", + "type": "int", + "underlyingType": "int", + "argNum": 2, + "expr": "pp.extra" + } + ], + "position": "testdata/test1/test1.go:44:10" + }, + { + "id": "{2} files remaining!", + "key": "%d files remaining!", + "message": "{2} files remaining!", + "translation": "", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ], + "position": "testdata/test1/test1.go:51:10" + }, + { + "id": "{N} more files remaining!", + "key": "%d more files remaining!", + "message": "{N} more files remaining!", + "translation": "", + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ], + "position": "testdata/test1/test1.go:56:10" + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "key": "Use the following code for your discount: %d\n", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ], + "position": "testdata/test1/test1.go:64:10" + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "key": "%s is out of order!", + "message": "{Device} is out of order!", + "translation": "", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ], + "position": "testdata/test1/test1.go:70:10" + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "key": "%.2[1]f miles traveled (%[1]f)", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ], + "position": "testdata/test1/test1.go:74:10" + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json.want b/vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json.want new file mode 100644 index 000000000..4d317af59 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json.want @@ -0,0 +1,188 @@ +{ + "language": "en-US", + "messages": [ + { + "id": "Hello world!", + "key": "Hello world!\n", + "message": "Hello world!", + "translation": "", + "position": "testdata/test1/test1.go:19:10" + }, + { + "id": "Hello {City}!", + "key": "Hello %s!\n", + "message": "Hello {City}!", + "translation": "", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ], + "position": "testdata/test1/test1.go:24:10" + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%s is visiting %s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ], + "position": "testdata/test1/test1.go:30:10" + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%[1]s is visiting %[3]s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "", + "comment": "Field names are placeholders.", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "pp.Person" + }, + { + "id": "Place", + "string": "%[3]s", + "type": "string", + "underlyingType": "string", + "argNum": 3, + "expr": "pp.Place", + "comment": "Place the person is visiting." + }, + { + "id": "Extra", + "string": "%[2]v", + "type": "int", + "underlyingType": "int", + "argNum": 2, + "expr": "pp.extra" + } + ], + "position": "testdata/test1/test1.go:44:10" + }, + { + "id": "{2} files remaining!", + "key": "%d files remaining!", + "message": "{2} files remaining!", + "translation": "", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ], + "position": "testdata/test1/test1.go:51:10" + }, + { + "id": "{N} more files remaining!", + "key": "%d more files remaining!", + "message": "{N} more files remaining!", + "translation": "", + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ], + "position": "testdata/test1/test1.go:56:10" + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "key": "Use the following code for your discount: %d\n", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ], + "position": "testdata/test1/test1.go:64:10" + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "key": "%s is out of order!", + "message": "{Device} is out of order!", + "translation": "", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ], + "position": "testdata/test1/test1.go:70:10" + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "key": "%.2[1]f miles traveled (%[1]f)", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ], + "position": "testdata/test1/test1.go:74:10" + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/messages.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/messages.gotext.json new file mode 100755 index 000000000..f92e4a1f5 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/messages.gotext.json @@ -0,0 +1,123 @@ +{ + "language": "de", + "messages": [ + { + "id": "Hello world!", + "key": "Hello world!\n", + "message": "Hello world!", + "translation": "Hallo Welt!" + }, + { + "id": "Hello {City}!", + "key": "Hello %s!\n", + "message": "Hello {City}!", + "translation": "Hallo {City}!", + "placeholders": [ + { + "id": "City", + "string": "%[1]s" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%s is visiting %s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} besucht {Place}!", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s" + }, + { + "id": "Place", + "string": "%[2]s" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%[1]s is visiting %[3]s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} besucht {Place}!", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s" + }, + { + "id": "Place", + "string": "%[3]s" + }, + { + "id": "Extra", + "string": "%[2]v" + } + ] + }, + { + "id": "{2} files remaining!", + "key": "%d files remaining!", + "message": "{N} files remaining!", + "translation": "Noch zwei Bestände zu gehen!", + "placeholders": [ + { + "id": "2", + "string": "%[1]d" + } + ] + }, + { + "id": "{N} more files remaining!", + "key": "%d more files remaining!", + "message": "{N} more files remaining!", + "translation": "Noch {N} Bestände zu gehen!", + "placeholders": [ + { + "id": "N", + "string": "%[1]d" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "key": "Use the following code for your discount: %d\n", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d" + } + ] + }, + { + "id": [ "msgOutOfOrder", "{Device} is out of order!" ], + "key": "%s is out of order!", + "message": "{Device} is out of order!", + "translation": "", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "key": "%.2[1]f miles traveled (%[1]f)", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f" + }, + { + "id": "Miles_1", + "string": "%[1]f" + } + ] + } + ] +} diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json new file mode 100755 index 000000000..f19e21d72 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json @@ -0,0 +1,137 @@ +{ + "language": "de", + "messages": [ + { + "id": "Hello world!", + "message": "Hello world!", + "translation": "Hallo Welt!" + }, + { + "id": "Hello {City}!", + "message": "Hello {City}!", + "translation": "Hallo {City}!", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} besucht {Place}!", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ] + }, + { + "id": "{2} files remaining!", + "message": "{2} files remaining!", + "translation": "Noch zwei Bestände zu gehen!", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ] + }, + { + "id": "{N} more files remaining!", + "message": "{N} more files remaining!", + "translation": "Noch {N} Bestände zu gehen!", + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ] + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "message": "{Device} is out of order!", + "translation": "", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json.want b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json.want new file mode 100755 index 000000000..f19e21d72 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/out.gotext.json.want @@ -0,0 +1,137 @@ +{ + "language": "de", + "messages": [ + { + "id": "Hello world!", + "message": "Hello world!", + "translation": "Hallo Welt!" + }, + { + "id": "Hello {City}!", + "message": "Hello {City}!", + "translation": "Hallo {City}!", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} besucht {Place}!", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ] + }, + { + "id": "{2} files remaining!", + "message": "{2} files remaining!", + "translation": "Noch zwei Bestände zu gehen!", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ] + }, + { + "id": "{N} more files remaining!", + "message": "{N} more files remaining!", + "translation": "Noch {N} Bestände zu gehen!", + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ] + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "message": "{Device} is out of order!", + "translation": "", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/messages.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/messages.gotext.json new file mode 100755 index 000000000..b984242f6 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/messages.gotext.json @@ -0,0 +1,91 @@ +{ + "language": "en-US", + "messages": [ + { + "id": "Hello world!", + "key": "Hello world!\n", + "message": "Hello world!", + "translation": "Hello world!" + }, + { + "id": "Hello {City}!", + "key": "Hello %s!\n", + "message": "Hello {City}!", + "translation": "Hello {City}!" + }, + { + "id": "Hello {Town}!", + "key": "Hello %s!\n", + "message": "Hello {Town}!", + "translation": "Hello {Town}!", + "placeholders": [ + { + "id": "Town", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "town", + "comment": "Town" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%s is visiting %s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} is visiting {Place}!" + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%[1]s is visiting %[3]s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} is visiting {Place}!" + }, + { + "id": "{2} files remaining!", + "key": "%d files remaining!", + "message": "{N} files remaining!", + "translation": "", + "placeholders": [ + { + "id": "2", + "string": "%[1]d" + } + ] + }, + { + "id": "{N} more files remaining!", + "key": "%d more files remaining!", + "message": "{N} more files remaining!", + "translation": { + "select": { + "feature": "plural", + "arg": "N", + "cases": { + "one": "One file remaining!", + "other": "There are {N} more files remaining!" + } + } + } + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "key": "Use the following code for your discount: %d\n", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "" + }, + { + "id": [ "msgOutOfOrder", "{Device} is out of order!" ], + "key": "%s is out of order!", + "message": "{Device} is out of order!", + "translation": "{Device} is out of order!" + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "key": "%.2[1]f miles traveled (%[1]f)", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "{Miles} miles traveled ({Miles_1})" + } + ] +} diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json new file mode 100755 index 000000000..59f92a5a6 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json @@ -0,0 +1,154 @@ +{ + "language": "en-US", + "messages": [ + { + "id": "Hello world!", + "message": "Hello world!", + "translation": "Hello world!" + }, + { + "id": "Hello {City}!", + "message": "Hello {City}!", + "translation": "Hello {City}!", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} is visiting {Place}!", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ] + }, + { + "id": "{2} files remaining!", + "message": "{2} files remaining!", + "translation": "{2} files remaining!", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ], + "fuzzy": true + }, + { + "id": "{N} more files remaining!", + "message": "{N} more files remaining!", + "translation": { + "select": { + "feature": "plural", + "arg": "N", + "cases": { + "one": { + "msg": "One file remaining!" + }, + "other": { + "msg": "There are {N} more files remaining!" + } + } + } + }, + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "Use the following code for your discount: {ReferralCode}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ], + "fuzzy": true + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "message": "{Device} is out of order!", + "translation": "{Device} is out of order!", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "{Miles} miles traveled ({Miles_1})", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want new file mode 100755 index 000000000..59f92a5a6 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/out.gotext.json.want @@ -0,0 +1,154 @@ +{ + "language": "en-US", + "messages": [ + { + "id": "Hello world!", + "message": "Hello world!", + "translation": "Hello world!" + }, + { + "id": "Hello {City}!", + "message": "Hello {City}!", + "translation": "Hello {City}!", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "message": "{Person} is visiting {Place}!", + "translation": "{Person} is visiting {Place}!", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ] + }, + { + "id": "{2} files remaining!", + "message": "{2} files remaining!", + "translation": "{2} files remaining!", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ], + "fuzzy": true + }, + { + "id": "{N} more files remaining!", + "message": "{N} more files remaining!", + "translation": { + "select": { + "feature": "plural", + "arg": "N", + "cases": { + "one": { + "msg": "One file remaining!" + }, + "other": { + "msg": "There are {N} more files remaining!" + } + } + } + }, + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "Use the following code for your discount: {ReferralCode}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ], + "fuzzy": true + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "message": "{Device} is out of order!", + "translation": "{Device} is out of order!", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "{Miles} miles traveled ({Miles_1})", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/messages.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/messages.gotext.json new file mode 100755 index 000000000..c80d1d2a7 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/messages.gotext.json @@ -0,0 +1,135 @@ +{ + "language": "zh", + "messages": [ + { + "id": "Hello world!", + "key": "Hello world!\n", + "message": "Hello world!", + "translation": "" + }, + { + "id": "Hello {City}!", + "key": "Hello %s!\n", + "message": "Hello {City}!", + "translation": "", + "placeholders": [ + { + "id": "City", + "string": "%[1]s" + } + ] + }, + { + "id": "Hello {Town}!", + "key": "Hello %s!\n", + "message": "Hello {Town}!", + "translation": "", + "placeholders": [ + { + "id": "Town", + "string": "%[1]s" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%s is visiting %s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s" + }, + { + "id": "Place", + "string": "%[2]s" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "key": "%[1]s is visiting %[3]s!\n", + "message": "{Person} is visiting {Place}!", + "translation": "", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s" + }, + { + "id": "Place", + "string": "%[3]s" + }, + { + "id": "Extra", + "string": "%[2]v" + } + ] + }, + { + "id": "{2} files remaining!", + "key": "%d files remaining!", + "message": "{2} files remaining!", + "translation": "", + "placeholders": [ + { + "id": "", + "string": "%[1]d" + } + ] + }, + { + "id": "{N} more files remaining!", + "key": "%d more files remaining!", + "message": "{N} more files remaining!", + "translation": "", + "placeholders": [ + { + "id": "N", + "string": "%[1]d" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "key": "Use the following code for your discount: %d\n", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d" + } + ] + }, + { + "id": [ "{Device} is out of order!", "msgOutOfOrder" ], + "key": "%s is out of order!", + "message": "{Device} is out of order!", + "translation": "", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "key": "%.2[1]f miles traveled (%[1]f)", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f" + }, + { + "id": "Miles_1", + "string": "%[1]f" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json new file mode 100755 index 000000000..9bede65ee --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json @@ -0,0 +1,137 @@ +{ + "language": "zh", + "messages": [ + { + "id": "Hello world!", + "message": "Hello world!", + "translation": "" + }, + { + "id": "Hello {City}!", + "message": "Hello {City}!", + "translation": "", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "message": "{Person} is visiting {Place}!", + "translation": "", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ] + }, + { + "id": "{2} files remaining!", + "message": "{2} files remaining!", + "translation": "", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ] + }, + { + "id": "{N} more files remaining!", + "message": "{N} more files remaining!", + "translation": "", + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ] + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "message": "{Device} is out of order!", + "translation": "", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want new file mode 100755 index 000000000..9bede65ee --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/out.gotext.json.want @@ -0,0 +1,137 @@ +{ + "language": "zh", + "messages": [ + { + "id": "Hello world!", + "message": "Hello world!", + "translation": "" + }, + { + "id": "Hello {City}!", + "message": "Hello {City}!", + "translation": "", + "placeholders": [ + { + "id": "City", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "city" + } + ] + }, + { + "id": "{Person} is visiting {Place}!", + "message": "{Person} is visiting {Place}!", + "translation": "", + "placeholders": [ + { + "id": "Person", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "person", + "comment": "The person of matter." + }, + { + "id": "Place", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "place", + "comment": "Place the person is visiting." + } + ] + }, + { + "id": "{2} files remaining!", + "message": "{2} files remaining!", + "translation": "", + "placeholders": [ + { + "id": "2", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "2" + } + ] + }, + { + "id": "{N} more files remaining!", + "message": "{N} more files remaining!", + "translation": "", + "placeholders": [ + { + "id": "N", + "string": "%[1]d", + "type": "int", + "underlyingType": "int", + "argNum": 1, + "expr": "n" + } + ] + }, + { + "id": "Use the following code for your discount: {ReferralCode}", + "message": "Use the following code for your discount: {ReferralCode}", + "translation": "", + "placeholders": [ + { + "id": "ReferralCode", + "string": "%[1]d", + "type": "./testdata/test1.referralCode", + "underlyingType": "int", + "argNum": 1, + "expr": "c" + } + ] + }, + { + "id": [ + "msgOutOfOrder", + "{Device} is out of order!" + ], + "message": "{Device} is out of order!", + "translation": "", + "comment": "This comment wins.\n", + "placeholders": [ + { + "id": "Device", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "device" + } + ] + }, + { + "id": "{Miles} miles traveled ({Miles_1})", + "message": "{Miles} miles traveled ({Miles_1})", + "translation": "", + "placeholders": [ + { + "id": "Miles", + "string": "%.2[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + }, + { + "id": "Miles_1", + "string": "%[1]f", + "type": "float64", + "underlyingType": "float64", + "argNum": 1, + "expr": "miles" + } + ] + } + ] +} \ No newline at end of file diff --git a/vendor/golang.org/x/text/message/pipeline/testdata/test1/test1.go b/vendor/golang.org/x/text/message/pipeline/testdata/test1/test1.go new file mode 100644 index 000000000..88051f932 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/test1.go @@ -0,0 +1,75 @@ +// 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 main + +import "golang.org/x/text/message" + +func main() { + p := message.NewPrinter(message.MatchLanguage("en")) + + // NOT EXTRACTED: strings passed to Println are not extracted. + p.Println("Hello world!") + + // NOT EXTRACTED: strings passed to Print are not extracted. + p.Print("Hello world!\n") + + // Extract and trim whitespace (TODO). + p.Printf("Hello world!\n") + + // NOT EXTRACTED: city is not used as a pattern or passed to %m. + city := "Amsterdam" + // This comment is extracted. + p.Printf("Hello %s!\n", city) + + person := "Sheila" + place := "Zürich" + + // Substitutions replaced by variable names. + p.Printf("%s is visiting %s!\n", + person, // The person of matter. + place, // Place the person is visiting. + ) + + pp := struct { + Person string // The person of matter. // TODO: get this comment. + Place string + extra int + }{ + person, place, 4, + } + + // extract will drop this comment in favor of the one below. + p.Printf("%[1]s is visiting %[3]s!\n", // Field names are placeholders. + pp.Person, + pp.extra, + pp.Place, // Place the person is visiting. + ) + + // Numeric literal becomes placeholder. + p.Printf("%d files remaining!", 2) + + const n = 2 + + // Constant identifier becomes placeholder. + p.Printf("%d more files remaining!", n) + + // Infer better names from type names. + type referralCode int + + const c = referralCode(5) + + // Use type name as placeholder. + p.Printf("Use the following code for your discount: %d\n", c) + + // Use constant name as message ID. + const msgOutOfOrder = "%s is out of order!" // This comment wins. + const device = "Soda machine" + // This message has two IDs. + p.Printf(msgOutOfOrder, device) + + // Multiple substitutions for same argument. + miles := 1.2345 + p.Printf("%.2[1]f miles traveled (%[1]f)", miles) +} diff --git a/vendor/golang.org/x/text/message/print.go b/vendor/golang.org/x/text/message/print.go index 5819cba2e..777e1724a 100644 --- a/vendor/golang.org/x/text/message/print.go +++ b/vendor/golang.org/x/text/message/print.go @@ -9,6 +9,7 @@ import ( "fmt" // TODO: consider copying interfaces from package fmt to avoid dependency. "math" "reflect" + "sync" "unicode/utf8" "golang.org/x/text/internal/format" @@ -37,21 +38,42 @@ const ( 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 - // the language - tag language.Tag // buffer for accumulating output. bytes.Buffer - // retain arguments across calls. - args []interface{} - // retain current argument number across calls - argNum int // arg holds the current item, as an interface{}. arg interface{} // value is used instead of arg for reflect values. @@ -60,47 +82,31 @@ type printer struct { // fmt is used to format basic items such as integers or strings. fmt formatInfo - // reordered records whether the format string used argument reordering. - reordered bool - // goodArgNum records whether the most recent reordering directive was valid. - goodArgNum bool // 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 - - toDecimal number.Formatter - toScientific number.Formatter -} - -func (p *printer) reset() { - p.Buffer.Reset() - p.argNum = 0 - p.reordered = false - p.panicking = false - p.erroring = false - p.fmt.init(&p.Buffer) } // 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.wid, p.fmt.widPresent } +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) 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 + return p.fmt.Minus case '+': - return p.fmt.plus || p.fmt.plusV + return p.fmt.Plus || p.fmt.PlusV case '#': - return p.fmt.sharp || p.fmt.sharpV + return p.fmt.Sharp || p.fmt.SharpV case ' ': - return p.fmt.space + return p.fmt.Space case '0': - return p.fmt.zero + return p.fmt.Zero } return false } @@ -116,28 +122,6 @@ func getField(v reflect.Value, i int) reflect.Value { return val } -// tooLarge reports whether the magnitude of the integer is -// too large to be used as a formatting width or precision. -func tooLarge(x int) bool { - const max int = 1e6 - return x > max || x < -max -} - -// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present. -func parsenum(s string, start, end int) (num int, isnum bool, newi int) { - if start >= end { - return 0, false, end - } - for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ { - if tooLarge(num) { - return 0, false, end // Overflow; crazy long number most likely. - } - num = num*10 + int(s[newi]-'0') - isnum = true - } - return -} - func (p *printer) unknownType(v reflect.Value) { if !v.IsValid() { p.WriteString(nilAngleString) @@ -181,23 +165,23 @@ func (p *printer) fmtBool(v bool, verb rune) { // 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 + sharp := p.fmt.Sharp + p.fmt.Sharp = leading0x p.fmt.fmt_integer(v, 16, unsigned, ldigits) - p.fmt.sharp = sharp + 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 { + if p.fmt.SharpV && !isSigned { p.fmt0x64(v, true) return } fallthrough case 'd': - if p.fmt.sharp || p.fmt.sharpV { + if p.fmt.Sharp || p.fmt.SharpV { p.fmt.fmt_integer(v, 10, isSigned, ldigits) } else { p.fmtDecimalInt(v, isSigned) @@ -235,19 +219,19 @@ func (p *printer) fmtFloat(v float64, size int, verb rune) { verb = 'g' fallthrough case 'g', 'G': - if p.fmt.sharp || p.fmt.sharpV { + 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 { + 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 { + if p.fmt.Sharp || p.fmt.SharpV { p.fmt.fmt_float(v, size, verb, 6) } else { p.fmtDecimalFloat(v, size, 6) @@ -259,9 +243,9 @@ func (p *printer) fmtFloat(v float64, size int, verb rune) { func (p *printer) setFlags(f *number.Formatter) { f.Flags &^= number.ElideSign - if p.fmt.plus || p.fmt.space { + if p.fmt.Plus || p.fmt.Space { f.Flags |= number.AlwaysSign - if !p.fmt.plus { + if !p.fmt.Plus { f.Flags |= number.ElideSign } } else { @@ -271,13 +255,13 @@ func (p *printer) setFlags(f *number.Formatter) { func (p *printer) updatePadding(f *number.Formatter) { f.Flags &^= number.PadMask - if p.fmt.minus { + if p.fmt.Minus { f.Flags |= number.PadAfterSuffix } else { f.Flags |= number.PadBeforePrefix } f.PadRune = ' ' - f.FormatWidth = uint16(p.fmt.wid) + f.FormatWidth = uint16(p.fmt.Width) } func (p *printer) initDecimal(minFrac, maxFrac int) { @@ -288,15 +272,15 @@ func (p *printer) initDecimal(minFrac, maxFrac int) { f.MaxFractionDigits = int16(maxFrac) p.setFlags(f) f.PadRune = 0 - if p.fmt.widPresent { - if p.fmt.zero { - wid := p.fmt.wid + 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 { + if p.fmt.Plus || p.fmt.Space { wid-- } if wid > 0 && wid > int(f.MinIntegerDigits) { @@ -319,9 +303,9 @@ func (p *printer) initScientific(minFrac, maxFrac int) { f.MinExponentDigits = 2 p.setFlags(f) f.PadRune = 0 - if p.fmt.widPresent { + if p.fmt.WidthPresent { f.Flags &^= number.PadMask - if p.fmt.zero { + if p.fmt.Zero { f.PadRune = f.Digit(0) f.Flags |= number.PadAfterPrefix } else { @@ -336,13 +320,13 @@ func (p *printer) fmtDecimalInt(v uint64, isSigned bool) { var d number.Decimal f := &p.toDecimal - if p.fmt.precPresent { + if p.fmt.PrecPresent { p.setFlags(f) - f.MinIntegerDigits = uint8(p.fmt.prec) + f.MinIntegerDigits = uint8(p.fmt.Prec) f.MaxIntegerDigits = 0 f.MinFractionDigits = 0 f.MaxFractionDigits = 0 - if p.fmt.widPresent { + if p.fmt.WidthPresent { p.updatePadding(f) } } else { @@ -356,8 +340,8 @@ func (p *printer) fmtDecimalInt(v uint64, isSigned bool) { func (p *printer) fmtDecimalFloat(v float64, size, prec int) { var d number.Decimal - if p.fmt.precPresent { - prec = p.fmt.prec + if p.fmt.PrecPresent { + prec = p.fmt.Prec } p.initDecimal(prec, prec) d.ConvertFloat(p.toDecimal.RoundingContext, v, size) @@ -368,8 +352,8 @@ func (p *printer) fmtDecimalFloat(v float64, size, prec int) { func (p *printer) fmtVariableFloat(v float64, size int) { prec := -1 - if p.fmt.precPresent { - prec = p.fmt.prec + if p.fmt.PrecPresent { + prec = p.fmt.Prec } var d number.Decimal p.initScientific(0, prec) @@ -408,8 +392,8 @@ func (p *printer) fmtVariableFloat(v float64, size int) { func (p *printer) fmtScientific(v float64, size, prec int) { var d number.Decimal - if p.fmt.precPresent { - prec = p.fmt.prec + if p.fmt.PrecPresent { + prec = p.fmt.Prec } p.initScientific(prec, prec) rc := p.toScientific.RoundingContext @@ -457,11 +441,11 @@ func (p *printer) fmtComplex(v complex128, size int, verb rune) { p.WriteString("i)") return } - oldPlus := p.fmt.plus - p.fmt.plus = true + 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 + p.fmt.Plus = oldPlus default: p.badVerb(verb) } @@ -470,7 +454,7 @@ func (p *printer) fmtComplex(v complex128, size int, verb rune) { func (p *printer) fmtString(v string, verb rune) { switch verb { case 'v': - if p.fmt.sharpV { + if p.fmt.SharpV { p.fmt.fmt_q(v) } else { p.fmt.fmt_s(v) @@ -491,7 +475,7 @@ func (p *printer) fmtString(v string, verb rune) { func (p *printer) fmtBytes(v []byte, verb rune, typeString string) { switch verb { case 'v', 'd': - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteString(typeString) if v == nil { p.WriteString(nilParenString) @@ -540,7 +524,7 @@ func (p *printer) fmtPointer(value reflect.Value, verb rune) { switch verb { case 'v': - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteByte('(') p.WriteString(value.Type().String()) p.WriteString(")(") @@ -554,14 +538,14 @@ func (p *printer) fmtPointer(value reflect.Value, verb rune) { if u == 0 { p.fmt.padString(nilAngleString) } else { - p.fmt0x64(uint64(u), !p.fmt.sharp) + p.fmt0x64(uint64(u), !p.fmt.Sharp) } } case 'p': - p.fmt0x64(uint64(u), !p.fmt.sharp) + 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.fmt.Sharp = true // Print as standard go. TODO: does this make sense? } p.fmtInteger(uint64(u), unsigned, verb) default: @@ -585,9 +569,9 @@ func (p *printer) catchPanic(arg interface{}, verb rune) { panic(err) } - oldFlags := p.fmt.fmtFlags + oldFlags := p.fmt.Parser // For this output we want default behavior. - p.fmt.clearflags() + p.fmt.ClearFlags() p.WriteString(percentBangString) p.WriteRune(verb) @@ -597,7 +581,7 @@ func (p *printer) catchPanic(arg interface{}, verb rune) { p.panicking = false p.WriteByte(')') - p.fmt.fmtFlags = oldFlags + p.fmt.Parser = oldFlags } } @@ -620,7 +604,7 @@ func (p *printer) handleMethods(verb rune) (handled bool) { } // If we're doing Go syntax and the argument knows how to supply it, take care of it now. - if p.fmt.sharpV { + if p.fmt.SharpV { if stringer, ok := p.arg.(fmt.GoStringer); ok { handled = true defer p.catchPanic(p.arg, verb) @@ -781,7 +765,7 @@ func (p *printer) printValue(value reflect.Value, verb rune, depth int) { case reflect.String: p.fmtString(f.String(), verb) case reflect.Map: - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteString(f.Type().String()) if f.IsNil() { p.WriteString(nilParenString) @@ -794,7 +778,7 @@ func (p *printer) printValue(value reflect.Value, verb rune, depth int) { keys := f.MapKeys() for i, key := range keys { if i > 0 { - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteString(commaSpaceString) } else { p.WriteByte(' ') @@ -804,25 +788,25 @@ func (p *printer) printValue(value reflect.Value, verb rune, depth int) { p.WriteByte(':') p.printValue(f.MapIndex(key), verb, depth+1) } - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteByte('}') } else { p.WriteByte(']') } case reflect.Struct: - if p.fmt.sharpV { + 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 { + if p.fmt.SharpV { p.WriteString(commaSpaceString) } else { p.WriteByte(' ') } } - if p.fmt.plusV || p.fmt.sharpV { + if p.fmt.PlusV || p.fmt.SharpV { if name := f.Type().Field(i).Name; name != "" { p.WriteString(name) p.WriteByte(':') @@ -834,7 +818,7 @@ func (p *printer) printValue(value reflect.Value, verb rune, depth int) { case reflect.Interface: value := f.Elem() if !value.IsValid() { - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteString(f.Type().String()) p.WriteString(nilParenString) } else { @@ -867,7 +851,7 @@ func (p *printer) printValue(value reflect.Value, verb rune, depth int) { return } } - if p.fmt.sharpV { + if p.fmt.SharpV { p.WriteString(f.Type().String()) if f.Kind() == reflect.Slice && f.IsNil() { p.WriteString(nilParenString) @@ -910,81 +894,6 @@ func (p *printer) printValue(value reflect.Value, verb rune, depth int) { } } -// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type. -func (p *printer) intFromArg() (num int, isInt bool) { - if p.argNum < len(p.args) { - arg := p.args[p.argNum] - num, isInt = arg.(int) // Almost always OK. - if !isInt { - // Work harder. - switch v := reflect.ValueOf(arg); v.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := v.Int() - if int64(int(n)) == n { - num = int(n) - isInt = true - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - n := v.Uint() - if int64(n) >= 0 && uint64(int(n)) == n { - num = int(n) - isInt = true - } - default: - // Already 0, false. - } - } - p.argNum++ - if tooLarge(num) { - num = 0 - isInt = false - } - } - return -} - -// parseArgNumber returns the value of the bracketed number, minus 1 -// (explicit argument numbers are one-indexed but we want zero-indexed). -// The opening bracket is known to be present at format[0]. -// The returned values are the index, the number of bytes to consume -// up to the closing paren, if present, and whether the number parsed -// ok. The bytes to consume will be 1 if no closing paren is present. -func parseArgNumber(format string) (index int, wid int, ok bool) { - // There must be at least 3 bytes: [n]. - if len(format) < 3 { - return 0, 1, false - } - - // Find closing bracket. - for i := 1; i < len(format); i++ { - if format[i] == ']' { - width, ok, newi := parsenum(format, 1, i) - if !ok || newi != i { - return 0, i + 1, false - } - return width - 1, i + 1, true // arg numbers are one-indexed and skip paren. - } - } - return 0, 1, false -} - -// updateArgNumber returns the next argument to evaluate, which is either the value of the passed-in -// argNum or the value of the bracketed integer that begins format[i:]. It also returns -// the new value of i, that is, the index of the next byte of the format to process. -func (p *printer) updateArgNumber(format string, i int) (newi int, found bool) { - if len(format) <= i || format[i] != '[' { - return i, false - } - p.reordered = true - index, wid, ok := parseArgNumber(format[i:]) - if ok && 0 <= index && index < len(p.args) { - p.argNum = index - return i + wid, true - } - p.goodArgNum = false - return i + wid, ok -} - func (p *printer) badArgNum(verb rune) { p.WriteString(percentBangString) p.WriteRune(verb) @@ -997,151 +906,27 @@ func (p *printer) missingArg(verb rune) { p.WriteString(missingString) } -func (p *printer) doPrintf(format string) { - end := len(format) - afterIndex := false // previous item in format was an index like [3]. -formatLoop: - for i := 0; i < end; { - p.goodArgNum = true - lasti := i - for i < end && format[i] != '%' { - i++ - } - if i > lasti { - p.WriteString(format[lasti:i]) - } - if i >= end { - // done processing format string - break - } - - // Process one verb - i++ - - // Do we have flags? - p.fmt.clearflags() - simpleFormat: - for ; i < end; i++ { - c := format[i] - switch c { - case '#': - p.fmt.sharp = true - case '0': - p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left. - case '+': - p.fmt.plus = true - case '-': - p.fmt.minus = true - p.fmt.zero = false // Do not pad with zeros to the right. - case ' ': - p.fmt.space = true - default: - // Fast path for common case of ascii lower case simple verbs - // without precision or width or argument indices. - if 'a' <= c && c <= 'z' && p.argNum < len(p.args) { - if c == 'v' { - // Go syntax - p.fmt.sharpV = p.fmt.sharp - p.fmt.sharp = false - // Struct-field syntax - p.fmt.plusV = p.fmt.plus - p.fmt.plus = false - } - p.printArg(p.Arg(p.argNum+1), rune(c)) - p.argNum++ - i++ - continue formatLoop - } - // Format is more complex than simple flags and a verb or is malformed. - break simpleFormat - } - } - - // Do we have an explicit argument index? - i, afterIndex = p.updateArgNumber(format, i) - - // Do we have width? - if i < end && format[i] == '*' { - i++ - p.fmt.wid, p.fmt.widPresent = p.intFromArg() - - if !p.fmt.widPresent { - p.WriteString(badWidthString) - } - - // We have a negative width, so take its value and ensure - // that the minus flag is set - if p.fmt.wid < 0 { - p.fmt.wid = -p.fmt.wid - p.fmt.minus = true - p.fmt.zero = false // Do not pad with zeros to the right. - } - afterIndex = false - } else { - p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end) - if afterIndex && p.fmt.widPresent { // "%[3]2d" - p.goodArgNum = false - } - } - - // Do we have precision? - if i+1 < end && format[i] == '.' { - i++ - if afterIndex { // "%[3].2d" - p.goodArgNum = false - } - i, afterIndex = p.updateArgNumber(format, i) - if i < end && format[i] == '*' { - i++ - p.fmt.prec, p.fmt.precPresent = p.intFromArg() - // Negative precision arguments don't make sense - if p.fmt.prec < 0 { - p.fmt.prec = 0 - p.fmt.precPresent = false - } - if !p.fmt.precPresent { - p.WriteString(badPrecString) - } - afterIndex = false - } else { - p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end) - if !p.fmt.precPresent { - p.fmt.prec = 0 - p.fmt.precPresent = true - } - } - } - - if !afterIndex { - i, afterIndex = p.updateArgNumber(format, i) - } - - if i >= end { +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) - break - } - - verb, w := utf8.DecodeRuneInString(format[i:]) - i += w - - switch { - case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec. - p.WriteByte('%') - case !p.goodArgNum: - p.badArgNum(verb) - case p.argNum >= len(p.args): // No argument left over to print for the current verb. - p.missingArg(verb) - case verb == 'v': - // Go syntax - p.fmt.sharpV = p.fmt.sharp - p.fmt.sharp = false - // Struct-field syntax - p.fmt.plusV = p.fmt.plus - p.fmt.plus = false - fallthrough + case format.StatusBadArgNum: + p.badArgNum(p.fmt.Verb) + case format.StatusMissingArg: + p.missingArg(p.fmt.Verb) default: - p.printArg(p.args[p.argNum], verb) - p.argNum++ + panic("unreachable") } } @@ -1149,10 +934,10 @@ formatLoop: // 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.reordered && p.argNum < len(p.args) && p.argNum != 0 { - p.fmt.clearflags() + 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.args[p.argNum:] { + for i, arg := range p.fmt.Args[p.fmt.ArgNum:] { if i > 0 { p.WriteString(commaSpaceString) } @@ -1160,7 +945,7 @@ formatLoop: p.WriteString(nilAngleString) } else { p.WriteString(reflect.TypeOf(arg).String()) - p.WriteByte('=') + p.WriteString("=") p.printArg(arg, 'v') } } -- cgit v1.2.3-1-g7c22