Fnlb/k8s grouper (#563)

* WIP: add k8s grouper

- This shares a great deal of behaviour with allGrouper. Once it's
tested, refactor that to share as much as possible

- Glide hell. Checked in the yaml and lock files but a glide i -v
will be required to bring vendor/ up-to-date. Will address once this
is ready.

* Update README. Make the watch tracking work.

(To follow: add the junk that was pulled in via the glide update.)

* Vendor updates.

* go fmt

* Use the allGrouper with a k8s-backed DBStore instead.

This is much tidier :-)

* Fix up go vet
This commit is contained in:
jan grant
2017-12-06 18:45:27 +00:00
committed by Reed Allman
parent 53a2b2702a
commit 8fc4bdcf97
2626 changed files with 626646 additions and 89487 deletions

View File

@@ -0,0 +1,46 @@
// 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 plural_test
import (
"golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func ExampleSelect() {
// Manually set some translations. This is typically done programmatically.
message.Set(language.English, "%d files remaining",
plural.Selectf(1, "%d",
"=0", "done!",
plural.One, "one file remaining",
plural.Other, "%[1]d files remaining",
))
message.Set(language.Dutch, "%d files remaining",
plural.Selectf(1, "%d",
"=0", "klaar!",
// One can also use a string instead of a Kind
"one", "nog één bestand te gaan",
"other", "nog %[1]d bestanden te gaan",
))
p := message.NewPrinter(language.English)
p.Printf("%d files remaining", 5)
p.Println()
p.Printf("%d files remaining", 1)
p.Println()
p = message.NewPrinter(language.Dutch)
p.Printf("%d files remaining", 1)
p.Println()
p.Printf("%d files remaining", 0)
p.Println()
// Output:
// 5 files remaining
// one file remaining
// nog één bestand te gaan
// klaar!
}

244
vendor/golang.org/x/text/feature/plural/message.go generated vendored Executable file
View File

