wadl 0.1.2 → 0.1.3.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.
@@ -0,0 +1,193 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # A component of wadl, the super cheap Ruby WADL client. #
5
+ # #
6
+ # Copyright (C) 2006-2008 Leonard Richardson #
7
+ # Copyright (C) 2010 Jens Wille #
8
+ # #
9
+ # Authors: #
10
+ # Leonard Richardson <leonardr@segfault.org> (Original author) #
11
+ # Jens Wille <jens.wille@uni-koeln.de> #
12
+ # #
13
+ # wadl is free software; you can redistribute it and/or modify it under the #
14
+ # terms of the GNU General Public License as published by the Free Software #
15
+ # Foundation; either version 3 of the License, or (at your option) any later #
16
+ # version. #
17
+ # #
18
+ # wadl is distributed in the hope that it will be useful, but WITHOUT ANY #
19
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
20
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
21
+ # details. #
22
+ # #
23
+ # You should have received a copy of the GNU General Public License along #
24
+ # with wadl. If not, see <http://www.gnu.org/licenses/>. #
25
+ # #
26
+ ###############################################################################
27
+ #++
28
+
29
+ require 'wadl'
30
+
31
+ module WADL
32
+
33
+ # The Address class keeps track of the user's path through a resource
34
+ # graph. Values for WADL parameters may be specified at any time using
35
+ # the bind method. An Address cannot be turned into a URI and header
36
+ # set until all required parameters have been bound to values.
37
+ #
38
+ # An Address object is built up through calls to Resource#address
39
+
40
+ class Address
41
+
42
+ attr_reader :path_fragments, :query_vars, :headers,
43
+ :path_params, :query_params, :header_params
44
+
45
+ def self.embedded_param_names(fragment)
46
+ fragment.scan(/\{(.+?)\}/).flatten
47
+ end
48
+
49
+ def initialize(path_fragments = [], query_vars = [], headers = {},
50
+ path_params = {}, query_params = {}, header_params = {})
51
+ @path_fragments, @query_vars, @headers = path_fragments, query_vars, headers
52
+ @path_params, @query_params, @header_params = path_params, query_params, header_params
53
+ end
54
+
55
+ # Perform a deep copy.
56
+ def deep_copy
57
+ Address.new(
58
+ _deep_copy_array(@path_fragments),
59
+ _deep_copy_array(@query_vars),
60
+ _deep_copy_hash(@headers),
61
+ @path_params.dup,
62
+ @query_params.dup,
63
+ @header_params.dup
64
+ )
65
+ end
66
+
67
+ def to_s
68
+ "Address:\n" <<
69
+ " Path fragments: #{@path_fragments.inspect}\n" <<
70
+ " Query variables: #{@query_vars.inspect}\n" <<
71
+ " Header variables: #{@headers.inspect}\n" <<
72
+ " Unbound path parameters: #{@path_params.inspect}\n" <<
73
+ " Unbound query parameters: #{@query_params.inspect}\n" <<
74
+ " Unbound header parameters: #{@header_params.inspect}\n"
75
+ end
76
+
77
+ alias_method :inspect, :to_s
78
+
79
+ # Binds some or all of the unbound variables in this address to values.
80
+ def bind!(args = {})
81
+ path_var_values = args[:path] || {}
82
+ query_var_values = args[:query] || {}
83
+ header_var_values = args[:headers] || {}
84
+
85
+ # Bind variables found in the path fragments.
86
+ path_params_to_delete = []
87
+
88
+ path_fragments.each { |fragment|
89
+ if fragment.respond_to?(:to_str)
90
+ # This fragment is a string which might contain {} substitutions.
91
+ # Make any substitutions available to the provided path variables.
92
+ self.class.embedded_param_names(fragment).each { |param_name|
93
+ value = path_var_values[param_name] || path_var_values[param_name.to_sym]
94
+
95
+ value = if param = path_params[param_name]
96
+ path_params_to_delete << param
97
+ param % value
98
+ else
99
+ Param.default.format(value, param_name)
100
+ end
101
+
102
+ fragment.gsub!("{#{param_name}}", value)
103
+ }
104
+ else
105
+ # This fragment is an array of Param objects (style 'matrix'
106
+ # or 'plain') which may be bound to strings. As substitutions
107
+ # happen, the array will become a mixed array of Param objects
108
+ # and strings.
109
+ fragment.each_with_index { |param, i|
110
+ if param.respond_to?(:name)
111
+ name = param.name
112
+
113
+ value = path_var_values[name] || path_var_values[name.to_sym]
114
+ value = param % value
115
+ fragment[i] = value if value
116
+
117
+ path_params_to_delete << param
118
+ end
119
+ }
120
+ end
121
+ }
122
+
123
+ # Delete any embedded path parameters that are now bound from
124
+ # our list of unbound parameters.
125
+ path_params_to_delete.each { |p| path_params.delete(p.name) }
126
+
127
+ # Bind query variable values to query parameters
128
+ query_var_values.each { |name, value|
129
+ param = query_params.delete(name.to_s)
130
+ query_vars << param % value if param
131
+ }
132
+
133
+ # Bind header variables to header parameters
134
+ header_var_values.each { |name, value|
135
+ param = header_params.delete(name.to_s)
136
+ headers[name] = param % value if param
137
+ }
138
+
139
+ self
140
+ end
141
+
142
+ def uri(args = {})
143
+ obj, uri = deep_copy.bind!(args), ''
144
+
145
+ # Build the path
146
+ obj.path_fragments.flatten.each { |fragment|
147
+ if fragment.respond_to?(:to_str)
148
+ embedded_param_names = self.class.embedded_param_names(fragment)
149
+
150
+ unless embedded_param_names.empty?
151
+ raise ArgumentError, %Q{Missing a value for required path parameter "#{embedded_param_names[0]}"!}
152
+ end
153
+
154
+ unless fragment.empty?
155
+ uri << '/' unless uri.empty? || uri =~ /\/\z/
156
+ uri << fragment
157
+ end
158
+ elsif fragment.required?
159
+ # This is a required Param that was never bound to a value.
160
+ raise ArgumentError, %Q{Missing a value for required path parameter "#{fragment.name}"!}
161
+ end
162
+ }
163
+
164
+ # Hunt for required unbound query parameters.
165
+ obj.query_params.each { |name, value|
166
+ if value.required?
167
+ raise ArgumentError, %Q{Missing a value for required query parameter "#{value.name}"!}
168
+ end
169
+ }
170
+
171
+ # Hunt for required unbound header parameters.
172
+ obj.header_params.each { |name, value|
173
+ if value.required?
174
+ raise ArgumentError, %Q{Missing a value for required header parameter "#{value.name}"!}
175
+ end
176
+ }
177
+
178
+ URIParts.new(uri, obj.query_vars, obj.headers)
179
+ end
180
+
181
+ private
182
+
183
+ def _deep_copy_hash(h)
184
+ h.inject({}) { |h, (k, v)| h[k] = v && v.dup; h }
185
+ end
186
+
187
+ def _deep_copy_array(a)
188
+ a.inject([]) { |a, e| a << (e && e.dup) }
189
+ end
190
+
191
+ end
192
+
193
+ end
@@ -0,0 +1,71 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # A component of wadl, the super cheap Ruby WADL client. #
5
+ # #
6
+ # Copyright (C) 2006-2008 Leonard Richardson #
7
+ # Copyright (C) 2010 Jens Wille #
8
+ # #
9
+ # Authors: #
10
+ # Leonard Richardson <leonardr@segfault.org> (Original author) #
11
+ # Jens Wille <jens.wille@uni-koeln.de> #
12
+ # #
13
+ # wadl is free software; you can redistribute it and/or modify it under the #
14
+ # terms of the GNU General Public License as published by the Free Software #
15
+ # Foundation; either version 3 of the License, or (at your option) any later #
16
+ # version. #
17
+ # #
18
+ # wadl is distributed in the hope that it will be useful, but WITHOUT ANY #
19
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
20
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
21
+ # details. #
22
+ # #
23
+ # You should have received a copy of the GNU General Public License along #
24
+ # with wadl. If not, see <http://www.gnu.org/licenses/>. #
25
+ # #
26
+ ###############################################################################
27
+ #++
28
+
29
+ require 'rexml/document'
30
+ require 'wadl'
31
+
32
+ module WADL
33
+
34
+ class Application < HasDocs
35
+
36
+ in_document 'application'
37
+ has_one Resources
38
+ has_many HTTPMethod, RepresentationFormat, FaultFormat
39
+
40
+ def self.from_wadl(wadl)
41
+ wadl = wadl.read if wadl.respond_to?(:read)
42
+ doc = REXML::Document.new(wadl)
43
+
44
+ application = from_element(nil, doc.root, need_finalization = [])
45
+ need_finalization.each { |x| x.finalize_creation }
46
+
47
+ application
48
+ end
49
+
50
+ def find_resource(symbol, *args, &block)
51
+ resource_list.find_resource(symbol, *args, &block)
52
+ end
53
+
54
+ def resource(symbol)
55
+ resource_list.resource(symbol)
56
+ end
57
+
58
+ def find_resource_by_path(symbol, *args, &block)
59
+ resource_list.find_resource_by_path(symbol, *args, &block)
60
+ end
61
+
62
+ def finalize_creation
63
+ resource_list.resources.each { |r|
64
+ define_singleton(r, :id, 'resource_list.find_resource')
65
+ define_singleton(r, :path, 'resource_list.find_resource_by_path')
66
+ } if resource_list
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,343 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # A component of wadl, the super cheap Ruby WADL client. #
5
+ # #
6
+ # Copyright (C) 2006-2008 Leonard Richardson #
7
+ # Copyright (C) 2010 Jens Wille #
8
+ # #
9
+ # Authors: #
10
+ # Leonard Richardson <leonardr@segfault.org> (Original author) #
11
+ # Jens Wille <jens.wille@uni-koeln.de> #
12
+ # #
13
+ # wadl is free software; you can redistribute it and/or modify it under the #
14
+ # terms of the GNU General Public License as published by the Free Software #
15
+ # Foundation; either version 3 of the License, or (at your option) any later #
16
+ # version. #
17
+ # #
18
+ # wadl is distributed in the hope that it will be useful, but WITHOUT ANY #
19
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
20
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more #
21
+ # details. #
22
+ # #
23
+ # You should have received a copy of the GNU General Public License along #
24
+ # with wadl. If not, see <http://www.gnu.org/licenses/>. #
25
+ # #
26
+ ###############################################################################
27
+ #++
28
+
29
+ require 'wadl'
30
+
31
+ module WADL
32
+
33
+ # A cheap way of defining an XML schema as Ruby classes and then parsing
34
+ # documents into instances of those classes.
35
+
36
+ class CheapSchema
37
+
38
+ @may_be_reference = false
39
+ @contents_are_mixed_data = false
40
+
41
+ ATTRIBUTES = %w[names members collections required_attributes attributes]
42
+
43
+ class << self
44
+
45
+ attr_reader(*ATTRIBUTES)
46
+
47
+ def init
48
+ @names, @members, @collections = {}, {}, {}
49
+ @required_attributes, @attributes = [], []
50
+ end
51
+
52
+ def inherit(from)
53
+ init
54
+
55
+ ATTRIBUTES.each { |attr|
56
+ value = from.send(attr)
57
+ instance_variable_set("@#{attr}", value.dup) if value
58
+ }
59
+
60
+ %w[may_be_reference contents_are_mixed_data].each { |attr|
61
+ instance_variable_set("@#{attr}", from.instance_variable_get("@#{attr}"))
62
+ }
63
+ end
64
+
65
+ def inherited(klass)
66
+ klass.inherit(self)
67
+ end
68
+
69
+ def may_be_reference?
70
+ @may_be_reference
71
+ end
72
+
73
+ def in_document(element_name)
74
+ @names[:element] = element_name
75
+ @names[:member] = element_name
76
+ @names[:collection] = element_name + 's'
77
+ end
78
+
79
+ def as_collection(collection_name)
80
+ @names[:collection] = collection_name
81
+ end
82
+
83
+ def as_member(member_name)
84
+ @names[:member] = member_name
85
+ end
86
+
87
+ def contents_are_mixed_data
88
+ @contents_are_mixed_data = true
89
+ end
90
+
91
+ def has_one(*classes)
92
+ classes.each { |klass|
93
+ @members[klass.names[:element]] = klass
94
+ dereferencing_instance_accessor(klass.names[:member])
95
+ }
96
+ end
97
+
98
+ def has_many(*classes)
99
+ classes.each { |klass|
100
+ @collections[klass.names[:element]] = klass
101
+
102
+ collection_name = klass.names[:collection]
103
+ dereferencing_instance_accessor(collection_name)
104
+
105
+ # Define a method for finding a specific element of this
106
+ # collection.
107
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
108
+ def find_#{klass.names[:element]}(*args, &block)
109
+ block ||= begin
110
+ name = args.shift.to_s
111
+ lambda { |match| match.matches?(name) }
112
+ end
113
+
114
+ auto_dereference = args.shift
115
+ auto_dereference = true if auto_dereference.nil?
116
+
117
+ match = #{collection_name}.find { |match|
118
+ block[match] || (
119
+ #{klass}.may_be_reference? &&
120
+ auto_dereference &&
121
+ block[match.dereference]
122
+ )
123
+ }
124
+
125
+ match && auto_dereference ? match.dereference : match
126
+ end
127
+ EOT
128
+ }
129
+ end
130
+
131
+ def dereferencing_instance_accessor(*symbols)
132
+ define_dereferencing_accessors(symbols,
133
+ 'd, v = dereference, :@%s; ' <<
134
+ 'd.instance_variable_get(v) if d.instance_variable_defined?(v)',
135
+ 'dereference.instance_variable_set(:@%s, value)'
136
+ )
137
+ end
138
+
139
+ def dereferencing_attr_accessor(*symbols)
140
+ define_dereferencing_accessors(symbols,
141
+ 'dereference.attributes["%s"]',
142
+ 'dereference.attributes["%s"] = value'
143
+ )
144
+ end
145
+
146
+ def has_attributes(*names)
147
+ has_required_or_attributes(names, @attributes)
148
+ end
149
+
150
+ def has_required(*names)
151
+ has_required_or_attributes(names, @required_attributes)
152
+ end
153
+
154
+ def may_be_reference
155
+ @may_be_reference = true
156
+
157
+ find_method_name = "find_#{names[:element]}"
158
+
159
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
160
+ def dereference
161
+ return self unless href = attributes['href']
162
+
163
+ unless @referenced
164
+ p = self
165
+
166
+ until @referenced || !p
167
+ begin
168
+ p = p.parent
169
+ end until !p || p.respond_to?(:#{find_method_name})
170
+
171
+ @referenced = p.#{find_method_name}(href, false) if p
172
+ end
173
+ end
174
+
175
+ dereference_with_context(@referenced) if @referenced
176
+ end
177
+ EOT
178
+ end
179
+
180
+ # Turn an XML element into an instance of this class.
181
+ def from_element(parent, element, need_finalization)
182
+ attributes = element.attributes
183
+
184
+ me = new
185
+ me.parent = parent
186
+
187
+ @collections.each { |name, klass|
188
+ me.instance_variable_set("@#{klass.names[:collection]}", [])
189
+ }
190
+
191
+ if may_be_reference? and href = attributes['href']
192
+ # Handle objects that are just references to other objects
193
+ # somewhere above this one in the hierarchy
194
+ href = href.dup
195
+ href.sub!(/\A#/, '') or warn "Warning: HREF #{href} should be ##{href}"
196
+
197
+ me.attributes['href'] = href
198
+ else
199
+ # Handle this element's attributes
200
+ @required_attributes.each { |name|
201
+ name = name.to_s
202
+
203
+ raise ArgumentError, %Q{Missing required attribute "#{name}" in element: #{element}} unless attributes[name]
204
+
205
+ me.attributes[name] = attributes[name]
206
+ me.index_key = attributes[name] if name == @index_attribute
207
+ }
208
+
209
+ @attributes.each { |name|
210
+ name = name.to_s
211
+
212
+ me.attributes[name] = attributes[name]
213
+ me.index_key = attributes[name] if name == @index_attribute
214
+ }
215
+ end
216
+
217
+ # Handle this element's children.
218
+ if @contents_are_mixed_data
219
+ me.instance_variable_set(:@contents, element.children)
220
+ else
221
+ element.each_element { |child|
222
+ if klass = @members[child.name] || @collections[child.name]
223
+ object = klass.from_element(me, child, need_finalization)
224
+
225
+ if klass == @members[child.name]
226
+ instance_variable_name = "@#{klass.names[:member]}"
227
+
228
+ if me.instance_variable_defined?(instance_variable_name)
229
+ raise "#{name} can only have one #{klass.name}, but several were specified in element: #{element}"
230
+ end
231
+
232
+ me.instance_variable_set(instance_variable_name, object)
233
+ else
234
+ me.instance_variable_get("@#{klass.names[:collection]}") << object
235
+ end
236
+ end
237
+ }
238
+ end
239
+
240
+ need_finalization << me if me.respond_to?(:finalize_creation)
241
+
242
+ me
243
+ end
244
+
245
+ private
246
+
247
+ def define_dereferencing_accessors(symbols, getter, setter)
248
+ symbols.each { |name|
249
+ name = name.to_s
250
+
251
+ class_eval <<-EOT, __FILE__, __LINE__ + 1 unless name =~ /\W/
252
+ def #{name}; #{getter % name}; end
253
+ def #{name}=(value); #{setter % name}; end
254
+ EOT
255
+ }
256
+ end
257
+
258
+ def has_required_or_attributes(names, var)
259
+ names.each { |name|
260
+ var << name
261
+ @index_attribute ||= name.to_s
262
+ name == :href ? attr_accessor(name) : dereferencing_attr_accessor(name)
263
+ }
264
+ end
265
+
266
+ end
267
+
268
+ attr_accessor :index_key, :href, :parent
269
+ attr_reader :attributes
270
+
271
+ def initialize
272
+ @attributes, @contents, @referenced = {}, nil, nil
273
+ end
274
+
275
+ # This object is a reference to another object. This method returns
276
+ # an object that acts like the other object, but also contains any
277
+ # neccessary context about this object. See the ResourceAndAddress
278
+ # implementation, in which a dereferenced resource contains
279
+ # information about the parent of the resource that referenced it
280
+ # (otherwise, there's no way to build the URI).
281
+ def dereference_with_context(referent)
282
+ referent
283
+ end
284
+
285
+ # A null implementation so that foo.dereference will always return the
286
+ # "real" object.
287
+ def dereference
288
+ self
289
+ end
290
+
291
+ # Returns whether or not the given name matches this object.
292
+ # By default, checks the index key for this class.
293
+ def matches?(name)
294
+ index_key == name
295
+ end
296
+
297
+ def to_s(indent = 0)
298
+ klass = self.class
299
+
300
+ i = ' ' * indent
301
+ s = "#{i}#{klass.name}\n"
302
+
303
+ if klass.may_be_reference? and href = attributes['href']
304
+ s << "#{i} href=#{href}\n"
305
+ else
306
+ [klass.required_attributes, klass.attributes].each { |list|
307
+ list.each { |attr|
308
+ val = attributes[attr.to_s]
309
+ s << "#{i} #{attr}=#{val}\n" if val
310
+ }
311
+ }
312
+
313
+ klass.members.each_value { |member_class|
314
+ o = send(member_class.names[:member])
315
+ s << o.to_s(indent + 1) if o
316
+ }
317
+
318
+ klass.collections.each_value { |collection_class|
319
+ c = send(collection_class.names[:collection])
320
+
321
+ if c && !c.empty?
322
+ s << "#{i} Collection of #{c.size} #{collection_class.name}(s)\n"
323
+ c.each { |o| s << o.to_s(indent + 2) }
324
+ end
325
+ }
326
+
327
+ if @contents && !@contents.empty?
328
+ sep = '-' * 80
329
+ s << "#{sep}\n#{@contents.join(' ')}\n#{sep}\n"
330
+ end
331
+ end
332
+
333
+ s
334
+ end
335
+
336
+ end
337
+
338
+ end
339
+
340
+ # Simple backport for Ruby <= 1.8.5
341
+ class Object # :nodoc:
342
+ def instance_variable_defined?(sym); instance_eval("defined?(#{sym})"); end
343
+ end unless Object.method_defined?(:instance_variable_defined?)