| // 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 number |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "golang.org/x/text/feature/plural" |
| "golang.org/x/text/internal/format" |
| "golang.org/x/text/internal/number" |
| "golang.org/x/text/language" |
| ) |
| |
| // A FormatFunc formates a number. |
| type FormatFunc func(x interface{}, opts ...Option) Formatter |
| |
| // NewFormat creates a FormatFunc based on another FormatFunc and new options. |
| // Use NewFormat to cash the creation of formatters. |
| func NewFormat(format FormatFunc, opts ...Option) FormatFunc { |
| o := *format(nil).options |
| n := len(o.options) |
| o.options = append(o.options[:n:n], opts...) |
| return func(x interface{}, opts ...Option) Formatter { |
| return newFormatter(&o, opts, x) |
| } |
| } |
| |
| type options struct { |
| verbs string |
| initFunc initFunc |
| options []Option |
| pluralFunc func(t language.Tag, scale int) (f plural.Form, n int) |
| } |
| |
| type optionFlag uint16 |
| |
| const ( |
| hasScale optionFlag = 1 << iota |
| hasPrecision |
| noSeparator |
| exact |
| ) |
| |
| type initFunc func(f *number.Formatter, t language.Tag) |
| |
| func newFormatter(o *options, opts []Option, value interface{}) Formatter { |
| if len(opts) > 0 { |
| n := *o |
| n.options = opts |
| o = &n |
| } |
| return Formatter{o, value} |
| } |
| |
| func newOptions(verbs string, f initFunc) *options { |
| return &options{verbs: verbs, initFunc: f} |
| } |
| |
| type Formatter struct { |
| *options |
| value interface{} |
| } |
| |
| // Format implements format.Formatter. It is for internal use only for now. |
| func (f Formatter) Format(state format.State, verb rune) { |
| // TODO: consider implementing fmt.Formatter instead and using the following |
| // piece of code. This allows numbers to be rendered mostly as expected |
| // when using fmt. But it may get weird with the spellout options and we |
| // may need more of format.State over time. |
| // lang := language.Und |
| // if s, ok := state.(format.State); ok { |
| // lang = s.Language() |
| // } |
| |
| lang := state.Language() |
| if !strings.Contains(f.verbs, string(verb)) { |
| fmt.Fprintf(state, "%%!%s(%T=%v)", string(verb), f.value, f.value) |
| return |
| } |
| var p number.Formatter |
| f.initFunc(&p, lang) |
| for _, o := range f.options.options { |
| o(lang, &p) |
| } |
| if w, ok := state.Width(); ok { |
| p.FormatWidth = uint16(w) |
| } |
| if prec, ok := state.Precision(); ok { |
| switch verb { |
| case 'd': |
| p.SetScale(0) |
| case 'f': |
| p.SetScale(prec) |
| case 'e': |
| p.SetPrecision(prec + 1) |
| case 'g': |
| p.SetPrecision(prec) |
| } |
| } |
| var d number.Decimal |
| d.Convert(p.RoundingContext, f.value) |
| state.Write(p.Format(nil, &d)) |
| } |
| |
| // Digits returns information about which logical digits will be presented to |
| // the user. This information is relevant, for instance, to determine plural |
| // forms. |
| func (f Formatter) Digits(buf []byte, tag language.Tag, scale int) number.Digits { |
| var p number.Formatter |
| f.initFunc(&p, tag) |
| if scale >= 0 { |
| // TODO: this only works well for decimal numbers, which is generally |
| // fine. |
| p.SetScale(scale) |
| } |
| var d number.Decimal |
| d.Convert(p.RoundingContext, f.value) |
| return number.FormatDigits(&d, p.RoundingContext) |
| } |