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