@@ -0,0 +1,244 @@
// 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 plural
import (
"fmt"
"io/ioutil"
"reflect"
"strconv"
"golang.org/x/text/internal/catmsg"
"golang.org/x/text/internal/number"
"golang.org/x/text/language"
"golang.org/x/text/message/catalog"
)
// TODO: consider deleting this interface. Maybe VisibleDigits is always
// sufficient and practical.
// Interface is used for types that can determine their own plural form.
type Interface interface {
// PluralForm reports the plural form for the given language of the
// underlying value. It also returns the integer value. If the integer value
// is larger than fits in n, PluralForm may return a value modulo
// 10,000,000.
PluralForm(t language.Tag, scale int) (f Form, n int)
}
// Selectf returns the first case for which its selector is a match for the
// arg-th substitution argument to a formatting call, formatting it as indicated
// by format.
//
// The cases argument are pairs of selectors and messages. Selectors are of type
// string or Form. Messages are of type string or catalog.Message. A selector
// matches an argument if:
// - it is "other" or Other
// - it matches the plural form of the argument: "zero", "one", "two", "few",
// or "many", or the equivalent Form
// - it is of the form "=x" where x is an integer that matches the value of
// the argument.
// - it is of the form "<x" where x is an integer that is larger than the
// argument.
//
// The format argument determines the formatting parameters for which to
// determine the plural form. This is especially relevant for non-integer
// values.
//
// The format string may be "", in which case a best-effort attempt is made to
// find a reasonable representation on which to base the plural form. Examples
// of format strings are:
// - %.2f decimal with scale 2
// - %.2e scientific notation with precision 3 (scale + 1)
// - %d integer
func Selectf(arg int, format string, cases ...interface{}) catalog.Message {
var p parser
// Intercept the formatting parameters of format by doing a dummy print.
fmt.Fprintf(ioutil.Discard, format, &p)
m := &message{arg, kindDefault, 0, cases}
switch p.verb {
case 'g':
m.kind = kindPrecision
m.scale = p.scale
case 'f':
m.kind = kindScale
m.scale = p.scale
case 'e':
m.kind = kindScientific
m.scale = p.scale
case 'd':
m.kind = kindScale
m.scale = 0
default:
// TODO: do we need to handle errors?
}
return m
}
type parser struct {
verb rune
scale int
}
func (p *parser) Format(s fmt.State, verb rune) {
p.verb = verb
p.scale = -1
if prec, ok := s.Precision(); ok {
p.scale = prec
}
}
type message struct {
arg int
kind int
scale int
cases []interface{}
}
const (
// Start with non-ASCII to allow skipping values.
kindDefault = 0x80 + iota
kindScale // verb f, number of fraction digits follows
kindScientific // verb e, number of fraction digits follows
kindPrecision // verb g, number of significant digits follows
)
var handle = catmsg.Register("golang.org/x/text/feature/plural:plural", execute)
func (m *message) Compile(e *catmsg.Encoder) error {
e.EncodeMessageType(handle)
e.EncodeUint(uint64(m.arg))
e.EncodeUint(uint64(m.kind))
if m.kind > kindDefault {
e.EncodeUint(uint64(m.scale))
}
forms := validForms(cardinal, e.Language())
for i := 0; i < len(m.cases); {
if err := compileSelector(e, forms, m.cases[i]); err != nil {
return err
}
if i++; i >= len(m.cases) {
return fmt.Errorf("plural: no message defined for selector %v", m.cases[i-1])
}
var msg catalog.Message
switch x := m.cases[i].(type) {
case string:
msg = catalog.String(x)
case catalog.Message:
msg = x
default:
return fmt.Errorf("plural: message of type %T; must be string or catalog.Message", x)
}
if err := e.EncodeMessage(msg); err != nil {
return err
}
i++
}
return nil
}
func compileSelector(e *catmsg.Encoder, valid []Form, selector interface{}) error {
form := Other
switch x := selector.(type) {
case string:
if x == "" {
return fmt.Errorf("plural: empty selector")
}
if c := x[0]; c == '=' || c == '<' {
val, err := strconv.ParseUint(x[1:], 10, 16)
if err != nil {
return fmt.Errorf("plural: invalid number in selector %q: %v", selector, err)
}
e.EncodeUint(uint64(c))
e.EncodeUint(val)
return nil
}
var ok bool
form, ok = countMap[x]
if !ok {
return fmt.Errorf("plural: invalid plural form %q", selector)
}
case Form:
form = x
default:
return fmt.Errorf("plural: selector of type %T; want string or Form", selector)
}
ok := false
for _, f := range valid {
if f == form {
ok = true
break
}
}
if !ok {
return fmt.Errorf("plural: form %q not supported for language %q", selector, e.Language())
}
e.EncodeUint(uint64(form))
return nil
}
func execute(d *catmsg.Decoder) bool {
lang := d.Language()
argN := int(d.DecodeUint())
kind := int(d.DecodeUint())
scale := -1 // default
if kind > kindDefault {
scale = int(d.DecodeUint())
}
form := Other
n := -1
if arg := d.Arg(argN); arg == nil {
// Default to Other.
} else if x, ok := arg.(number.VisibleDigits); ok {
d := x.Digits(nil, lang, scale)
form, n = cardinal.matchDisplayDigits(lang, &d)
} else if x, ok := arg.(Interface); ok {
// This covers lists and formatters from the number package.
form, n = x.PluralForm(lang, scale)
} else {
var f number.Formatter
switch kind {
case kindScale:
f.InitDecimal(lang)
f.SetScale(scale)
case kindScientific:
f.InitScientific(lang)
f.SetScale(scale)
case kindPrecision:
f.InitDecimal(lang)
f.SetPrecision(scale)
case kindDefault:
// sensible default
f.InitDecimal(lang)
if k := reflect.TypeOf(arg).Kind(); reflect.Int <= k && k <= reflect.Uintptr {
f.SetScale(0)
} else {
f.SetScale(2)
}
}
var dec number.Decimal // TODO: buffer in Printer
dec.Convert(f.RoundingContext, arg)
v := number.FormatDigits(&dec, f.RoundingContext)
if !v.NaN && !v.Inf {
form, n = cardinal.matchDisplayDigits(d.Language(), &v)
}
}
for !d.Done() {
f := d.DecodeUint()
if (f == '=' && n == int(d.DecodeUint())) ||
(f == '<' && 0 <= n && n < int(d.DecodeUint())) ||
form == Form(f) ||
Other == Form(f) {
return d.ExecuteMessage()
}
d.SkipMessage()
}
return false
}

197
vendor/golang.org/x/text/feature/plural/message_test.go generated vendored Normal file
View File

