| // Copyright 2015 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package currency | |
| import ( | |
| "fmt" | |
| "sort" | |
| "golang.org/x/text/internal/format" | |
| "golang.org/x/text/internal/language/compact" | |
| "golang.org/x/text/internal/number" | |
| "golang.org/x/text/language" | |
| ) | |
| // Amount is an amount-currency unit pair. | |
| type Amount struct { | |
| amount interface{} // Change to decimal(64|128). | |
| currency Unit | |
| } | |
| // Currency reports the currency unit of this amount. | |
| func (a Amount) Currency() Unit { return a.currency } | |
| // TODO: based on decimal type, but may make sense to customize a bit. | |
| // func (a Amount) Decimal() | |
| // func (a Amount) Int() (int64, error) | |
| // func (a Amount) Fraction() (int64, error) | |
| // func (a Amount) Rat() *big.Rat | |
| // func (a Amount) Float() (float64, error) | |
| // func (a Amount) Scale() uint | |
| // func (a Amount) Precision() uint | |
| // func (a Amount) Sign() int | |
| // | |
| // Add/Sub/Div/Mul/Round. | |
| // Format implements fmt.Formatter. It accepts format.State for | |
| // language-specific rendering. | |
| func (a Amount) Format(s fmt.State, verb rune) { | |
| v := formattedValue{ | |
| currency: a.currency, | |
| amount: a.amount, | |
| format: defaultFormat, | |
| } | |
| v.Format(s, verb) | |
| } | |
| // formattedValue is currency amount or unit that implements language-sensitive | |
| // formatting. | |
| type formattedValue struct { | |
| currency Unit | |
| amount interface{} // Amount, Unit, or number. | |
| format *options | |
| } | |
| // Format implements fmt.Formatter. It accepts format.State for | |
| // language-specific rendering. | |
| func (v formattedValue) Format(s fmt.State, verb rune) { | |
| var tag language.Tag | |
| var lang compact.ID | |
| if state, ok := s.(format.State); ok { | |
| tag = state.Language() | |
| lang, _ = compact.RegionalID(compact.Tag(tag)) | |
| } | |
| // Get the options. Use DefaultFormat if not present. | |
| opt := v.format | |
| if opt == nil { | |
| opt = defaultFormat | |
| } | |
| cur := v.currency | |
| if cur.index == 0 { | |
| cur = opt.currency | |
| } | |
| sym := opt.symbol(lang, cur) | |
| if v.amount != nil { | |
| var f number.Formatter | |
| f.InitDecimal(tag) | |
| scale, increment := opt.kind.Rounding(cur) | |
| f.RoundingContext.SetScale(scale) | |
| f.RoundingContext.Increment = uint32(increment) | |
| f.RoundingContext.IncrementScale = uint8(scale) | |
| f.RoundingContext.Mode = number.ToNearestAway | |
| d := f.Append(nil, v.amount) | |
| fmt.Fprint(s, sym, " ", string(d)) | |
| } else { | |
| fmt.Fprint(s, sym) | |
| } | |
| } | |
| // Formatter decorates a given number, Unit or Amount with formatting options. | |
| type Formatter func(amount interface{}) formattedValue | |
| // func (f Formatter) Options(opts ...Option) Formatter | |
| // TODO: call this a Formatter or FormatFunc? | |
| var dummy = USD.Amount(0) | |
| // adjust creates a new Formatter based on the adjustments of fn on f. | |
| func (f Formatter) adjust(fn func(*options)) Formatter { | |
| var o options = *(f(dummy).format) | |
| fn(&o) | |
| return o.format | |
| } | |
| // Default creates a new Formatter that defaults to currency unit c if a numeric | |
| // value is passed that is not associated with a currency. | |
| func (f Formatter) Default(currency Unit) Formatter { | |
| return f.adjust(func(o *options) { o.currency = currency }) | |
| } | |
| // Kind sets the kind of the underlying currency unit. | |
| func (f Formatter) Kind(k Kind) Formatter { | |
| return f.adjust(func(o *options) { o.kind = k }) | |
| } | |
| var defaultFormat *options = ISO(dummy).format | |
| var ( | |
| // Uses Narrow symbols. Overrides Symbol, if present. | |
| NarrowSymbol Formatter = Formatter(formNarrow) | |
| // Use Symbols instead of ISO codes, when available. | |
| Symbol Formatter = Formatter(formSymbol) | |
| // Use ISO code as symbol. | |
| ISO Formatter = Formatter(formISO) | |
| // TODO: | |
| // // Use full name as symbol. | |
| // Name Formatter | |
| ) | |
| // options configures rendering and rounding options for an Amount. | |
| type options struct { | |
| currency Unit | |
| kind Kind | |
| symbol func(compactIndex compact.ID, c Unit) string | |
| } | |
| func (o *options) format(amount interface{}) formattedValue { | |
| v := formattedValue{format: o} | |
| switch x := amount.(type) { | |
| case Amount: | |
| v.amount = x.amount | |
| v.currency = x.currency | |
| case *Amount: | |
| v.amount = x.amount | |
| v.currency = x.currency | |
| case Unit: | |
| v.currency = x | |
| case *Unit: | |
| v.currency = *x | |
| default: | |
| if o.currency.index == 0 { | |
| panic("cannot format number without a currency being set") | |
| } | |
| // TODO: Must be a number. | |
| v.amount = x | |
| v.currency = o.currency | |
| } | |
| return v | |
| } | |
| var ( | |
| optISO = options{symbol: lookupISO} | |
| optSymbol = options{symbol: lookupSymbol} | |
| optNarrow = options{symbol: lookupNarrow} | |
| ) | |
| // These need to be functions, rather than curried methods, as curried methods | |
| // are evaluated at init time, causing tables to be included unconditionally. | |
| func formISO(x interface{}) formattedValue { return optISO.format(x) } | |
| func formSymbol(x interface{}) formattedValue { return optSymbol.format(x) } | |
| func formNarrow(x interface{}) formattedValue { return optNarrow.format(x) } | |
| func lookupISO(x compact.ID, c Unit) string { return c.String() } | |
| func lookupSymbol(x compact.ID, c Unit) string { return normalSymbol.lookup(x, c) } | |
| func lookupNarrow(x compact.ID, c Unit) string { return narrowSymbol.lookup(x, c) } | |
| type symbolIndex struct { | |
| index []uint16 // position corresponds with compact index of language. | |
| data []curToIndex | |
| } | |
| var ( | |
| normalSymbol = symbolIndex{normalLangIndex, normalSymIndex} | |
| narrowSymbol = symbolIndex{narrowLangIndex, narrowSymIndex} | |
| ) | |
| func (x *symbolIndex) lookup(lang compact.ID, c Unit) string { | |
| for { | |
| index := x.data[x.index[lang]:x.index[lang+1]] | |
| i := sort.Search(len(index), func(i int) bool { | |
| return index[i].cur >= c.index | |
| }) | |
| if i < len(index) && index[i].cur == c.index { | |
| x := index[i].idx | |
| start := x + 1 | |
| end := start + uint16(symbols[x]) | |
| if start == end { | |
| return c.String() | |
| } | |
| return symbols[start:end] | |
| } | |
| if lang == 0 { | |
| break | |
| } | |
| lang = lang.Parent() | |
| } | |
| return c.String() | |
| } |