mirror of
https://github.com/Luzifer/mondash.git
synced 2024-11-10 08:30:02 +00:00
216 lines
5.5 KiB
Go
216 lines
5.5 KiB
Go
|
// 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"
|
||
|
"golang.org/x/text/internal/format"
|
||
|
"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.
|
||
|
|
||
|
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 int
|
||
|
if state, ok := s.(format.State); ok {
|
||
|
lang, _ = language.CompactIndex(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 int, 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 int, c Unit) string { return c.String() }
|
||
|
func lookupSymbol(x int, c Unit) string { return normalSymbol.lookup(x, c) }
|
||
|
func lookupNarrow(x int, 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 int, 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 = int(internal.Parent[lang])
|
||
|
}
|
||
|
return c.String()
|
||
|
}
|