roadforest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,81 @@
1
+ =begin
2
+ I think this code might be useful soon
3
+ module RoadForest
4
+ module ResourceMixin
5
+ module FormParsing
6
+ def self.content_types_accepted
7
+ [
8
+ ["application/x-www-form-urlencoded", :handle_url_encoded_form],
9
+ ["multipart/form-data", :handle_multipart_form]
10
+ ]
11
+ end
12
+
13
+ def process_post
14
+ content_type = Webmachine::MediaType.parse(request.content_type)
15
+ if content_type.match?("application/x-www-form-urlencoded")
16
+ handle_url_encoded_form
17
+ elsif content_type.match?("multipart/form-data")
18
+ handle_multipart_form
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def parse_url_encoded_form
25
+ Hash[URI::decode_www_form(request.body)]
26
+ end
27
+
28
+ def parse_multipart_form
29
+ form_data = {}
30
+ reader = multipart_reader(request.content_type, form_data)
31
+
32
+ request.body.each do |chunk|
33
+ reader.write(chunk)
34
+ end
35
+
36
+ return form_data
37
+ end
38
+
39
+ def multipart_reader(content_type, form_data)
40
+ content_type = Webmachine::MediaType.parse(content_type)
41
+ reader = MultipartParser::Reader.new(content_type.params["boundary"])
42
+ reader.on_error do |message|
43
+ raise message
44
+ end
45
+
46
+ reader.on_part do |part|
47
+ case part.mime
48
+ when nil
49
+ form_data[part.name] = ""
50
+ part.on_data do |data|
51
+ form_data[part.name] << data
52
+ end
53
+ else
54
+ temp_file = Tempfile.new(part.filename)
55
+
56
+ form_data[part.name] = [part, temp_file]
57
+
58
+ part.on_data do |data|
59
+ temp_file.write(data)
60
+ end
61
+
62
+ part.on_end do
63
+ temp_file.close
64
+ end
65
+ end
66
+ end
67
+
68
+ return reader
69
+ end
70
+
71
+ def handle_url_encoded_form
72
+ handle_form(parse_url_encoded_form)
73
+ end
74
+
75
+ def handle_multipart_form
76
+ handle_form(parse_multipart_form)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ =end
@@ -0,0 +1,21 @@
1
+ require 'roadforest/resource/rdf/read-only'
2
+ require 'roadforest/resource/role/writable'
3
+
4
+ module RoadForest
5
+ module Resource
6
+ module RDF
7
+ #Used for a simple resource that has properties that can be updated, but
8
+ #doesn't have any children - for example a comment
9
+ class LeafItem < ReadOnly
10
+ register :leaf
11
+
12
+ include Role::Writable
13
+
14
+ def allowed_methods
15
+ super + Role::Writable.allowed_methods
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'roadforest/resource/rdf/read-only'
2
+ require 'roadforest/resource/role/has-children'
3
+ module RoadForest
4
+ module Resource
5
+ module RDF
6
+ #Used for a resource that simply represents a list of other resources
7
+ #without having any properties itself - a list of posts, for instance
8
+ class List < ReadOnly
9
+ register :list
10
+
11
+ include Role::HasChildren
12
+
13
+ def allowed_methods
14
+ super + Role::HasChildren.allowed_methods
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require 'roadforest/resource/rdf/read-only'
2
+ require 'roadforest/resource/role/writable'
3
+ require 'roadforest/resource/role/has-children'
4
+ module RoadForest
5
+ module Resource
6
+ module RDF
7
+ #Used for a resource that has properties itself that can be updated, and
8
+ #also has one or more kinds of dependent or subordinate resources - can new
9
+ #resources be created by posting to this one? e.g. a post, which can have
10
+ #comments
11
+ class ParentItem < ReadOnly
12
+ register :parent
13
+
14
+ include Role::Writable
15
+ include Role::HasChildren
16
+
17
+ def allowed_methods
18
+ (super + [Role::Writable, Role::HasChildren].inject([]) do |list, mod|
19
+ list + mod.allowed_methods
20
+ end).uniq
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,100 @@
1
+ require 'roadforest/resource/handlers'
2
+ require 'roadforest/application/parameters'
3
+
4
+ module RoadForest
5
+ module Resource
6
+ module RDF
7
+ #Used for a resource that presents a read-only representation
8
+ class ReadOnly < Webmachine::Resource
9
+ def self.register(method_name)
10
+ Handlers.register(method_name, self)
11
+ end
12
+
13
+ register :read_only
14
+
15
+ attr_accessor :model, :trace
16
+
17
+ ### RoadForest interface
18
+
19
+ def params
20
+ params = Application::Parameters.new do |params|
21
+ params.path_info = @request.path_info
22
+ params.query_params = @request.query_params
23
+ params.path_tokens = @request.path_tokens
24
+ end
25
+ end
26
+
27
+ def request_accept_header
28
+ request.headers["Accept"] || "*/*"
29
+ end
30
+
31
+ def response_content_type=(type)
32
+ response.headers["Content-Type"] = type
33
+ end
34
+
35
+ def response_body=(body)
36
+ response.body = body
37
+ end
38
+
39
+ def retrieve_model
40
+ absolutize(@model.canonical_host, @model.retrieve)
41
+ end
42
+ alias retreive_model retrieve_model
43
+
44
+ # def known_methods
45
+ # super + ["PATCH"]
46
+ # end
47
+
48
+ def model_supports(action)
49
+ @model.respond_to?(action)
50
+ end
51
+ ### Webmachine interface
52
+
53
+ def trace?
54
+ !!@trace
55
+ end
56
+
57
+ #Overridden rather than metaprogram content type methods
58
+ def send(*args)
59
+ if args.length == 1 and not model.nil?
60
+ model.type_handling.fetch(args.first).call(self)
61
+ else
62
+ super
63
+ end
64
+ rescue KeyError
65
+ super
66
+ end
67
+
68
+ def method(name)
69
+ if model.nil?
70
+ super
71
+ else
72
+ model.type_handling.fetch(name).method(:call)
73
+ end
74
+ rescue KeyError
75
+ super
76
+ end
77
+
78
+ def content_types_provided
79
+ model.type_handling.renderers.type_map
80
+ end
81
+
82
+ def resource_exists?
83
+ @model.exists?
84
+ end
85
+
86
+ def generate_etag
87
+ @model.etag
88
+ end
89
+
90
+ def last_modified
91
+ @model.last_modified
92
+ end
93
+
94
+ def expires
95
+ @model.expires
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,4 @@
1
+ require 'roadforest/resource/rdf/leaf-item'
2
+ require 'roadforest/resource/rdf/parent-item'
3
+ require 'roadforest/resource/rdf/list'
4
+ require 'roadforest/resource/rdf/read-only'
@@ -0,0 +1,22 @@
1
+ module RoadForest
2
+ module Resource
3
+ module Role
4
+ module HasChildren
5
+ def self.allowed_methods
6
+ %w[POST]
7
+ end
8
+
9
+ def post_is_create
10
+ false
11
+ end
12
+
13
+ def process_post
14
+ parser = model.type_handling.choose_parser(request.content_type || 'application/octet-stream')
15
+
16
+ parser.add_child(self)
17
+ return true
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require 'roadforest/resource/role/has-children'
2
+
3
+ module RoadForest
4
+ module Resource
5
+ module Role
6
+ module Writable
7
+ class IncludeOrder < StandardError; end
8
+
9
+ def self.allowed_methods
10
+ %w[POST PUT DELETE]
11
+ end
12
+
13
+ def self.included(mod)
14
+ if mod.ancestors.include?(HasChildren)
15
+ #Might regret this later - some kind of "I promise to fix it?"
16
+ raise IncludeOrder, "Writable has to be included before HasChildren"
17
+ end
18
+ end
19
+
20
+ def post_is_create
21
+ true
22
+ end
23
+
24
+ def content_types_accepted
25
+ model.type_handling.parsers.type_map
26
+ end
27
+
28
+ def request_body
29
+ @request.body
30
+ end
31
+
32
+ def known_content_type(content_type)
33
+ content_type = Webmachine::MediaType.parse(content_type)
34
+ content_types_accepted.any?{|ct, _| content_type.match?(ct)}
35
+ end
36
+
37
+ def delete_resource
38
+ @model.delete(params)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ #This file is intended as the single entry point for RoadForest server code
2
+ require 'roadforest/application'
3
+ require 'roadforest/models'
@@ -0,0 +1,77 @@
1
+ require 'roadforest/test-support/trace-formatter'
2
+ require 'webmachine/headers'
3
+ require 'webmachine/request'
4
+ require 'webmachine/response'
5
+ require 'webmachine/decision/flow'
6
+ module RoadForest
7
+ module TestSupport
8
+ class DispatcherFacade < BasicObject
9
+ def initialize(dispatcher)
10
+ @dispatcher = dispatcher
11
+ end
12
+
13
+ def method_missing(method, *args, &block)
14
+ @dispatcher.__send__(method, *args, &block)
15
+ end
16
+
17
+ def dispatch(request, response)
18
+ if resource = @dispatcher.find_resource(request, response)
19
+ FSM.new(resource, request, response).run
20
+ else
21
+ Webmachine.render_error(404, request, response)
22
+ end
23
+ end
24
+ end
25
+
26
+ class FSM < ::Webmachine::Decision::FSM
27
+ def self.trace_on
28
+ unless ancestors.include? Webmachine::Trace::FSM
29
+ include Webmachine::Trace::FSM
30
+ end
31
+ Webmachine::Trace.trace_store = :memory
32
+ end
33
+
34
+ def self.dump_trace
35
+ puts trace_dump
36
+ end
37
+
38
+ def self.trace_dump
39
+ Webmachine::Trace.traces.map do |trace|
40
+ TraceFormatter.new(Webmachine::Trace.fetch(trace))
41
+ end.join("\n")
42
+ end
43
+
44
+ #Um, actually *don't* handle exceptions
45
+ def handle_exceptions
46
+ yield.tap do |result|
47
+ #p result #ok
48
+ end
49
+ end
50
+
51
+ def initialize_tracing
52
+ return if self.class.ancestors.include? Webmachine::Trace::FSM
53
+ super
54
+ end
55
+
56
+ def run
57
+ state = Webmachine::Decision::Flow::START
58
+ trace_request(request)
59
+ loop do
60
+ trace_decision(state)
61
+ result = handle_exceptions { send(state) }
62
+ case result
63
+ when Fixnum # Response code
64
+ respond(result)
65
+ break
66
+ when Symbol # Next state
67
+ state = result
68
+ else # You bwoke it
69
+ raise InvalidResource, t('fsm_broke', :state => state, :result => result.inspect)
70
+ end
71
+ end
72
+ ensure
73
+ trace_response(response)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,151 @@
1
+ require 'addressable/uri'
2
+ require 'roadforest/test-support/dispatcher-facade'
3
+ module RoadForest
4
+ module TestSupport
5
+ class HTTPClient
6
+ def initialize(app, url)
7
+ @app = app
8
+ @default_url = Addressable::URI.parse(url)
9
+ @exchanges = []
10
+ @dispatcher = DispatcherFacade.new(@app.dispatcher)
11
+ end
12
+ attr_reader :exchanges
13
+
14
+ def inspect
15
+ "#<#{self.class.name}:#{"%0xd" % object_id} #{exchanges.length} exchanges>"
16
+ end
17
+
18
+ def do_request(request)
19
+ uri = request.url
20
+
21
+ uri = Addressable::URI.parse(uri)
22
+ uri = @default_url.join(uri)
23
+
24
+ exchange = Exchange.new
25
+
26
+ exchange.method = request.method
27
+ exchange.uri = uri
28
+ exchange.body = request.body
29
+ exchange.dispatcher = @dispatcher
30
+
31
+ @exchanges << exchange
32
+
33
+ exchange.header('Host', [uri.host, uri.port].compact.join(':'))
34
+ exchange.header('Accept', '*/*')
35
+ request.headers.each do |name, value|
36
+ exchange.header(name, value)
37
+ end
38
+
39
+ yield exchange if block_given?
40
+
41
+ #puts; puts "#{__FILE__}:#{__LINE__} => #{(request).inspect}"
42
+
43
+ exchange.do_request
44
+
45
+ response = HTTP::Response.new
46
+ response.headers = exchange.response.headers.dup
47
+ response.status = exchange.response.code
48
+ response.body_string = exchange.response.body
49
+
50
+ #puts; puts "#{__FILE__}:#{__LINE__} => #{(response).inspect}"
51
+ return response
52
+ end
53
+
54
+ class Exchange
55
+ def initialize
56
+ @uri = nil
57
+ @method = nil
58
+ @headers = Webmachine::Headers.new
59
+ @query_params = {}
60
+ @req = nil
61
+ @res = nil
62
+ end
63
+ attr_accessor :uri, :method, :body, :dispatcher
64
+ attr_reader :headers, :query_params
65
+
66
+ # Returns the request object.
67
+ def request
68
+ @req || webmachine_test_error('No request object yet. Issue a request first.')
69
+ end
70
+
71
+ # Returns the response object after a request has been made.
72
+ def response
73
+ @res || webmachine_test_error('No response yet. Issue a request first!')
74
+ end
75
+
76
+ # Set a single header for the next request.
77
+ def header(name, value)
78
+ @headers[name] = value
79
+ end
80
+
81
+ def headers=(hash)
82
+ hash.each do |key, value|
83
+ header(key, value)
84
+ end
85
+ end
86
+
87
+ def query_param(name, value)
88
+ @query_params[name] = value
89
+ end
90
+
91
+ def query_params=(hash)
92
+ hash.each do |key, value|
93
+ query_param(key, value)
94
+ end
95
+ end
96
+
97
+ def do_request
98
+ self.uri = Addressable::URI.parse(uri)
99
+ uri.query_values = (uri.query_values || {}).merge(query_params)
100
+
101
+
102
+ @req = Webmachine::Request.new(method, uri, headers, RequestBody.new(body))
103
+ @res = Webmachine::Response.new
104
+
105
+ dispatcher.dispatch(@req, @res)
106
+
107
+ return @res
108
+ end
109
+
110
+ class RequestBody
111
+ # @return the request from Mongrel
112
+
113
+ # @param request the request from Mongrel
114
+ def initialize(body)
115
+ @raw_body = body
116
+ end
117
+
118
+ def body
119
+ @body =
120
+ case @raw_body
121
+ when IO, StringIO
122
+ @raw_body.rewind
123
+ @raw_body.read
124
+ when String
125
+ @raw_body
126
+ else
127
+ raise "Can't handle body type: #{@raw_body.class}"
128
+ end
129
+
130
+ end
131
+
132
+ # @return [String] the request body as a string
133
+ def to_s
134
+ body
135
+ end
136
+
137
+ # @yield [chunk]
138
+ # @yieldparam [String] chunk a chunk of the request body
139
+ def each(&block)
140
+ yield(body)
141
+ end
142
+ end # class RequestBody
143
+
144
+ private
145
+ def webmachine_test_error(msg)
146
+ raise Webmachine::Test::Error.new(msg)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,67 @@
1
+ module RoadForest
2
+ module Testing
3
+ class MatchesQuery
4
+ def initialize(&block)
5
+ @query = ::RDF::Query.new(&block)
6
+ end
7
+
8
+ def matches?(actual)
9
+ @actual = actual
10
+ solutions = @query.execute(actual)
11
+ not solutions.empty?
12
+ end
13
+
14
+ def failure_message_for_should
15
+ "expected #{@query.inspect} to return solutions on #{@actual.inspect}, but didn't"
16
+ end
17
+ end
18
+
19
+ class ListEquivalence
20
+ def initialize(expected)
21
+ @expected = expected
22
+ end
23
+
24
+ def matches?(actual)
25
+ @actual = actual
26
+ @actual_extra = @actual - @expected
27
+ @expected_extra = @expected - @actual
28
+ @actual_extra.empty? and @expected_extra.empty?
29
+ end
30
+
31
+ def failure_message_for_should
32
+ "expected #{@actual.inspect} to have the same elements as #{@expected.inspect}"
33
+ end
34
+ end
35
+
36
+ class StatementsFromGraph
37
+ def initialize(graph)
38
+ @graph = graph
39
+ end
40
+
41
+ def that_match_query(query)
42
+ @graph.query(query).to_a
43
+ end
44
+ end
45
+
46
+ module HelperMethods
47
+ def statements_from_graph(graph)
48
+ StatementsFromGraph.new(graph)
49
+ end
50
+ end
51
+
52
+ module MatcherMethods
53
+ def match_query(&block)
54
+ MatchesQuery.new(&block)
55
+ end
56
+
57
+ def be_equivalent_to(list)
58
+ ListEquivalence.new(list)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ RSpec::configure do |config|
65
+ config.include RoadForest::Testing::MatcherMethods
66
+ config.include RoadForest::Testing::HelperMethods
67
+ end
@@ -0,0 +1,23 @@
1
+ require 'roadforest/rdf/graph-store'
2
+ require 'roadforest/remote-host'
3
+ require 'roadforest/test-support/http-client'
4
+ module RoadForest::TestSupport
5
+ class RemoteHost < ::RoadForest::RemoteHost
6
+ def initialize(app)
7
+ @app = app
8
+ super(app.canonical_host)
9
+ end
10
+
11
+ def build_graph_store
12
+ RoadForest::RDF::GraphStore.new
13
+ end
14
+
15
+ def http_client
16
+ @http_client ||= HTTPClient.new(@app, @url)
17
+ end
18
+
19
+ def http_exchanges
20
+ http_client.exchanges
21
+ end
22
+ end
23
+ end