roadforest 0.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.
Files changed (71) hide show
  1. data/examples/file-management.rb +98 -0
  2. data/lib/roadforest/application/dispatcher.rb +54 -0
  3. data/lib/roadforest/application/parameters.rb +39 -0
  4. data/lib/roadforest/application/path-provider.rb +18 -0
  5. data/lib/roadforest/application/route-adapter.rb +24 -0
  6. data/lib/roadforest/application/services-host.rb +10 -0
  7. data/lib/roadforest/application.rb +42 -0
  8. data/lib/roadforest/blob-model.rb +56 -0
  9. data/lib/roadforest/content-handling/engine.rb +113 -0
  10. data/lib/roadforest/content-handling/media-type.rb +222 -0
  11. data/lib/roadforest/content-handling/type-handlers/jsonld.rb +172 -0
  12. data/lib/roadforest/http/adapters/excon.rb +47 -0
  13. data/lib/roadforest/http/graph-response.rb +20 -0
  14. data/lib/roadforest/http/graph-transfer.rb +112 -0
  15. data/lib/roadforest/http/message.rb +91 -0
  16. data/lib/roadforest/model.rb +151 -0
  17. data/lib/roadforest/models.rb +2 -0
  18. data/lib/roadforest/rdf/context-fascade.rb +25 -0
  19. data/lib/roadforest/rdf/document.rb +23 -0
  20. data/lib/roadforest/rdf/focus-list.rb +19 -0
  21. data/lib/roadforest/rdf/focus-wrapping.rb +30 -0
  22. data/lib/roadforest/rdf/graph-copier.rb +16 -0
  23. data/lib/roadforest/rdf/graph-focus.rb +95 -0
  24. data/lib/roadforest/rdf/graph-reading.rb +145 -0
  25. data/lib/roadforest/rdf/graph-store.rb +217 -0
  26. data/lib/roadforest/rdf/investigation.rb +90 -0
  27. data/lib/roadforest/rdf/normalization.rb +150 -0
  28. data/lib/roadforest/rdf/parcel.rb +47 -0
  29. data/lib/roadforest/rdf/post-focus.rb +35 -0
  30. data/lib/roadforest/rdf/resource-pattern.rb +60 -0
  31. data/lib/roadforest/rdf/resource-query.rb +58 -0
  32. data/lib/roadforest/rdf/source-rigor/credence/any.rb +9 -0
  33. data/lib/roadforest/rdf/source-rigor/credence/none-if-role-absent.rb +19 -0
  34. data/lib/roadforest/rdf/source-rigor/credence/role-if-available.rb +19 -0
  35. data/lib/roadforest/rdf/source-rigor/credence-annealer.rb +22 -0
  36. data/lib/roadforest/rdf/source-rigor/credence.rb +29 -0
  37. data/lib/roadforest/rdf/source-rigor/http-investigator.rb +20 -0
  38. data/lib/roadforest/rdf/source-rigor/investigator.rb +17 -0
  39. data/lib/roadforest/rdf/source-rigor/null-investigator.rb +10 -0
  40. data/lib/roadforest/rdf/source-rigor.rb +44 -0
  41. data/lib/roadforest/rdf/update-focus.rb +73 -0
  42. data/lib/roadforest/rdf/vocabulary.rb +11 -0
  43. data/lib/roadforest/rdf.rb +6 -0
  44. data/lib/roadforest/remote-host.rb +96 -0
  45. data/lib/roadforest/resource/handlers.rb +43 -0
  46. data/lib/roadforest/resource/http/form-parsing.rb +81 -0
  47. data/lib/roadforest/resource/rdf/leaf-item.rb +21 -0
  48. data/lib/roadforest/resource/rdf/list.rb +19 -0
  49. data/lib/roadforest/resource/rdf/parent-item.rb +26 -0
  50. data/lib/roadforest/resource/rdf/read-only.rb +100 -0
  51. data/lib/roadforest/resource/rdf.rb +4 -0
  52. data/lib/roadforest/resource/role/has-children.rb +22 -0
  53. data/lib/roadforest/resource/role/writable.rb +43 -0
  54. data/lib/roadforest/server.rb +3 -0
  55. data/lib/roadforest/test-support/dispatcher-facade.rb +77 -0
  56. data/lib/roadforest/test-support/http-client.rb +151 -0
  57. data/lib/roadforest/test-support/matchers.rb +67 -0
  58. data/lib/roadforest/test-support/remote-host.rb +23 -0
  59. data/lib/roadforest/test-support/trace-formatter.rb +140 -0
  60. data/lib/roadforest/test-support.rb +2 -0
  61. data/lib/roadforest/utility/class-registry.rb +49 -0
  62. data/lib/roadforest.rb +2 -0
  63. data/spec/client.rb +152 -0
  64. data/spec/credence-annealer.rb +44 -0
  65. data/spec/graph-copier.rb +87 -0
  66. data/spec/graph-store.rb +142 -0
  67. data/spec/media-types.rb +14 -0
  68. data/spec/rdf-parcel.rb +158 -0
  69. data/spec/update-focus.rb +117 -0
  70. data/spec_support/gem_test_suite.rb +0 -0
  71. metadata +241 -0
