addressive 0.1.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'addressive'
3
+ s.version = '0.1.0.alpha'
4
+ s.date = '2011-11-23'
5
+ s.authors = ["HannesG"]
6
+ s.email = %q{hannes.georg@googlemail.com}
7
+ s.summary = 'A system which should help bringing different Rack applications together.'
8
+ s.homepage = 'https://github.com/hannesg/addressive'
9
+ s.description = ''
10
+
11
+ s.require_paths = ['lib']
12
+
13
+ s.files = Dir.glob('lib/**/**/*.rb') + ['addressive.gemspec']
14
+
15
+ s.add_dependency 'uri_template', '~> 0.1.4'
16
+
17
+ s.add_development_dependency 'simplecov'
18
+ s.add_development_dependency 'rspec'
19
+ s.add_development_dependency 'yard'
20
+ s.add_development_dependency 'rack'
21
+
22
+ end
@@ -0,0 +1,229 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ module Addressive
19
+
20
+ # A graph is basically a hash of nodes and some builder methods.
21
+ # Graphs are only used to generate nodes and their relations.
22
+ # They will be GCed when they are done.
23
+ class Graph
24
+
25
+ # An app builder is used to add uris for a certain app to a node.
26
+ class AppBuilder
27
+
28
+ # @private
29
+ def initialize(node, factory, app)
30
+ @node = node
31
+ @app = app
32
+ @spec_factory = factory
33
+ end
34
+
35
+ # Sets a default value for an option.
36
+ def default(name, value)
37
+ @spec_factory.defaults[name] = value
38
+ end
39
+
40
+ # Adds one or more uri specs for a given name. It uses the current app as the default app for all specs.
41
+ def uri(name,*args)
42
+ specs = @node.uri_spec(name)
43
+ specs << @spec_factory.convert(*args)
44
+ return specs
45
+ end
46
+
47
+ end
48
+
49
+ # A NodeBuilder is used to build a Node inside a Graph.
50
+ # This class should not be generated directly, it's created for you by {Builder#node}.
51
+ class NodeBuilder
52
+
53
+ attr_reader :node
54
+
55
+ # @private
56
+ def initialize(network,node)
57
+ @network = network
58
+ @node = node
59
+ @spec_factory = network.spec_factory.derive({})
60
+ end
61
+
62
+ # Adds an edge from the current node to a node with the given name.
63
+ #
64
+ # @example
65
+ # nw = Addressive::Graph.new{
66
+ # #create a node named :app_a
67
+ # node :app_a do
68
+ # # Creates an edge to the node named :app_b. The name of the edge will be :app_b, too.
69
+ # edge :app_b
70
+ #
71
+ # # Creates another edge to the node named :app_b with edge name :app_c.
72
+ # edge :app_c, :app_b
73
+ #
74
+ # # Edge takes a block. Same behavior as Builder#node.
75
+ # edge :app_d do
76
+ #
77
+ # edge :app_a
78
+ #
79
+ # end
80
+ # end
81
+ # }
82
+ # # :app_a now references :app_b twice, :app_d once and is referenced only by :app_d.
83
+ #
84
+ # @yield BlockBuilder
85
+ #
86
+ def edge(as , name = as, &block)
87
+ @node.edges[as] = @network.build.node(name,&block).node
88
+ return self
89
+ end
90
+
91
+ alias ref edge
92
+
93
+ # Sets a default value for an option.
94
+ def default(name, value)
95
+ @spec_factory.defaults[name] = value
96
+ end
97
+
98
+ # Adds one or more uri specs for a given name.
99
+ def uri(name,*args)
100
+ @node.uri_spec(name) << @spec_factory.convert(args)
101
+ return @node.uri_spec(name)
102
+ end
103
+
104
+ # Adds an rack-application to this node.
105
+ #
106
+ # @example
107
+ #
108
+ # Addressive::Graph.new{
109
+ #
110
+ # node :a_node do
111
+ #
112
+ # app lambda{|env| [200,{},['App 1']]} do
113
+ # uri :show, '/a_node/app1'
114
+ # end
115
+ #
116
+ # app lambda{|env| [200,{},['App 2']]} do
117
+ # uri :show, '/a_node/app2'
118
+ # end
119
+ #
120
+ # end
121
+ #
122
+ # }
123
+ #
124
+ # @yield {AppBuilder}
125
+ # @return {AppBuilder}
126
+ def app(app, options = {}, &block)
127
+ app = app.to_app if app.respond_to? :to_app
128
+ sf = @spec_factory.derive(options.merge(:app=>app))
129
+ builder = AppBuilder.new(@node,sf,app)
130
+ if block
131
+ if block.arity == 1
132
+ yield builder
133
+ else
134
+ builder.instance_eval(&block)
135
+ end
136
+ end
137
+ unless @node.apps.include? app
138
+ @node.apps << app
139
+ if app.respond_to? :generate_uri_specs
140
+ app.generate_uri_specs(builder)
141
+ end
142
+ end
143
+ return builder
144
+ end
145
+
146
+ end
147
+
148
+ # A Builder is used to construct a network.
149
+ # It's here so that a {Graph} can have only read methods, while a {Builder} does the heavy lifting.
150
+ # This class should not be generated directly, it's created for you by {Graph#build} and {Graph#initialize}.
151
+ class Builder
152
+
153
+ # @private
154
+ def initialize(network)
155
+ @network = network
156
+ end
157
+
158
+ # Creates or edits the node with the given name in the current network and yields and returns a NodeBuilder.
159
+ # This NodeBuilder can then be use to actually describe the node.
160
+ #
161
+ # @yield {NodeBuilder}
162
+ # @return {NodeBuilder}
163
+ def node(name, &block)
164
+ n = @network.node(name)
165
+ if block
166
+ if block.arity == 1
167
+ yield @network.node_builder_class.new(@network, n)
168
+ else
169
+ @network.node_builder_class.new(@network, n).instance_eval(&block)
170
+ end
171
+ end
172
+ return @network.node_builder_class.new(@network, n)
173
+ end
174
+
175
+ # Sets a network-wide default.
176
+ # @note
177
+ # Only nodes created after this default has been set will receive this value.
178
+ #
179
+ def default(name,value)
180
+ @network.spec_factory.defaults[name]=value
181
+ end
182
+
183
+ end
184
+
185
+ # @private
186
+ def configure(node)
187
+ return node
188
+ end
189
+
190
+ # @private
191
+ attr_reader :node_class, :node_builder_class, :spec_factory
192
+
193
+ # Creates a new Graph.
194
+ # @yield {Builder}
195
+ def initialize(&block)
196
+ @defaults = {}
197
+ @node_class = Class.new(Node)
198
+ @node_builder_class = Class.new(NodeBuilder)
199
+ @spec_factory = URISpecFactory.new({})
200
+ @nodes = Hash.new{|hsh, name| hsh[name] = configure(node_class.new) }
201
+ build(&block)
202
+ end
203
+
204
+ # Creates, yields and returns a {Builder} for this network.
205
+ #
206
+ # @yield {Builder}
207
+ # @return {Builder}
208
+ def build(&block)
209
+ if block
210
+ if block.arity == 1
211
+ yield Builder.new(self)
212
+ else
213
+ Builder.new(self).instance_eval(&block)
214
+ end
215
+ end
216
+ return Builder.new(self)
217
+ end
218
+
219
+ # Return the node with the given name.
220
+ # @note
221
+ # A Graph will automatically create nodes if they don't exists.
222
+ def [](name)
223
+ return @nodes[name]
224
+ end
225
+
226
+ alias node []
227
+
228
+ end
229
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ module Addressive
19
+
20
+ # A module to visualize
21
+ module Graphviz
@@ -0,0 +1,43 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ require 'rack/request'
19
+
20
+ module Addressive
21
+
22
+ # A patched rack-request, which uses the addressive data if for the GET and uri methods.
23
+ class Request < Rack::Request
24
+
25
+ def GET
26
+ if @env[ADDRESSIVE_ENV_KEY]
27
+ return @env[ADDRESSIVE_ENV_KEY].variables
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def uri(*args)
34
+ if @env[ADDRESSIVE_ENV_KEY]
35
+ @env[ADDRESSIVE_ENV_KEY].uri(*args)
36
+ else
37
+ self.url
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,337 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ require 'rack/request'
19
+ require 'thread'
20
+
21
+ module Addressive
22
+
23
+ class Match < Struct.new(:node,:action,:variables, :spec, :data)
24
+
25
+ include URIBuilder
26
+
27
+ end
28
+
29
+ # A router which can be used as a rack application and routes requests to nodes and their apps.
30
+ #
31
+ class Router
32
+
33
+ # @private
34
+ class Tree
35
+
36
+ # @private
37
+ class Direct < self
38
+
39
+ class EachingProc < Proc
40
+
41
+ def initialize(routes)
42
+ @routes = routes
43
+ end
44
+
45
+ alias each call
46
+
47
+ def size
48
+ @routes.size
49
+ end
50
+
51
+ end
52
+
53
+ def initialize
54
+ @routes = []
55
+ end
56
+
57
+ def preroute(path, uri)
58
+ return self
59
+ end
60
+
61
+ def <<(spec)
62
+ @routes << spec
63
+ end
64
+
65
+ def clear!
66
+ @routes = []
67
+ end
68
+
69
+ def size
70
+ @routes.size
71
+ end
72
+
73
+ def done!
74
+ # compile *gnihihi*
75
+ code = ['def each(path,uri); routes = Enumerator.new(@routes);',
76
+ *@routes.each_with_index.map{|r,i|
77
+ "route = routes.next
78
+ vars = route.template.extract(#{r.template.absolute? ? 'uri' : 'path'})
79
+ if vars
80
+ yield( route, vars, #{i} )
81
+ end
82
+ "
83
+ } ,'; end'].join
84
+ instance_eval(code)
85
+ end
86
+
87
+ def each(path,uri)
88
+ @routes.each_with_index do |route,i|
89
+ vars = route.template.extract(route.template.absolute? ? uri : path)
90
+ if vars
91
+ yield( route, vars, i )
92
+ end
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ # @private
99
+ class SubstringSplit < self
100
+
101
+ def initialize(substring, *partitions)
102
+ @substring = substring
103
+ @partitions = partitions
104
+ end
105
+
106
+ def preroute(path,uri)
107
+ @partitions[ uri.include?(@substring) ? 0 : 1 ].preroute(uri,path)
108
+ end
109
+
110
+ def <<(spec)
111
+ if spec.template.send(:tokens).select(&:literal?).none?{|tk| tk.string.include? @substring }
112
+ # okay, this template does not require the substring.
113
+ # so it can be matched, even when the substring is missing
114
+ @partitions[1] << spec
115
+ end
116
+ @partitions[0] << spec
117
+ end
118
+
119
+
120
+ def clear!
121
+ @partitions[0].clear!
122
+ @partitions[1].clear!
123
+ end
124
+
125
+ def done!
126
+ @partitions[0].done!
127
+ @partitions[1].done!
128
+ end
129
+
130
+ end
131
+
132
+ # @private
133
+ class PrefixSplit < self
134
+
135
+ def initialize(prefix, *partitions)
136
+ @prefix = prefix
137
+ @partitions = partitions
138
+ end
139
+
140
+ def preroute(path,uri)
141
+ @partitions[ path.start_with?(@prefix) ? 0 : 1 ].preroute(uri,path)
142
+ end
143
+
144
+ def <<(spec)
145
+ if spec.template.absolute?
146
+ @partitions[0] << spec
147
+ @partitions[1] << spec
148
+ else
149
+ tkns = spec.template.send(:tokens)
150
+ if tkns.empty?
151
+ # emtpy uri, no problem?!
152
+ @partitions[0] << spec
153
+ @partitions[1] << spec
154
+ return
155
+ end
156
+ tk = tkns.first
157
+ if tk.literal?
158
+ if tk.string.start_with?(@prefix)
159
+ @partitions[0] << spec
160
+ elsif @prefix.start_with?(tk.string) and tkns[1]
161
+ @partitions[0] << spec
162
+ @partitions[1] << spec
163
+ else
164
+ @partitions[1] << spec
165
+ end
166
+ else
167
+ @partitions[0] << spec
168
+ @partitions[1] << spec
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+
175
+ def clear!
176
+ @partitions[0].clear!
177
+ @partitions[1].clear!
178
+ end
179
+
180
+ def done!
181
+ @partitions[0].done!
182
+ @partitions[1].done!
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
189
+ DEBUG_NAME = 'addressive-router'.freeze
190
+ DEBUG_NULL = lambda{|_| nil}
191
+
192
+ attr_reader :routes, :actions
193
+
194
+ def initialize(tree = Tree::Direct.new)
195
+ @routes = []
196
+ @actions = {}
197
+ @tree = tree
198
+ @immaterial = true
199
+ @mutex = Mutex.new
200
+ @sealed = false
201
+ end
202
+
203
+ # Add a nodes specs to this router.
204
+ def add(*nodes)
205
+ @mutex.synchronize do
206
+ raise "Trying to add nodes to a sealed router!" if @sealed
207
+ nodes.each do |node|
208
+ node.uri_specs.each do |action, specs|
209
+ specs.each do |spec|
210
+ if spec.valid? and spec.app and spec.route != false
211
+ @routes << spec
212
+ @actions[spec] = [node, action]
213
+ end
214
+ end
215
+ end
216
+ end
217
+ @immaterial = true
218
+ end
219
+ return self
220
+ end
221
+
222
+ # Routes the env to an app and it's node.
223
+ def call(env)
224
+ rr = Rack::Request.new(env)
225
+ l = env['rack.logger']
226
+ db = l ? l.method(:debug) : DEBUG_NULL
227
+ db.call(DEBUG_NAME) do
228
+ "[ ? ] #{rr.url.inspect}"
229
+ end
230
+ matches = routes_for(rr.path, rr.url)
231
+ result = nil
232
+ matches.each do |addressive|
233
+ env[ADDRESSIVE_ENV_KEY] = addressive
234
+ begin
235
+ result = addressive.spec.app.call(env)
236
+ db.call(DEBUG_NAME) do
237
+ "[#{result[0]}] #{addressive.spec.template.pattern} with #{addressive.variables.inspect} on #{addressive.spec.app} ( route #{addressive.data[:'routes.scanned']} / #{addressive.data[:'routes.total']} ) after #{'%.6f' % addressive.data[:duration]}"
238
+ end
239
+ rescue
240
+ db.call(DEBUG_NAME) do
241
+ "[!!!] #{addressive.spec.template.pattern} with #{addressive.variables.inspect} on #{addressive.spec.app} ( route #{addressive.data[:'routes.scanned']} / #{addressive.data[:'routes.total']} ) after #{'%.6f' % addressive.data[:duration]}"
242
+ end
243
+ raise
244
+ end
245
+ if result[0] != 404
246
+ break
247
+ end
248
+ end
249
+ if result.nil?
250
+ db.call(DEBUG_NAME) do
251
+ "[404] Nothing found"
252
+ end
253
+ return not_found(env)
254
+ end
255
+ return result
256
+ end
257
+
258
+ class RouteEnumerator
259
+
260
+ include Enumerable
261
+
262
+ # @private
263
+ def initialize(routes,path,url,actions)
264
+ @routes,@path,@url,@actions = routes, path, url, actions
265
+ end
266
+
267
+ # @yield {Addressive::Match}
268
+ def each
269
+ total = @routes.size
270
+ scan_time = Time.now
271
+ @routes.each(@path,@url) do |spec, vars, scanned|
272
+ node, action = @actions[spec];
273
+ t = Time.now
274
+ yield Match.new(node, action, vars, spec, {:'routes.scanned'=>scanned,:'routes.total'=>total,:duration => (t - scan_time)})
275
+ # still here?, the passed time should be added
276
+ scan_time += (Time.now - t)
277
+ end
278
+
279
+ end
280
+
281
+ end
282
+
283
+ #
284
+ # @param path String the path to look for
285
+ # @param uri String the full uri
286
+ # @return {RouteEnumerator} an enumerator which yields the requested routes
287
+ def routes_for(path, uri)
288
+ materialize!
289
+ return RouteEnumerator.new(@tree.preroute(path,uri),path,uri,@actions)
290
+ end
291
+
292
+ # @private
293
+ def to_app
294
+ return self
295
+ end
296
+
297
+ # Prevents further editing of this router.
298
+ # This is not mandatory, but encouraged if later changes are not required.
299
+ #
300
+ # @example
301
+ # rtr = Addressive::Router.new
302
+ # rtr.add( Addressive.node ) # okay
303
+ # rtr.seal!
304
+ # rtr.add( Addressive.node ) #!> Exception
305
+ #
306
+ def seal!
307
+ @mutex.synchronize do
308
+ @sealed = true
309
+ end
310
+ materialize!
311
+ end
312
+
313
+ protected
314
+
315
+ # This method is called when no route was found.
316
+ def not_found(env)
317
+ [404,{'Content-Type'=>'text/plain','X-Cascade'=>'pass'},['Ooooopppps 404!']]
318
+ end
319
+
320
+ private
321
+
322
+ def materialize!
323
+ return unless @immaterial
324
+ @mutex.synchronize do
325
+ return unless @immaterial
326
+ @routes.sort_by!{|spec| spec.template.static_characters }.reverse!
327
+ @tree.clear!
328
+ @routes.each do |spec|
329
+ @tree << spec
330
+ end
331
+ @tree.done!
332
+ @immaterial = false
333
+ end
334
+ end
335
+
336
+ end
337
+ end
@@ -0,0 +1,71 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ require 'rack/file'
19
+
20
+ module Addressive
21
+
22
+ # Similiar to Rack::Static but lives happily on a node.
23
+ # Emits just one uri-spec with name ":get" and a simple catch-all uri.
24
+ #
25
+ # @example
26
+ # node = Addressive.node{
27
+ # app Addressive::Static.new(:root=>'/tmp')
28
+ # }
29
+ # node.uri(:get,'file'=>'baz').to_s #=> "/baz"
30
+ #
31
+ # # Create a file, so this won't be a 404:
32
+ # File.new('/tmp/baz','w').close
33
+ # router = Addressive::Router.new.add(node)
34
+ # status,headers,body = router.call('rack.url_scheme'=>'http','PATH_INFO'=>'/baz','HTTP_HOST'=>'example.example')
35
+ # body.class #=> Rack::File
36
+ # body.path #=> "/tmp/baz"
37
+ #
38
+ class Static
39
+
40
+ def generate_uri_specs(builder)
41
+
42
+ builder.uri :get, '/{+file}'
43
+
44
+ end
45
+
46
+ def call(env)
47
+ env['PATH_INFO'] = env['addressive'].variables['file']
48
+ return @file_server.call(env)
49
+ end
50
+
51
+ # @param [Hash] options
52
+ # @option options [#call,Rack::File] :file_server A class compatible to Rack::File. If supplied this will be used instead of the default rack file server
53
+ # @option options [String] :root The directory to search files in. Same as for Rack::File.
54
+ # @option options [Obect] :cache_control Same as for Rack::File.
55
+ def initialize(options={})
56
+ unless options.key? :file_server
57
+ root = options[:root] || Dir.pwd
58
+ cache_control = options[:cache_control]
59
+ @file_server = Rack::File.new(root, cache_control)
60
+ else
61
+ @file_server = options[:file_server]
62
+ end
63
+ end
64
+
65
+ def to_s
66
+ "Static[#{@file_server.inspect}]"
67
+ end
68
+
69
+ end
70
+
71
+ end
data/lib/addressive.rb ADDED
@@ -0,0 +1,404 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This program is free software: you can redistribute it and/or modify
3
+ # it under the terms of the Affero GNU General Public License as published by
4
+ # the Free Software Foundation, either version 3 of the License, or
5
+ # (at your option) any later version.
6
+ #
7
+ # This program is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ # GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License
13
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
+ #
15
+ # (c) 2011 by Hannes Georg
16
+ #
17
+
18
+ $LOAD_PATH << File.expand_path('../../uri_template7/lib/',File.dirname(__FILE__))
19
+ require File.expand_path('../../uri_template7/lib/uri_template',File.dirname(__FILE__))
20
+ require 'ostruct'
21
+
22
+ # Addressive is library which should make it possible for different rack-applications to live together on the same server.
23
+ # To accomplish this, addressive supplies you with:
24
+ # - A model for a graph in which each node is one application.
25
+ # - A template-based uri-builder which is aware of the network and can generate uri for any reachable application.
26
+ # - A router which can route rack-requests based on the known templates.
27
+ #
28
+ # What needs to be done by the apps:
29
+ # - Provide uri templates.
30
+ #
31
+ module Addressive
32
+
33
+ autoload :Static, 'addressive/static'
34
+ autoload :Request, 'addressive/request'
35
+ autoload :Router, 'addressive/router'
36
+ autoload :Graph, 'addressive/graph'
37
+
38
+ ADDRESSIVE_ENV_KEY = 'addressive'.freeze
39
+
40
+ class Error < StandardError
41
+
42
+ end
43
+
44
+ # This error is raised whenever no uri spec could be found.
45
+ class NoURISpecFound < Error
46
+
47
+ attr_reader :builder
48
+
49
+ def initialize(builder)
50
+ @builder = builder
51
+ super("No URISpec found for #{builder.inspect}. Only got: #{builder.node.uri_specs.keys.join(', ')}")
52
+ end
53
+
54
+ end
55
+
56
+ class NoEdgeFound < Error
57
+
58
+ attr_reader :node, :edge
59
+
60
+ def initialize(node, edge)
61
+ @node, @edge = node, edge
62
+ super("No Edge '#{edge.inspect}' found, only got '#{node.edges.keys.map(&:inspect).join('\', \'')}'")
63
+ end
64
+
65
+ end
66
+
67
+ # A module for any class, which can create uris.
68
+ module URIBuilder
69
+
70
+ def self.new(*args)
71
+ URIBuilderClass.new(*args)
72
+ end
73
+
74
+ def derive_uri_builder(action, variables, node)
75
+ origin = self.origin rescue nil
76
+ return URIBuilderClass.new( origin, action, variables, node )
77
+ end
78
+
79
+ protected :derive_uri_builder
80
+
81
+ # Creates a new URIBuilder with the given arguments.
82
+ # Given Hashes are merged into the variables. The last symbol is used to determine the action. Everything else is used to traverse the node graph.
83
+ #
84
+ # @example
85
+ # node = Addressive::Node.new
86
+ # node.uri_spec(:show) << Addressive::URISpec.new( URITemplate.new('/an/uri/with/{var}') )
87
+ # bldr = Addressive::URIBuilder.new(node)
88
+ # bldr.uri(:show, 'var'=>'VAR!').to_s #=> '/an/uri/with/VAR%21'
89
+ #
90
+ # @return URIBuilder
91
+ def uri(*args)
92
+ return uri_builder_delegate.uri(*args) if uri_builder_delegate
93
+ hashes, path = args.collect_concat{|a| a.respond_to?(:to_addressive) ? a.to_addressive : [a] }.partition{|x| x.kind_of? Hash}
94
+ node = self.node
95
+ action = self.action
96
+ if path.size >= 1
97
+ node = node.traverse(*path[0..-2])
98
+ action = path.last
99
+ end
100
+ derive_uri_builder(action, hashes.inject(self.variables || {}, &:merge), node)
101
+ end
102
+
103
+ private
104
+
105
+ def uri_builder_delegate
106
+ nil
107
+ end
108
+
109
+ end
110
+
111
+ # A builder which creates uri based on a Node an action and some variables.
112
+ class URIBuilderClass
113
+
114
+ include URIBuilder
115
+
116
+ attr_reader :origin,:node,:variables,:action
117
+
118
+ # @private
119
+ def initialize(origin, action=:default, vars={}, node=origin)
120
+ @origin = origin
121
+ @node = node
122
+ @action = action
123
+ @variables = vars
124
+ end
125
+
126
+ # @private
127
+ def inspect
128
+ '<URIBuilder '+self.action.inspect+' '+self.variables.inspect+'>'
129
+ end
130
+
131
+ # Actually creates the URI as a string
132
+ #
133
+ # @return String
134
+ def to_s
135
+ return uri_builder_delegate.to_s if uri_builder_delegate
136
+ specs = uri_builder_specs
137
+ # if a.none? ????
138
+ if specs.none?
139
+ raise NoURISpecFound.new(self)
140
+ end
141
+ return specs.first.template.expand(self.variables || {}).to_s
142
+ end
143
+
144
+ def humanization_key
145
+ specs = uri_builder_specs
146
+ if specs.none?
147
+ return super
148
+ else
149
+ return specs.first.app._(:action,self.action,self.variables || {} )
150
+ end
151
+ end
152
+ private
153
+ def uri_builder_specs
154
+ return @specs ||= begin
155
+ varnames = (self.variables || {} ).keys
156
+ self.node.uri_spec(self.action).select{|s| s.valid? and (s.variables - varnames).none? }.sort_by{|s| (varnames - s.variables).size }
157
+ end
158
+ end
159
+ end
160
+
161
+ # A specification is a combination of an URI template and some meta-data ( like the app this spec belongs to ).
162
+ class URISpec < OpenStruct
163
+
164
+ attr_accessor :template
165
+
166
+ def valid?
167
+ !!@template
168
+ end
169
+
170
+ def variables
171
+ @template ? @template.variables : []
172
+ end
173
+
174
+ def initialize(template, *args)
175
+ @template = template
176
+ super(*args)
177
+ end
178
+
179
+ def inspect
180
+ ['#<',self.class.name,': ',template.inspect,*@table.map{|k,v| " #{k}=#{v.inspect}"},'>'].join
181
+ end
182
+
183
+ end
184
+
185
+ # A URISpecFactory contains all information necessary to create a URISpec.
186
+ # This class exists because it's annoying to pass around default values by hand and
187
+ # a factory makes it possible to apply post-processing options.
188
+ class URISpecFactory
189
+
190
+ def converter(defaults = self.all_defaults)
191
+ lambda{|spec|
192
+ if spec.kind_of? URISpec
193
+ [ normalize( spec.dup, defaults ) ]
194
+ elsif spec.kind_of? URITemplate
195
+ [ normalize( URISpec.new( spec ) , defaults) ]
196
+ elsif spec.kind_of? String
197
+ [ normalize( URISpec.new( URITemplate.new(spec) ) , defaults) ]
198
+ elsif spec.kind_of? Array
199
+ spec.map(&self.converter(defaults))
200
+ elsif spec.kind_of? Hash
201
+ nd = defaults.merge(spec)
202
+ self.converter(nd).call(nd[:template])
203
+ else
204
+ []
205
+ end
206
+ }
207
+ end
208
+
209
+ protected :converter
210
+
211
+ def normalize( spec, defaults = self.all_defaults )
212
+ if defaults.key? :app
213
+ spec.app = defaults[:app]
214
+ end
215
+ unless spec.template.absolute?
216
+ if defaults[:prefix]
217
+ spec.template = URITemplate.apply( defaults[:prefix] , :/ , spec.template)
218
+ end
219
+ end
220
+ return spec
221
+ end
222
+
223
+ attr_reader :defaults
224
+
225
+ def initialize(defaults, parent = nil)
226
+ @defaults = defaults
227
+ @parent = parent
228
+ end
229
+
230
+ def convert(*args)
231
+ args.map(&converter).flatten
232
+ end
233
+
234
+ alias << convert
235
+
236
+ def all_defaults
237
+ @parent ? @parent.all_defaults.merge(defaults) : defaults
238
+ end
239
+
240
+ def derive(nu_defaults)
241
+ self.class.new(nu_defaults, self)
242
+ end
243
+
244
+ end
245
+
246
+ # A list of {URISpec}s. Useful because it checks the input.
247
+ class URISpecList
248
+
249
+ include Enumerable
250
+
251
+ def initialize(factory, source = [])
252
+ @specs = []
253
+ self.<<(*source)
254
+ end
255
+
256
+ def each(&block)
257
+ @specs.each(&block)
258
+ end
259
+
260
+ def <<(*args)
261
+ args = args.flatten
262
+ fails = args.select{|a| !a.kind_of? URISpec }
263
+ raise ArgumentError, "Expected to receive only URISpecs but, got #{fails.map(&:inspect).join(', ')}" if fails.size != 0
264
+ @specs.push( *args )
265
+ end
266
+
267
+ alias push <<
268
+
269
+ end
270
+
271
+ # The node is the most important class. A node bundles informations about all uri specs for a certain application.
272
+ # Furthermore nodes can reference each other so that one can traverse them.
273
+ #
274
+ # @example
275
+ # node1 = Addressive::Node.new
276
+ # node2 = Addressive::Node.new
277
+ # node1.edges[:another_node] = node2
278
+ # node1.traverse(:another_node) #=> node2
279
+ #
280
+ class Node
281
+
282
+ attr_reader :edges, :meta, :uri_specs
283
+ attr_reader :apps
284
+ attr_accessor :defaults
285
+
286
+ include URIBuilder
287
+
288
+ def initialize
289
+ @edges = {}
290
+ @meta = {}
291
+ @apps = []
292
+ @uri_specs = Hash.new{|hsh,name| hsh[name]=URISpecList.new(@defaults) }
293
+ @uri_builder_delegate = URIBuilder.new(self)
294
+ end
295
+
296
+ # Traverses some edges to another ( or the same ) node.
297
+ #
298
+ # @example
299
+ # node1 = Addressive::Node.new
300
+ # node2 = Addressive::Node.new
301
+ # node3 = Addressive::Node.new
302
+ # node1.edges[:n2] = node2
303
+ # node2.edges[:n3] = node3
304
+ # node3.edges[:n1] = node1
305
+ # node1.traverse() #=> node1
306
+ # node1.traverse(:n2) #=> node2
307
+ # node1.traverse(:n2,:n3,:n1) #=> node1
308
+ #
309
+ def traverse(*args)
310
+ return self if args.none?
311
+ if edges[args.first].kind_of? Addressive::Node
312
+ return edges[args.first].traverse(*args[1..-1])
313
+ else
314
+ raise NoEdgeFound.new(self, args.first)
315
+ end
316
+ end
317
+
318
+ def uri_spec(name)
319
+ @uri_specs[name]
320
+ end
321
+
322
+ def inspect
323
+ if @meta['name']
324
+ return "<#{self.class.name}: #{@meta['name']}>"
325
+ else
326
+ super
327
+ end
328
+ end
329
+
330
+ private
331
+ def uri_builder_delegate
332
+ @uri_builder_delegate
333
+ end
334
+
335
+ end
336
+
337
+ # Creates a new node and yields a builder for it
338
+ #
339
+ # This method makes it easy to create a tree-like structur by implictly creating and returning a root node ( by default called :root ). This should be used in most cases, as you will likely have just one connected component in an addressive graph. For complexer graphs you can use {Addressive#graph}.
340
+ #
341
+ # @example
342
+ # node = Addressive.node do
343
+ # edge( :another ) do
344
+ # default :prefix, '/another'
345
+ # uri :default ,'/'
346
+ # end
347
+ # uri :default ,'/'
348
+ # end
349
+ #
350
+ # node.uri.to_s #=> '/'
351
+ # node.uri(:another,:default).to_s #=> '/another/'
352
+ #
353
+ # @param name [Symbol] a name for this node
354
+ # @yield {NodeBuilder}
355
+ # @return {Graph}
356
+ #
357
+ def self.node(name=:root,&block)
358
+ Graph.new{ |bldr|
359
+ bldr.node name, &block
360
+ }[name]
361
+ end
362
+
363
+ # Creates a new graph and yields a builder for it
364
+ #
365
+ # This is good, if you want to create a graph with disconnected nodes or multiple root-nodes.
366
+ #
367
+ # @example
368
+ # graph = Addressive.graph do
369
+ #
370
+ # node :shared do
371
+ # uri :default, '/shared'
372
+ # end
373
+ #
374
+ # node :foo do
375
+ # uri :default ,'/foo'
376
+ # edge :shared
377
+ # end
378
+ #
379
+ # node :bar do
380
+ # uri :default, '/bar'
381
+ # edge :shared
382
+ # end
383
+ #
384
+ # end
385
+ #
386
+ # # You can then get the nodes with {Addressive::Graph.[]}
387
+ # graph[:foo].uri.to_s #=> '/foo'
388
+ # graph[:bar].uri.to_s #=> '/bar'
389
+ # # :foo and :bar can both reach :shared, but not each other.
390
+ # # Neither can :shared reach any other node.
391
+ # graph[:foo].traverse(:shared) #=> graph[:shared]
392
+ # graph[:bar].traverse(:shared) #=> graph[:shared]
393
+ # graph[:foo].traverse(:bar) #!> Addressive::NoEdgeFound
394
+ # graph[:shared].traverse(:bar) #!> Addressive::NoEdgeFound
395
+ #
396
+ # @yield {Builder}
397
+ # @return {Node}
398
+ #
399
+ def self.graph(&block)
400
+ Graph.new(&block)
401
+ end
402
+
403
+ end
404
+
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: addressive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.alpha
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - HannesG
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: uri_template
16
+ requirement: &7411800 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *7411800
25
+ - !ruby/object:Gem::Dependency
26
+ name: simplecov
27
+ requirement: &7408500 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *7408500
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &7407760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *7407760
47
+ - !ruby/object:Gem::Dependency
48
+ name: yard
49
+ requirement: &7460580 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *7460580
58
+ - !ruby/object:Gem::Dependency
59
+ name: rack
60
+ requirement: &7459920 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *7459920
69
+ description: ''
70
+ email: hannes.georg@googlemail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/addressive/request.rb
76
+ - lib/addressive/graphviz.rb
77
+ - lib/addressive/graph.rb
78
+ - lib/addressive/router.rb
79
+ - lib/addressive/static.rb
80
+ - lib/addressive.rb
81
+ - addressive.gemspec
82
+ homepage: https://github.com/hannesg/addressive
83
+ licenses: []
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>'
98
+ - !ruby/object:Gem::Version
99
+ version: 1.3.1
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 1.8.10
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: A system which should help bringing different Rack applications together.
106
+ test_files: []
107
+ has_rdoc: