1// Copyright 2017 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 number
6
7import (
8	"fmt"
9
10	"golang.org/x/text/internal/number"
11	"golang.org/x/text/language"
12)
13
14// An Option configures a Formatter.
15type Option option
16
17type option func(tag language.Tag, f *number.Formatter)
18
19// TODO: SpellOut requires support of the ICU RBNF format.
20// func SpellOut() Option
21
22// NoSeparator causes a number to be displayed without grouping separators.
23func NoSeparator() Option {
24	return func(t language.Tag, f *number.Formatter) {
25		f.GroupingSize = [2]uint8{}
26	}
27}
28
29// MaxIntegerDigits limits the number of integer digits, eliminating the
30// most significant digits.
31func MaxIntegerDigits(max int) Option {
32	return func(t language.Tag, f *number.Formatter) {
33		if max >= 1<<8 {
34			max = (1 << 8) - 1
35		}
36		f.MaxIntegerDigits = uint8(max)
37	}
38}
39
40// MinIntegerDigits specifies the minimum number of integer digits, adding
41// leading zeros when needed.
42func MinIntegerDigits(min int) Option {
43	return func(t language.Tag, f *number.Formatter) {
44		if min >= 1<<8 {
45			min = (1 << 8) - 1
46		}
47		f.MinIntegerDigits = uint8(min)
48	}
49}
50
51// MaxFractionDigits specifies the maximum number of fractional digits.
52func MaxFractionDigits(max int) Option {
53	return func(t language.Tag, f *number.Formatter) {
54		if max >= 1<<15 {
55			max = (1 << 15) - 1
56		}
57		f.MaxFractionDigits = int16(max)
58	}
59}
60
61// MinFractionDigits specifies the minimum number of fractional digits.
62func MinFractionDigits(min int) Option {
63	return func(t language.Tag, f *number.Formatter) {
64		if min >= 1<<8 {
65			min = (1 << 8) - 1
66		}
67		f.MinFractionDigits = uint8(min)
68	}
69}
70
71// Precision sets the maximum number of significant digits. A negative value
72// means exact.
73func Precision(prec int) Option {
74	return func(t language.Tag, f *number.Formatter) {
75		f.SetPrecision(prec)
76	}
77}
78
79// Scale simultaneously sets MinFractionDigits and MaxFractionDigits to the
80// given value.
81func Scale(decimals int) Option {
82	return func(t language.Tag, f *number.Formatter) {
83		f.SetScale(decimals)
84	}
85}
86
87// IncrementString sets the incremental value to which numbers should be
88// rounded. For instance: Increment("0.05") will cause 1.44 to round to 1.45.
89// IncrementString also sets scale to the scale of the increment.
90func IncrementString(decimal string) Option {
91	increment := 0
92	scale := 0
93	d := decimal
94	p := 0
95	for ; p < len(d) && '0' <= d[p] && d[p] <= '9'; p++ {
96		increment *= 10
97		increment += int(d[p]) - '0'
98	}
99	if p < len(d) && d[p] == '.' {
100		for p++; p < len(d) && '0' <= d[p] && d[p] <= '9'; p++ {
101			increment *= 10
102			increment += int(d[p]) - '0'
103			scale++
104		}
105	}
106	if p < len(d) {
107		increment = 0
108		scale = 0
109	}
110	return func(t language.Tag, f *number.Formatter) {
111		f.Increment = uint32(increment)
112		f.IncrementScale = uint8(scale)
113		f.SetScale(scale)
114	}
115}
116
117func noop(language.Tag, *number.Formatter) {}
118
119// PatternOverrides allows users to specify alternative patterns for specific
120// languages. The Pattern will be overridden for all languages in a subgroup as
121// well. The function will panic for invalid input. It is best to create this
122// option at startup time.
123// PatternOverrides must be the first Option passed to a formatter.
124func PatternOverrides(patterns map[string]string) Option {
125	// TODO: make it so that it does not have to be the first option.
126	// TODO: use -x-nochild to indicate it does not override child tags.
127	m := map[language.Tag]*number.Pattern{}
128	for k, v := range patterns {
129		tag := language.MustParse(k)
130		p, err := number.ParsePattern(v)
131		if err != nil {
132			panic(fmt.Errorf("number: PatternOverrides: %v", err))
133		}
134		m[tag] = p
135	}
136	return func(t language.Tag, f *number.Formatter) {
137		// TODO: Use language grouping relation instead of parent relation.
138		// TODO: Should parent implement the grouping relation?
139		for lang := t; ; lang = t.Parent() {
140			if p, ok := m[lang]; ok {
141				f.Pattern = *p
142				break
143			}
144			if lang == language.Und {
145				break
146			}
147		}
148	}
149}
150
151// FormatWidth sets the total format width.
152func FormatWidth(n int) Option {
153	if n <= 0 {
154		return noop
155	}
156	return func(t language.Tag, f *number.Formatter) {
157		f.FormatWidth = uint16(n)
158		if f.PadRune == 0 {
159			f.PadRune = ' '
160		}
161	}
162}
163
164// Pad sets the rune to be used for filling up to the format width.
165func Pad(r rune) Option {
166	return func(t language.Tag, f *number.Formatter) {
167		f.PadRune = r
168	}
169}
170
171// TODO:
172// - FormatPosition (using type aliasing?)
173// - Multiplier: find a better way to represent and figure out what to do
174//   with clashes with percent/permille.
175// - NumberingSystem(nu string): not accessable in number.Info now. Also, should
176//      this be keyed by language or generic?
177// - SymbolOverrides(symbols map[string]map[number.SymbolType]string) Option
178