xenon 0.0.1 → 0.0.2
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/.gitignore +20 -19
- data/.rspec +3 -0
- data/Guardfile +48 -0
- data/LICENSE +22 -0
- data/README.md +4 -1
- data/lib/xenon.rb +237 -2
- data/lib/xenon/errors.rb +4 -0
- data/lib/xenon/headers.rb +114 -0
- data/lib/xenon/headers/accept.rb +33 -0
- data/lib/xenon/headers/accept_charset.rb +59 -0
- data/lib/xenon/headers/accept_encoding.rb +63 -0
- data/lib/xenon/headers/accept_language.rb +59 -0
- data/lib/xenon/headers/authorization.rb +96 -0
- data/lib/xenon/headers/cache_control.rb +56 -0
- data/lib/xenon/headers/content_type.rb +23 -0
- data/lib/xenon/media_type.rb +140 -0
- data/lib/xenon/parsers/basic_rules.rb +55 -0
- data/lib/xenon/parsers/header_rules.rb +14 -0
- data/lib/xenon/parsers/media_type.rb +52 -0
- data/lib/xenon/quoted_string.rb +10 -0
- data/lib/xenon/routing.rb +133 -0
- data/lib/xenon/version.rb +1 -1
- data/spec/spec_helper.rb +91 -0
- data/spec/xenon/headers/accept_charset_spec.rb +31 -0
- data/spec/xenon/headers/accept_encoding_spec.rb +40 -0
- data/spec/xenon/headers/accept_language_spec.rb +33 -0
- data/spec/xenon/headers/accept_spec.rb +54 -0
- data/spec/xenon/headers/authorization_spec.rb +47 -0
- data/spec/xenon/headers/cache_control_spec.rb +64 -0
- data/spec/xenon/media_type_spec.rb +267 -0
- data/xenon.gemspec +6 -1
- metadata +70 -9
- data/LICENSE.txt +0 -22
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/media_type'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class Headers
|
6
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.2
|
7
|
+
class Accept < ListHeader 'Accept'
|
8
|
+
def initialize(*media_ranges)
|
9
|
+
super(media_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :media_ranges, :values
|
13
|
+
|
14
|
+
def self.parse(s)
|
15
|
+
tree = Parsers::AcceptHeader.new.parse(s)
|
16
|
+
Parsers::AcceptHeaderTransform.new.apply(tree)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Parsers
|
22
|
+
class AcceptHeader < Parslet::Parser
|
23
|
+
include MediaTypeRules
|
24
|
+
rule(:accept) { (media_range >> (comma >> media_range).repeat).as(:accept) }
|
25
|
+
root(:accept)
|
26
|
+
end
|
27
|
+
|
28
|
+
class AcceptHeaderTransform < MediaTypeTransform
|
29
|
+
rule(accept: sequence(:mr)) { Headers::Accept.new(*mr) }
|
30
|
+
rule(accept: simple(:mr)) { Headers::Accept.new(mr) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class CharsetRange
|
6
|
+
attr_reader :charset, :q
|
7
|
+
|
8
|
+
DEFAULT_Q = 1.0
|
9
|
+
|
10
|
+
def initialize(charset, q = DEFAULT_Q)
|
11
|
+
@charset = charset
|
12
|
+
@q = Float(q) || DEFAULT_Q
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
@q <=> other.q
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
s = @charset.dup
|
21
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.3
|
28
|
+
class AcceptCharset < ListHeader 'Accept-Charset'
|
29
|
+
def initialize(*charset_ranges)
|
30
|
+
super(charset_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :charset_ranges, :values
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::AcceptCharsetHeader.new.parse(s)
|
37
|
+
Parsers::AcceptCharsetHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Parsers
|
43
|
+
class AcceptCharsetHeader < Parslet::Parser
|
44
|
+
include BasicRules, WeightRules
|
45
|
+
rule(:charset) { token.as(:charset) >> sp? }
|
46
|
+
rule(:wildcard) { str('*').as(:charset) >> sp? }
|
47
|
+
rule(:charset_range) { (charset | wildcard) >> weight.maybe }
|
48
|
+
rule(:accept_charset) { (charset_range >> (comma >> charset_range).repeat).as(:accept_charset) }
|
49
|
+
root(:accept_charset)
|
50
|
+
end
|
51
|
+
|
52
|
+
class AcceptCharsetHeaderTransform < Parslet::Transform
|
53
|
+
rule(charset: simple(:c), q: simple(:q)) { CharsetRange.new(c.str, q.str) }
|
54
|
+
rule(charset: simple(:c)) { CharsetRange.new(c.str) }
|
55
|
+
rule(accept_charset: sequence(:cr)) { Headers::AcceptCharset.new(*cr) }
|
56
|
+
rule(accept_charset: simple(:cr)) { Headers::AcceptCharset.new(cr) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class ContentCodingRange
|
6
|
+
attr_reader :coding, :q
|
7
|
+
|
8
|
+
DEFAULT_Q = 1.0
|
9
|
+
|
10
|
+
def initialize(coding, q = DEFAULT_Q)
|
11
|
+
@coding = coding
|
12
|
+
@q = Float(q) || DEFAULT_Q
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
@q <=> other.q
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
s = @coding.dup
|
21
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.4
|
28
|
+
class AcceptEncoding < ListHeader 'Accept-Encoding'
|
29
|
+
def initialize(*coding_ranges)
|
30
|
+
super(coding_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :coding_ranges, :values
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::AcceptEncodingHeader.new.parse(s)
|
37
|
+
Parsers::AcceptEncodingHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Parsers
|
43
|
+
class AcceptEncodingHeader < Parslet::Parser
|
44
|
+
include BasicRules, WeightRules
|
45
|
+
%w(identity compress x-compress deflate gzip x-gzip).each do |c|
|
46
|
+
rule(c.tr('-', '_').to_sym) { str(c).as(:coding) >> sp? }
|
47
|
+
end
|
48
|
+
rule(:coding) { compress | x_compress | deflate | gzip | x_gzip }
|
49
|
+
rule(:wildcard) { str('*').as(:coding) >> sp? }
|
50
|
+
rule(:coding_range) { (coding | identity | wildcard) >> weight.maybe }
|
51
|
+
rule(:accept_encoding) { (coding_range >> (comma >> coding_range).repeat).maybe.as(:accept_encoding) }
|
52
|
+
root(:accept_encoding)
|
53
|
+
end
|
54
|
+
|
55
|
+
class AcceptEncodingHeaderTransform < Parslet::Transform
|
56
|
+
rule(coding: simple(:c), q: simple(:q)) { ContentCodingRange.new(c.str, q.str) }
|
57
|
+
rule(coding: simple(:c)) { ContentCodingRange.new(c.str) }
|
58
|
+
rule(accept_encoding: sequence(:er)) { Headers::AcceptEncoding.new(*er) }
|
59
|
+
rule(accept_encoding: simple(:cc)) { Headers::AcceptEncoding.new(cc) }
|
60
|
+
rule(accept_encoding: nil) { Headers::AcceptEncoding.new }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class LanguageRange
|
6
|
+
attr_reader :language, :q
|
7
|
+
|
8
|
+
DEFAULT_Q = 1.0
|
9
|
+
|
10
|
+
def initialize(language, q = DEFAULT_Q)
|
11
|
+
@language = language
|
12
|
+
@q = Float(q) || DEFAULT_Q
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
@q <=> other.q
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
s = @language.dup
|
21
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.5
|
28
|
+
class AcceptLanguage < ListHeader 'Accept-Language'
|
29
|
+
def initialize(*language_ranges)
|
30
|
+
super(language_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :language_ranges, :values
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::AcceptLanguageHeader.new.parse(s)
|
37
|
+
Parsers::AcceptLanguageHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Parsers
|
43
|
+
class AcceptLanguageHeader < Parslet::Parser
|
44
|
+
include BasicRules, WeightRules
|
45
|
+
rule(:language) { (alpha.repeat(1, 8) >> (str('-') >> alphanum.repeat(1, 8)).maybe).as(:language) >> sp? }
|
46
|
+
rule(:wildcard) { str('*').as(:language) >> sp? }
|
47
|
+
rule(:language_range) { (language | wildcard) >> weight.maybe }
|
48
|
+
rule(:accept_language) { (language_range >> (comma >> language_range).repeat).as(:accept_language) }
|
49
|
+
root(:accept_language)
|
50
|
+
end
|
51
|
+
|
52
|
+
class AcceptLanguageHeaderTransform < Parslet::Transform
|
53
|
+
rule(language: simple(:e), q: simple(:q)) { LanguageRange.new(e.str, q.str) }
|
54
|
+
rule(language: simple(:e)) { LanguageRange.new(e.str) }
|
55
|
+
rule(accept_language: sequence(:lr)) { Headers::AcceptLanguage.new(*lr) }
|
56
|
+
rule(accept_language: simple(:lr)) { Headers::AcceptLanguage.new(lr) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'xenon/headers'
|
3
|
+
require 'xenon/parsers/header_rules'
|
4
|
+
require 'xenon/quoted_string'
|
5
|
+
|
6
|
+
module Xenon
|
7
|
+
class BasicCredentials
|
8
|
+
attr_reader :username, :password
|
9
|
+
|
10
|
+
def initialize(username, password)
|
11
|
+
@username = username
|
12
|
+
@password = password
|
13
|
+
end
|
14
|
+
|
15
|
+
def token
|
16
|
+
Base64.strict_encode64("#{@username}:#{@password}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.decode(s)
|
20
|
+
str = Base64.strict_decode64(s)
|
21
|
+
username, password = str.split(':', 2)
|
22
|
+
BasicCredentials.new(username, password)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"Basic #{token}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class GenericCredentials
|
31
|
+
using QuotedString
|
32
|
+
|
33
|
+
attr_reader :scheme, :token, :params
|
34
|
+
|
35
|
+
def initialize(scheme, token: nil, params: {})
|
36
|
+
@scheme = scheme
|
37
|
+
@token = token
|
38
|
+
@params = params
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
s = @scheme.dup
|
43
|
+
s << ' ' << @token if @token
|
44
|
+
s << ' ' << @params.map { |n, v| "#{n}=#{v.quote}" }.join(', ')
|
45
|
+
s
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Headers
|
50
|
+
# http://tools.ietf.org/html/rfc7235#section-4.2
|
51
|
+
class Authorization < Header 'Authorization'
|
52
|
+
attr_reader :credentials
|
53
|
+
|
54
|
+
def initialize(credentials)
|
55
|
+
@credentials = credentials
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.parse(s)
|
59
|
+
tree = Parsers::AuthorizationHeader.new.parse(s)
|
60
|
+
Parsers::AuthorizationHeaderTransform.new.apply(tree)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
@credentials.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Parsers
|
70
|
+
class AuthorizationHeader < Parslet::Parser
|
71
|
+
include BasicRules
|
72
|
+
rule(:token68) { ((alpha | digit | match(/[\-\._~\+\/]/)) >> str('=').repeat).repeat(1).as(:token) }
|
73
|
+
rule(:auth_scheme) { token.as(:auth_scheme) }
|
74
|
+
rule(:name) { token.as(:name) }
|
75
|
+
rule(:value) { token.as(:value) }
|
76
|
+
rule(:auth_param) { (name >> bws >> str('=') >> bws >> (token | quoted_string).as(:value)).as(:auth_param) }
|
77
|
+
rule(:auth_params) { (auth_param.maybe >> (ows >> comma >> ows >> auth_param).repeat).as(:auth_params) }
|
78
|
+
rule(:credentials) { auth_scheme >> sp >> (token68 | auth_params) }
|
79
|
+
rule(:authorization) { credentials.as(:authorization) }
|
80
|
+
root(:authorization)
|
81
|
+
end
|
82
|
+
|
83
|
+
class AuthorizationHeaderTransform < BasicTransform
|
84
|
+
rule(auth_param: { name: simple(:n), value: simple(:v) }) { [n, v] }
|
85
|
+
rule(auth_params: subtree(:x)) { { foo: x } }
|
86
|
+
rule(auth_scheme: simple(:s), token: simple(:t)) {
|
87
|
+
case s
|
88
|
+
when 'Basic' then BasicCredentials.decode(t)
|
89
|
+
else GenericCredentials.new(s, token: t)
|
90
|
+
end
|
91
|
+
}
|
92
|
+
rule(auth_scheme: simple(:s), auth_params: subtree(:p)) { GenericCredentials.new(s, params: Hash[p]) }
|
93
|
+
rule(authorization: simple(:c)) { Headers::Authorization.new(c) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
require 'xenon/quoted_string'
|
4
|
+
|
5
|
+
module Xenon
|
6
|
+
class CacheDirective
|
7
|
+
using QuotedString
|
8
|
+
|
9
|
+
attr_reader :name, :value
|
10
|
+
|
11
|
+
def initialize(name, value = nil)
|
12
|
+
@name = name
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
s = @name.dup
|
18
|
+
s << '=' << @value.quote if @value
|
19
|
+
s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Headers
|
24
|
+
# http://tools.ietf.org/html/rfc7234#section-5.2
|
25
|
+
class CacheControl < ListHeader 'Cache-Control'
|
26
|
+
def initialize(*directives)
|
27
|
+
super(directives)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :directives, :values
|
31
|
+
|
32
|
+
def self.parse(s)
|
33
|
+
tree = Parsers::CacheControlHeader.new.parse(s)
|
34
|
+
Parsers::CacheControlHeaderTransform.new.apply(tree)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Parsers
|
40
|
+
class CacheControlHeader < Parslet::Parser
|
41
|
+
include BasicRules
|
42
|
+
rule(:name) { token.as(:name) }
|
43
|
+
rule(:value) { str('=') >> (token | quoted_string).as(:value) }
|
44
|
+
rule(:directive) { (name >> value.maybe).as(:directive) >> sp? }
|
45
|
+
rule(:cache_control) { (directive >> (comma >> directive).repeat).as(:cache_control) }
|
46
|
+
root(:cache_control)
|
47
|
+
end
|
48
|
+
|
49
|
+
class CacheControlHeaderTransform < BasicTransform
|
50
|
+
rule(directive: { name: simple(:n), value: simple(:v) }) { CacheDirective.new(n, v) }
|
51
|
+
rule(directive: { name: simple(:n) }) { CacheDirective.new(n) }
|
52
|
+
rule(cache_control: sequence(:d)) { Headers::CacheControl.new(*d) }
|
53
|
+
rule(cache_control: simple(:d)) { Headers::CacheControl.new(d) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/media_type'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class Headers
|
6
|
+
# http://tools.ietf.org/html/rfc7231#section-3.1.1.5
|
7
|
+
class ContentType < Header 'Content-Type'
|
8
|
+
attr_reader :content_type
|
9
|
+
|
10
|
+
def initialize(content_type)
|
11
|
+
@content_type = content_type
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(s)
|
15
|
+
ContentType.new(Xenon::ContentType.parse(s))
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@content_type.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'xenon/errors'
|
2
|
+
require 'xenon/parsers/media_type'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
|
6
|
+
class MediaType
|
7
|
+
attr_reader :type, :subtype, :params
|
8
|
+
|
9
|
+
def initialize(type, subtype, params = {})
|
10
|
+
@type = type
|
11
|
+
@subtype = subtype
|
12
|
+
@params = params
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse(s)
|
16
|
+
tree = Parsers::MediaType.new.parse(s)
|
17
|
+
Parsers::MediaTypeTransform.new.apply(tree)
|
18
|
+
rescue Parslet::ParseFailed
|
19
|
+
raise Xenon::ParseError.new("Invalid media type (#{s})")
|
20
|
+
end
|
21
|
+
|
22
|
+
%w(application audio image message multipart text video).each do |type|
|
23
|
+
define_method "#{type}?" do
|
24
|
+
@type == type
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def experimental?
|
29
|
+
@subtype.start_with?('x.') # not x- see http://tools.ietf.org/html/rfc6838#section-3.4
|
30
|
+
end
|
31
|
+
|
32
|
+
def personal?
|
33
|
+
@subtype.start_with?('prs.')
|
34
|
+
end
|
35
|
+
|
36
|
+
def vendor?
|
37
|
+
@subtype.start_with?('vnd.')
|
38
|
+
end
|
39
|
+
|
40
|
+
%w(ber der fastinfoset json wbxml xml zip).each do |format|
|
41
|
+
define_method "#{format}?" do
|
42
|
+
@subtype == format || @subtype.end_with?("+#{format}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_q(q)
|
47
|
+
MediaRange.new(self, q)
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_charset(charset)
|
51
|
+
ContentType.new(self, charset)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#{@type}/#{@subtype}" << @params.map { |n, v| v ? "; #{n}=#{v}" : "; #{n}" }.join
|
56
|
+
end
|
57
|
+
|
58
|
+
JSON = MediaType.new('application', 'json')
|
59
|
+
XML = MediaType.new('application', 'xml')
|
60
|
+
end
|
61
|
+
|
62
|
+
class ContentType
|
63
|
+
attr_reader :media_type, :charset
|
64
|
+
|
65
|
+
DEFAULT_CHARSET = 'utf-8' # historically iso-8859-1 but see http://tools.ietf.org/html/rfc7231#appendix-B
|
66
|
+
|
67
|
+
def initialize(media_type, charset = DEFAULT_CHARSET)
|
68
|
+
@media_type = media_type
|
69
|
+
@charset = charset
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.parse(s)
|
73
|
+
media_type = MediaType.parse(s)
|
74
|
+
charset = media_type.params.delete('charset') || DEFAULT_CHARSET
|
75
|
+
ContentType.new(media_type, charset)
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
"#{@media_type}; charset=#{@charset}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class MediaRange
|
84
|
+
include Comparable
|
85
|
+
|
86
|
+
DEFAULT_Q = 1.0
|
87
|
+
|
88
|
+
attr_reader :type, :subtype, :q, :params
|
89
|
+
|
90
|
+
def initialize(type, subtype, params = {})
|
91
|
+
@type = type
|
92
|
+
@subtype = subtype
|
93
|
+
@q = Float(params.delete('q')) rescue DEFAULT_Q
|
94
|
+
@params = params
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.parse(s)
|
98
|
+
tree = Parsers::MediaRange.new.parse(s)
|
99
|
+
Parsers::MediaTypeTransform.new.apply(tree)
|
100
|
+
rescue Parslet::ParseFailed
|
101
|
+
raise Xenon::ParseError.new("Invalid media range (#{s})")
|
102
|
+
end
|
103
|
+
|
104
|
+
def <=>(other)
|
105
|
+
dt = compare_types(@type, other.type)
|
106
|
+
return dt if dt != 0
|
107
|
+
ds = compare_types(@subtype, other.subtype)
|
108
|
+
return ds if ds != 0
|
109
|
+
dp = params.size <=> other.params.size
|
110
|
+
return dp if dp != 0
|
111
|
+
@q <=> other.q
|
112
|
+
end
|
113
|
+
|
114
|
+
def =~(media_type)
|
115
|
+
(type == '*' || type == media_type.type) &&
|
116
|
+
(subtype == '*' || subtype == media_type.subtype) &&
|
117
|
+
params.all? { |n, v| media_type.params[n] == v }
|
118
|
+
end
|
119
|
+
|
120
|
+
alias_method :===, :=~
|
121
|
+
|
122
|
+
def to_s
|
123
|
+
s = "#{@type}/#{@subtype}"
|
124
|
+
s << @params.map { |n, v| v ? "; #{n}=#{v}" : "; #{n}" }.join
|
125
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
126
|
+
s
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def compare_types(a, b)
|
132
|
+
if a == b then 0
|
133
|
+
elsif a == '*' then -1
|
134
|
+
elsif b == '*' then 1
|
135
|
+
else 0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|