@@ -0,0 +1,197 @@
// 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 plural
import (
"fmt"
"strings"
"testing"
"golang.org/x/text/internal/catmsg"
"golang.org/x/text/language"
"golang.org/x/text/message/catalog"
)
func TestSelect(t *testing.T) {
lang := language.English
type test struct {
arg interface{}
result string
err string
}
testCases := []struct {
desc string
msg catalog.Message
err string
tests []test
}{{
desc: "basic",
msg: Selectf(1, "%d", "one", "foo", "other", "bar"),
tests: []test{
{arg: 0, result: "bar"},
{arg: 1, result: "foo"},
{arg: 2, result: "bar"},
{arg: opposite(1), result: "bar"},
{arg: opposite(2), result: "foo"},
{arg: "unknown", result: "bar"}, // other
},
}, {
desc: "comparisons",
msg: Selectf(1, "%d",
"=0", "zero",
"=1", "one",
"one", "cannot match", // never matches
"<5", "<5", // never matches
"=5", "=5",
Other, "other"),
tests: []test{
{arg: 0, result: "zero"},
{arg: 1, result: "one"},
{arg: 2, result: "<5"},
{arg: 4, result: "<5"},
{arg: 5, result: "=5"},
{arg: 6, result: "other"},
{arg: "unknown", result: "other"},
},
}, {
desc: "fractions",
msg: Selectf(1, "%.2f", "one", "foo", "other", "bar"),
tests: []test{
// fractions are always plural in english
{arg: 0, result: "bar"},
{arg: 1, result: "bar"},
},
}, {
desc: "decimal without fractions",
msg: Selectf(1, "%.0f", "one", "foo", "other", "bar"),
tests: []test{
// fractions are always plural in english
{arg: 0, result: "bar"},
{arg: 1, result: "foo"},
},
}, {
desc: "scientific",
msg: Selectf(1, "%.0e", "one", "foo", "other", "bar"),
tests: []test{
{arg: 0, result: "bar"},
{arg: 1, result: "foo"},
},
}, {
desc: "variable",
msg: Selectf(1, "%.1g", "one", "foo", "other", "bar"),
tests: []test{
// fractions are always plural in english
{arg: 0, result: "bar"},
{arg: 1, result: "foo"},
{arg: 2, result: "bar"},
},
}, {
desc: "default",
msg: Selectf(1, "", "one", "foo", "other", "bar"),
tests: []test{
{arg: 0, result: "bar"},
{arg: 1, result: "foo"},
{arg: 2, result: "bar"},
{arg: 1.0, result: "bar"},
},
}, {
desc: "nested",
msg: Selectf(1, "", "other", Selectf(2, "", "one", "foo", "other", "bar")),
tests: []test{
{arg: 0, result: "bar"},
{arg: 1, result: "foo"},
{arg: 2, result: "bar"},
},
}, {
desc: "arg unavailable",
msg: Selectf(100, "%.2f", "one", "foo", "other", "bar"),
tests: []test{{arg: 1, result: "bar"}},
}, {
desc: "no match",
msg: Selectf(1, "%.2f", "one", "foo"),
tests: []test{{arg: 0, result: "bar", err: catmsg.ErrNoMatch.Error()}},
}, {
desc: "error invalid form",
err: `invalid plural form "excessive"`,
msg: Selectf(1, "%d", "excessive", "foo"),
}, {
desc: "error form not used by language",
err: `form "many" not supported for language "en"`,
msg: Selectf(1, "%d", "many", "foo"),
}, {
desc: "error invalid selector",
err: `selector of type int; want string or Form`,
msg: Selectf(1, "%d", 1, "foo"),
}, {
desc: "error missing message",
err: `no message defined for selector one`,
msg: Selectf(1, "%d", "one"),
}, {
desc: "error invalid number",
err: `invalid number in selector "<1.00"`,
msg: Selectf(1, "%d", "<1.00"),
}, {
desc: "error empty selector",
err: `empty selector`,
msg: Selectf(1, "%d", "", "foo"),
}, {
desc: "error invalid message",
err: `message of type int; must be string or catalog.Message`,
msg: Selectf(1, "%d", "one", 3),
}, {
desc: "nested error",
err: `empty selector`,
msg: Selectf(1, "", "other", Selectf(2, "", "")),
}}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
data, err := catmsg.Compile(lang, nil, tc.msg)
chkError(t, err, tc.err)
for _, tx := range tc.tests {
t.Run(fmt.Sprint(tx.arg), func(t *testing.T) {
r := renderer{arg: tx.arg}
d := catmsg.NewDecoder(lang, &r, nil)
err := d.Execute(data)
chkError(t, err, tx.err)
if r.result != tx.result {
t.Errorf("got %q; want %q", r.result, tx.result)
}
})
}
})
}
}
func chkError(t *testing.T, got error, want string) {
if (got == nil && want != "") ||
(got != nil && (want == "" || !strings.Contains(got.Error(), want))) {
t.Fatalf("got %v; want %v", got, want)
}
if got != nil {
t.SkipNow()
}
}
type renderer struct {
arg interface{}
result string
}
func (r *renderer) Render(s string) { r.result += s }
func (r *renderer) Arg(i int) interface{} {
if i > 10 { // Allow testing "arg unavailable" path
return nil
}
return r.arg
}
type opposite int
func (o opposite) PluralForm(lang language.Tag, scale int) (Form, int) {
if o == 1 {
return Other, 1
}
return One, int(o)
}

View File

