1// Copyright 2015 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package currency 6 7import ( 8 "fmt" 9 "io" 10 "sort" 11 12 "golang.org/x/text/internal/format" 13 "golang.org/x/text/internal/language/compact" 14) 15 16// Amount is an amount-currency unit pair. 17type Amount struct { 18 amount interface{} // Change to decimal(64|128). 19 currency Unit 20} 21 22// Currency reports the currency unit of this amount. 23func (a Amount) Currency() Unit { return a.currency } 24 25// TODO: based on decimal type, but may make sense to customize a bit. 26// func (a Amount) Decimal() 27// func (a Amount) Int() (int64, error) 28// func (a Amount) Fraction() (int64, error) 29// func (a Amount) Rat() *big.Rat 30// func (a Amount) Float() (float64, error) 31// func (a Amount) Scale() uint 32// func (a Amount) Precision() uint 33// func (a Amount) Sign() int 34// 35// Add/Sub/Div/Mul/Round. 36 37var space = []byte(" ") 38 39// Format implements fmt.Formatter. It accepts format.State for 40// language-specific rendering. 41func (a Amount) Format(s fmt.State, verb rune) { 42 v := formattedValue{ 43 currency: a.currency, 44 amount: a.amount, 45 format: defaultFormat, 46 } 47 v.Format(s, verb) 48} 49 50// formattedValue is currency amount or unit that implements language-sensitive 51// formatting. 52type formattedValue struct { 53 currency Unit 54 amount interface{} // Amount, Unit, or number. 55 format *options 56} 57 58// Format implements fmt.Formatter. It accepts format.State for 59// language-specific rendering. 60func (v formattedValue) Format(s fmt.State, verb rune) { 61 var lang compact.ID 62 if state, ok := s.(format.State); ok { 63 lang, _ = compact.RegionalID(compact.Tag(state.Language())) 64 } 65 66 // Get the options. Use DefaultFormat if not present. 67 opt := v.format 68 if opt == nil { 69 opt = defaultFormat 70 } 71 cur := v.currency 72 if cur.index == 0 { 73 cur = opt.currency 74 } 75 76 // TODO: use pattern. 77 io.WriteString(s, opt.symbol(lang, cur)) 78 if v.amount != nil { 79 s.Write(space) 80 81 // TODO: apply currency-specific rounding 82 scale, _ := opt.kind.Rounding(cur) 83 if _, ok := s.Precision(); !ok { 84 fmt.Fprintf(s, "%.*f", scale, v.amount) 85 } else { 86 fmt.Fprint(s, v.amount) 87 } 88 } 89} 90 91// Formatter decorates a given number, Unit or Amount with formatting options. 92type Formatter func(amount interface{}) formattedValue 93 94// func (f Formatter) Options(opts ...Option) Formatter 95 96// TODO: call this a Formatter or FormatFunc? 97 98var dummy = USD.Amount(0) 99 100// adjust creates a new Formatter based on the adjustments of fn on f. 101func (f Formatter) adjust(fn func(*options)) Formatter { 102 var o options = *(f(dummy).format) 103 fn(&o) 104 return o.format 105} 106 107// Default creates a new Formatter that defaults to currency unit c if a numeric 108// value is passed that is not associated with a currency. 109func (f Formatter) Default(currency Unit) Formatter { 110 return f.adjust(func(o *options) { o.currency = currency }) 111} 112 113// Kind sets the kind of the underlying currency unit. 114func (f Formatter) Kind(k Kind) Formatter { 115 return f.adjust(func(o *options) { o.kind = k }) 116} 117 118var defaultFormat *options = ISO(dummy).format 119 120var ( 121 // Uses Narrow symbols. Overrides Symbol, if present. 122 NarrowSymbol Formatter = Formatter(formNarrow) 123 124 // Use Symbols instead of ISO codes, when available. 125 Symbol Formatter = Formatter(formSymbol) 126 127 // Use ISO code as symbol. 128 ISO Formatter = Formatter(formISO) 129 130 // TODO: 131 // // Use full name as symbol. 132 // Name Formatter 133) 134 135// options configures rendering and rounding options for an Amount. 136type options struct { 137 currency Unit 138 kind Kind 139 140 symbol func(compactIndex compact.ID, c Unit) string 141} 142 143func (o *options) format(amount interface{}) formattedValue { 144 v := formattedValue{format: o} 145 switch x := amount.(type) { 146 case Amount: 147 v.amount = x.amount 148 v.currency = x.currency 149 case *Amount: 150 v.amount = x.amount 151 v.currency = x.currency 152 case Unit: 153 v.currency = x 154 case *Unit: 155 v.currency = *x 156 default: 157 if o.currency.index == 0 { 158 panic("cannot format number without a currency being set") 159 } 160 // TODO: Must be a number. 161 v.amount = x 162 v.currency = o.currency 163 } 164 return v 165} 166 167var ( 168 optISO = options{symbol: lookupISO} 169 optSymbol = options{symbol: lookupSymbol} 170 optNarrow = options{symbol: lookupNarrow} 171) 172 173// These need to be functions, rather than curried methods, as curried methods 174// are evaluated at init time, causing tables to be included unconditionally. 175func formISO(x interface{}) formattedValue { return optISO.format(x) } 176func formSymbol(x interface{}) formattedValue { return optSymbol.format(x) } 177func formNarrow(x interface{}) formattedValue { return optNarrow.format(x) } 178 179func lookupISO(x compact.ID, c Unit) string { return c.String() } 180func lookupSymbol(x compact.ID, c Unit) string { return normalSymbol.lookup(x, c) } 181func lookupNarrow(x compact.ID, c Unit) string { return narrowSymbol.lookup(x, c) } 182 183type symbolIndex struct { 184 index []uint16 // position corresponds with compact index of language. 185 data []curToIndex 186} 187 188var ( 189 normalSymbol = symbolIndex{normalLangIndex, normalSymIndex} 190 narrowSymbol = symbolIndex{narrowLangIndex, narrowSymIndex} 191) 192 193func (x *symbolIndex) lookup(lang compact.ID, c Unit) string { 194 for { 195 index := x.data[x.index[lang]:x.index[lang+1]] 196 i := sort.Search(len(index), func(i int) bool { 197 return index[i].cur >= c.index 198 }) 199 if i < len(index) && index[i].cur == c.index { 200 x := index[i].idx 201 start := x + 1 202 end := start + uint16(symbols[x]) 203 if start == end { 204 return c.String() 205 } 206 return symbols[start:end] 207 } 208 if lang == 0 { 209 break 210 } 211 lang = lang.Parent() 212 } 213 return c.String() 214} 215