rack-accept 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1 / April 2010
2
+
3
+ * Initial release
data/README ADDED
@@ -0,0 +1,70 @@
1
+ Rack::Accept
2
+ ============
3
+
4
+ rack-accept is a suite of tools for Rack/Ruby applications that eases the
5
+ complexity of reading and writing Accept, Accept-Charset, Accept-Encoding, and
6
+ Accept-Language HTTP request headers.
7
+
8
+ Installation
9
+ ------------
10
+
11
+ Using RubyGems:
12
+
13
+ $ sudo gem install rack-accept
14
+
15
+ From a local copy:
16
+
17
+ $ git clone git://github.com/mjijackson/rack-accept.git
18
+ $ rake package && sudo rake install
19
+
20
+ Usage
21
+ -----
22
+
23
+ rack-accept implements the Rack middleware interface and may be used with any
24
+ Rack-based application. Simply insert the Rack::Accept module in your Rack
25
+ middleware pipeline and access the Rack::Accept::Request object in the
26
+ "rack-accept.request" environment key, as in the following example:
27
+
28
+ require 'rack/accept'
29
+
30
+ use Rack::Accept
31
+
32
+ app = lambda {|env|
33
+ accept = env['rack-accept.request']
34
+ response = Rack::Response.new
35
+
36
+ if accept.media_type?('text/html')
37
+ response['Content-Type'] = 'text/html'
38
+ response.write "<p>Hello. You accept text/html!</p>"
39
+ else
40
+ response['Content-Type'] = 'text/plain'
41
+ response.write "Apparently you don't accept text/html. Too bad."
42
+ end
43
+
44
+ response.finish
45
+ }
46
+
47
+ run app
48
+
49
+ License
50
+ -------
51
+
52
+ Copyright 2010 Michael J. I. Jackson
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining a copy
55
+ of this software and associated documentation files (the "Software"), to deal
56
+ in the Software without restriction, including without limitation the rights
57
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
58
+ copies of the Software, and to permit persons to whom the Software is
59
+ furnished to do so, subject to the following conditions:
60
+
61
+ The above copyright notice and this permission notice shall be included in
62
+ all copies or substantial portions of the Software.
63
+
64
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
65
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
66
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
67
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
68
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
69
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
70
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,78 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+
4
+ task :default => :test
5
+
6
+ CLEAN.include %w< doc/api >
7
+ CLOBBER.include %w< dist >
8
+
9
+ # TESTS #######################################################################
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.test_files = FileList['test/*_test.rb']
13
+ end
14
+
15
+ # DOCS ########################################################################
16
+
17
+ desc "Generate all documentation"
18
+ task :doc => %w< doc:api doc:etc >
19
+
20
+ namespace :doc do
21
+
22
+ desc "Generate API documentation (in doc/api)"
23
+ task :api => FileList['lib/**/*.rb'] do |t|
24
+ rm_rf 'doc/api'
25
+ sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
26
+ hanna
27
+ --op doc/api
28
+ --promiscuous
29
+ --charset utf8
30
+ --fmt html
31
+ --inline-source
32
+ --line-numbers
33
+ --accessor option_accessor=RW
34
+ --main Rack::Accept
35
+ --title 'Rack::Accept API Documentation'
36
+ #{t.prerequisites.join(' ')}
37
+ SH
38
+ end
39
+
40
+ desc "Generate extra documentation"
41
+ task :etc do
42
+ end
43
+
44
+ end
45
+
46
+ # PACKAGING ###################################################################
47
+
48
+ if defined?(Gem)
49
+ $spec = eval("#{File.read('rack-accept.gemspec')}")
50
+
51
+ def package(ext='')
52
+ "dist/rack-accept-#{$spec.version}" + ext
53
+ end
54
+
55
+ desc "Build packages"
56
+ task :package => %w< .gem .tar.gz >.map {|e| package(e) }
57
+
58
+ desc "Build and install as local gem"
59
+ task :install => package('.gem') do
60
+ sh "gem install #{package('.gem')}"
61
+ end
62
+
63
+ directory 'dist/'
64
+
65
+ file package('.gem') => %w< dist/ rack-accept.gemspec > + $spec.files do |f|
66
+ sh "gem build rack-accept.gemspec"
67
+ mv File.basename(f.name), f.name
68
+ end
69
+
70
+ file package('.tar.gz') => %w< dist/ > + $spec.files do |f|
71
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
72
+ end
73
+
74
+ desc "Upload gem to rubygems.org"
75
+ task :release => package('.gem') do |t|
76
+ sh "gem push #{package('.gem')}"
77
+ end
78
+ end
@@ -0,0 +1,24 @@
1
+ module Rack::Accept
2
+
3
+ # The current version of rack-accept.
4
+ VERSION = [0, 1]
5
+
6
+ # Returns the current version of rack-accept as a string.
7
+ def self.version
8
+ VERSION.join('.')
9
+ end
10
+
11
+ # Enables Rack::Accept to be used as a Rack middleware.
12
+ def self.new(app, &block)
13
+ Context.new(app, &block)
14
+ end
15
+
16
+ autoload :Charset, 'rack/accept/charset'
17
+ autoload :Context, 'rack/accept/context'
18
+ autoload :Encoding, 'rack/accept/encoding'
19
+ autoload :Header, 'rack/accept/header'
20
+ autoload :Language, 'rack/accept/language'
21
+ autoload :MediaType, 'rack/accept/media_type'
22
+ autoload :Request, 'rack/accept/request'
23
+
24
+ end
@@ -0,0 +1,63 @@
1
+ module Rack::Accept
2
+
3
+ # Represents an HTTP Accept-Charset header according to the HTTP 1.1
4
+ # specification, and provides several convenience methods for determining
5
+ # acceptable character sets.
6
+ #
7
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more
8
+ # information.
9
+ class Charset
10
+
11
+ include Header
12
+
13
+ attr_reader :qvalues
14
+
15
+ def initialize(header)
16
+ @qvalues = parse(header)
17
+ end
18
+
19
+ # The name of this header.
20
+ def name
21
+ 'Accept-Charset'
22
+ end
23
+
24
+ # The value of this header, built from its internal representation.
25
+ def value
26
+ join(@qvalues)
27
+ end
28
+
29
+ # Returns an array of all character set values that were specified in the
30
+ # original header, in no particular order.
31
+ def values
32
+ @qvalues.keys
33
+ end
34
+
35
+ # Determines the quality factor (qvalue) of the given +charset+,
36
+ # according to the specifications of the original header.
37
+ def qvalue(charset)
38
+ m = matches(charset)
39
+ if m.empty?
40
+ charset == 'iso-8859-1' ? 1 : 0
41
+ else
42
+ @qvalues[m.first]
43
+ end
44
+ end
45
+
46
+ # Returns an array of character sets from the original header that match
47
+ # the given +charset+, ordered by precedence.
48
+ def matches(charset)
49
+ values.select {|v|
50
+ v == charset || v == '*'
51
+ }.sort {|a, b|
52
+ # "*" gets least precedence, any others should be equal.
53
+ a == '*' ? 1 : (b == '*' ? -1 : 0)
54
+ }
55
+ end
56
+
57
+ # Returns a string representation of this header.
58
+ def to_s
59
+ [name, value].join(': ')
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ module Rack::Accept
2
+
3
+ # Implements the Rack middleware interface.
4
+ class Context
5
+
6
+ attr_reader :app
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ yield self if block_given?
11
+ end
12
+
13
+ # Inserts a new Rack::Accept::Request object into the environment before
14
+ # handing the request to the app immediately downstream.
15
+ def call(env)
16
+ env['rack-accept.request'] ||= Request.new(env)
17
+ @app.call(env)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ module Rack::Accept
2
+
3
+ # Represents an HTTP Accept-Encoding header according to the HTTP 1.1
4
+ # specification, and provides several convenience methods for determining
5
+ # acceptable content encodings.
6
+ #
7
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more
8
+ # information.
9
+ class Encoding
10
+
11
+ include Header
12
+
13
+ attr_reader :qvalues
14
+
15
+ def initialize(header)
16
+ @qvalues = parse(header)
17
+ end
18
+
19
+ # The name of this header.
20
+ def name
21
+ 'Accept-Encoding'
22
+ end
23
+
24
+ # The value of this header, built from its internal representation.
25
+ def value
26
+ join(@qvalues)
27
+ end
28
+
29
+ # Returns an array of all encoding values that were specified in the
30
+ # original header, in no particular order.
31
+ def values
32
+ @qvalues.keys
33
+ end
34
+
35
+ # Determines the quality factor (qvalue) of the given +encoding+,
36
+ # according to the specifications of the original header.
37
+ def qvalue(encoding)
38
+ m = matches(encoding)
39
+ if m.empty?
40
+ encoding == 'identity' ? 1 : 0
41
+ else
42
+ @qvalues[m.first]
43
+ end
44
+ end
45
+
46
+ # Returns an array of encodings from the original header that match
47
+ # the given +encoding+, ordered by precedence.
48
+ def matches(encoding)
49
+ values.select {|v|
50
+ v == encoding || v == '*'
51
+ }.sort {|a, b|
52
+ # "*" gets least precedence, any others should be equal.
53
+ a == '*' ? 1 : (b == '*' ? -1 : 0)
54
+ }
55
+ end
56
+
57
+ # Returns a string representation of this header.
58
+ def to_s
59
+ [name, value].join(': ')
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,96 @@
1
+ module Rack::Accept
2
+
3
+ # Contains methods that are useful for working with Accept-style HTTP
4
+ # headers. The MediaType, Charset, Encoding, and Language classes all mixin
5
+ # this module.
6
+ module Header
7
+
8
+ # Parses the value of an Accept-style request header into a hash of
9
+ # acceptable values and their respective quality factors (qvalues). The
10
+ # +join+ method may be used on the resulting hash to obtain a header
11
+ # string that is the semantic equivalent of the one provided.
12
+ def parse(header)
13
+ qvalues = {}
14
+
15
+ header.to_s.split(/,\s*/).map do |part|
16
+ m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
17
+
18
+ if m
19
+ qvalues[m[1]] = (m[2] || 1).to_f
20
+ else
21
+ raise "Invalid header value: #{part.inspect}"
22
+ end
23
+ end
24
+
25
+ qvalues
26
+ end
27
+ module_function :parse
28
+
29
+ # Returns a string suitable for use as the value of an Accept-style HTTP
30
+ # header from the map of acceptable values to their respective quality
31
+ # factors (qvalues). The +parse+ method may be used on the resulting string
32
+ # to obtain a hash that is the equivalent of the one provided.
33
+ def join(qvalues)
34
+ qvalues.map {|k, v| k + (v == 1 ? '' : ";q=#{v}") }.join(', ')
35
+ end
36
+ module_function :join
37
+
38
+ # Parses a media type string into its relevant pieces. The return value
39
+ # will be an array with three values: 1) the content type, 2) the content
40
+ # subtype, and 3) the media type parameters. An empty array is returned if
41
+ # no match can be made.
42
+ def parse_media_type(media_type)
43
+ m = media_type.to_s.match(/^([a-z*]+)\/([a-z*-]+)(?:;([a-z0-9=]+))?$/)
44
+ m ? [m[1], m[2], m[3] || ''] : []
45
+ end
46
+ module_function :parse_media_type
47
+
48
+ module PublicInstanceMethods
49
+ # Returns the quality factor (qvalue) of the given +value+. This method
50
+ # is the only method that must be overridden in child classes in order
51
+ # for them to be able to use all other methods of this module.
52
+ def qvalue(value)
53
+ 1
54
+ end
55
+
56
+ # Determines if the given +value+ is acceptable (does not have a qvalue
57
+ # of 0) according to this header.
58
+ def accept?(value)
59
+ qvalue(value) != 0
60
+ end
61
+
62
+ # Returns a copy of the given +values+ array, sorted by quality factor
63
+ # (qvalue). Each element of the returned array is itself an array
64
+ # containing two objects: 1) the value's qvalue and 2) the original
65
+ # value.
66
+ #
67
+ # It is important to note that when performing this sort the order of
68
+ # the original values is preserved so long as the qvalue for each input
69
+ # value is the same. This expectation can be useful for example when
70
+ # trying to determine which of a variety of options has the highest
71
+ # qvalue. If the user prefers using one option over another (for any
72
+ # number of reasons), he should put it first in +values+. He may then
73
+ # use the first result with confidence that it is both most acceptable
74
+ # to the user and most convenient for him as well.
75
+ def sort(values)
76
+ values.map {|v| [ qvalue(v), v ] }.sort.reverse
77
+ end
78
+
79
+ # Determines the most preferred value to use of those provided in
80
+ # +values+. See the documentation for #sort for more information on
81
+ # exactly how the sorting is done.
82
+ #
83
+ # If +keep_unacceptables+ is false (the default) and no values are
84
+ # acceptable the return value will be +nil+. Otherwise, the most
85
+ # acceptable value will be returned.
86
+ def best_of(values, keep_unacceptables=false)
87
+ s = sort(values)
88
+ s.reject! {|q, v| q == 0 } unless keep_unacceptables
89
+ s.first && s.first[1]
90
+ end
91
+ end
92
+
93
+ include PublicInstanceMethods
94
+
95
+ end
96
+ end
@@ -0,0 +1,61 @@
1
+ module Rack::Accept
2
+
3
+ # Represents an HTTP Accept-Language header according to the HTTP 1.1
4
+ # specification, and provides several convenience methods for determining
5
+ # acceptable content languages.
6
+ #
7
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more
8
+ # information.
9
+ class Language
10
+
11
+ include Header
12
+
13
+ attr_reader :qvalues
14
+
15
+ def initialize(header)
16
+ @qvalues = parse(header)
17
+ end
18
+
19
+ # The name of this header.
20
+ def name
21
+ 'Accept-Language'
22
+ end
23
+
24
+ # The value of this header, built from its internal representation.
25
+ def value
26
+ join(@qvalues)
27
+ end
28
+
29
+ # Returns an array of all language values that were specified in the
30
+ # original header, in no particular order.
31
+ def values
32
+ @qvalues.keys
33
+ end
34
+
35
+ # Determines the quality factor (qvalue) of the given +language+,
36
+ # according to the specifications of the original header.
37
+ def qvalue(language)
38
+ return 1 if @qvalues.empty?
39
+ m = matches(language)
40
+ return 0 if m.empty?
41
+ @qvalues[m.first]
42
+ end
43
+
44
+ # Returns an array of languages from the original header that match
45
+ # the given +language+, ordered by precedence.
46
+ def matches(language)
47
+ values.select {|v|
48
+ v == language || v == '*' || (language.match(/^(.+?)-/) && v == $1)
49
+ }.sort {|a, b|
50
+ # "*" gets least precedence, any others are compared based on length.
51
+ a == '*' ? -1 : (b == '*' ? 1 : a.length <=> b.length)
52
+ }.reverse
53
+ end
54
+
55
+ # Returns a string representation of this header.
56
+ def to_s
57
+ [name, value].join(': ')
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,67 @@
1
+ module Rack::Accept
2
+
3
+ # Represents an HTTP Accept header according to the HTTP 1.1 specification,
4
+ # and provides several convenience methods for determining acceptable media
5
+ # types.
6
+ #
7
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more
8
+ # information.
9
+ class MediaType
10
+
11
+ include Header
12
+
13
+ attr_reader :qvalues
14
+
15
+ def initialize(header)
16
+ @qvalues = parse(header)
17
+ end
18
+
19
+ # The name of this header.
20
+ def name
21
+ 'Accept'
22
+ end
23
+
24
+ # The value of this header, built from its internal representation.
25
+ def value
26
+ join(@qvalues)
27
+ end
28
+
29
+ # Returns an array of all media type values that were specified in the
30
+ # original header, in no particular order.
31
+ def values
32
+ @qvalues.keys
33
+ end
34
+
35
+ # Determines the quality factor (qvalue) of the given +media_type+,
36
+ # according to the specifications of the original header.
37
+ def qvalue(media_type)
38
+ return 1 if @qvalues.empty?
39
+ m = matches(media_type)
40
+ return 0 if m.empty?
41
+ @qvalues[m.first]
42
+ end
43
+
44
+ # Returns an array of media types from the original header that match
45
+ # the given +media_type+, ordered by precedence.
46
+ def matches(media_type)
47
+ type, subtype, params = parse_media_type(media_type)
48
+ values.select {|v|
49
+ if v == media_type || v == '*/*'
50
+ true
51
+ else
52
+ t, s, p = parse_media_type(v)
53
+ t == type && (s == subtype || s == '*') && (p == params || p == '')
54
+ end
55
+ }.sort_by {|v|
56
+ # Most specific gets precedence.
57
+ v.length
58
+ }.reverse
59
+ end
60
+
61
+ # Returns a string representation of this header.
62
+ def to_s
63
+ [name, value].join(': ')
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,96 @@
1
+ module Rack::Accept
2
+
3
+ # A container class for convenience methods when rack-accept is used on the
4
+ # request level (as a Rack middleware, for example). This class manages a
5
+ # lightweight cache of various header instances to speed up execution.
6
+ #
7
+ # This class currently does not extend Rack::Request because that class
8
+ # currently provides some limited functionality when it comes to determining
9
+ # the proper encoding to use with a request and I didn't want to confuse the
10
+ # user.
11
+ class Request
12
+
13
+ attr_reader :env
14
+
15
+ def initialize(env)
16
+ @env = env
17
+ end
18
+
19
+ # Provides access to the Rack::Accept::MediaType instance for this request.
20
+ def media_type
21
+ @media_type ||= MediaType.new(env['HTTP_ACCEPT'])
22
+ end
23
+
24
+ # Provides access to the Rack::Accept::Charset instance for this request.
25
+ def charset
26
+ @charset ||= Charset.new(env['HTTP_ACCEPT_CHARSET'])
27
+ end
28
+
29
+ # Provides access to the Rack::Accept::Encoding instance for this request.
30
+ def encoding
31
+ @encoding ||= Encoding.new(env['HTTP_ACCEPT_ENCODING'])
32
+ end
33
+
34
+ # Provides access to the Rack::Accept::Language instance for this request.
35
+ def language
36
+ @language ||= Language.new(env['HTTP_ACCEPT_LANGUAGE'])
37
+ end
38
+
39
+ # Returns true if the +Accept+ request header indicates the given media
40
+ # type is acceptable, false otherwise.
41
+ def media_type?(value)
42
+ media_type.accept?(value)
43
+ end
44
+
45
+ # Returns true if the +Accept-Charset+ request header indicates the given
46
+ # character set is acceptable, false otherwise.
47
+ def charset?(value)
48
+ charset.accept?(value)
49
+ end
50
+
51
+ # Returns true if the +Accept-Encoding+ request header indicates the given
52
+ # encoding is acceptable, false otherwise.
53
+ def encoding?(value)
54
+ encoding.accept?(value)
55
+ end
56
+
57
+ # Returns true if the +Accept-Language+ request header indicates the given
58
+ # language is acceptable, false otherwise.
59
+ def language?(value)
60
+ language.accept?(value)
61
+ end
62
+
63
+ # Determines the best media type to use in a response from the given media
64
+ # types, if any is acceptable. For more information on how this value is
65
+ # determined, see the documentation for
66
+ # Rack::Accept::Header::PublicInstanceMethods#sort.
67
+ def best_media_type(values)
68
+ media_type.best_of(values)
69
+ end
70
+
71
+ # Determines the best character set to use in a response from the given
72
+ # character sets, if any is acceptable. For more information on how this
73
+ # value is determined, see the documentation for
74
+ # Rack::Accept::Header::PublicInstanceMethods#sort.
75
+ def best_charset(values)
76
+ charset.best_of(values)
77
+ end
78
+
79
+ # Determines the best encoding to use in a response from the given
80
+ # encodings, if any is acceptable. For more information on how this value
81
+ # is determined, see the documentation for
82
+ # Rack::Accept::Header::PublicInstanceMethods#sort.
83
+ def best_encoding(values)
84
+ encoding.best_of(values)
85
+ end
86
+
87
+ # Determines the best language to use in a response from the given
88
+ # languages, if any is acceptable. For more information on how this value
89
+ # is determined, see the documentation for
90
+ # Rack::Accept::Header::PublicInstanceMethods#sort.
91
+ def best_language(values)
92
+ language.best_of(values)
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rack-accept'
3
+ s.version = '0.1'
4
+ s.date = '2010-04-01'
5
+
6
+ s.summary = 'HTTP Accept* tools for Rack'
7
+ s.description = 'HTTP Accept, Accept-Charset, Accept-Encoding, and Accept-Language tools for Rack'
8
+
9
+ s.author = 'Michael J. I. Jackson'
10
+ s.email = 'mjijackson@gmail.com'
11
+
12
+ s.require_paths = %w< lib >
13
+
14
+ s.files = Dir['lib/**/*.rb'] +
15
+ Dir['test/*.rb'] +
16
+ %w< CHANGES rack-accept.gemspec Rakefile README >
17
+
18
+ s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/ }
19
+
20
+ s.add_dependency('rack', '>= 0.4')
21
+ s.add_development_dependency 'rake'
22
+
23
+ s.has_rdoc = true
24
+ s.rdoc_options = %w< --line-numbers --inline-source --title Rack::Accept --main Rack::Accept >
25
+ s.extra_rdoc_files = %w< CHANGES README >
26
+
27
+ s.homepage = 'http://github.com/mjijackson/rack-accept'
28
+
29
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class CharsetTest < Test::Unit::TestCase
4
+
5
+ C = Rack::Accept::Charset
6
+
7
+ def test_qvalue
8
+ c = C.new('')
9
+ assert_equal(0, c.qvalue('unicode-1-1'))
10
+ assert_equal(1, c.qvalue('iso-8859-1'))
11
+
12
+ c = C.new('unicode-1-1')
13
+ assert_equal(1, c.qvalue('unicode-1-1'))
14
+ assert_equal(0, c.qvalue('iso-8859-5'))
15
+ assert_equal(1, c.qvalue('iso-8859-1'))
16
+
17
+ c = C.new('unicode-1-1, *;q=0.5')
18
+ assert_equal(1, c.qvalue('unicode-1-1'))
19
+ assert_equal(0.5, c.qvalue('iso-8859-5'))
20
+ assert_equal(0.5, c.qvalue('iso-8859-1'))
21
+
22
+ c = C.new('iso-8859-1;q=0, *;q=0.5')
23
+ assert_equal(0.5, c.qvalue('iso-8859-5'))
24
+ assert_equal(0, c.qvalue('iso-8859-1'))
25
+ end
26
+
27
+ def test_matches
28
+ c = C.new('iso-8859-1, iso-8859-5, *')
29
+ assert_equal(%w{*}, c.matches(''))
30
+ assert_equal(%w{iso-8859-1 *}, c.matches('iso-8859-1'))
31
+ assert_equal(%w{*}, c.matches('unicode-1-1'))
32
+ end
33
+
34
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class EncodingTest < Test::Unit::TestCase
4
+
5
+ E = Rack::Accept::Encoding
6
+
7
+ def test_qvalue
8
+ e = E.new('')
9
+ assert_equal(0, e.qvalue('gzip'))
10
+ assert_equal(1, e.qvalue('identity'))
11
+
12
+ e = E.new('gzip, *;q=0.5')
13
+ assert_equal(1, e.qvalue('gzip'))
14
+ assert_equal(0.5, e.qvalue('identity'))
15
+ end
16
+
17
+ def test_matches
18
+ e = E.new('gzip, identity, *')
19
+ assert_equal(%w{*}, e.matches(''))
20
+ assert_equal(%w{gzip *}, e.matches('gzip'))
21
+ assert_equal(%w{*}, e.matches('compress'))
22
+ end
23
+
24
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class HeaderTest < Test::Unit::TestCase
4
+
5
+ H = Rack::Accept::Header
6
+
7
+ def test_parse_and_join
8
+ # Accept
9
+ header = 'text/plain; q=0.5, text/html, text/html;level=2, text/html;level=1;q=0.3, text/x-c, image/*; q=0.2'
10
+ expect = {
11
+ 'text/plain' => 0.5,
12
+ 'text/html' => 1,
13
+ 'text/html;level=2' => 1,
14
+ 'text/html;level=1' => 0.3,
15
+ 'text/x-c' => 1,
16
+ 'image/*' => 0.2
17
+ }
18
+ assert_equal(expect, H.parse(header))
19
+ assert_equal(expect, H.parse(H.join(expect)))
20
+
21
+ # Accept-Charset
22
+ header = 'iso-8859-5, unicode-1-1;q=0.8'
23
+ expect = { 'iso-8859-5' => 1, 'unicode-1-1' => 0.8 }
24
+ assert_equal(expect, H.parse(header))
25
+ assert_equal(expect, H.parse(H.join(expect)))
26
+
27
+ # Accept-Encoding
28
+ header = 'gzip;q=1.0, identity; q=0.5, *;q=0'
29
+ expect = { 'gzip' => 1, 'identity' => 0.5, '*' => 0 }
30
+ assert_equal(expect, H.parse(header))
31
+ assert_equal(expect, H.parse(H.join(expect)))
32
+
33
+ # Accept-Language
34
+ header = 'da, en-gb;q=0.8, en;q=0.7'
35
+ expect = { 'da' => 1, 'en-gb' => 0.8, 'en' => 0.7 }
36
+ assert_equal(expect, H.parse(header))
37
+ assert_equal(expect, H.parse(H.join(expect)))
38
+ end
39
+
40
+ def test_parse_media_type
41
+ assert_equal([], H.parse_media_type(''))
42
+ assert_equal(['*', '*', ''], H.parse_media_type('*/*'))
43
+ assert_equal(['text', '*', ''], H.parse_media_type('text/*'))
44
+ assert_equal(['text', 'html', ''], H.parse_media_type('text/html'))
45
+ assert_equal(['text', 'html', 'level=1'], H.parse_media_type('text/html;level=1'))
46
+ assert_equal(['text', 'x-dvi', ''], H.parse_media_type('text/x-dvi'))
47
+ end
48
+
49
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ begin
4
+ require 'rack'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'rack'
8
+ end
9
+
10
+ testdir = File.dirname(__FILE__)
11
+ $LOAD_PATH.unshift(testdir) unless $LOAD_PATH.include?(testdir)
12
+
13
+ libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
14
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
15
+
16
+ require 'test/unit'
17
+ require 'rack/accept'
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class LanguageTest < Test::Unit::TestCase
4
+
5
+ L = Rack::Accept::Language
6
+
7
+ def test_qvalue
8
+ l = L.new('')
9
+ assert_equal(1, l.qvalue('en'))
10
+
11
+ l = L.new('en;q=0.5, en-gb')
12
+ assert_equal(0.5, l.qvalue('en'))
13
+ assert_equal(1, l.qvalue('en-gb'))
14
+ assert_equal(0, l.qvalue('da'))
15
+ end
16
+
17
+ def test_matches
18
+ l = L.new('da, *, en')
19
+ assert_equal(%w{*}, l.matches(''))
20
+ assert_equal(%w{da *}, l.matches('da'))
21
+ assert_equal(%w{en *}, l.matches('en'))
22
+ assert_equal(%w{en *}, l.matches('en-gb'))
23
+ assert_equal(%w{*}, l.matches('eng'))
24
+
25
+ l = L.new('en, en-gb')
26
+ assert_equal(%w{en-gb en}, l.matches('en-gb'))
27
+ end
28
+
29
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class MediaTypeTest < Test::Unit::TestCase
4
+
5
+ M = Rack::Accept::MediaType
6
+
7
+ def test_qvalue
8
+ m = M.new('text/html, text/*;q=0.3, */*;q=0.5')
9
+ assert_equal(0.5, m.qvalue('image/png'))
10
+ assert_equal(0.3, m.qvalue('text/plain'))
11
+ assert_equal(1, m.qvalue('text/html'))
12
+
13
+ m = M.new('text/html')
14
+ assert_equal(0, m.qvalue('image/png'))
15
+
16
+ m = M.new('')
17
+ assert_equal(1, m.qvalue('text/html'))
18
+ end
19
+
20
+ def test_matches
21
+ m = M.new('text/*, text/html, text/html;level=1, */*')
22
+ assert_equal(%w{*/*}, m.matches(''))
23
+ assert_equal(%w{*/*}, m.matches('image/jpeg'))
24
+ assert_equal(%w{text/* */*}, m.matches('text/plain'))
25
+ assert_equal(%w{text/html text/* */*}, m.matches('text/html'))
26
+ assert_equal(%w{text/html;level=1 text/html text/* */*}, m.matches('text/html;level=1'))
27
+ end
28
+
29
+ end
@@ -0,0 +1,93 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class RequestTest < Test::Unit::TestCase
4
+
5
+ R = Rack::Accept::Request
6
+
7
+ def test_media_type
8
+ request = R.new('HTTP_ACCEPT' => 'text/*;q=0, text/html')
9
+ assert(request.media_type?('text/html'))
10
+ assert(request.media_type?('text/html;level=1'))
11
+ assert(!request.media_type?('text/plain'))
12
+ assert(!request.media_type?('image/png'))
13
+
14
+ request = R.new('HTTP_ACCEPT' => '*/*')
15
+ assert(request.media_type?('image/png'))
16
+ end
17
+
18
+ def test_best_media_type
19
+ request = R.new('HTTP_ACCEPT' => 'text/*;q=0.5, text/html')
20
+ assert_equal('text/html', request.best_media_type(%w< text/plain text/html >))
21
+ assert_equal('text/plain', request.best_media_type(%w< text/plain image/png >))
22
+ assert_equal('text/plain', request.best_media_type(%w< text/plain text/javascript >))
23
+ assert_equal(nil, request.best_media_type(%w< image/png >))
24
+ end
25
+
26
+ def test_charset
27
+ request = R.new('HTTP_ACCEPT_CHARSET' => 'iso-8859-5, unicode-1-1;q=0.8')
28
+ assert(request.charset?('iso-8859-5'))
29
+ assert(request.charset?('unicode-1-1'))
30
+ assert(request.charset?('iso-8859-1'))
31
+ assert(!request.charset?('utf-8'))
32
+
33
+ request = R.new('HTTP_ACCEPT_CHARSET' => 'iso-8859-1;q=0')
34
+ assert(!request.charset?('iso-8859-1'))
35
+ end
36
+
37
+ def test_best_charset
38
+ request = R.new('HTTP_ACCEPT_CHARSET' => 'iso-8859-5, unicode-1-1;q=0.8')
39
+ assert_equal('iso-8859-5', request.best_charset(%w< iso-8859-5 unicode-1-1 >))
40
+ assert_equal('iso-8859-5', request.best_charset(%w< iso-8859-5 utf-8 >))
41
+ assert_equal('iso-8859-1', request.best_charset(%w< iso-8859-1 utf-8 >))
42
+ assert_equal(nil, request.best_charset(%w< utf-8 >))
43
+ end
44
+
45
+ def test_encoding
46
+ request = R.new('HTTP_ACCEPT_ENCODING' => '')
47
+ assert(request.encoding?('identity'))
48
+ assert(!request.encoding?('gzip'))
49
+
50
+ request = R.new('HTTP_ACCEPT_ENCODING' => 'gzip')
51
+ assert(request.encoding?('identity'))
52
+ assert(request.encoding?('gzip'))
53
+ assert(!request.encoding?('compress'))
54
+
55
+ request = R.new('HTTP_ACCEPT_ENCODING' => 'gzip;q=0, *')
56
+ assert(request.encoding?('compress'))
57
+ assert(request.encoding?('identity'))
58
+ assert(!request.encoding?('gzip'))
59
+
60
+ request = R.new('HTTP_ACCEPT_ENCODING' => 'identity;q=0')
61
+ assert(!request.encoding?('identity'))
62
+
63
+ request = R.new('HTTP_ACCEPT_ENCODING' => '*;q=0')
64
+ assert(!request.encoding?('identity'))
65
+ end
66
+
67
+ def test_best_encoding
68
+ request = R.new('HTTP_ACCEPT_ENCODING' => 'gzip, compress')
69
+ assert_equal('gzip', request.best_encoding(%w< gzip compress >))
70
+ assert_equal('identity', request.best_encoding(%w< identity compress >))
71
+ assert_equal(nil, request.best_encoding(%w< zip >))
72
+ end
73
+
74
+ def test_language
75
+ request = R.new({})
76
+ assert(request.language?('en'))
77
+ assert(request.language?('da'))
78
+
79
+ request = R.new('HTTP_ACCEPT_LANGUAGE' => 'en;q=0.5, en-gb')
80
+ assert(request.language?('en'))
81
+ assert(request.language?('en-gb'))
82
+ assert(!request.language?('da'))
83
+ end
84
+
85
+ def test_best_language
86
+ request = R.new('HTTP_ACCEPT_LANGUAGE' => 'en;q=0.5, en-gb')
87
+ assert_equal('en-gb', request.best_language(%w< en en-gb >))
88
+ assert_equal('en', request.best_language(%w< en da >))
89
+ assert_equal('en-us', request.best_language(%w< en-us en-au >))
90
+ assert_equal(nil, request.best_language(%w< da >))
91
+ end
92
+
93
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-accept
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - Michael J. I. Jackson
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-04-01 00:00:00 -06:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rack
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 0
28
+ - 4
29
+ version: "0.4"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: rake
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ description: HTTP Accept, Accept-Charset, Accept-Encoding, and Accept-Language tools for Rack
45
+ email: mjijackson@gmail.com
46
+ executables: []
47
+
48
+ extensions: []
49
+
50
+ extra_rdoc_files:
51
+ - CHANGES
52
+ - README
53
+ files:
54
+ - lib/rack/accept/charset.rb
55
+ - lib/rack/accept/context.rb
56
+ - lib/rack/accept/encoding.rb
57
+ - lib/rack/accept/header.rb
58
+ - lib/rack/accept/language.rb
59
+ - lib/rack/accept/media_type.rb
60
+ - lib/rack/accept/request.rb
61
+ - lib/rack/accept.rb
62
+ - test/charset_test.rb
63
+ - test/encoding_test.rb
64
+ - test/header_test.rb
65
+ - test/helper.rb
66
+ - test/language_test.rb
67
+ - test/media_type_test.rb
68
+ - test/request_test.rb
69
+ - CHANGES
70
+ - rack-accept.gemspec
71
+ - Rakefile
72
+ - README
73
+ has_rdoc: true
74
+ homepage: http://github.com/mjijackson/rack-accept
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --line-numbers
80
+ - --inline-source
81
+ - --title
82
+ - Rack::Accept
83
+ - --main
84
+ - Rack::Accept
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.3.6
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: HTTP Accept* tools for Rack
108
+ test_files:
109
+ - test/charset_test.rb
110
+ - test/encoding_test.rb
111
+ - test/header_test.rb
112
+ - test/language_test.rb
113
+ - test/media_type_test.rb
114
+ - test/request_test.rb