This repository has been archived by the owner on Jul 20, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
parse.go
190 lines (171 loc) · 5.67 KB
/
parse.go
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
package client
import (
"errors"
"fmt"
"math/big"
"strings"
"github.com/threefoldtech/rivine/types"
)
var errUnableToParseSize = errors.New("unable to parse size")
// PeriodUnits turns a period in terms of blocks to a number of weeks.
func PeriodUnits(blocks types.BlockHeight) string {
return fmt.Sprint(blocks / 1008) // 1008 blocks per week
}
// ParsePeriod converts a number of weeks to a number of blocks.
func ParsePeriod(period string) (string, error) {
var weeks float64
_, err := fmt.Sscan(period, &weeks)
if err != nil {
return "", errUnableToParseSize
}
blocks := int(weeks * 1008) // 1008 blocks per week
return fmt.Sprint(blocks), nil
}
// YesNo returns "Yes" if b is true, and "No" if b is false.
func YesNo(b bool) string {
if b {
return "Yes"
}
return "No"
}
// CurrencyConvertor is used to parse a currency in it's default unit,
// and turn it into its in-memory smallest unit. Simiarly it allow you to
// turn the in-memory smallest unit into a string version of the default init.
type CurrencyConvertor struct {
scalar *big.Int
precision uint // amount of zeros after the comma
coinUnit string
}
// NewCurrencyConvertor creates a new currency convertor
// using the given currency units.
//
// See CurrencyConvertor for more information.
func NewCurrencyConvertor(units types.CurrencyUnits, coinUnit string) CurrencyConvertor {
oneCoinStr := units.OneCoin.String()
precision := uint(len(oneCoinStr) - 1)
return CurrencyConvertor{
scalar: units.OneCoin.Big(),
precision: precision,
coinUnit: coinUnit,
}
}
// ParseCoinString parses the given string assumed to be in the default unit,
// and parses it into an in-memory currency unit of the smallest unit.
// It will fail if the given string is invalid or too precise.
func (cc CurrencyConvertor) ParseCoinString(str string) (types.Currency, error) {
initialParts := strings.SplitN(str, ".", 2)
// remove formatting for whole number part
var err error
initialParts[0], err = stripColonFromCoinString(initialParts[0])
if err != nil {
return types.Currency{}, err
}
if len(initialParts) == 1 {
// a round number, simply multiply and go
i, ok := big.NewInt(0).SetString(initialParts[0], 10)
if !ok {
return types.Currency{}, errors.New("invalid round currency coin amount")
}
if i.Cmp(big.NewInt(0)) == -1 {
return types.Currency{}, errors.New("invalid round currency coin amount: cannot be negative")
}
return types.NewCurrency(i.Mul(i, cc.scalar)), nil
}
whole := initialParts[0]
dac := initialParts[1]
sn := uint(cc.precision)
if l := uint(len(dac)); l < sn {
sn = l
}
whole += initialParts[1][:sn]
dac = dac[sn:]
for i := range dac {
if dac[i] != '0' {
return types.Currency{}, errors.New("invalid or too precise currency coin amount")
}
}
i, ok := big.NewInt(0).SetString(whole, 10)
if !ok {
return types.Currency{}, errors.New("invalid currency coin amount")
}
if i.Cmp(big.NewInt(0)) == -1 {
return types.Currency{}, errors.New("invalid round currency coin amount: cannot be negative")
}
i.Mul(i, big.NewInt(0).Exp(
big.NewInt(10), big.NewInt(int64(cc.precision-sn)), nil))
c := types.NewCurrency(i)
if c.Cmp64(0) == -1 {
return types.Currency{}, errors.New("invalid round currency coin amount: cannot be negative")
}
return c, nil
}
// stripColonFromCoinString removes comma formatting from a coin string representing a
// currency. The formatting is expected to be groups of thousands (i.e. 3 digits).
// Invalid formatting returns an error.
func stripColonFromCoinString(str string) (string, error) {
groups := strings.Split(str, ",")
if len(groups) == 1 {
// no formatting
return str, nil
}
// all groups must have a lenght of 3, except for the first one which can
// have a length of 1-3
if len(groups[0]) < 1 || len(groups[0]) > 3 {
return "", errors.New("Inconsistent use of , formatting")
}
for i := 1; i < len(groups); i++ {
if len(groups[i]) != 3 {
return "", errors.New("Inconsistent use of , formatting")
}
}
return strings.Join(groups, ""), nil
}
// ToCoinString turns the in-memory currency unit,
// into a string version of the default currency unit.
// This can never fail, as the only thing it can do is make a number smaller.
func (cc CurrencyConvertor) ToCoinString(c types.Currency) string {
if c.Equals64(0) {
return "0"
}
str := c.String()
if cc.precision == 0 {
return str
}
l := uint(len(str))
if l > cc.precision {
idx := l - cc.precision
str = strings.TrimRight(str[:idx]+"."+str[idx:], "0")
str = strings.TrimRight(str, ".")
if len(str) == 0 {
return "0"
}
// make more readable
for i := uint(3); idx > i; i += 3 {
str = str[:idx-i] + "," + str[idx-i:]
}
return str
}
str = "0." + strings.Repeat("0", int(cc.precision-l)) + str
str = strings.TrimRight(str, "0")
str = strings.TrimRight(str, ".")
return str
}
// ToCoinStringWithUnit turns the in-memory currency unit,
// into a string version of the default currency unit.
// This can never fail, as the only thing it can do is make a number smaller.
// It also adds the unit of the coin behind the coin.
func (cc CurrencyConvertor) ToCoinStringWithUnit(c types.Currency) string {
return cc.ToCoinString(c) + " " + cc.coinUnit
}
// CoinArgDescription is used to print a helpful arg description message,
// for this convertor.
func (cc CurrencyConvertor) CoinArgDescription(argName string) string {
if cc.precision < 1 {
return fmt.Sprintf(
"argument %s (expressed in default unit %s) has to be a positive natural number (no digits after comma are allowed)",
argName, cc.coinUnit)
}
return fmt.Sprintf(
"argument %s (expressed in default unit %s) can (only) have up to %d digits after comma and has to be positive",
argName, cc.coinUnit, cc.precision)
}