kdl 0.1.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+ require 'kdl/types/country/iso3166_countries'
2
+ require 'kdl/types/country/iso3166_subdivisions'
3
+
4
+ module KDL
5
+ module Types
6
+ class Country < Value
7
+ attr_reader :name, :alpha2, :alpha3, :numeric_code
8
+
9
+ def initialize(value, format: nil, type: 'country-3')
10
+ super
11
+ @name = value.fetch(:name, '')
12
+ @alpha3 = value.fetch(:alpha3, nil)
13
+ @alpha2 = value.fetch(:alpha2, nil)
14
+ @numeric_code = value.fetch(:numeric_code, nil)
15
+ end
16
+
17
+ def self.call(value, type = 'country-3')
18
+ return nil unless value.is_a? ::KDL::Value::String
19
+
20
+ country = COUNTRIES3[value.value.upcase]
21
+ raise ArgumentError, 'invalid country-3' if country.nil?
22
+
23
+ new(country, type: type)
24
+ end
25
+ end
26
+ Country3 = Country
27
+ MAPPING['country-3'] = Country3
28
+
29
+ class Country2 < Country
30
+ def initialize(value, format: nil, type: 'country-2')
31
+ super
32
+ end
33
+
34
+ def self.call(value, type = 'country-2')
35
+ return nil unless value.is_a? ::KDL::Value::String
36
+
37
+ country = COUNTRIES2[value.value.upcase]
38
+ raise ArgumentError, 'invalid country-3' if country.nil?
39
+
40
+ new(country, type: type)
41
+ end
42
+ end
43
+ MAPPING['country-2'] = Country2
44
+
45
+ class CountrySubdivision < Value
46
+ attr_reader :country, :name
47
+
48
+ def initialize(value, type: 'country-subdivision', country:, name:, **kwargs)
49
+ super(value, type: type, **kwargs)
50
+ @country = country
51
+ @name = name
52
+ end
53
+
54
+ def self.call(value, type = 'country-subdivision')
55
+ return nil unless value.is_a? ::KDL::Value::String
56
+
57
+ country2 = value.value.split('-').first
58
+ country = Country::COUNTRIES2[country2.upcase]
59
+ raise ArgumentError, 'invalid country' unless country
60
+
61
+ subdivision = COUNTRY_SUBDIVISIONS.dig(country2.upcase, value.value)
62
+ raise ArgumentError, 'invalid country subdivision' unless subdivision
63
+
64
+ new(value.value, type: type, name: subdivision, country: country)
65
+ end
66
+ end
67
+ MAPPING['country-subdivision'] = CountrySubdivision
68
+ end
69
+ end
@@ -0,0 +1,189 @@
1
+ module KDL
2
+ module Types
3
+ class Currency < Value
4
+ # From https://en.wikipedia.org/wiki/ISO_4217#Active_codes
5
+ CURRENCIES = {
6
+ 'AED' => { numeric_code: 784, minor_unit: 2, name: 'United Arab Emirates dirham' }.freeze,
7
+ 'AFN' => { numeric_code: 971, minor_unit: 2, name: 'Afghan afghani' }.freeze,
8
+ 'ALL' => { numeric_code: 8, minor_unit: 2, name: 'Albanian lek' }.freeze,
9
+ 'AMD' => { numeric_code: 51, minor_unit: 2, name: 'Armenian dram' }.freeze,
10
+ 'ANG' => { numeric_code: 532, minor_unit: 2, name: 'Netherlands Antillean guilder' }.freeze,
11
+ 'AOA' => { numeric_code: 973, minor_unit: 2, name: 'Angolan kwanza' }.freeze,
12
+ 'ARS' => { numeric_code: 32, minor_unit: 2, name: 'Argentine peso' }.freeze,
13
+ 'AUD' => { numeric_code: 36, minor_unit: 2, name: 'Australian dollar' }.freeze,
14
+ 'AWG' => { numeric_code: 533, minor_unit: 2, name: 'Aruban florin' }.freeze,
15
+ 'AZN' => { numeric_code: 944, minor_unit: 2, name: 'Azerbaijani manat' }.freeze,
16
+ 'BAM' => { numeric_code: 977, minor_unit: 2, name: 'Bosnia and Herzegovina convertible mark' }.freeze,
17
+ 'BBD' => { numeric_code: 52, minor_unit: 2, name: 'Barbados dollar' }.freeze,
18
+ 'BDT' => { numeric_code: 50, minor_unit: 2, name: 'Bangladeshi taka' }.freeze,
19
+ 'BGN' => { numeric_code: 975, minor_unit: 2, name: 'Bulgarian lev' }.freeze,
20
+ 'BHD' => { numeric_code: 48, minor_unit: 3, name: 'Bahraini dinar' }.freeze,
21
+ 'BIF' => { numeric_code: 108, minor_unit: 0, name: 'Burundian franc' }.freeze,
22
+ 'BMD' => { numeric_code: 60, minor_unit: 2, name: 'Bermudian dollar' }.freeze,
23
+ 'BND' => { numeric_code: 96, minor_unit: 2, name: 'Brunei dollar' }.freeze,
24
+ 'BOB' => { numeric_code: 68, minor_unit: 2, name: 'Boliviano' }.freeze,
25
+ 'BOV' => { numeric_code: 984, minor_unit: 2, name: 'Bolivian Mvdol (funds code)' }.freeze,
26
+ 'BRL' => { numeric_code: 986, minor_unit: 2, name: 'Brazilian real' }.freeze,
27
+ 'BSD' => { numeric_code: 44, minor_unit: 2, name: 'Bahamian dollar' }.freeze,
28
+ 'BTN' => { numeric_code: 64, minor_unit: 2, name: 'Bhutanese ngultrum' }.freeze,
29
+ 'BWP' => { numeric_code: 72, minor_unit: 2, name: 'Botswana pula' }.freeze,
30
+ 'BYN' => { numeric_code: 933, minor_unit: 2, name: 'Belarusian ruble' }.freeze,
31
+ 'BZD' => { numeric_code: 84, minor_unit: 2, name: 'Belize dollar' }.freeze,
32
+ 'CAD' => { numeric_code: 124, minor_unit: 2, name: 'Canadian dollar' }.freeze,
33
+ 'CDF' => { numeric_code: 976, minor_unit: 2, name: 'Congolese franc' }.freeze,
34
+ 'CHE' => { numeric_code: 947, minor_unit: 2, name: 'WIR euro (complementary currency)' }.freeze,
35
+ 'CHF' => { numeric_code: 756, minor_unit: 2, name: 'Swiss franc' }.freeze,
36
+ 'CHW' => { numeric_code: 948, minor_unit: 2, name: 'WIR franc (complementary currency)' }.freeze,
37
+ 'CLF' => { numeric_code: 990, minor_unit: 4, name: 'Unidad de Fomento (funds code)' }.freeze,
38
+ 'CLP' => { numeric_code: 152, minor_unit: 0, name: 'Chilean peso' }.freeze,
39
+ 'CNY' => { numeric_code: 156, minor_unit: 2, name: 'Chinese yuan[8]' }.freeze,
40
+ 'COP' => { numeric_code: 170, minor_unit: 2, name: 'Colombian peso' }.freeze,
41
+ 'COU' => { numeric_code: 970, minor_unit: 2, name: 'Unidad de Valor Real (UVR) (funds code)' }.freeze,
42
+ 'CRC' => { numeric_code: 188, minor_unit: 2, name: 'Costa Rican colon' }.freeze,
43
+ 'CUC' => { numeric_code: 931, minor_unit: 2, name: 'Cuban convertible peso' }.freeze,
44
+ 'CUP' => { numeric_code: 192, minor_unit: 2, name: 'Cuban peso' }.freeze,
45
+ 'CVE' => { numeric_code: 132, minor_unit: 2, name: 'Cape Verdean escudo' }.freeze,
46
+ 'CZK' => { numeric_code: 203, minor_unit: 2, name: 'Czech koruna' }.freeze,
47
+ 'DJF' => { numeric_code: 262, minor_unit: 0, name: 'Djiboutian franc' }.freeze,
48
+ 'DKK' => { numeric_code: 208, minor_unit: 2, name: 'Danish krone' }.freeze,
49
+ 'DOP' => { numeric_code: 214, minor_unit: 2, name: 'Dominican peso' }.freeze,
50
+ 'DZD' => { numeric_code: 12, minor_unit: 2, name: 'Algerian dinar' }.freeze,
51
+ 'EGP' => { numeric_code: 818, minor_unit: 2, name: 'Egyptian pound' }.freeze,
52
+ 'ERN' => { numeric_code: 232, minor_unit: 2, name: 'Eritrean nakfa' }.freeze,
53
+ 'ETB' => { numeric_code: 230, minor_unit: 2, name: 'Ethiopian birr' }.freeze,
54
+ 'EUR' => { numeric_code: 978, minor_unit: 2, name: 'Euro' }.freeze,
55
+ 'FJD' => { numeric_code: 242, minor_unit: 2, name: 'Fiji dollar' }.freeze,
56
+ 'FKP' => { numeric_code: 238, minor_unit: 2, name: 'Falkland Islands pound' }.freeze,
57
+ 'GBP' => { numeric_code: 826, minor_unit: 2, name: 'Pound sterling' }.freeze,
58
+ 'GEL' => { numeric_code: 981, minor_unit: 2, name: 'Georgian lari' }.freeze,
59
+ 'GHS' => { numeric_code: 936, minor_unit: 2, name: 'Ghanaian cedi' }.freeze,
60
+ 'GIP' => { numeric_code: 292, minor_unit: 2, name: 'Gibraltar pound' }.freeze,
61
+ 'GMD' => { numeric_code: 270, minor_unit: 2, name: 'Gambian dalasi' }.freeze,
62
+ 'GNF' => { numeric_code: 324, minor_unit: 0, name: 'Guinean franc' }.freeze,
63
+ 'GTQ' => { numeric_code: 320, minor_unit: 2, name: 'Guatemalan quetzal' }.freeze,
64
+ 'GYD' => { numeric_code: 328, minor_unit: 2, name: 'Guyanese dollar' }.freeze,
65
+ 'HKD' => { numeric_code: 344, minor_unit: 2, name: 'Hong Kong dollar' }.freeze,
66
+ 'HNL' => { numeric_code: 340, minor_unit: 2, name: 'Honduran lempira' }.freeze,
67
+ 'HRK' => { numeric_code: 191, minor_unit: 2, name: 'Croatian kuna' }.freeze,
68
+ 'HTG' => { numeric_code: 332, minor_unit: 2, name: 'Haitian gourde' }.freeze,
69
+ 'HUF' => { numeric_code: 348, minor_unit: 2, name: 'Hungarian forint' }.freeze,
70
+ 'IDR' => { numeric_code: 360, minor_unit: 2, name: 'Indonesian rupiah' }.freeze,
71
+ 'ILS' => { numeric_code: 376, minor_unit: 2, name: 'Israeli new shekel' }.freeze,
72
+ 'INR' => { numeric_code: 356, minor_unit: 2, name: 'Indian rupee' }.freeze,
73
+ 'IQD' => { numeric_code: 368, minor_unit: 3, name: 'Iraqi dinar' }.freeze,
74
+ 'IRR' => { numeric_code: 364, minor_unit: 2, name: 'Iranian rial' }.freeze,
75
+ 'ISK' => { numeric_code: 352, minor_unit: 0, name: 'Icelandic króna (plural: krónur)' }.freeze,
76
+ 'JMD' => { numeric_code: 388, minor_unit: 2, name: 'Jamaican dollar' }.freeze,
77
+ 'JOD' => { numeric_code: 400, minor_unit: 3, name: 'Jordanian dinar' }.freeze,
78
+ 'JPY' => { numeric_code: 392, minor_unit: 0, name: 'Japanese yen' }.freeze,
79
+ 'KES' => { numeric_code: 404, minor_unit: 2, name: 'Kenyan shilling' }.freeze,
80
+ 'KGS' => { numeric_code: 417, minor_unit: 2, name: 'Kyrgyzstani som' }.freeze,
81
+ 'KHR' => { numeric_code: 116, minor_unit: 2, name: 'Cambodian riel' }.freeze,
82
+ 'KMF' => { numeric_code: 174, minor_unit: 0, name: 'Comoro franc' }.freeze,
83
+ 'KPW' => { numeric_code: 408, minor_unit: 2, name: 'North Korean won' }.freeze,
84
+ 'KRW' => { numeric_code: 410, minor_unit: 0, name: 'South Korean won' }.freeze,
85
+ 'KWD' => { numeric_code: 414, minor_unit: 3, name: 'Kuwaiti dinar' }.freeze,
86
+ 'KYD' => { numeric_code: 136, minor_unit: 2, name: 'Cayman Islands dollar' }.freeze,
87
+ 'KZT' => { numeric_code: 398, minor_unit: 2, name: 'Kazakhstani tenge' }.freeze,
88
+ 'LAK' => { numeric_code: 418, minor_unit: 2, name: 'Lao kip' }.freeze,
89
+ 'LBP' => { numeric_code: 422, minor_unit: 2, name: 'Lebanese pound' }.freeze,
90
+ 'LKR' => { numeric_code: 144, minor_unit: 2, name: 'Sri Lankan rupee' }.freeze,
91
+ 'LRD' => { numeric_code: 430, minor_unit: 2, name: 'Liberian dollar' }.freeze,
92
+ 'LSL' => { numeric_code: 426, minor_unit: 2, name: 'Lesotho loti' }.freeze,
93
+ 'LYD' => { numeric_code: 434, minor_unit: 3, name: 'Libyan dinar' }.freeze,
94
+ 'MAD' => { numeric_code: 504, minor_unit: 2, name: 'Moroccan dirham' }.freeze,
95
+ 'MDL' => { numeric_code: 498, minor_unit: 2, name: 'Moldovan leu' }.freeze,
96
+ 'MGA' => { numeric_code: 969, minor_unit: 2, name: 'Malagasy ariary' }.freeze,
97
+ 'MKD' => { numeric_code: 807, minor_unit: 2, name: 'Macedonian denar' }.freeze,
98
+ 'MMK' => { numeric_code: 104, minor_unit: 2, name: 'Myanmar kyat' }.freeze,
99
+ 'MNT' => { numeric_code: 496, minor_unit: 2, name: 'Mongolian tögrög' }.freeze,
100
+ 'MOP' => { numeric_code: 446, minor_unit: 2, name: 'Macanese pataca' }.freeze,
101
+ 'MRU' => { numeric_code: 929, minor_unit: 2, name: 'Mauritanian ouguiya' }.freeze,
102
+ 'MUR' => { numeric_code: 480, minor_unit: 2, name: 'Mauritian rupee' }.freeze,
103
+ 'MVR' => { numeric_code: 462, minor_unit: 2, name: 'Maldivian rufiyaa' }.freeze,
104
+ 'MWK' => { numeric_code: 454, minor_unit: 2, name: 'Malawian kwacha' }.freeze,
105
+ 'MXN' => { numeric_code: 484, minor_unit: 2, name: 'Mexican peso' }.freeze,
106
+ 'MXV' => { numeric_code: 979, minor_unit: 2, name: 'Mexican Unidad de Inversion (UDI) (funds code)' }.freeze,
107
+ 'MYR' => { numeric_code: 458, minor_unit: 2, name: 'Malaysian ringgit' }.freeze,
108
+ 'MZN' => { numeric_code: 943, minor_unit: 2, name: 'Mozambican metical' }.freeze,
109
+ 'NAD' => { numeric_code: 516, minor_unit: 2, name: 'Namibian dollar' }.freeze,
110
+ 'NGN' => { numeric_code: 566, minor_unit: 2, name: 'Nigerian naira' }.freeze,
111
+ 'NIO' => { numeric_code: 558, minor_unit: 2, name: 'Nicaraguan córdoba' }.freeze,
112
+ 'NOK' => { numeric_code: 578, minor_unit: 2, name: 'Norwegian krone' }.freeze,
113
+ 'NPR' => { numeric_code: 524, minor_unit: 2, name: 'Nepalese rupee' }.freeze,
114
+ 'NZD' => { numeric_code: 554, minor_unit: 2, name: 'New Zealand dollar' }.freeze,
115
+ 'OMR' => { numeric_code: 512, minor_unit: 3, name: 'Omani rial' }.freeze,
116
+ 'PAB' => { numeric_code: 590, minor_unit: 2, name: 'Panamanian balboa' }.freeze,
117
+ 'PEN' => { numeric_code: 604, minor_unit: 2, name: 'Peruvian sol' }.freeze,
118
+ 'PGK' => { numeric_code: 598, minor_unit: 2, name: 'Papua New Guinean kina' }.freeze,
119
+ 'PHP' => { numeric_code: 608, minor_unit: 2, name: 'Philippine peso' }.freeze,
120
+ 'PKR' => { numeric_code: 586, minor_unit: 2, name: 'Pakistani rupee' }.freeze,
121
+ 'PLN' => { numeric_code: 985, minor_unit: 2, name: 'Polish złoty' }.freeze,
122
+ 'PYG' => { numeric_code: 600, minor_unit: 0, name: 'Paraguayan guaraní' }.freeze,
123
+ 'QAR' => { numeric_code: 634, minor_unit: 2, name: 'Qatari riyal' }.freeze,
124
+ 'RON' => { numeric_code: 946, minor_unit: 2, name: 'Romanian leu' }.freeze,
125
+ 'RSD' => { numeric_code: 941, minor_unit: 2, name: 'Serbian dinar' }.freeze,
126
+ 'RUB' => { numeric_code: 643, minor_unit: 2, name: 'Russian ruble' }.freeze,
127
+ 'RWF' => { numeric_code: 646, minor_unit: 0, name: 'Rwandan franc' }.freeze,
128
+ 'SAR' => { numeric_code: 682, minor_unit: 2, name: 'Saudi riyal' }.freeze,
129
+ 'SBD' => { numeric_code: 90, minor_unit: 2, name: 'Solomon Islands dollar' }.freeze,
130
+ 'SCR' => { numeric_code: 690, minor_unit: 2, name: 'Seychelles rupee' }.freeze,
131
+ 'SDG' => { numeric_code: 938, minor_unit: 2, name: 'Sudanese pound' }.freeze,
132
+ 'SEK' => { numeric_code: 752, minor_unit: 2, name: 'Swedish krona (plural: kronor)' }.freeze,
133
+ 'SGD' => { numeric_code: 702, minor_unit: 2, name: 'Singapore dollar' }.freeze,
134
+ 'SHP' => { numeric_code: 654, minor_unit: 2, name: 'Saint Helena pound' }.freeze,
135
+ 'SLL' => { numeric_code: 694, minor_unit: 2, name: 'Sierra Leonean leone' }.freeze,
136
+ 'SOS' => { numeric_code: 706, minor_unit: 2, name: 'Somali shilling' }.freeze,
137
+ 'SRD' => { numeric_code: 968, minor_unit: 2, name: 'Surinamese dollar' }.freeze,
138
+ 'SSP' => { numeric_code: 728, minor_unit: 2, name: 'South Sudanese pound' }.freeze,
139
+ 'STN' => { numeric_code: 930, minor_unit: 2, name: 'São Tomé and Príncipe dobra' }.freeze,
140
+ 'SVC' => { numeric_code: 222, minor_unit: 2, name: 'Salvadoran colón' }.freeze,
141
+ 'SYP' => { numeric_code: 760, minor_unit: 2, name: 'Syrian pound' }.freeze,
142
+ 'SZL' => { numeric_code: 748, minor_unit: 2, name: 'Swazi lilangeni' }.freeze,
143
+ 'THB' => { numeric_code: 764, minor_unit: 2, name: 'Thai baht' }.freeze,
144
+ 'TJS' => { numeric_code: 972, minor_unit: 2, name: 'Tajikistani somoni' }.freeze,
145
+ 'TMT' => { numeric_code: 934, minor_unit: 2, name: 'Turkmenistan manat' }.freeze,
146
+ 'TND' => { numeric_code: 788, minor_unit: 3, name: 'Tunisian dinar' }.freeze,
147
+ 'TOP' => { numeric_code: 776, minor_unit: 2, name: 'Tongan paʻanga' }.freeze,
148
+ 'TRY' => { numeric_code: 949, minor_unit: 2, name: 'Turkish lira' }.freeze,
149
+ 'TTD' => { numeric_code: 780, minor_unit: 2, name: 'Trinidad and Tobago dollar' }.freeze,
150
+ 'TWD' => { numeric_code: 901, minor_unit: 2, name: 'New Taiwan dollar' }.freeze,
151
+ 'TZS' => { numeric_code: 834, minor_unit: 2, name: 'Tanzanian shilling' }.freeze,
152
+ 'UAH' => { numeric_code: 980, minor_unit: 2, name: 'Ukrainian hryvnia' }.freeze,
153
+ 'UGX' => { numeric_code: 800, minor_unit: 0, name: 'Ugandan shilling' }.freeze,
154
+ 'USD' => { numeric_code: 840, minor_unit: 2, name: 'United States dollar' }.freeze,
155
+ 'USN' => { numeric_code: 997, minor_unit: 2, name: 'United States dollar (next day) (funds code)' }.freeze,
156
+ 'UYI' => { numeric_code: 940, minor_unit: 0, name: 'Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code)' }.freeze,
157
+ 'UYU' => { numeric_code: 858, minor_unit: 2, name: 'Uruguayan peso' }.freeze,
158
+ 'UYW' => { numeric_code: 927, minor_unit: 4, name: 'Unidad previsional' }.freeze,
159
+ 'UZS' => { numeric_code: 860, minor_unit: 2, name: 'Uzbekistan som' }.freeze,
160
+ 'VED' => { numeric_code: 926, minor_unit: 2, name: 'Venezuelan bolívar digital' }.freeze,
161
+ 'VES' => { numeric_code: 928, minor_unit: 2, name: 'Venezuelan bolívar soberano' }.freeze,
162
+ 'VND' => { numeric_code: 704, minor_unit: 0, name: 'Vietnamese đồng' }.freeze,
163
+ 'VUV' => { numeric_code: 548, minor_unit: 0, name: 'Vanuatu vatu' }.freeze,
164
+ 'WST' => { numeric_code: 882, minor_unit: 2, name: 'Samoan tala' }.freeze,
165
+ 'XAF' => { numeric_code: 950, minor_unit: 0, name: 'CFA franc BEAC' }.freeze,
166
+ 'XAG' => { numeric_code: 961, minor_unit: nil, name: 'Silver (one troy ounce)' }.freeze,
167
+ 'XAU' => { numeric_code: 959, minor_unit: nil, name: 'Gold (one troy ounce)' }.freeze,
168
+ 'XBA' => { numeric_code: 955, minor_unit: nil, name: 'European Composite Unit (EURCO) (bond market unit)' }.freeze,
169
+ 'XBB' => { numeric_code: 956, minor_unit: nil, name: 'European Monetary Unit (E.M.U.-6) (bond market unit)' }.freeze,
170
+ 'XBC' => { numeric_code: 957, minor_unit: nil, name: 'European Unit of Account 9 (E.U.A.-9) (bond market unit)' }.freeze,
171
+ 'XBD' => { numeric_code: 958, minor_unit: nil, name: 'European Unit of Account 17 (E.U.A.-17) (bond market unit)' }.freeze,
172
+ 'XCD' => { numeric_code: 951, minor_unit: 2, name: 'East Caribbean dollar' }.freeze,
173
+ 'XDR' => { numeric_code: 960, minor_unit: nil, name: 'Special drawing rights' }.freeze,
174
+ 'XOF' => { numeric_code: 952, minor_unit: 0, name: 'CFA franc BCEAO' }.freeze,
175
+ 'XPD' => { numeric_code: 964, minor_unit: nil, name: 'Palladium (one troy ounce)' }.freeze,
176
+ 'XPF' => { numeric_code: 953, minor_unit: 0, name: 'CFP franc (franc Pacifique)' }.freeze,
177
+ 'XPT' => { numeric_code: 962, minor_unit: nil, name: 'Platinum (one troy ounce)' }.freeze,
178
+ 'XSU' => { numeric_code: 994, minor_unit: nil, name: 'SUCRE' }.freeze,
179
+ 'XTS' => { numeric_code: 963, minor_unit: nil, name: 'Code reserved for testing' }.freeze,
180
+ 'XUA' => { numeric_code: 965, minor_unit: nil, name: 'ADB Unit of Account' }.freeze,
181
+ 'XXX' => { numeric_code: 999, minor_unit: nil, name: 'No currency' }.freeze,
182
+ 'YER' => { numeric_code: 886, minor_unit: 2, name: 'Yemeni rial' }.freeze,
183
+ 'ZAR' => { numeric_code: 710, minor_unit: 2, name: 'South African rand' }.freeze,
184
+ 'ZMW' => { numeric_code: 967, minor_unit: 2, name: 'Zambian kwacha' }.freeze,
185
+ 'ZWL' => { numeric_code: 932, minor_unit: 2, name: 'Zimbabwean dollar' }.freeze
186
+ }.freeze
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,26 @@
1
+ require 'kdl/types/currency/iso4217_currencies'
2
+
3
+ module KDL
4
+ module Types
5
+ class Currency < Value
6
+ attr_reader :numeric_code, :minor_unit, :name
7
+
8
+ def initialize(value, format: nil, type: 'currency')
9
+ super
10
+ @numeric = value.fetch(:numeric, nil)
11
+ @minor_unit = value.fetch(:minor_unit, nil)
12
+ @name = value.fetch(:name, '')
13
+ end
14
+
15
+ def self.call(value, type = 'currency')
16
+ return nil unless value.is_a? ::KDL::Value::String
17
+
18
+ currency = CURRENCIES[value.value.upcase]
19
+ raise ArgumentError, 'invalid currency' if currency.nil?
20
+
21
+ new(currency, type: type)
22
+ end
23
+ end
24
+ MAPPING['currency'] = Currency
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ require 'time'
2
+
3
+ module KDL
4
+ module Types
5
+ class DateTime < Value
6
+ def self.call(value, type = 'date-time')
7
+ return nil unless value.is_a? ::KDL::Value::String
8
+
9
+ time = ::Time.iso8601(value.value)
10
+ new(time, type: type)
11
+ end
12
+ end
13
+ MAPPING['date-time'] = DateTime
14
+
15
+ class Time < Value
16
+ # TODO: this is not a perfect ISO8601 time string
17
+ REGEX = /^T?((?:2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9](?:\.[0-9]+)?(?:Z|[+-]\d\d:\d\d)?)$/
18
+
19
+ def self.call(value, type = 'time')
20
+ return nil unless value.is_a? ::KDL::Value::String
21
+
22
+ match = REGEX.match(value.value)
23
+ raise ArgumentError, 'invalid time' if match.nil?
24
+
25
+ time = ::Time.iso8601("#{::Date.today.iso8601}T#{match[1]}")
26
+ new(time, type: type)
27
+ end
28
+ end
29
+ MAPPING['time'] = Time
30
+
31
+ class Date < Value
32
+ def self.call(value, type = 'date')
33
+ return nil unless value.is_a? ::KDL::Value::String
34
+
35
+ date = ::Date.iso8601(value.value)
36
+ new(date, type: type)
37
+ end
38
+ end
39
+ MAPPING['date'] = Date
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ module KDL
2
+ module Types
3
+ class Decimal < Value
4
+ def self.call(value, type = 'decimal')
5
+ return nil unless value.is_a? ::KDL::Value::String
6
+
7
+ big_decimal = BigDecimal(value.value)
8
+ new(big_decimal, type: type)
9
+ end
10
+ end
11
+ MAPPING['decimal'] = Decimal
12
+ end
13
+ end
@@ -0,0 +1,147 @@
1
+ # Shamelessly borrowed from https://github.com/rails/rails/tree/main/activesupport
2
+ #
3
+ # Copyright (c) 2005-2021 David Heinemeier Hansson
4
+
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require 'strscan'
25
+
26
+ module KDL
27
+ module Types
28
+ class Duration < Value
29
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
30
+ #
31
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
32
+ #
33
+ # This parser allows negative parts to be present in pattern.
34
+ class ISO8601Parser # :nodoc:
35
+ class ParsingError < ::ArgumentError; end
36
+
37
+ PERIOD_OR_COMMA = /\.|,/
38
+ PERIOD = '.'
39
+ COMMA = ','
40
+
41
+ SIGN_MARKER = /\A-|\+|/
42
+ DATE_MARKER = /P/
43
+ TIME_MARKER = /T/
44
+ DATE_COMPONENT = /(-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
45
+ TIME_COMPONENT = /(-?\d+(?:[.,]\d+)?)(H|M|S)/
46
+
47
+ DATE_TO_PART = { 'Y' => :years, 'M' => :months, 'W' => :weeks, 'D' => :days }
48
+ TIME_TO_PART = { 'H' => :hours, 'M' => :minutes, 'S' => :seconds }
49
+
50
+ DATE_COMPONENTS = %i[years months days]
51
+ TIME_COMPONENTS = %i[hours minutes seconds]
52
+
53
+ attr_reader :parts, :scanner
54
+ attr_accessor :mode, :sign
55
+
56
+ def initialize(string)
57
+ @scanner = StringScanner.new(string)
58
+ @parts = {}
59
+ @mode = :start
60
+ @sign = 1
61
+ end
62
+
63
+ def parse!
64
+ until finished?
65
+ case mode
66
+ when :start
67
+ if scan(SIGN_MARKER)
68
+ self.sign = scanner.matched == '-' ? -1 : 1
69
+ self.mode = :sign
70
+ else
71
+ raise_parsing_error
72
+ end
73
+
74
+ when :sign
75
+ if scan(DATE_MARKER)
76
+ self.mode = :date
77
+ else
78
+ raise_parsing_error
79
+ end
80
+
81
+ when :date
82
+ if scan(TIME_MARKER)
83
+ self.mode = :time
84
+ elsif scan(DATE_COMPONENT)
85
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
86
+ else
87
+ raise_parsing_error
88
+ end
89
+
90
+ when :time
91
+ if scan(TIME_COMPONENT)
92
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
93
+ else
94
+ raise_parsing_error
95
+ end
96
+
97
+ end
98
+ end
99
+
100
+ validate!
101
+ parts
102
+ end
103
+
104
+ private
105
+
106
+ def finished?
107
+ scanner.eos?
108
+ end
109
+
110
+ # Parses number which can be a float with either comma or period.
111
+ def number
112
+ (PERIOD_OR_COMMA =~ scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
113
+ end
114
+
115
+ def scan(pattern)
116
+ scanner.scan(pattern)
117
+ end
118
+
119
+ def raise_parsing_error(reason = nil)
120
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
121
+ end
122
+
123
+ # Checks for various semantic errors as stated in ISO 8601 standard.
124
+ def validate!
125
+ raise_parsing_error('is empty duration') if parts.empty?
126
+
127
+ # Mixing any of Y, M, D with W is invalid.
128
+ if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
129
+ raise_parsing_error('mixing weeks with other date parts not allowed')
130
+ end
131
+
132
+ # Specifying an empty T part is invalid.
133
+ if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
134
+ raise_parsing_error('time part marker is present but time part is empty')
135
+ end
136
+
137
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
138
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
139
+ raise_parsing_error '(only last part can be fractional)'
140
+ end
141
+
142
+ true
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,28 @@
1
+ require 'kdl/types/duration/iso8601_parser'
2
+
3
+ module KDL
4
+ module Types
5
+ class Duration < Value
6
+ attr_reader :years, :months, :weeks, :days, :hours, :minutes, :seconds
7
+
8
+ def initialize(parts = {}, format: nil, type: 'duration')
9
+ super
10
+ @years = parts.fetch(:years, 0)
11
+ @months = parts.fetch(:months, 0)
12
+ @weeks = parts.fetch(:weeks, 0)
13
+ @days = parts.fetch(:days, 0)
14
+ @hours = parts.fetch(:hours, 0)
15
+ @minutes = parts.fetch(:minutes, 0)
16
+ @seconds = parts.fetch(:seconds, 0)
17
+ end
18
+
19
+ def self.call(value, type = 'duration')
20
+ return nil unless value.is_a? ::KDL::Value::String
21
+
22
+ parts = ISO8601Parser.new(value.value).parse!
23
+ new(parts, type: type)
24
+ end
25
+ end
26
+ MAPPING['duration'] = Duration
27
+ end
28
+ end
@@ -0,0 +1,151 @@
1
+ require_relative '../hostname/validator'
2
+
3
+ module KDL
4
+ module Types
5
+ class Email < Value
6
+ class Parser
7
+ def initialize(string, idn: false)
8
+ @string = string
9
+ @idn = idn
10
+ @tokenizer = Tokenizer.new(string, idn: idn)
11
+ end
12
+
13
+ def parse
14
+ local = ''
15
+ unicode_domain = nil
16
+ domain = nil
17
+ context = :start
18
+
19
+ loop do
20
+ type, value = @tokenizer.next_token
21
+
22
+ case type
23
+ when :part
24
+ case context
25
+ when :start, :after_dot
26
+ local += value
27
+ context = :after_part
28
+ else
29
+ raise ArgumentError, "invalid email #{@string} (unexpected part #{value} at #{context})"
30
+ end
31
+ when :dot
32
+ case context
33
+ when :after_part
34
+ local += value
35
+ context = :after_dot
36
+ else
37
+ raise ArgumentError, "invalid email #{@string} (unexpected dot at #{context})"
38
+ end
39
+ when :at
40
+ case context
41
+ when :after_part
42
+ context = :after_at
43
+ end
44
+ when :domain
45
+ case context
46
+ when :after_at
47
+ validator = (@idn ? IDNHostname : Hostname)::Validator.new(value)
48
+ raise ArgumentError, "invalid hostname #{value}" unless validator.valid?
49
+
50
+ unicode_domain = validator.unicode
51
+ domain = validator.ascii
52
+ context = :after_domain
53
+ else
54
+ raise ArgumentError, "invalid email #{@string} (unexpected domain at #{context})"
55
+ end
56
+ when :end
57
+ case context
58
+ when :after_domain
59
+ if local.size > 64
60
+ raise ArgumentError, "invalid email #{@string} (local part length #{local.size} exceeds maximaum of 64)"
61
+ end
62
+
63
+ return [local, domain, unicode_domain]
64
+ else
65
+ raise ArgumentError, "invalid email #{@string} (unexpected end at #{context})"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ class Tokenizer
73
+ LOCAL_PART_ASCII = %r{[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~]}.freeze
74
+ LOCAL_PART_IDN = /[^\x00-\x1f\s".@]/.freeze
75
+
76
+ def initialize(string, idn: false)
77
+ @string = string
78
+ @idn = idn
79
+ @index = 0
80
+ @after_at = false
81
+ end
82
+
83
+ def next_token
84
+ if @after_at
85
+ if @index < @string.size
86
+ domain_start = @index
87
+ @index = @string.size
88
+ return [:domain, @string[domain_start..-1]]
89
+ else
90
+ return [:end, nil]
91
+ end
92
+ end
93
+ @context = nil
94
+ @buffer = ''
95
+ loop do
96
+ c = @string[@index]
97
+ return [:end, nil] if c.nil?
98
+
99
+ case @context
100
+ when nil
101
+ case c
102
+ when '.'
103
+ @index += 1
104
+ return [:dot, '.']
105
+ when '@'
106
+ @after_at = true
107
+ @index += 1
108
+ return [:at, '@']
109
+ when '"'
110
+ @context = :quote
111
+ @index += 1
112
+ when local_part_chars
113
+ @context = :part
114
+ @buffer += c
115
+ @index += 1
116
+ else
117
+ raise ArgumentError, "invalid email #{@string} (unexpected #{c})"
118
+ end
119
+ when :part
120
+ case c
121
+ when local_part_chars
122
+ @buffer += c
123
+ @index += 1
124
+ when '.', '@'
125
+ return [:part, @buffer]
126
+ else
127
+ raise ArgumentError, "invalid email #{@string} (unexpected #{c})"
128
+ end
129
+ when :quote
130
+ case c
131
+ when '"'
132
+ n = @string[@index + 1]
133
+ raise ArgumentError, "invalid email #{@string} (unexpected #{c})" unless n == '.' || n == '@'
134
+
135
+ @index += 1
136
+ return [:part, @buffer]
137
+ else
138
+ @buffer += c
139
+ @index += 1
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def local_part_chars
146
+ @idn ? LOCAL_PART_IDN : LOCAL_PART_ASCII
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end