rack-accept-media-types 0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+