aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/golang.org/x/text/message
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/text/message')
-rw-r--r--vendor/golang.org/x/text/message/catalog.go12
-rw-r--r--vendor/golang.org/x/text/message/catalog/catalog.go161
-rw-r--r--vendor/golang.org/x/text/message/catalog/catalog_test.go148
-rw-r--r--vendor/golang.org/x/text/message/catalog/dict.go49
-rw-r--r--vendor/golang.org/x/text/message/catalog/go19.go15
-rw-r--r--vendor/golang.org/x/text/message/catalog/gopre19.go23
-rw-r--r--vendor/golang.org/x/text/message/catalog_test.go43
-rw-r--r--vendor/golang.org/x/text/message/doc.go6
-rw-r--r--[-rwxr-xr-x]vendor/golang.org/x/text/message/fmt_test.go30
-rw-r--r--vendor/golang.org/x/text/message/format.go150
-rw-r--r--vendor/golang.org/x/text/message/message.go103
-rw-r--r--vendor/golang.org/x/text/message/message_test.go4
-rw-r--r--vendor/golang.org/x/text/message/pipeline/extract.go314
-rw-r--r--vendor/golang.org/x/text/message/pipeline/generate.go314
-rw-r--r--vendor/golang.org/x/text/message/pipeline/message.go221
-rw-r--r--vendor/golang.org/x/text/message/pipeline/pipeline.go308
-rw-r--r--vendor/golang.org/x/text/message/pipeline/pipeline_test.go127
-rw-r--r--vendor/golang.org/x/text/message/pipeline/rewrite.go268
-rw-r--r--vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go83
-rw-r--r--vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go.want83
-rw-r--r--vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json188
-rw-r--r--vendor/golang.org/x/text/message/pipeline/testdata/test1/extracted.gotext.json.want188
-rwxr-xr-xvendor/golang.org/x/text/message/pipeline/testdata/test1/locales/de/messages.gotext.json123
-rwxr-xr-xvendor/golang.org/x/text/message/pipeline/testdata/test1/locales/en-US/messages.gotext.json91
-rwxr-xr-xvendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/messages.gotext.json135
-rw-r--r--vendor/golang.org/x/text/message/pipeline/testdata/test1/test1.go75
-rw-r--r--vendor/golang.org/x/text/message/print.go427
27 files changed, 3138 insertions, 551 deletions
diff --git a/vendor/golang.org/x/text/message/catalog.go b/vendor/golang.org/x/text/message/catalog.go
index 2f65b4b..068271d 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 957444c..34a30d3 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 97ab4d8..08bfdc7 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 1810fab..a0eb818 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 0000000..147fc7c
--- /dev/null
+++ b/vendor/golang.org/x/text/message/catalog/go19.go
@@ -0,0 +1,15 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.9
+
+package catalog
+
+import "golang.org/x/text/internal/catmsg"
+
+// A Message holds a collection of translations for the same phrase that may
+// vary based on the values of substitution arguments.
+type Message = catmsg.Message
+
+type firstInSequence = catmsg.FirstOf
diff --git a/vendor/golang.org/x/text/message/catalog/gopre19.go b/vendor/golang.org/x/text/message/catalog/gopre19.go
new file mode 100644
index 0000000..a9753b9
--- /dev/null
+++ b/vendor/golang.org/x/text/message/catalog/gopre19.go
@@ -0,0 +1,23 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.9
+
+package catalog
+
+import "golang.org/x/text/internal/catmsg"
+
+// A Message holds a collection of translations for the same phrase that may
+// vary based on the values of substitution arguments.
+type Message interface {
+ catmsg.Message
+}
+
+func firstInSequence(m []Message) catmsg.Message {
+ a := []catmsg.Message{}
+ for _, m := range m {
+ a = append(a, m)
+ }
+ return catmsg.FirstOf(a)
+}
diff --git a/vendor/golang.org/x/text/message/catalog_test.go b/vendor/golang.org/x/text/message/catalog_test.go
new file mode 100644
index 0000000..7a2301c
--- /dev/null
+++ b/vendor/golang.org/x/text/message/catalog_test.go
@@ -0,0 +1,43 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package message
+
+import (
+ "strings"
+ "testing"
+
+ "golang.org/x/text/language"
+ "golang.org/x/text/message/catalog"
+)
+
+func TestMatchLanguage(t *testing.T) {
+ c := catalog.NewBuilder(catalog.Fallback(language.English))
+ c.SetString(language.Bengali, "", "")
+ c.SetString(language.English, "", "")
+ c.SetString(language.German, "", "")
+
+ testCases := []struct {
+ args string // '|'-separated list
+ want string
+ }{{
+ args: "de-CH",
+ want: "de",
+ }, {
+ args: "bn-u-nu-latn|en-US,en;q=0.9,de;q=0.8,nl;q=0.7",
+ want: "bn-u-nu-latn",
+ }, {
+ args: "gr",
+ want: "en",
+ }}
+ for _, tc := range testCases {
+ DefaultCatalog = c
+ t.Run(tc.args, func(t *testing.T) {
+ got := MatchLanguage(strings.Split(tc.args, "|")...)
+ if got != language.Make(tc.want) {
+ t.Errorf("got %q; want %q", got, tc.want)
+ }
+ })
+ }
+}
diff --git a/vendor/golang.org/x/text/message/doc.go b/vendor/golang.org/x/text/message/doc.go
index 89c1592..2f7effd 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
index 2110bb5..2d6872b 100755..100644
--- 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(<nil>) %!s(*message.A=<nil>) %!s(<nil>) {} %!s(<nil>)"
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 d3340d1..a47d17d 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 ba4f95a..a3473bf 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 091ed3b..326f716 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 0000000..379cc6d
--- /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 0000000..8d6f07b
--- /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.
+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.
+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) {
+ // TODO: add in external input. Right now we assume that all files are
+ // manually created and stored in the textdata directory.
+
+ // Build up index of translations and original messages.
+ translations := map[language.Tag]map[string]Message{}
+ languages := []language.Tag{}
+ langVars := []string{}
+ usedKeys := map[string]int{}
+
+ for _, loc := range s.Translations {
+ 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)
+
+ 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/message.go b/vendor/golang.org/x/text/message/pipeline/message.go
new file mode 100644
index 0000000..b9b0b3a
--- /dev/null
+++ b/vendor/golang.org/x/text/message/pipeline/message.go
@@ -0,0 +1,221 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pipeline
+
+import (
+ "encoding/json"
+ "strings"
+
+ "golang.org/x/text/language"
+)
+
+// TODO: these definitions should be moved to a package so that the can be used
+// by other tools.
+
+// The file contains the structures used to define translations of a certain
+// messages.
+//
+// A translation may have multiple translations strings, or messages, depending
+// on the feature values of the various arguments. For instance, consider
+// a hypothetical translation from English to English, where the source defines
+// the format string "%d file(s) remaining".
+// See the examples directory for examples of extracted messages.
+
+// 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"`
+ Meaning string `json:"meaning,omitempty"`
+ Message Text `json:"message"`
+ Translation Text `json:"translation"`
+
+ Comment string `json:"comment,omitempty"`
+ TranslatorComment string `json:"translatorComment,omitempty"`
+
+ Placeholders []Placeholder `json:"placeholders,omitempty"`
+
+ // TODO: default placeholder syntax is {foo}. Allow alternative escaping
+ // like `foo`.
+
+ // Extraction information.
+ Position string `json:"position,omitempty"` // filePosition:line
+}
+
+// Placeholder reports the placeholder for the given ID if it is defined or nil
+// otherwise.
+func (m *Message) Placeholder(id string) *Placeholder {
+ for _, p := range m.Placeholders {
+ if p.ID == id {
+ return &p
+ }
+ }
+ return nil
+}
+
+// Substitute replaces placeholders in msg with their original value.
+func (m *Message) Substitute(msg string) (sub string, err error) {
+ last := 0
+ for i := 0; i < len(msg); {
+ pLeft := strings.IndexByte(msg[i:], '{')
+ if pLeft == -1 {
+ break
+ }
+ pLeft += i
+ pRight := strings.IndexByte(msg[pLeft:], '}')
+ if pRight == -1 {
+ return "", errorf("unmatched '}'")
+ }
+ pRight += pLeft
+ id := strings.TrimSpace(msg[pLeft+1 : pRight])
+ i = pRight + 1
+ if id != "" && id[0] == '$' {
+ continue
+ }
+ sub += msg[last:pLeft]
+ last = i
+ ph := m.Placeholder(id)
+ if ph == nil {
+ return "", errorf("unknown placeholder %q in message %q", id, msg)
+ }
+ sub += ph.String
+ }
+ sub += msg[last:]
+ return sub, err
+}
+
+// A Placeholder is a part of the message that should not be changed by a
+// translator. It can be used to hide or prettify format strings (e.g. %d or
+// {{.Count}}), hide HTML, or mark common names that should not be translated.
+type Placeholder struct {
+ // ID is the placeholder identifier without the curly braces.
+ ID string `json:"id"`
+
+ // String is the string with which to replace the placeholder. This may be a
+ // formatting string (for instance "%d" or "{{.Count}}") or a literal string
+ // (<div>).
+ String string `json:"string"`
+
+ Type string `json:"type"`
+ UnderlyingType string `json:"underlyingType"`
+ // ArgNum and Expr are set if the placeholder is a substitution of an
+ // argument.
+ ArgNum int `json:"argNum,omitempty"`
+ Expr string `json:"expr,omitempty"`
+
+ Comment string `json:"comment,omitempty"`
+ Example string `json:"example,omitempty"`
+
+ // Features contains the features that are available for the implementation
+ // of this argument.
+ Features []Feature `json:"features,omitempty"`
+}
+
+// An argument contains information about the arguments passed to a message.
+type argument struct {
+ // ArgNum corresponds to the number that should be used for explicit argument indexes (e.g.
+ // "%[1]d").
+ ArgNum int `json:"argNum,omitempty"`
+
+ used bool // Used by Placeholder
+ Type string `json:"type"`
+ UnderlyingType string `json:"underlyingType"`
+ Expr string `json:"expr"`
+ Value string `json:"value,omitempty"`
+ Comment string `json:"comment,omitempty"`
+ Position string `json:"position,omitempty"`
+}
+
+// Feature holds information about a feature that can be implemented by
+// an Argument.
+type Feature struct {
+ Type string `json:"type"` // Right now this is only gender and plural.
+
+ // TODO: possible values and examples for the language under consideration.
+
+}
+
+// Text defines a message to be displayed.
+type Text struct {
+ // Msg and Select contains the message to be displayed. Msg may be used as
+ // a fallback value if none of the select cases match.
+ Msg string `json:"msg,omitempty"`
+ Select *Select `json:"select,omitempty"`
+
+ // Var defines a map of variables that may be substituted in the selected
+ // message.
+ Var map[string]Text `json:"var,omitempty"`
+
+ // Example contains an example message formatted with default values.
+ Example string `json:"example,omitempty"`
+}
+
+// IsEmpty reports whether this Text can generate anything.
+func (t *Text) IsEmpty() bool {
+ return t.Msg == "" && t.Select == nil && t.Var == nil
+}
+
+// rawText erases the UnmarshalJSON method.
+type rawText Text
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (t *Text) UnmarshalJSON(b []byte) error {
+ if b[0] == '"' {
+ return json.Unmarshal(b, &t.Msg)
+ }
+ return json.Unmarshal(b, (*rawText)(t))
+}
+
+// MarshalJSON implements json.Marshaler.
+func (t *Text) MarshalJSON() ([]byte, error) {
+ if t.Select == nil && t.Var == nil && t.Example == "" {
+ return json.Marshal(t.Msg)
+ }
+ return json.Marshal((*rawText)(t))
+}
+
+// IDList is a set identifiers that each may refer to possibly different
+// versions of the same message. When looking up a messages, the first
+// identifier in the list takes precedence.
+type IDList []string
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (id *IDList) UnmarshalJSON(b []byte) error {
+ if b[0] == '"' {
+ *id = []string{""}
+ return json.Unmarshal(b, &((*id)[0]))
+ }
+ return json.Unmarshal(b, (*[]string)(id))
+}
+
+// MarshalJSON implements json.Marshaler.
+func (id *IDList) MarshalJSON() ([]byte, error) {
+ if len(*id) == 1 {
+ return json.Marshal((*id)[0])
+ }
+ return json.Marshal((*[]string)(id))
+}
+
+// Select selects a Text based on the feature value associated with a feature of
+// a certain argument.
+type Select struct {
+ Feature string `json:"feature"` // Name of Feature type (e.g plural)
+ Arg string `json:"arg"` // The placeholder ID
+ Cases map[string]Text `json:"cases"`
+}
+
+// TODO: order matters, but can we derive the ordering from the case keys?
+// type Case struct {
+// Key string `json:"key"`
+// Value Text `json:"value"`
+// }
diff --git a/vendor/golang.org/x/text/message/pipeline/pipeline.go b/vendor/golang.org/x/text/message/pipeline/pipeline.go
new file mode 100644
index 0000000..8087bf6
--- /dev/null
+++ b/vendor/golang.org/x/text/message/pipeline/pipeline.go
@@ -0,0 +1,308 @@
+// 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"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "text/template"
+ "unicode"
+
+ "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 {
+ panic("unimplemented")
+ return nil
+
+}
+
+// Export writes out the messages to translation out files.
+func (s *State) Export() error {
+ panic("unimplemented")
+ 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 0000000..327e90d
--- /dev/null
+++ b/vendor/golang.org/x/text/message/pipeline/pipeline_test.go
@@ -0,0 +1,127 @@
+// 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")
+
+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) {
+ t.Helper()
+ 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
+ // TODO:
+ // for range s.Config.Actions {
+ // // TODO: do the actions.
+ // }
+ // chk(t, s.Export()) // TODO
+ 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 {
+ s = path.Clean(filepath.ToSlash(s))
+ if i := strings.LastIndex(s, "Size:"); i != -1 {
+ s = s[:i]
+ }
+ if i := strings.LastIndex(s, "Total table size"); i != -1 {
+ s = s[:i]
+ }
+ return 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 0000000..cf1511f
--- /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
+// "--- <filename>" 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 0000000..8195287
--- /dev/null
+++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go
@@ -0,0 +1,83 @@
+// 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)": 7,
+ "%[1]s is visiting %[3]s!\n": 3,
+ "%d files remaining!": 4,
+ "%d more files remaining!": 5,
+ "%s is out of order!": 6,
+ "%s is visiting %s!\n": 2,
+ "Hello %s!\n": 1,
+ "Hello world!\n": 0,
+}
+
+var deIndex = []uint32{ // 9 elements
+ 0x00000000, 0x00000011, 0x00000023, 0x0000003d,
+ 0x00000057, 0x00000075, 0x00000094, 0x00000094,
+ 0x00000094,
+} // Size: 60 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{ // 9 elements
+ 0x00000000, 0x00000012, 0x00000024, 0x00000042,
+ 0x00000060, 0x00000060, 0x000000a3, 0x000000ba,
+ 0x000000d9,
+} // Size: 60 bytes
+
+const en_USData string = "" + // Size: 217 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!\x14\x01\x81\x01\x00\x02\x14\x02One file remaini" +
+ "ng!\x00&\x02There are %[1]d more files remaining!\x02%[1]s is out of ord" +
+ "er!\x02%.2[1]f miles traveled (%[1]f)"
+
+var zhIndex = []uint32{ // 9 elements
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000,
+} // Size: 60 bytes
+
+const zhData string = ""
+
+// Total table size 545 bytes (0KiB); checksum: 343E0210
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 0000000..8195287
--- /dev/null
+++ b/vendor/golang.org/x/text/message/pipeline/testdata/test1/catalog_gen.go.want
@@ -0,0 +1,83 @@
+// 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)": 7,
+ "%[1]s is visiting %[3]s!\n": 3,
+ "%d files remaining!": 4,
+ "%d more files remaining!": 5,
+ "%s is out of order!": 6,
+ "%s is visiting %s!\n": 2,
+ "Hello %s!\n": 1,
+ "Hello world!\n": 0,
+}
+
+var deIndex = []uint32{ // 9 elements
+ 0x00000000, 0x00000011, 0x00000023, 0x0000003d,
+ 0x00000057, 0x00000075, 0x00000094, 0x00000094,
+ 0x00000094,
+} // Size: 60 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{ // 9 elements
+ 0x00000000, 0x00000012, 0x00000024, 0x00000042,
+ 0x00000060, 0x00000060, 0x000000a3, 0x000000ba,
+ 0x000000d9,
+} // Size: 60 bytes
+
+const en_USData string = "" + // Size: 217 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!\x14\x01\x81\x01\x00\x02\x14\x02One file remaini" +
+ "ng!\x00&\x02There are %[1]d more files remaining!\x02%[1]s is out of ord" +
+ "er!\x02%.2[1]f miles traveled (%[1]f)"
+
+var zhIndex = []uint32{ // 9 elements
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000,
+} // Size: 60 bytes
+
+const zhData string = ""
+
+// Total table size 545 bytes (0KiB); checksum: 343E0210
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 0000000..4d317af
--- /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 0000000..4d317af
--- /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 0000000..f92e4a1
--- /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/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 0000000..b984242
--- /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/zh/messages.gotext.json b/vendor/golang.org/x/text/message/pipeline/testdata/test1/locales/zh/messages.gotext.json
new file mode 100755
index 0000000..c80d1d2
--- /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/test1.go b/vendor/golang.org/x/text/message/pipeline/testdata/test1/test1.go
new file mode 100644
index 0000000..88051f9
--- /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 5819cba..777e172 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 = "<invalid reflect.Value>"
)
+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')
}
}