rack-accept-media-types 0.9

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.
@@ -0,0 +1,3 @@
1
+ doc/
2
+ pkg/
3
+ .yardoc
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2009 Martin Aumont (mynyml)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ .gitignore
2
+ LICENSE
3
+ Manifest
4
+ README
5
+ Rakefile
6
+ examples.rb
7
+ lib/rack/accept_media_types.rb
8
+ rack-accept-media-types.gemspec
9
+ specs.watchr
10
+ test/test_accept_media_types.rb
11
+ test/test_helper.rb
data/README ADDED
@@ -0,0 +1,25 @@
1
+ ===== Sumarry
2
+
3
+ Rack convenience middleware for simplified handling of Accept header
4
+ (env['HTTP_ACCEPT']). Allows ordering of its values (accepted media types)
5
+ according to their "quality" (preference level).
6
+
7
+ This wrapper is typically used to determine the request's prefered media
8
+ type (see example below).
9
+
10
+ ===== Install
11
+
12
+ gem install rack-accept-media-types --source http://gemcutter.org
13
+
14
+ ===== Examples
15
+
16
+ env['HTTP_ACCEPT'] #=> 'application/xml;q=0.8,text/html,text/plain;q=0.9'
17
+
18
+ req = Rack::Request.new(env)
19
+ req.accept_media_types #=> ['text/html', 'text/plain', 'application/xml']
20
+ req.accept_media_types.prefered #=> 'text/html'
21
+
22
+ ===== Links
23
+
24
+ source:: http://github.com/mynyml/rack-accept-media-types
25
+ rdocs:: http://docs.github.com/mynyml/rack-accept-media-types
@@ -0,0 +1,25 @@
1
+ def gem_opt
2
+ defined?(Gem) ? "-rubygems" : ""
3
+ end
4
+
5
+ # --------------------------------------------------
6
+ # Tests
7
+ # --------------------------------------------------
8
+ task(:default => :test)
9
+
10
+ desc "Run tests"
11
+ task(:test) do
12
+ system "ruby #{gem_opt} test/test_accept_media_types.rb"
13
+ end
14
+
15
+ # --------------------------------------------------
16
+ # Docs
17
+ # --------------------------------------------------
18
+ desc "Generate YARD Documentation"
19
+ task(:yardoc) do
20
+ require 'yard'
21
+ files = %w( lib/**/*.rb )
22
+ options = %w( -o doc/yard --readme README --files LICENSE )
23
+ YARD::CLI::Yardoc.run *(options + files)
24
+ end
25
+
@@ -0,0 +1,56 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'rack'
4
+ require 'simple_example' # gem install mynyml-simple_example
5
+
6
+ root = Pathname(__FILE__).dirname.expand_path
7
+ require root + 'lib/rack/accept_media_types'
8
+
9
+ include SimpleExample
10
+ puts SimpleExample::Format.separator = '-'*10
11
+
12
+
13
+ # simple
14
+ env = {'HTTP_ACCEPT' => 'text/html,text/plain'}
15
+ example do
16
+ types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'])
17
+ #=> ["text/html", "text/plain"]
18
+ types.prefered
19
+ #=> "text/html"
20
+ end
21
+
22
+ # with quality values
23
+ env = {'HTTP_ACCEPT' => 'text/html;q=0.5,text/plain;q=0.9'}
24
+ example do
25
+ types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'])
26
+ #=> ["text/plain", "text/html"]
27
+ types.prefered
28
+ #=> "text/plain"
29
+ end
30
+
31
+ # rejects types with invalid quality values
32
+ env = {'HTTP_ACCEPT' => 'text/html;q=0,text/plain;q=1.1,application/xml'}
33
+ example do
34
+ types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'])
35
+ #=> ["application/xml"]
36
+ types.prefered
37
+ #=> "application/xml"
38
+ end
39
+
40
+ # no accept header means client accepts all types (rfc2616-sec14.1)
41
+ env = {'HTTP_ACCEPT' => nil}
42
+ example do
43
+ types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'])
44
+ #=> ["*/*"]
45
+ types.prefered
46
+ #=> "*/*"
47
+ end
48
+
49
+ # to avoid getting a wildcard media-range, pass an empty string instead
50
+ env = {'HTTP_ACCEPT' => nil}
51
+ example do
52
+ types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
53
+ #=> []
54
+ types.prefered
55
+ #=> nil
56
+ end
@@ -0,0 +1,128 @@
1
+ module Rack
2
+ class Request
3
+ # The media types of the HTTP_ACCEPT header ordered according to their
4
+ # "quality" (preference level), without any media type parameters.
5
+ #
6
+ # ===== Examples
7
+ #
8
+ # env['HTTP_ACCEPT'] #=> 'application/xml;q=0.8,text/html,text/plain;q=0.9'
9
+ #
10
+ # req = Rack::Request.new(env)
11
+ # req.accept_media_types #=> ['text/html', 'text/plain', 'application/xml']
12
+ # req.accept_media_types.prefered #=> 'text/html'
13
+ #
14
+ # For more information, see:
15
+ # * Acept header: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
16
+ # * Quality values: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
17
+ #
18
+ # ===== Returns
19
+ # AcceptMediaTypes:: ordered list of accept header's media types
20
+ #
21
+ def accept_media_types
22
+ @accept_media_types ||= Rack::AcceptMediaTypes.new(@env['HTTP_ACCEPT'])
23
+ end
24
+ end
25
+
26
+ # AcceptMediaTypes is intended for wrapping env['HTTP_ACCEPT'].
27
+ #
28
+ # It allows ordering of its values (accepted media types) according to their
29
+ # "quality" (preference level).
30
+ #
31
+ # This wrapper is typically used to determine the request's prefered media
32
+ # type (see example below).
33
+ #
34
+ # ===== Examples
35
+ #
36
+ # env['HTTP_ACCEPT'] #=> 'application/xml;q=0.8,text/html,text/plain;q=0.9'
37
+ #
38
+ # types = Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'])
39
+ # types #=> ['text/html', 'text/plain', 'application/xml']
40
+ # types.prefered #=> 'text/html'
41
+ #
42
+ # ===== Notes
43
+ #
44
+ # For simplicity, media type parameters are striped, as they are seldom used
45
+ # in practice. Users who need them are excepted to parse the Accept header
46
+ # manually.
47
+ #
48
+ # ===== References
49
+ #
50
+ # HTTP 1.1 Specs:
51
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
52
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
53
+ #
54
+ class AcceptMediaTypes < Array
55
+
56
+ #--
57
+ # NOTE
58
+ # Reason for special handling of nil accept header:
59
+ #
60
+ # "If no Accept header field is present, then it is assumed that the client
61
+ # accepts all media types."
62
+ #
63
+ def initialize(header)
64
+ if header.nil?
65
+ replace(['*/*'])
66
+ else
67
+ replace(order(header.split(',')))
68
+ end
69
+ end
70
+
71
+ # The client's prefered media type.
72
+ def prefered
73
+ first
74
+ end
75
+
76
+ private
77
+
78
+ # Order media types by quality values, remove invalid types, and return media ranges.
79
+ #
80
+ def order(types) #:nodoc:
81
+ types.map {|type| AcceptMediaType.new(type) }.reverse.sort.reverse.select {|type| type.valid? }.map {|type| type.range }
82
+ end
83
+
84
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
85
+ #
86
+ class AcceptMediaType #:nodoc:
87
+ include Comparable
88
+
89
+ # media-range = ( "*/*"
90
+ # | ( type "/" "*" )
91
+ # | ( type "/" subtype )
92
+ # ) *( ";" parameter )
93
+ attr_accessor :range
94
+
95
+ # qvalue = ( "0" [ "." 0*3DIGIT ] )
96
+ # | ( "1" [ "." 0*3("0") ] )
97
+ attr_accessor :quality
98
+
99
+ def initialize(type)
100
+ self.range, *params = type.split(';')
101
+ self.quality = extract_quality(params)
102
+ end
103
+
104
+ def <=>(type)
105
+ self.quality <=> type.quality
106
+ end
107
+
108
+ # "A weight is normalized to a real number in the range 0 through 1,
109
+ # where 0 is the minimum and 1 the maximum value. If a parameter has a
110
+ # quality value of 0, then content with this parameter is `not
111
+ # acceptable' for the client."
112
+ #
113
+ def valid?
114
+ self.quality.between?(0.1, 1)
115
+ end
116
+
117
+ private
118
+ # Extract value from 'q=FLOAT' parameter if present, otherwise assume 1
119
+ #
120
+ # "The default value is q=1."
121
+ #
122
+ def extract_quality(params)
123
+ q = params.detect {|p| p.match(/q=\d\.?\d{0,3}/) }
124
+ q ? q.split('=').last.to_f : 1.0
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rack-accept-media-types'
3
+ s.version = "0.9"
4
+ s.summary = "Rack middleware for simplified handling of Accept header"
5
+ s.description = "Rack middleware for simplified handling of Accept header. Accept header parser."
6
+ s.author = "mynyml"
7
+ s.email = "mynyml@gmail.com"
8
+ s.homepage = "http://github.com/mynyml/rack-accept-media-types"
9
+ s.rubyforge_project = "rack-accept-media-types"
10
+ s.has_rdoc = false
11
+ s.require_path = "lib"
12
+ s.files = File.read("Manifest").strip.split("\n")
13
+
14
+ s.add_development_dependency 'minitest'
15
+ s.add_development_dependency 'rack'
16
+ end
@@ -0,0 +1,35 @@
1
+ # Run me with:
2
+ #
3
+ # $ watchr specs.watchr
4
+
5
+ # --------------------------------------------------
6
+ # Helpers
7
+ # --------------------------------------------------
8
+ def run(cmd)
9
+ puts(cmd)
10
+ system(cmd)
11
+ end
12
+
13
+ def run_all_tests
14
+ system( "rake -s test" )
15
+ end
16
+
17
+ # --------------------------------------------------
18
+ # Watchr Rules
19
+ # --------------------------------------------------
20
+ watch( '^test/test_accept_media_types\.rb' ) { run_all_tests }
21
+ watch( '^lib/rack/accept_media_types\.rb' ) { run_all_tests }
22
+ watch( '^test/test_helper\.rb' ) { run_all_tests }
23
+
24
+ # --------------------------------------------------
25
+ # Signal Handling
26
+ # --------------------------------------------------
27
+ # Ctrl-\
28
+ Signal.trap('QUIT') do
29
+ puts " --- Running all tests ---\n\n"
30
+ run_all_tests
31
+ end
32
+
33
+ # Ctrl-C
34
+ Signal.trap('INT') { abort("\n") }
35
+
@@ -0,0 +1,104 @@
1
+ require 'test/test_helper'
2
+
3
+ Accept = Rack::AcceptMediaTypes
4
+
5
+ class AcceptMediaTypesTest < MiniTest::Unit::TestCase
6
+
7
+ test "media type list" do
8
+ header = 'text/html,text/plain'
9
+ assert_equal %w( text/html text/plain ).to_set, Accept.new(header).to_set
10
+ end
11
+
12
+ test "ordered by quality value (highest first)" do
13
+ header = 'text/html;q=0.5,text/plain;q=0.9'
14
+ assert_equal %w( text/plain text/html ), Accept.new(header)
15
+ end
16
+
17
+ test "default quality value is 1" do
18
+ header = 'text/plain;q=0.1,text/html'
19
+ assert_equal %w( text/html text/plain ), Accept.new(header)
20
+ end
21
+
22
+ test "equal quality types keep original order" do
23
+ header = 'text/html,text/plain;q=0.9,application/xml'
24
+ assert_equal %w( text/html application/xml text/plain ), Accept.new(header)
25
+ end
26
+
27
+ test "prefered type" do
28
+ header = 'text/html;q=0.2,text/plain;q=0.5'
29
+ assert_equal 'text/plain', Accept.new(header).prefered
30
+ end
31
+
32
+ test "types with out of range quality values are ignored" do
33
+ header = 'text/html,text/plain;q=1.1'
34
+ assert_equal %w( text/html ), Accept.new(header)
35
+
36
+ header = 'text/html,text/plain;q=0'
37
+ assert_equal %w( text/html ), Accept.new(header)
38
+ end
39
+
40
+ test "custom media types are NOT ignored" do
41
+ # verifying that the media types exist in Rack::Mime::MIME_TYPES is
42
+ # explicitly outside the scope of this library.
43
+ header = 'application/x-custom'
44
+ assert_equal %w( application/x-custom ), Accept.new(header)
45
+ end
46
+
47
+ test "media-range parameters are discarted" do
48
+ header = 'text/html;version=5;q=0.5,text/plain'
49
+ assert_equal %w( text/plain text/html ), Accept.new(header)
50
+ end
51
+
52
+ test "accept-extension parameters are discarted" do
53
+ header = 'text/html;q=0.5;token=value,text/plain'
54
+ assert_equal %w( text/plain text/html ), Accept.new(header)
55
+ end
56
+
57
+ test "nil accept header" do
58
+ header = nil
59
+ assert_equal %w( */* ), Accept.new(header)
60
+ end
61
+
62
+ test "empty accept header" do
63
+ header = ''
64
+ assert_equal [], Accept.new(header)
65
+ end
66
+
67
+ test "all accepted types are invalid" do
68
+ header = 'text/html;q=2,application/xml;q=0'
69
+ assert_equal [], Accept.new(header)
70
+ end
71
+
72
+ test "Request class integration" do
73
+ env = {'HTTP_ACCEPT' => 'text/html;q=0.5;token=value,text/plain'}
74
+ assert_equal %w( text/plain text/html ), Rack::Request.new(env).accept_media_types
75
+ end
76
+
77
+ test "memoizes request's accept_media_types" do
78
+ env = {'HTTP_ACCEPT' => 'text/html;q=0.5;token=value,text/plain'}
79
+ req = Rack::Request.new(env)
80
+ types1 = req.accept_media_types
81
+ types2 = req.accept_media_types
82
+ assert_same types1, types2
83
+ end
84
+ end
85
+
86
+ __END__
87
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
88
+ 14.1 Accept
89
+
90
+ Accept = "Accept" ":"
91
+ #( media-range [ accept-params ] )
92
+
93
+ media-range = ( "*/*"
94
+ | ( type "/" "*" )
95
+ | ( type "/" subtype )
96
+ ) *( ";" parameter )
97
+ accept-params = ";" "q" "=" qvalue *( accept-extension )
98
+ accept-extension = ";" token [ "=" ( token | quoted-string ) ]
99
+
100
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
101
+ 3.9 Quality Values
102
+
103
+ qvalue = ( "0" [ "." 0*3DIGIT ] )
104
+ | ( "1" [ "." 0*3("0") ] )
@@ -0,0 +1,22 @@
1
+ require 'pathname'
2
+ require 'minitest/autorun'
3
+ require 'rack'
4
+ begin
5
+ require 'phocus'
6
+ require 'redgreen' #http://gemcutter.org/gems/mynyml-redgreen
7
+ require 'ruby-debug'
8
+ rescue LoadError, RuntimeError
9
+ end
10
+
11
+ root = Pathname(__FILE__).dirname.parent.expand_path
12
+ $:.unshift(root.join('lib'))
13
+
14
+ require 'rack/accept_media_types'
15
+
16
+ class MiniTest::Unit::TestCase
17
+ def self.test(name, &block)
18
+ name = :"test_#{name.gsub(/\s/,'_')}"
19
+ define_method(name, &block)
20
+ end
21
+ end
22
+
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-accept-media-types
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.9"
5
+ platform: ruby
6
+ authors:
7
+ - mynyml
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-21 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: minitest
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Rack middleware for simplified handling of Accept header. Accept header parser.
36
+ email: mynyml@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - LICENSE
46
+ - Manifest
47
+ - README
48
+ - Rakefile
49
+ - examples.rb
50
+ - lib/rack/accept_media_types.rb
51
+ - rack-accept-media-types.gemspec
52
+ - specs.watchr
53
+ - test/test_accept_media_types.rb
54
+ - test/test_helper.rb
55
+ has_rdoc: false
56
+ homepage: http://github.com/mynyml/rack-accept-media-types
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project: rack-accept-media-types
79
+ rubygems_version: 1.3.5
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Rack middleware for simplified handling of Accept header
83
+ test_files: []
84
+