@@ -0,0 +1,98 @@
1
+ require 'rdf/vocab/skos'
2
+
3
+ module FileManagementExample
4
+ module Vocabulary
5
+ class LC < ::RDF::Vocabulary("http://lrdesign.com/vocabularies/logical-construct#"); end
6
+ end
7
+
8
+ class ServicesHost < ::RoadForest::Application::ServicesHost
9
+ attr_accessor :file_records, :destination_dir
10
+
11
+ def initialize
12
+ @file_records = []
13
+ end
14
+ end
15
+
16
+ FileRecord = Struct.new(:name, :resolved)
17
+
18
+ class Application < RoadForest::Application
19
+ def setup
20
+ router.add :root, [], :read_only, Models::Navigation
21
+ router.add :unresolved_needs, ["unresolved_needs"], :parent, Models::UnresolvedNeedsList
22
+ router.add_traced :need, ["needs",'*'], :leaf, Models::Need
23
+ router.add :file_content, ["files","*"], :leaf, Models::NeedContent
24
+ end
25
+
26
+ module Models
27
+ class Navigation < RoadForest::RDFModel
28
+ def exists?
29
+ true
30
+ end
31
+
32
+ def update(graph)
33
+ return false
34
+ end
35
+
36
+ def nav_entry(graph, name, path)
37
+ graph.add_node([:skos, :hasTopConcept], "#" + name) do |entry|
38
+ entry[:rdf, :type] = [:skos, "Concept"]
39
+ entry[:skos, :label] = name
40
+ entry[:foaf, "page"] = path
41
+ end
42
+ end
43
+
44
+ def fill_graph(graph)
45
+ graph[:rdf, "type"] = [:skos, "ConceptScheme"]
46
+ nav_entry(graph, "Unresolved", path_for(:unresolved_needs))
47
+ end
48
+ end
49
+
50
+ class UnresolvedNeedsList < RoadForest::RDFModel
51
+ def exists?
52
+ true
53
+ end
54
+
55
+ def update(graph)
56
+ end
57
+
58
+ def add_child(graph)
59
+ new_file = FileRecord.new(graph.first(:lc, "name"), false)
60
+ services.file_records << new_file
61
+ end
62
+
63
+ def fill_graph(graph)
64
+ graph.add_list(:lc, "needs") do |list|
65
+ services.file_records.each do |record|
66
+ if !record.resolved
67
+ list << path_for(:need, '*' => record.name)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ class NeedContent < RoadForest::BlobModel
75
+ add_type "text/plain", TypeHandlers::Handler.new
76
+ end
77
+
78
+ class Need < RoadForest::RDFModel
79
+ def data
80
+ @data = services.file_records.find do |record|
81
+ record.name == params.remainder
82
+ end
83
+ end
84
+
85
+ def graph_update(graph)
86
+ data.resolved = graph[[:lc, "resolved"]]
87
+ new_graph
88
+ end
89
+
90
+ def fill_graph(graph)
91
+ graph[[:lc, "resolved"]] = data.resolved
92
+ graph[[:lc, "name"]] = data.name
93
+ graph[[:lc, "contents"]] = path_for(:file_content)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,54 @@
1
+ require 'webmachine'
2
+
3
+ module RoadForest
4
+ class Dispatcher < Webmachine::Dispatcher
5
+ include Resource::Handlers
6
+ def initialize(services)
7
+ super(method(:create_resource))
8
+ @services = services
9
+ @route_names = {}
10
+ end
11
+ attr_accessor :services
12
+
13
+ def resource_route(resource, name, path_spec, bindings)
14
+ route = Route.new(path_spec, resource, bindings || {})
15
+ yield route if block_given?
16
+ @route_names[name] = route
17
+ @routes << route
18
+ route
19
+ end
20
+
21
+ def add_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
22
+ resource = bundle_typed_resource(resource_type, model_class, name)
23
+ resource_route(resource, name, path_spec, bindings, &block)
24
+ end
25
+ alias add add_route
26
+
27
+ def add_traced_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
28
+ resource = bundle_traced_resource(resource_type, model_class, name)
29
+ resource_route(resource, name, path_spec, bindings, &block)
30
+ end
31
+ alias add_traced add_traced_route
32
+
33
+ def route_for_name(name)
34
+ @route_names.fetch(name)
35
+ end
36
+
37
+ class Route < Webmachine::Dispatcher::Route
38
+ # Create a complete URL for this route, doing any necessary variable
39
+ # substitution.
40
+ # @param [Hash] vars values for the path variables
41
+ # @return [String] the valid URL for the route
42
+ def build_path(vars = {})
43
+ "/" + path_spec.map do |segment|
44
+ case segment
45
+ when '*',Symbol
46
+ vars.fetch(segment)
47
+ when String
48
+ segment
49
+ end
50
+ end.join("/")
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ module RoadForest
2
+ class Application
3
+ #Parameters extracted from a URL, which a model object can use to identify
4
+ #the resource being discussed
5
+ class Parameters
6
+ def initialize
7
+ @path_info = {}
8
+ @query_params = {}
9
+ @path_tokens = []
10
+ yield self if block_given?
11
+ end
12
+ attr_accessor :path_info, :query_params, :path_tokens
13
+
14
+ def [](field_name)
15
+ return path_tokens if field_Name == '*'
16
+ @path_info[field_name] || @query_params[field_name]
17
+ end
18
+
19
+ def fetch(field_name)
20
+ return path_tokens if field_Name == '*'
21
+ @path_info[field_name] || @query_params.fetch(field_name)
22
+ end
23
+
24
+ def slice(*fields)
25
+ fields.each_with_object({}) do |name, hash|
26
+ hash[name] = self[name]
27
+ end
28
+ end
29
+
30
+ def remainder
31
+ @remainder = @path_tokens.join("/")
32
+ end
33
+
34
+ def to_hash
35
+ (query_params||{}).merge(path_info||{}).merge('*' => path_tokens)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module RoadForest
2
+ class PathProvider
3
+ def initialize(dispatcher)
4
+ @dispatcher = dispatcher
5
+ end
6
+
7
+ # Get the URL to the given resource, with optional variables to be used
8
+ # for bindings in the path spec.
9
+ # @param [Webmachine::Resource] resource the resource to link to
10
+ # @param [Hash] vars the values for the required path variables
11
+ # @raise [RuntimeError] Raised if the resource is not routable.
12
+ # @return [String] the URL
13
+ def path_for(name, vars = {})
14
+ route = @dispatcher.route_for_name(name)
15
+ ::RDF::URI.parse(route.build_path(vars))
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module RoadForest
2
+ class Application
3
+ class RouteAdapter
4
+ def initialize(resource_class, &setup_block)
5
+ @resource_class = resource_class
6
+ @setup_block = setup_block
7
+ end
8
+
9
+ def new(request, response)
10
+ resource = @resource_class.new(request, response)
11
+ @setup_block[resource, request, response]
12
+ resource
13
+ end
14
+
15
+ def <(klass)
16
+ if klass <= Webmachine::Resource
17
+ return true
18
+ else
19
+ return false
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ class RoadForest::Application
2
+ #XXX Worth doing some meta to get sanity checking of configs here? Better
3
+ #fail early if there's no DB configured, right?
4
+ class ServicesHost
5
+ def initialize
6
+ end
7
+
8
+ attr_accessor :router, :canonical_host, :type_handling
9
+ end
10
+ end
@@ -0,0 +1,42 @@
1
+ require 'webmachine/application'
2
+ module RoadForest
3
+ class Application < Webmachine::Application; end
4
+ end
5
+
6
+ require 'roadforest/resource/handlers'
7
+ require 'roadforest/application/dispatcher'
8
+ require 'roadforest/application/path-provider'
9
+ require 'roadforest/application/services-host'
10
+ require 'roadforest/resource/rdf'
11
+ require 'roadforest/content-handling/engine'
12
+
13
+ module RoadForest
14
+ class Application
15
+ include Resource::Handlers
16
+
17
+ def initialize(canonical_host, services, configuration = nil, dispatcher = nil)
18
+ @canonical_host = ::RDF::URI.parse(canonical_host)
19
+ configuration ||= Webmachine::Configuration.default
20
+ dispatcher ||= Dispatcher.new(services)
21
+ super(configuration, dispatcher)
22
+ self.services = services
23
+
24
+ setup
25
+ end
26
+
27
+ def setup
28
+ end
29
+
30
+ attr_reader :services, :canonical_host
31
+
32
+ alias router dispatcher
33
+
34
+ def services=(service_host)
35
+ router.services = service_host
36
+ @services = service_host
37
+ @services.canonical_host = @canonical_host
38
+ @services.router = PathProvider.new(@dispatcher)
39
+ @services.type_handling ||= ContentHandling::Engine.default
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ require 'roadforest/model'
2
+ require 'roadforest/content-handling/type-handlers/jsonld'
3
+
4
+ module RoadForest
5
+ class BlobModel < Model
6
+ TypeHandlers = RoadForest::MediaType::Handlers
7
+ class << self
8
+ def type_handling
9
+ @engine ||= ContentHandling::Engine.new
10
+ end
11
+
12
+ def add_type(type, handler)
13
+ add_parser(type, handler)
14
+ add_renderer(type, handler)
15
+ end
16
+ alias add add_type
17
+
18
+ def add_parser(type, handler)
19
+ type_handling.add_parser(type, handler)
20
+ end
21
+
22
+ def add_renderer(type, handler)
23
+ type_handling.add_renderer(type, handler)
24
+ end
25
+ end
26
+
27
+ def type_handling
28
+ self.class.type_handling
29
+ end
30
+
31
+ def destination_dir
32
+ Pathname.new(services.destination_dir)
33
+ end
34
+
35
+ def sub_path
36
+ params.remainder
37
+ end
38
+
39
+ def path
40
+ destination_dir.join(sub_path)
41
+ end
42
+
43
+ def retrieve
44
+ File::open(path)
45
+ end
46
+
47
+ def update(incoming)
48
+ File::open(path, "w") do |file|
49
+ incoming.each do |chunk|
50
+ file.write(chunk)
51
+ end
52
+ end
53
+ return nil
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,113 @@
1
+ require 'roadforest/content-handling/media-type'
2
+
3
+ module RoadForest
4
+ module ContentHandling
5
+ class Engine
6
+ class TypeHandlerList
7
+ def initialize(prefix)
8
+ @prefix = prefix
9
+ @types = MediaTypeList.new
10
+ @handlers = {}
11
+ @type_map = []
12
+ @symbol_lookup = {}
13
+ end
14
+ attr_reader :handlers, :types, :type_map
15
+
16
+ def add(handler)
17
+ type = handler.type
18
+ @types.add(type)
19
+ @handlers[type] = handler
20
+ symbol = handler_symbol(type)
21
+ raise "Type collision: #{type} already in #{self.inspect}" if @symbol_lookup.has_key?(symbol)
22
+ @type_map << [type.content_type_header, symbol]
23
+ @symbol_lookup[symbol] = handler
24
+ end
25
+
26
+ def handler_symbol(type)
27
+ "#{@prefix}_#{type.accept_header.gsub(/\W/, "_")}".to_sym
28
+ end
29
+
30
+ def fetch(symbol, &block)
31
+ @symbol_lookup.fetch(symbol, &block)
32
+ end
33
+
34
+ def reset
35
+ @handlers.clear
36
+ end
37
+
38
+ def handler_for(type)
39
+ type = MediaType.parse(type)
40
+ @handlers.fetch(type)
41
+ rescue KeyError
42
+ raise "No Content-Type handler for #{content_type}"
43
+ end
44
+ end
45
+
46
+ def self.default
47
+ require 'roadforest/content-handling/type-handlers/jsonld'
48
+ self.new.tap do |engine|
49
+ engine.add "application/ld+json", RoadForest::MediaType::Handlers::JSONLD.new
50
+ end
51
+ end
52
+
53
+ def initialize
54
+ @renderers = TypeHandlerList.new("provide")
55
+ @parsers = TypeHandlerList.new("accept")
56
+ @type_mapping = {}
57
+ end
58
+ attr_reader :renderers, :parsers
59
+
60
+ def add_type(type, handler)
61
+ type = MediaType.parse(type)
62
+ add_parser(type, handler)
63
+ add_renderer(type, handler)
64
+ end
65
+ alias add add_type
66
+
67
+ def add_parser(type, object)
68
+ type = MediaType.parse(type)
69
+ wrapper = RoadForest::MediaType::Handlers::Wrap::Parse.new(type, object)
70
+ parsers.add(wrapper)
71
+ end
72
+ alias accept add_parser
73
+
74
+ def add_renderer(type, object)
75
+ type = MediaType.parse(type)
76
+ wrapper = RoadForest::MediaType::Handlers::Wrap::Render.new(type, object)
77
+ renderers.add(wrapper)
78
+ end
79
+ alias provide add_renderer
80
+
81
+ def fetch(symbol)
82
+ @renderers.fetch(symbol){ @parsers.fetch(symbol) }
83
+ end
84
+
85
+ def choose_renderer(header)
86
+ content_type = choose_media_type(renderers.types, header)
87
+ return renderers.handler_for(content_type)
88
+ end
89
+
90
+ def each_renderer(&block)
91
+ renderers.handlers.enum_for(:each_pair) unless block_given?
92
+ renderers.handlers.each_pair(&block)
93
+ end
94
+
95
+ def choose_parser(header)
96
+ content_type = choose_media_type(parsers.types, header)
97
+ return parsers.handler_for(content_type)
98
+ end
99
+
100
+ def each_parser(&block)
101
+ parsers.handlers.enum_for(:each_pair) unless block_given?
102
+ parsers.handlers.each_pair(&block)
103
+ end
104
+
105
+ # Given the 'Accept' header and provided types, chooses an
106
+ # appropriate media type.
107
+ def choose_media_type(provided, header)
108
+ requested = MediaTypeList.build(header.split(/\s*,\s*/))
109
+ requested.best_match_from(provided)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,222 @@
1
+ module RoadForest
2
+ module ContentHandling
3
+ #@credit goes to Sean Cribbs & Ruby Webmachine for the basis of this code
4
+ #
5
+ # Encapsulates a MIME media type, with logic for matching types.
6
+ class MediaType
7
+ # Matches valid media types
8
+ MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\s*\S+\s*)*)\s*$/
9
+
10
+ # Matches sub-type parameters
11
+ PARAMS_REGEX = /;\s*([^=]+)=([^;=\s]+)/
12
+
13
+ # Creates a new MediaType by parsing an alternate representation.
14
+ # @param [MediaType, String, Array<String,Hash>] obj the raw type
15
+ # to be parsed
16
+ # @return [MediaType] the parsed media type
17
+ # @raise [ArgumentError] when the type could not be parsed
18
+ def self.parse(obj)
19
+ case obj
20
+ when MediaType
21
+ obj
22
+ when MEDIA_TYPE_REGEX
23
+ type, raw_params = $1, $2
24
+ params = Hash[raw_params.scan(PARAMS_REGEX)]
25
+ new(type, params)
26
+ else
27
+ unless Array === obj && String === obj[0] && Hash === obj[1]
28
+ raise ArgumentError, "Invalid media type #{obj.inspect}"
29
+ end
30
+ type = parse(obj[0])
31
+ type.params.merge!(obj[1])
32
+ type
33
+ end
34
+ end
35
+
36
+ # @return [String] the MIME media type
37
+ attr_accessor :type
38
+
39
+ # @return [Hash] any type parameters, e.g. charset
40
+ attr_accessor :params
41
+
42
+ # @param [String] type the main media type, e.g. application/json
43
+ # @param [Hash] params the media type parameters
44
+ def initialize(type, params={})
45
+ @type, @params = type, params
46
+ @quality = (@params.delete('q') || "1.0").to_f
47
+ end
48
+
49
+ attr_reader :quality
50
+
51
+ # Detects whether the {MediaType} represents an open wildcard
52
+ # type, that is, "*/*" without any {#params}.
53
+ def matches_all?
54
+ @type == "*/*" && @params.empty?
55
+ end
56
+
57
+ # @return [true,false] Are these two types strictly equal?
58
+ # @param other the other media type.
59
+ # @see MediaType.parse
60
+ def ==(other)
61
+ other = self.class.parse(other)
62
+ other.type == type && other.params == params
63
+ end
64
+
65
+ # Detects whether this {MediaType} matches the other {MediaType},
66
+ # taking into account wildcards. Sub-type parameters are treated
67
+ # strictly.
68
+ # @param [MediaType, String, Array<String,Hash>] other the other type
69
+ # @return [true,false] whether it is an acceptable match
70
+ def exact_match?(other)
71
+ other = self.class.parse(other)
72
+ type_matches?(other) && other.params == params
73
+ end
74
+
75
+ # Detects whether the {MediaType} is an acceptable match for the
76
+ # other {MediaType}, taking into account wildcards and satisfying
77
+ # all requested parameters, but allowing this type to have extra
78
+ # specificity.
79
+ # @param [MediaType, String, Array<String,Hash>] other the other type
80
+ # @return [true,false] whether it is an acceptable match
81
+ def match?(other)
82
+ other = self.class.parse(other)
83
+ type_matches?(other) && params_match?(other.params)
84
+ end
85
+ alias =~ match?
86
+
87
+ # Detects whether the passed sub-type parameters are all satisfied
88
+ # by this {MediaType}. The receiver is allowed to have other
89
+ # params than the ones specified, but all specified must be equal.
90
+ # @param [Hash] params the requested params
91
+ # @return [true,false] whether it is an acceptable match
92
+ def params_match?(other)
93
+ other.all? do |k,v|
94
+ params[k] == v
95
+ end
96
+ end
97
+
98
+ def accept_header
99
+ [type, "q=#{quality}", *params.map {|k,v| "#{k}=#{v}" }].join(";")
100
+ end
101
+
102
+ def content_type_header
103
+ [type, *params.map {|k,v| "#{k}=#{v}" }].join(";")
104
+ end
105
+ alias to_s content_type_header
106
+
107
+ # @return [String] The major type, e.g. "application", "text", "image"
108
+ def major
109
+ @major ||= type.split("/").first
110
+ end
111
+
112
+ # @return [String] the minor or sub-type, e.g. "json", "html", "jpeg"
113
+ def minor
114
+ @minor ||= type.split("/").last
115
+ end
116
+
117
+ def precedence_index
118
+ [
119
+ @major == "*" ? 0 : 1,
120
+ @minor == "*" ? 0 : 1,
121
+ (@params.keys - %w{q}).length
122
+ ]
123
+ end
124
+
125
+ # @param [MediaType] other the other type
126
+ # @return [true,false] whether the main media type is acceptable,
127
+ # ignoring params and taking into account wildcards
128
+ def type_matches?(other)
129
+ other = self.class.parse(other)
130
+ if ["*", "*/*", type].include?(other.type)
131
+ true
132
+ else
133
+ other.major == major && other.minor == "*"
134
+ end
135
+ end
136
+ end
137
+
138
+ class MediaTypeList
139
+ # Given an acceptance list, create a PriorityList from them.
140
+ def self.build(list)
141
+ return list if self === list
142
+
143
+ case list
144
+ when Array
145
+ when String
146
+ list = list.split(/\s*,\s*/)
147
+ else
148
+ raise "Cannot build a MediaTypeList from #{list.inspect}"
149
+ end
150
+
151
+ new.tap do |plist|
152
+ list.each {|item| plist.add_header_val(item) }
153
+ end
154
+ end
155
+
156
+ include Enumerable
157
+
158
+ # Creates a {PriorityList}.
159
+ # @see PriorityList::build
160
+ def initialize
161
+ @list = []
162
+ end
163
+
164
+ def accept_header
165
+ @list.map(&:accept_header).join(", ")
166
+ end
167
+ alias to_s accept_header
168
+
169
+ #Given another MediaTypeList, find the media type that is the best match
170
+ #between them - generally, the idea is to match an Accept header with a
171
+ #local list of provided types
172
+ def best_match_from(other)
173
+ other.max_by do |their_type|
174
+ best_type = self.by_precedence.find do |our_type|
175
+ their_type =~ our_type
176
+ end
177
+ if best_type.nil?
178
+ 0
179
+ else
180
+ best_type.quality * their_type.quality
181
+ end
182
+ end
183
+ end
184
+
185
+ def by_precedence
186
+ self.sort do |left, right|
187
+ right.precedence_index <=> left.precedence_index
188
+ end.enum_for(:each)
189
+ end
190
+
191
+ # Adds an acceptable item with the given priority to the list.
192
+ # @param [Float] q the priority
193
+ # @param [String] choice the acceptable item
194
+ def add(type)
195
+ @list << type
196
+ end
197
+
198
+ # Given a raw acceptable value from an acceptance header,
199
+ # parse and add it to the list.
200
+ # @param [String] c the raw acceptable item
201
+ # @see #add
202
+ def add_header_val(type_string)
203
+ add(MediaType.parse(type_string))
204
+ rescue ArgumentError
205
+ raise "Invalid media type"
206
+ end
207
+
208
+ # Iterates over the list in priority order, that is, taking
209
+ # into account the order in which items were added as well as
210
+ # their priorities.
211
+ # @yield [q,v]
212
+ # @yieldparam [Float] q the acceptable item's priority
213
+ # @yieldparam [String] v the acceptable item
214
+ def each
215
+ return enum_for(:each) unless block_given?
216
+ @list.each do |item|
217
+ yield item
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end