-
Notifications
You must be signed in to change notification settings - Fork 36
/
write-all.js
141 lines (122 loc) · 4.14 KB
/
write-all.js
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
import assignDeep from 'assign-deep'
import { isValidNumber, parseNumber } from './num-util.js'
import writeCurrency from './write-currency/index.js'
import writeDecimal from './write-decimal/index.js'
import writeInt from './write-int.js'
/**
* Verificar se uma opção é válida.
*
* @method isValidOpt
* @param {string} val Valor da opção.
* @param {Array} vals Valores para checagem.
* @returns {boolean} Informação da validade da opção.
*/
export const isValidOpt = (val, vals) => {
return vals.includes(val)
}
/**
* Passar um número escrito por extenso para o modo negativo.
*
* @method toNegative
* @param {string} num Valor escrito por extenso.
* @param {string} [mode='formal'] Opção sobre o modo a ser escrito.
* @returns {string} Valor como negativo.
*/
export const toNegative = (num, mode = 'formal') => {
return mode === 'informal'
? `menos ${num}`
: `${num} negativo`
}
/**
* Escrever números por extenso.
*
* @param {string|number|bigint} num Número para ser escrito por extenso.
* @param {object} opts Opções para configurar modo de escrita.
* @returns {string} Número escrito por extenso.
*/
export default (num, opts) => {
if (typeof num === 'bigint') {
num = num.toString()
}
if (typeof num !== 'string' && typeof num !== 'number') {
throw new TypeError('Must be a string, number or bigint')
}
let defaultOpts = {
mode: 'number',
locale: 'br',
negative: 'formal',
scale: 'short',
currency: {
type: 'BRL'
},
number: {
gender: 'm',
decimal: 'formal',
decimalSeparator: 'comma'
}
}
// Usando o pacote 'assign-deep' no lugar de Object.assign(),
// pois esse último substitui completamente todas as propriedades
// de um objeto que está dentro de outro objeto.
opts = assignDeep(defaultOpts, opts)
if (
!isValidOpt(opts.mode, [ 'number', 'currency' ]) ||
!isValidOpt(opts.locale, [ 'pt', 'br' ]) ||
!isValidOpt(opts.negative, [ 'formal', 'informal' ]) ||
!isValidOpt(opts.scale, [ 'short', 'long' ]) ||
!isValidOpt(opts.currency.type, [ 'BRL', 'EUR', 'CVE' ]) ||
!isValidOpt(opts.number.gender, [ 'm', 'f' ]) ||
!isValidOpt(opts.number.decimal, [ 'formal', 'informal' ]) ||
!isValidOpt(opts.number.decimalSeparator, [ 'comma', 'dot' ])
) {
throw new Error('Invalid option')
}
const decimalSeparatorIsDot = opts.number.decimalSeparator === 'dot' || typeof num === 'number'
if (!isValidNumber(num, decimalSeparatorIsDot)) {
throw new Error('Invalid number')
}
const { isNegative, integer, decimal } = parseNumber(num, decimalSeparatorIsDot)
if (opts.mode === 'currency') {
const iso = opts.currency.type
const locale = opts.locale
const decimalCents = decimal.slice(0, 2)
const numText = writeCurrency(iso, locale, integer, decimalCents, opts.scale)
return isNegative
? toNegative(numText, opts.negative)
: numText
}
if (opts.mode === 'number') {
const intNameSingular = opts.number.gender === 'f' ? 'inteira' : 'inteiro'
const intName = parseInt(integer) === 1 ? intNameSingular : `${intNameSingular}s`
const intText = writeInt(integer, opts.locale, opts.number.gender, opts.scale)
const decText = writeDecimal(decimal, opts.locale, opts.number.decimal)
if (integer === '0' && decimal === '0') {
return intText
}
// Se tem a parte inteira e não tem a parte decimal
if (integer !== '0' && decimal === '0') {
return isNegative
? toNegative(intText, opts.negative)
: intText
}
// Se não tem a parte inteira e tem a parte decimal
if (integer === '0' && decimal !== '0') {
let number = opts.number.decimal === 'informal'
? `zero ${decText}`
: decText
return isNegative
? toNegative(number, opts.negative)
: number
}
// Se tem a parte inteira e a parte decimal
if (integer !== '0' && decimal !== '0') {
if (opts.number.decimal === 'informal') {
return `${intText} ${decText}`
}
const numText = `${intText} ${intName} e ${decText}`
return isNegative
? toNegative(numText, opts.negative)
: numText
}
}
}