kdl 0.1.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +4 -3
- data/.gitignore +1 -0
- data/.gitmodules +3 -0
- data/README.md +16 -2
- data/Rakefile +11 -6
- data/bin/console +2 -0
- data/bin/kdl +8 -0
- data/bin/racc +29 -0
- data/bin/rake +29 -0
- data/bin/setup +1 -0
- data/kdl.gemspec +5 -5
- data/lib/kdl/document.rb +1 -2
- data/lib/kdl/kdl.tab.rb +254 -167
- data/lib/kdl/kdl.yy +44 -27
- data/lib/kdl/node.rb +30 -7
- data/lib/kdl/string_dumper.rb +45 -0
- data/lib/kdl/tokenizer.rb +52 -30
- data/lib/kdl/types/base64.rb +15 -0
- data/lib/kdl/types/country/iso3166_countries.rb +512 -0
- data/lib/kdl/types/country/iso3166_subdivisions.rb +3927 -0
- data/lib/kdl/types/country.rb +69 -0
- data/lib/kdl/types/currency/iso4217_currencies.rb +189 -0
- data/lib/kdl/types/currency.rb +26 -0
- data/lib/kdl/types/date_time.rb +41 -0
- data/lib/kdl/types/decimal.rb +13 -0
- data/lib/kdl/types/duration/iso8601_parser.rb +147 -0
- data/lib/kdl/types/duration.rb +28 -0
- data/lib/kdl/types/email/parser.rb +151 -0
- data/lib/kdl/types/email.rb +43 -0
- data/lib/kdl/types/hostname/validator.rb +51 -0
- data/lib/kdl/types/hostname.rb +32 -0
- data/lib/kdl/types/ip.rb +32 -0
- data/lib/kdl/types/irl/parser.rb +123 -0
- data/lib/kdl/types/irl.rb +46 -0
- data/lib/kdl/types/regex.rb +13 -0
- data/lib/kdl/types/url.rb +30 -0
- data/lib/kdl/types/url_template.rb +328 -0
- data/lib/kdl/types/uuid.rb +17 -0
- data/lib/kdl/types.rb +22 -0
- data/lib/kdl/value.rb +42 -10
- data/lib/kdl/version.rb +1 -1
- data/lib/kdl.rb +4 -2
- metadata +48 -7
@@ -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
|