-
Notifications
You must be signed in to change notification settings - Fork 21
/
meta.ex
338 lines (272 loc) · 9.65 KB
/
meta.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
defmodule Cldr.Number.Format.Meta do
@moduledoc """
Describes the metadata that drives
number formatting and provides functions to
update the struct.
## Format definition
The `:format` is a keyword list that with two
elements:
* `:positive` which is a keyword list for
formatting a number >= zero
* `:negative` which is a keyword list for
formatting negative number
There are two formats because we can format in
an accounting style (that is, numbers surrounded
by `()`) or any other arbitrary style. Typically
the format for a negative number is the same as
that for a positive number with a prepended
minus sign.
## Localisation of number formatting
Number formatting is always localised to either
the currency processes locale or a locale
provided as an option to `Cldr.Number.to_string/3`.
The metadata is independent of the localisation
process. Signs (`+`/`-`), grouping (`,`), decimal markers
(`.`) and exponent signs are all localised when
the number is formatted.
## Formatting directives
The formats - positive and negative - are defined
in the metadata struct, as a keyword list of keywords
and values.
The simplest formatting list might be:
```
[format: _]`
```
The `:format` keyword indicates
that this is where the formatting number will be
substituted into the format pattern.
Another example would be for formatting a negative
number:
```
[minus: _, format: _]
```
which will format with a localised minus sign
followed by the formatted number. Note that the
keyword value for `:minus` and `:format` are
ignored.
## List of formatting keywords
The following is the full list of formatting
keywords which can be used to format a
number. A `_` in the keyword format is
used to denote `:dont_care`.
* `[format: _]` inserts the formatted number
exclusive of any sign
* `[minus: _]` inserts a localised minus
sign
* `[plus: _]` inserts a localised plus sign
* `[percent: _]` inserts a localised percent sign
* `[permille: _]` inserts a localised permille sign
* `[literal: "string"]` inserts `string` into the
format without any processing
* `[currency: 1..4]` inserts a localised currency
symbol of the given `type`. A `:currency` must be
provided as an option to `Cldr.Number.Formatter.Decimal.to_string/3`.
* `[pad: "char"]` inserts the correct number of `char`s
to pad the number format to the width specified by
`:padding_length` in the `%Meta{}` struct. The `:pad`
can be anywhere in the format list but it is most
typically inserted before or after the `:format`
keyword. The assumption is that the `char` is a single
binary character but this is not checked.
## Currency symbol formatting
Currency are localised and have four ways of being
presented. The different types are defined in the
`[currency: type]` keyword where `type` is an integer
in the range `1..4` These types will insert
into the final format:
1. The standard currency symbol like `$`,`¥` or `€`
2. The ISO currency code (like `USD` and `JPY`)
3. The localised and pluralised currency display name
like "Australian dollar" or "Australian dollars"
4. The narrow currency symbol if defined for a locale
"""
defstruct integer_digits: %{max: 0, min: 1},
fractional_digits: %{max: 0, min: 0},
significant_digits: %{max: 0, min: 0},
exponent_digits: 0,
exponent_sign: false,
scientific_rounding: 0,
grouping: %{
fraction: %{first: 0, rest: 0},
integer: %{first: 0, rest: 0}
},
round_nearest: 0,
padding_length: 0,
padding_char: " ",
multiplier: 1,
currency: nil,
format: [
positive: [format: "#"],
negative: [minus: '-', format: :same_as_positive]
],
number: 0
@typedoc "Metadata type that drives how to format a number"
@type t :: %__MODULE__{}
@doc """
Returns a new number formatting metadata
struct.
"""
@spec new :: t()
def new do
%__MODULE__{}
end
@doc """
Set the minimum, and optionally maximum, integer digits to
format.
"""
@spec put_integer_digits(t(), non_neg_integer, non_neg_integer) :: t()
def put_integer_digits(%__MODULE__{} = meta, min, max \\ 0)
when is_integer(min) and is_integer(max) do
meta
|> Map.put(:integer_digits, %{min: min, max: max})
end
@doc """
Set the minimum, and optionally maximum, fractional digits to
format.
"""
@spec put_fraction_digits(t(), non_neg_integer, non_neg_integer) :: t()
def put_fraction_digits(%__MODULE__{} = meta, min, max \\ 0)
when is_integer(min) and is_integer(max) do
meta
|> Map.put(:fractional_digits, %{min: min, max: max})
end
@doc """
Set the minimum, and optionally maximum, significant digits to
format.
"""
@spec put_significant_digits(t(), non_neg_integer, non_neg_integer) :: t()
def put_significant_digits(%__MODULE__{} = meta, min, max \\ 0)
when is_integer(min) and is_integer(max) do
meta
|> Map.put(:significant_digits, %{min: min, max: max})
end
@doc """
Set the number of exponent digits to
format.
"""
@spec put_exponent_digits(t(), non_neg_integer) :: t()
def put_exponent_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
meta
|> Map.put(:exponent_digits, digits)
end
@doc """
Set whether to add the sign of the exponent to
the format.
"""
@spec put_exponent_sign(t(), boolean) :: t()
def put_exponent_sign(%__MODULE__{} = meta, flag) when is_boolean(flag) do
meta
|> Map.put(:exponent_sign, flag)
end
@doc """
Set the increment to which the number should
be rounded.
"""
@spec put_round_nearest_digits(t(), non_neg_integer) :: t()
def put_round_nearest_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
meta
|> Map.put(:round_nearest, digits)
end
@doc """
Set the number of scientific digits to which the number should
be rounded.
"""
@spec put_scientific_rounding_digits(t(), non_neg_integer) :: t()
def put_scientific_rounding_digits(%__MODULE__{} = meta, digits) when is_integer(digits) do
meta
|> Map.put(:scientific_rounding, digits)
end
@spec put_padding_length(t(), non_neg_integer) :: t()
def put_padding_length(%__MODULE__{} = meta, digits) when is_integer(digits) do
meta
|> Map.put(:padding_length, digits)
end
@doc """
Set the padding character to be used when
padding the formatted number.
"""
@spec put_padding_char(t(), String.t()) :: t()
def put_padding_char(%__MODULE__{} = meta, char) when is_binary(char) do
meta
|> Map.put(:padding_char, char)
end
@doc """
Sets the multiplier for the number.
Before formatting, the number is multiplied
by this amount. This is useful when
formatting as a percent or permille.
"""
@spec put_multiplier(t(), non_neg_integer) :: t()
def put_multiplier(%__MODULE__{} = meta, multiplier) when is_integer(multiplier) do
meta
|> Map.put(:multiplier, multiplier)
end
@doc """
Sets the number of digits in a group or
optionally the first group and subsequent
groups for the integer part of a number.
The grouping character is defined by the locale
defined for the current process or supplied
as the `:locale` option to `to_string/3`.
"""
@spec put_integer_grouping(t(), non_neg_integer, non_neg_integer) :: t()
def put_integer_grouping(%__MODULE__{} = meta, first, rest)
when is_integer(first) and is_integer(rest) do
grouping =
meta
|> Map.get(:grouping)
|> Map.put(:integer, %{first: first, rest: rest})
Map.put(meta, :grouping, grouping)
end
@spec put_integer_grouping(t(), non_neg_integer) :: t()
def put_integer_grouping(%__MODULE__{} = meta, all) when is_integer(all) do
grouping =
meta
|> Map.get(:grouping)
|> Map.put(:integer, %{first: all, rest: all})
Map.put(meta, :grouping, grouping)
end
@doc """
Sets the number of digits in a group or
optionally the first group and subsequent
groups for the fractional part of a number.
The grouping character is defined by the locale
defined for the current process or supplied
as the `:locale` option to `to_string/3`.
"""
@spec put_fraction_grouping(t(), non_neg_integer, non_neg_integer) :: t()
def put_fraction_grouping(%__MODULE__{} = meta, first, rest)
when is_integer(first) and is_integer(rest) do
grouping =
meta
|> Map.get(:grouping)
|> Map.put(:fraction, %{first: first, rest: rest})
Map.put(meta, :grouping, grouping)
end
@spec put_fraction_grouping(t(), non_neg_integer) :: t()
def put_fraction_grouping(%__MODULE__{} = meta, all) when is_integer(all) do
grouping =
meta
|> Map.get(:grouping)
|> Map.put(:fraction, %{first: all, rest: all})
Map.put(meta, :grouping, grouping)
end
@doc """
Set the metadata format for the positive
and negative number format.
Note that this is the parsed format as a simple keyword
list, not a binary representation.
Its up to each formatting engine to transform its input
into this form. See `Cldr.Number.Format.Meta` module
documentation for the available keywords.
"""
@spec put_format(t(), Keyword.t(), Keyword.t()) :: t()
def put_format(%__MODULE__{} = meta, positive_format, negative_format) do
meta
|> Map.put(:format, positive: positive_format, negative: negative_format)
end
@spec put_format(t(), Keyword.t()) :: t()
def put_format(%__MODULE__{} = meta, positive_format) do
put_format(meta, positive_format, minus: '-', format: :same_as_positive)
end
end