123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- // 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"
- "io"
- "sort"
- "golang.org/x/text/internal/format"
- "golang.org/x/text/internal/language/compact"
- )
- // 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.
- var space = []byte(" ")
- // 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 lang compact.ID
- if state, ok := s.(format.State); ok {
- lang, _ = compact.RegionalID(compact.Tag(state.Language()))
- }
- // 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
- }
- // TODO: use pattern.
- io.WriteString(s, opt.symbol(lang, cur))
- if v.amount != nil {
- s.Write(space)
- // TODO: apply currency-specific rounding
- scale, _ := opt.kind.Rounding(cur)
- if _, ok := s.Precision(); !ok {
- fmt.Fprintf(s, "%.*f", scale, v.amount)
- } else {
- fmt.Fprint(s, v.amount)
- }
- }
- }
- // 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()
- }
|