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