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