rack-accept 0.1

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.
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