@@ -13,6 +13,7 @@
package plural
import (
"golang.org/x/text/internal/number"
"golang.org/x/text/language"
)
@@ -109,21 +110,25 @@ func getIntApprox(digits []byte, start, end, nMod, big int) (n int) {
// 123 []byte{1, 2, 3} 3 0
// 123.4 []byte{1, 2, 3, 4} 3 1
// 123.40 []byte{1, 2, 3, 4} 3 2
// 100000 []byte{1} 6......0
// 100000.00 []byte{1} 6......3
// 100000 []byte{1} 6 0
// 100000.00 []byte{1} 6 3
func (p *Rules) MatchDigits(t language.Tag, digits []byte, exp, scale int) Form {
index, _ := language.CompactIndex(t)
endN := len(digits) + exp
// Differentiate up to including mod 1000000 for the integer part.
n := getIntApprox(digits, 0, endN, 6, 1000000)
n := getIntApprox(digits, 0, exp, 6, 1000000)
// Differentiate up to including mod 100 for the fractional part.
f := getIntApprox(digits, endN, endN+scale, 2, 100)
f := getIntApprox(digits, exp, exp+scale, 2, 100)
return matchPlural(p, index, n, f, scale)
}
func (p *Rules) matchDisplayDigits(t language.Tag, d *number.Digits) (Form, int) {
n := getIntApprox(d.Digits, 0, int(d.Exp), 6, 1000000)
return p.MatchDigits(t, d.Digits, int(d.Exp), d.NumFracDigits()), n
}
func validForms(p *Rules, t language.Tag) (forms []Form) {
index, _ := language.CompactIndex(t)
offset := p.langToIndex[index]
@@ -145,6 +150,25 @@ func (p *Rules) matchComponents(t language.Tag, n, f, scale int) Form {
return matchPlural(p, index, n, f, scale)
}
// MatchPlural returns the plural form for the given language and plural
// operands (as defined in
// http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules):
// where
// n absolute value of the source number (integer and decimals)
// input
// i integer digits of n.
// v number of visible fraction digits in n, with trailing zeros.
// w number of visible fraction digits in n, without trailing zeros.
// f visible fractional digits in n, with trailing zeros (f = t * 10^(v-w))
// t visible fractional digits in n, without trailing zeros.
//
// If any of the operand values is too large to fit in an int, it is okay to
// pass the value modulo 10,000,000.
func (p *Rules) MatchPlural(lang language.Tag, i, v, w, f, t int) Form {
index, _ := language.CompactIndex(lang)
return matchPlural(p, index, i, f, v)
}
func matchPlural(p *Rules, index int, n, f, v int) Form {
nMask := p.inclusionMasks[n%maxMod]
// Compute the fMask inline in the rules below, as it is relatively rare.

View File

@@ -28,6 +28,8 @@ func TestGetIntApprox(t *testing.T) {
{"123", 0, 2, 2, 12},
{"123", 3, 4, 2, 0},
{"12345", 3, 4, 2, 4},
{"40", 0, 1, 2, 4},
{"1", 0, 7, 2, big},
{"123", 0, 5, 2, big},
{"123", 0, 5, 3, big},
@@ -114,7 +116,7 @@ func testPlurals(t *testing.T, p *Rules, testCases []pluralTest) {
for i := range digits {
digits[i] -= '0'
}
if f := p.MatchDigits(tag, digits, 0, 0); f != Form(tc.form) {
if f := p.MatchDigits(tag, digits, len(digits), 0); f != Form(tc.form) {
t.Errorf("MatchDigits: got %v; want %v", f, Form(tc.form))
}
})
@@ -139,14 +141,25 @@ func testPlurals(t *testing.T, p *Rules, testCases []pluralTest) {
num := fmt.Sprintf("%[1]d.%0[3]*[2]d", n/m, n%m, scale)
name := fmt.Sprintf("%s:dec(%s)", loc, num)
t.Run(name, func(t *testing.T) {
ff := n % m
tt := ff
w := scale
for tt > 0 && tt%10 == 0 {
w--
tt /= 10
}
if f := p.MatchPlural(tag, n/m, scale, w, ff, tt); f != Form(tc.form) {
t.Errorf("MatchPlural: got %v; want %v", f, Form(tc.form))
}
if f := p.matchComponents(tag, n/m, n%m, scale); f != Form(tc.form) {
t.Errorf("matchComponents: got %v; want %v", f, Form(tc.form))
}
exp := strings.IndexByte(num, '.')
digits := []byte(strings.Replace(num, ".", "", 1))
for i := range digits {
digits[i] -= '0'
}
if f := p.MatchDigits(tag, digits, -scale, scale); f != Form(tc.form) {
if f := p.MatchDigits(tag, digits, exp, scale); f != Form(tc.form) {
t.Errorf("MatchDigits: got %v; want %v", f, Form(tc.form))
}
})