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,43 @@
|
|
1
|
+
require_relative './email/parser'
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module Types
|
5
|
+
class Email < Value
|
6
|
+
attr_reader :local, :domain
|
7
|
+
|
8
|
+
def initialize(value, local:, domain:, **kwargs)
|
9
|
+
super(value, **kwargs)
|
10
|
+
@local = local
|
11
|
+
@domain = domain
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.call(value, type = 'email')
|
15
|
+
local, domain = Parser.new(value.value).parse
|
16
|
+
|
17
|
+
new(value.value, type: type, local: local, domain: domain)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
MAPPING['email'] = Email
|
22
|
+
|
23
|
+
class IDNEmail < Email
|
24
|
+
attr_reader :unicode_domain
|
25
|
+
|
26
|
+
def initialize(value, unicode_domain:, **kwargs)
|
27
|
+
super(value, **kwargs)
|
28
|
+
@unicode_domain = unicode_domain
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.call(value, type = 'email')
|
32
|
+
local, domain, unicode_domain = Email::Parser.new(value.value, idn: true).parse
|
33
|
+
|
34
|
+
new("#{local}@#{domain}", type: type, local: local, domain: domain, unicode_domain: unicode_domain)
|
35
|
+
end
|
36
|
+
|
37
|
+
def unicode_value
|
38
|
+
"#{local}@#{unicode_domain}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
MAPPING['idn-email'] = IDNEmail
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'simpleidn'
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module Types
|
5
|
+
class Hostname < Value
|
6
|
+
class Validator
|
7
|
+
PART_RGX = /^[a-z0-9_][a-z0-9_\-]{0,62}$/i
|
8
|
+
|
9
|
+
attr_reader :string
|
10
|
+
alias ascii string
|
11
|
+
alias unicode string
|
12
|
+
|
13
|
+
def initialize(string)
|
14
|
+
@string = string
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
return false if @string.length > 253
|
19
|
+
|
20
|
+
@string.split('.').all? { |x| valid_part?(x) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def valid_part?(part)
|
26
|
+
return false if part.empty?
|
27
|
+
return false if part.start_with?('-') || part.end_with?('-')
|
28
|
+
|
29
|
+
part =~ PART_RGX
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class IDNHostname < Hostname
|
35
|
+
class Validator < Hostname::Validator
|
36
|
+
attr_reader :unicode
|
37
|
+
|
38
|
+
def initialize(string)
|
39
|
+
is_ascii = string.split('.').any? { |x| x.start_with?('xn--') }
|
40
|
+
if is_ascii
|
41
|
+
super(string)
|
42
|
+
@unicode = SimpleIDN.to_unicode(string)
|
43
|
+
else
|
44
|
+
super(SimpleIDN.to_ascii(string))
|
45
|
+
@unicode = string
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative './hostname/validator'
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module Types
|
5
|
+
class Hostname < Value
|
6
|
+
def self.call(value, type = 'hostname')
|
7
|
+
validator = Validator.new(value.value)
|
8
|
+
raise ArgumentError, "invalid hostname #{value}" unless validator.valid?
|
9
|
+
|
10
|
+
new(value.value, type: type)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
MAPPING['hostname'] = Hostname
|
14
|
+
|
15
|
+
class IDNHostname < Hostname
|
16
|
+
attr_reader :unicode_value
|
17
|
+
|
18
|
+
def initialize(value, unicode_value:, **kwargs)
|
19
|
+
super(value, **kwargs)
|
20
|
+
@unicode_value = unicode_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.call(value, type = 'idn-hostname')
|
24
|
+
validator = Validator.new(value.value)
|
25
|
+
raise ArgumentError, "invalid hostname #{value}" unless validator.valid?
|
26
|
+
|
27
|
+
new(validator.ascii, type: type, unicode_value: validator.unicode)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
MAPPING['idn-hostname'] = IDNHostname
|
31
|
+
end
|
32
|
+
end
|
data/lib/kdl/types/ip.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module KDL
|
2
|
+
module Types
|
3
|
+
class IP < Value
|
4
|
+
def self.call(value, type = ip_type)
|
5
|
+
return nil unless value.is_a? ::KDL::Value::String
|
6
|
+
|
7
|
+
ip = ::IPAddr.new(value.value)
|
8
|
+
raise ArgumentError, "invalid #{ip_type} address" unless valid_ip?(ip)
|
9
|
+
|
10
|
+
new(ip, type: type)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.valid_ip?(ip)
|
14
|
+
ip.__send__(:"#{ip_type}?")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class IPV4 < IP
|
19
|
+
def self.ip_type
|
20
|
+
'ipv4'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
MAPPING['ipv4'] = IPV4
|
24
|
+
|
25
|
+
class IPV6 < IP
|
26
|
+
def self.ip_type
|
27
|
+
'ipv6'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
MAPPING['ipv6'] = IPV6
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module KDL
|
2
|
+
module Types
|
3
|
+
class IRLReference < Value
|
4
|
+
class Parser
|
5
|
+
RGX = /^(?:(?:([a-z][a-z0-9+.\-]+)):\/\/([^@]+@)?([^\/?#]+)?)?(\/?[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/i.freeze
|
6
|
+
PERCENT_RGX = /%[a-f0-9]{2}/i.freeze
|
7
|
+
|
8
|
+
RESERVED_URL_CHARS = %w[! # $ & ' ( ) * + , / : ; = ? @ \[ \] %]
|
9
|
+
UNRESERVED_URL_CHARS = %w[A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
10
|
+
a b c d e f g h i j k l m n o p q r s t u v w x y z
|
11
|
+
0 1 2 3 4 5 6 7 8 9 - _ . ~].freeze
|
12
|
+
URL_CHARS = RESERVED_URL_CHARS + UNRESERVED_URL_CHARS
|
13
|
+
|
14
|
+
def initialize(string)
|
15
|
+
@string = string
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse
|
19
|
+
scheme, auth, domain, path, search, hash = *parse_url
|
20
|
+
|
21
|
+
if @string.ascii_only?
|
22
|
+
unicode_path = Parser.decode(path)
|
23
|
+
unicode_search = Parser.decode(search)
|
24
|
+
unicode_hash = Parser.decode(hash)
|
25
|
+
else
|
26
|
+
unicode_path = path
|
27
|
+
path = Parser.encode(unicode_path)
|
28
|
+
unicode_search = search
|
29
|
+
search_params = unicode_search ? unicode_search.split('&').map { |x| x.split('=') } : nil
|
30
|
+
search = search_params ? search_params.map { |k, v| "#{Parser.encode(k)}=#{Parser.encode(v)}" }.join('&') : nil
|
31
|
+
unicode_hash = hash
|
32
|
+
hash = Parser.encode(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
if domain
|
36
|
+
validator = IDNHostname::Validator.new(domain)
|
37
|
+
domain = validator.ascii
|
38
|
+
unicode_domain = validator.unicode
|
39
|
+
else
|
40
|
+
unicode_domain = domain
|
41
|
+
end
|
42
|
+
|
43
|
+
unicode_value = Parser.build_uri_string(scheme, auth, unicode_domain, unicode_path, unicode_search, unicode_hash)
|
44
|
+
ascii_value = Parser.build_uri_string(scheme, auth, domain, path, search, hash)
|
45
|
+
|
46
|
+
[ascii_value,
|
47
|
+
{ unicode_value: unicode_value,
|
48
|
+
unicode_domain: unicode_domain,
|
49
|
+
unicode_path: unicode_path,
|
50
|
+
unicode_search: unicode_search,
|
51
|
+
unicode_hash: unicode_hash }]
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_url
|
55
|
+
match = RGX.match(@string)
|
56
|
+
raise ArgumentError, "invalid IRL `#{@string}'" if match.nil?
|
57
|
+
|
58
|
+
_, *parts = *match
|
59
|
+
raise ArgumentError, "invalid IRL `#{@string}'" unless parts.all? { |part| Parser.valid_url_part?(part) }
|
60
|
+
|
61
|
+
parts
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.valid_url_part?(string)
|
65
|
+
return true unless string
|
66
|
+
|
67
|
+
string.chars.all? do |char|
|
68
|
+
!char.ascii_only? || URL_CHARS.include?(char)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.encode(string)
|
73
|
+
return string unless string
|
74
|
+
|
75
|
+
string.chars
|
76
|
+
.map { |c| c.ascii_only? ? c : percent_encode(c) }
|
77
|
+
.join
|
78
|
+
.force_encoding('utf-8')
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.decode(string)
|
82
|
+
return string unless string
|
83
|
+
|
84
|
+
string.gsub(PERCENT_RGX) do |match|
|
85
|
+
char = match[1, 2].to_i(16).chr
|
86
|
+
if RESERVED_URL_CHARS.include?(char)
|
87
|
+
match
|
88
|
+
else
|
89
|
+
char
|
90
|
+
end
|
91
|
+
end.force_encoding('utf-8')
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.percent_encode(c)
|
95
|
+
c.bytes.map { |b| "%#{b.to_s(16)}" }.join.upcase
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.build_uri_string(scheme, auth, domain, path, search, hash)
|
99
|
+
string = ''
|
100
|
+
string += "#{scheme}://" if scheme
|
101
|
+
string += auth if auth
|
102
|
+
string += domain if domain
|
103
|
+
string += path if path
|
104
|
+
string += "?#{search}" if search
|
105
|
+
string += "##{hash}" if hash
|
106
|
+
string
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class IRL < IRLReference
|
112
|
+
class Parser < IRLReference::Parser
|
113
|
+
def parse_url
|
114
|
+
parts = super
|
115
|
+
scheme, * = parts
|
116
|
+
raise ArgumentError, "invalid IRL `#{@string}'" if scheme.nil? || scheme.empty?
|
117
|
+
|
118
|
+
parts
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative './irl/parser'
|
2
|
+
|
3
|
+
module KDL
|
4
|
+
module Types
|
5
|
+
class IRLReference < Value
|
6
|
+
attr_reader :unicode_value,
|
7
|
+
:unicode_domain,
|
8
|
+
:unicode_path,
|
9
|
+
:unicode_search,
|
10
|
+
:unicode_hash
|
11
|
+
|
12
|
+
def initialize(value, unicode_value:, unicode_domain:, unicode_path:, unicode_search:, unicode_hash:, **kwargs)
|
13
|
+
super(value, **kwargs)
|
14
|
+
@unicode_value = unicode_value
|
15
|
+
@unicode_domain = unicode_domain
|
16
|
+
@unicode_path = unicode_path
|
17
|
+
@unicode_search = unicode_search
|
18
|
+
@unicode_hash = unicode_hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.call(value, type = 'irl-reference')
|
22
|
+
return nil unless value.is_a? ::KDL::Value::String
|
23
|
+
|
24
|
+
ascii_value, params = parser(value.value).parse
|
25
|
+
|
26
|
+
new(URI.parse(ascii_value), type: type, **params)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parser(string)
|
30
|
+
IRLReference::Parser.new(string)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
MAPPING['irl-reference'] = IRLReference
|
34
|
+
|
35
|
+
class IRL < IRLReference
|
36
|
+
def self.call(value, type = 'irl')
|
37
|
+
super(value, type)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.parser(string)
|
41
|
+
IRL::Parser.new(string)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
MAPPING['irl'] = IRL
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module KDL
|
2
|
+
module Types
|
3
|
+
class Regex < Value
|
4
|
+
def self.call(value, type = 'regex')
|
5
|
+
return nil unless value.is_a? ::KDL::Value::String
|
6
|
+
|
7
|
+
regex = ::Regexp.new(value.value)
|
8
|
+
new(regex, type: type)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
MAPPING['regex'] = Regex
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module KDL
|
2
|
+
module Types
|
3
|
+
class URLReference < Value
|
4
|
+
def self.call(value, type = 'url-reference')
|
5
|
+
return nil unless value.is_a? ::KDL::Value::String
|
6
|
+
|
7
|
+
uri = parse_url(value.value)
|
8
|
+
new(uri, type: type)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.parse_url(string)
|
12
|
+
URI.parse(string)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
MAPPING['url-reference'] = URLReference
|
16
|
+
|
17
|
+
class URL < URLReference
|
18
|
+
def self.call(value, type = 'url')
|
19
|
+
super(value, type)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse_url(string)
|
23
|
+
super.tap do |uri|
|
24
|
+
raise 'invalid URL' if uri.scheme.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
MAPPING['url'] = URL
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
module KDL
|
2
|
+
module Types
|
3
|
+
class URLTemplate < Value
|
4
|
+
UNRESERVED = /[a-zA-Z0-9\-._~]/.freeze
|
5
|
+
RESERVED = %r{[:/?#\[\]@!$&'()*+,;=]}.freeze
|
6
|
+
|
7
|
+
def self.call(value, type = 'url-template')
|
8
|
+
return nil unless value.is_a? ::KDL::Value::String
|
9
|
+
|
10
|
+
parts = Parser.parse(value.value)
|
11
|
+
new(parts, type: type)
|
12
|
+
end
|
13
|
+
|
14
|
+
def expand(variables)
|
15
|
+
result = value.map { |v| v.expand(variables) }.join
|
16
|
+
parser = IRLReference::Parser.new(result)
|
17
|
+
uri, * = parser.parse
|
18
|
+
URI(uri)
|
19
|
+
end
|
20
|
+
|
21
|
+
class Parser
|
22
|
+
def self.parse(string)
|
23
|
+
new(string).parse
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(string)
|
27
|
+
@string = string
|
28
|
+
@index = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse
|
32
|
+
result = []
|
33
|
+
until (token = next_token).nil?
|
34
|
+
result << token
|
35
|
+
end
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_token
|
40
|
+
buffer = ''
|
41
|
+
context = nil
|
42
|
+
expansion_type = nil
|
43
|
+
loop do
|
44
|
+
c = @string[@index]
|
45
|
+
case context
|
46
|
+
when nil
|
47
|
+
case c
|
48
|
+
when '{'
|
49
|
+
context = :expansion
|
50
|
+
buffer = ''
|
51
|
+
n = @string[@index + 1]
|
52
|
+
expansion_type = case n
|
53
|
+
when '+' then ReservedExpansion
|
54
|
+
when '#' then FragmentExpansion
|
55
|
+
when '.' then LabelExpansion
|
56
|
+
when '/' then PathExpantion
|
57
|
+
when ';' then ParameterExpansion
|
58
|
+
when '?' then QueryExpansion
|
59
|
+
when '&' then QueryContinuation
|
60
|
+
else StringExpansion
|
61
|
+
end
|
62
|
+
@index += (expansion_type == StringExpansion ? 1 : 2)
|
63
|
+
when nil then return nil
|
64
|
+
else
|
65
|
+
buffer = c
|
66
|
+
@index += 1
|
67
|
+
context = :literal
|
68
|
+
end
|
69
|
+
when :literal
|
70
|
+
case c
|
71
|
+
when '{', nil then return StringLiteral.new(buffer)
|
72
|
+
else
|
73
|
+
buffer << c
|
74
|
+
@index += 1
|
75
|
+
end
|
76
|
+
when :expansion
|
77
|
+
case c
|
78
|
+
when '}'
|
79
|
+
@index += 1
|
80
|
+
return parse_expansion(buffer, expansion_type)
|
81
|
+
when nil
|
82
|
+
raise ArgumentError, 'unterminated expansion'
|
83
|
+
else
|
84
|
+
buffer << c
|
85
|
+
@index += 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_expansion(string, type)
|
92
|
+
variables = string.split(',').map do |str|
|
93
|
+
case str
|
94
|
+
when /(.*)\*$/
|
95
|
+
Variable.new(Regexp.last_match(1),
|
96
|
+
explode: true,
|
97
|
+
allow_reserved: type.allow_reserved?,
|
98
|
+
with_name: type.with_name?,
|
99
|
+
keep_empties: type.keep_empties?)
|
100
|
+
when /(.*):(\d+)/
|
101
|
+
Variable.new(Regexp.last_match(1),
|
102
|
+
limit: Regexp.last_match(2).to_i,
|
103
|
+
allow_reserved: type.allow_reserved?,
|
104
|
+
with_name: type.with_name?,
|
105
|
+
keep_empties: type.keep_empties?)
|
106
|
+
else
|
107
|
+
Variable.new(str,
|
108
|
+
allow_reserved: type.allow_reserved?,
|
109
|
+
with_name: type.with_name?,
|
110
|
+
keep_empties: type.keep_empties?)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
type.new(variables)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Variable
|
118
|
+
attr_reader :name
|
119
|
+
|
120
|
+
def initialize(name, limit: nil, explode: false, allow_reserved: false, with_name: false, keep_empties: false)
|
121
|
+
@name = name.to_sym
|
122
|
+
@limit = limit
|
123
|
+
@explode = explode
|
124
|
+
@allow_reserved = allow_reserved
|
125
|
+
@with_name = with_name
|
126
|
+
@keep_empties = keep_empties
|
127
|
+
end
|
128
|
+
|
129
|
+
def expand(value)
|
130
|
+
if @explode
|
131
|
+
case value
|
132
|
+
when Array
|
133
|
+
value.map { |v| prefix(encode(v)) }
|
134
|
+
when Hash
|
135
|
+
value.map { |k, v| prefix(encode(v), k) }
|
136
|
+
else
|
137
|
+
[prefix(encode(value))]
|
138
|
+
end
|
139
|
+
elsif @limit
|
140
|
+
[prefix(limit(value))].compact
|
141
|
+
else
|
142
|
+
[prefix(flatten(value))].compact
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def limit(string)
|
147
|
+
return nil unless string
|
148
|
+
|
149
|
+
encode(string[0, @limit])
|
150
|
+
end
|
151
|
+
|
152
|
+
def flatten(value)
|
153
|
+
case value
|
154
|
+
when String
|
155
|
+
encode(value)
|
156
|
+
when Array, Hash
|
157
|
+
result = value.to_a
|
158
|
+
.flatten
|
159
|
+
.compact
|
160
|
+
.map { |v| encode(v) }
|
161
|
+
result.empty? ? nil : result.join(',')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def encode(string)
|
166
|
+
return nil unless string
|
167
|
+
|
168
|
+
string.to_s
|
169
|
+
.chars
|
170
|
+
.map do |c|
|
171
|
+
if UNRESERVED.match?(c) || (@allow_reserved && RESERVED.match?(c))
|
172
|
+
c
|
173
|
+
else
|
174
|
+
IRLReference::Parser.percent_encode(c)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
.join
|
178
|
+
.force_encoding('utf-8')
|
179
|
+
end
|
180
|
+
|
181
|
+
def prefix(string, override = nil)
|
182
|
+
return nil unless string
|
183
|
+
|
184
|
+
key = override || @name
|
185
|
+
|
186
|
+
if @with_name || override
|
187
|
+
if string.empty? && !@keep_empties
|
188
|
+
encode(key)
|
189
|
+
else
|
190
|
+
"#{encode(key)}=#{string}"
|
191
|
+
end
|
192
|
+
else
|
193
|
+
string
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Part
|
199
|
+
def expand_variables(values)
|
200
|
+
@variables.reduce([]) do |list, variable|
|
201
|
+
expanded = variable.expand(values[variable.name])
|
202
|
+
expanded ? list + expanded : list
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def separator
|
207
|
+
','
|
208
|
+
end
|
209
|
+
|
210
|
+
def prefix
|
211
|
+
''
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.allow_reserved?
|
215
|
+
false
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.with_name?
|
219
|
+
false
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.keep_empties?
|
223
|
+
false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class StringLiteral < Part
|
228
|
+
def initialize(value)
|
229
|
+
super()
|
230
|
+
@value = value
|
231
|
+
end
|
232
|
+
|
233
|
+
def expand(*)
|
234
|
+
@value
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
class StringExpansion < Part
|
239
|
+
def initialize(variables)
|
240
|
+
super()
|
241
|
+
@variables = variables
|
242
|
+
end
|
243
|
+
|
244
|
+
def expand(values)
|
245
|
+
expanded = expand_variables(values)
|
246
|
+
return '' if expanded.empty?
|
247
|
+
|
248
|
+
prefix + expanded.join(separator)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class ReservedExpansion < StringExpansion
|
253
|
+
def self.allow_reserved?
|
254
|
+
true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class FragmentExpansion < StringExpansion
|
259
|
+
def prefix
|
260
|
+
'#'
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.allow_reserved?
|
264
|
+
true
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class LabelExpansion < StringExpansion
|
269
|
+
def prefix
|
270
|
+
'.'
|
271
|
+
end
|
272
|
+
|
273
|
+
def separator
|
274
|
+
'.'
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
class PathExpantion < StringExpansion
|
279
|
+
def prefix
|
280
|
+
'/'
|
281
|
+
end
|
282
|
+
|
283
|
+
def separator
|
284
|
+
'/'
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class ParameterExpansion < StringExpansion
|
289
|
+
def prefix
|
290
|
+
';'
|
291
|
+
end
|
292
|
+
|
293
|
+
def separator
|
294
|
+
';'
|
295
|
+
end
|
296
|
+
|
297
|
+
def self.with_name?
|
298
|
+
true
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
class QueryExpansion < StringExpansion
|
303
|
+
def prefix
|
304
|
+
'?'
|
305
|
+
end
|
306
|
+
|
307
|
+
def separator
|
308
|
+
'&'
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.with_name?
|
312
|
+
true
|
313
|
+
end
|
314
|
+
|
315
|
+
def self.keep_empties?
|
316
|
+
true
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
class QueryContinuation < QueryExpansion
|
321
|
+
def prefix
|
322
|
+
'&'
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
MAPPING['url-template'] = URLTemplate
|
327
|
+
end
|
328
|
+
end
|