You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

123 lines
3.0 KiB

  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. package number
  5. import (
  6. "fmt"
  7. "strings"
  8. "golang.org/x/text/feature/plural"
  9. "golang.org/x/text/internal/format"
  10. "golang.org/x/text/internal/number"
  11. "golang.org/x/text/language"
  12. )
  13. // A FormatFunc formates a number.
  14. type FormatFunc func(x interface{}, opts ...Option) Formatter
  15. // NewFormat creates a FormatFunc based on another FormatFunc and new options.
  16. // Use NewFormat to cash the creation of formatters.
  17. func NewFormat(format FormatFunc, opts ...Option) FormatFunc {
  18. o := *format(nil).options
  19. n := len(o.options)
  20. o.options = append(o.options[:n:n], opts...)
  21. return func(x interface{}, opts ...Option) Formatter {
  22. return newFormatter(&o, opts, x)
  23. }
  24. }
  25. type options struct {
  26. verbs string
  27. initFunc initFunc
  28. options []Option
  29. pluralFunc func(t language.Tag, scale int) (f plural.Form, n int)
  30. }
  31. type optionFlag uint16
  32. const (
  33. hasScale optionFlag = 1 << iota
  34. hasPrecision
  35. noSeparator
  36. exact
  37. )
  38. type initFunc func(f *number.Formatter, t language.Tag)
  39. func newFormatter(o *options, opts []Option, value interface{}) Formatter {
  40. if len(opts) > 0 {
  41. n := *o
  42. n.options = opts
  43. o = &n
  44. }
  45. return Formatter{o, value}
  46. }
  47. func newOptions(verbs string, f initFunc) *options {
  48. return &options{verbs: verbs, initFunc: f}
  49. }
  50. type Formatter struct {
  51. *options
  52. value interface{}
  53. }
  54. // Format implements format.Formatter. It is for internal use only for now.
  55. func (f Formatter) Format(state format.State, verb rune) {
  56. // TODO: consider implementing fmt.Formatter instead and using the following
  57. // piece of code. This allows numbers to be rendered mostly as expected
  58. // when using fmt. But it may get weird with the spellout options and we
  59. // may need more of format.State over time.
  60. // lang := language.Und
  61. // if s, ok := state.(format.State); ok {
  62. // lang = s.Language()
  63. // }
  64. lang := state.Language()
  65. if !strings.Contains(f.verbs, string(verb)) {
  66. fmt.Fprintf(state, "%%!%s(%T=%v)", string(verb), f.value, f.value)
  67. return
  68. }
  69. var p number.Formatter
  70. f.initFunc(&p, lang)
  71. for _, o := range f.options.options {
  72. o(lang, &p)
  73. }
  74. if w, ok := state.Width(); ok {
  75. p.FormatWidth = uint16(w)
  76. }
  77. if prec, ok := state.Precision(); ok {
  78. switch verb {
  79. case 'd':
  80. p.SetScale(0)
  81. case 'f':
  82. p.SetScale(prec)
  83. case 'e':
  84. p.SetPrecision(prec + 1)
  85. case 'g':
  86. p.SetPrecision(prec)
  87. }
  88. }
  89. var d number.Decimal
  90. d.Convert(p.RoundingContext, f.value)
  91. state.Write(p.Format(nil, &d))
  92. }
  93. // Digits returns information about which logical digits will be presented to
  94. // the user. This information is relevant, for instance, to determine plural
  95. // forms.
  96. func (f Formatter) Digits(buf []byte, tag language.Tag, scale int) number.Digits {
  97. var p number.Formatter
  98. f.initFunc(&p, tag)
  99. if scale >= 0 {
  100. // TODO: this only works well for decimal numbers, which is generally
  101. // fine.
  102. p.SetScale(scale)
  103. }
  104. var d number.Decimal
  105. d.Convert(p.RoundingContext, f.value)
  106. return number.FormatDigits(&d, p.RoundingContext)
  107. }