kdl 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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