addressive 0.1.0.alpha
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.
- data/addressive.gemspec +22 -0
- data/lib/addressive/graph.rb +229 -0
- data/lib/addressive/graphviz.rb +21 -0
- data/lib/addressive/request.rb +43 -0
- data/lib/addressive/router.rb +337 -0
- data/lib/addressive/static.rb +71 -0
- data/lib/addressive.rb +404 -0
- metadata +107 -0
data/addressive.gemspec
ADDED
@@ -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:
|