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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +4 -3
- data/.gitmodules +3 -0
- data/README.md +16 -2
- data/Rakefile +11 -6
- data/bin/console +2 -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 +237 -167
- data/lib/kdl/kdl.yy +40 -24
- data/lib/kdl/node.rb +33 -8
- data/lib/kdl/string_dumper.rb +45 -0
- data/lib/kdl/tokenizer.rb +163 -71
- 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 +47 -8
- data/.travis.yml +0 -6
@@ -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
|