lebowski 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. data/History.md +3 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +84 -0
  4. data/README.md +146 -0
  5. data/Rakefile +42 -0
  6. data/bin/lebowski +26 -0
  7. data/bin/lebowski-spec +29 -0
  8. data/bin/lebowski-start-server +24 -0
  9. data/lib/lebowski.rb +15 -0
  10. data/lib/lebowski/core.rb +35 -0
  11. data/lib/lebowski/foundation.rb +52 -0
  12. data/lib/lebowski/foundation/application.rb +315 -0
  13. data/lib/lebowski/foundation/core.rb +61 -0
  14. data/lib/lebowski/foundation/core_query.rb +231 -0
  15. data/lib/lebowski/foundation/dom_element.rb +114 -0
  16. data/lib/lebowski/foundation/errors/argument_invalid_type.rb +31 -0
  17. data/lib/lebowski/foundation/errors/unexpected_type.rb +30 -0
  18. data/lib/lebowski/foundation/mixins/collection_item_view_support.rb +87 -0
  19. data/lib/lebowski/foundation/mixins/delegate_support.rb +22 -0
  20. data/lib/lebowski/foundation/mixins/inline_text_field_support.rb +29 -0
  21. data/lib/lebowski/foundation/mixins/key_check.rb +35 -0
  22. data/lib/lebowski/foundation/mixins/list_item_view_support.rb +36 -0
  23. data/lib/lebowski/foundation/mixins/positioned_element.rb +20 -0
  24. data/lib/lebowski/foundation/mixins/stall_support.rb +79 -0
  25. data/lib/lebowski/foundation/mixins/user_actions.rb +302 -0
  26. data/lib/lebowski/foundation/mixins/wait_actions.rb +44 -0
  27. data/lib/lebowski/foundation/object_array.rb +305 -0
  28. data/lib/lebowski/foundation/panes/alert.rb +117 -0
  29. data/lib/lebowski/foundation/panes/main.rb +20 -0
  30. data/lib/lebowski/foundation/panes/menu.rb +100 -0
  31. data/lib/lebowski/foundation/panes/modal.rb +21 -0
  32. data/lib/lebowski/foundation/panes/palette.rb +21 -0
  33. data/lib/lebowski/foundation/panes/pane.rb +24 -0
  34. data/lib/lebowski/foundation/panes/panel.rb +25 -0
  35. data/lib/lebowski/foundation/panes/picker.rb +43 -0
  36. data/lib/lebowski/foundation/panes/sheet.rb +21 -0
  37. data/lib/lebowski/foundation/proxy_factory.rb +87 -0
  38. data/lib/lebowski/foundation/proxy_object.rb +670 -0
  39. data/lib/lebowski/foundation/sc_object.rb +38 -0
  40. data/lib/lebowski/foundation/views/button.rb +20 -0
  41. data/lib/lebowski/foundation/views/checkbox.rb +63 -0
  42. data/lib/lebowski/foundation/views/collection.rb +304 -0
  43. data/lib/lebowski/foundation/views/container.rb +32 -0
  44. data/lib/lebowski/foundation/views/disclosure.rb +59 -0
  45. data/lib/lebowski/foundation/views/grid.rb +21 -0
  46. data/lib/lebowski/foundation/views/label.rb +30 -0
  47. data/lib/lebowski/foundation/views/list.rb +67 -0
  48. data/lib/lebowski/foundation/views/list_item.rb +280 -0
  49. data/lib/lebowski/foundation/views/menu_item.rb +27 -0
  50. data/lib/lebowski/foundation/views/radio.rb +32 -0
  51. data/lib/lebowski/foundation/views/segmented.rb +97 -0
  52. data/lib/lebowski/foundation/views/select_field.rb +139 -0
  53. data/lib/lebowski/foundation/views/support/simple_item_array.rb +249 -0
  54. data/lib/lebowski/foundation/views/text_field.rb +108 -0
  55. data/lib/lebowski/foundation/views/view.rb +108 -0
  56. data/lib/lebowski/runtime.rb +7 -0
  57. data/lib/lebowski/runtime/errors/remote_control_command_execution_error.rb +9 -0
  58. data/lib/lebowski/runtime/errors/remote_control_command_timeout_error.rb +9 -0
  59. data/lib/lebowski/runtime/errors/remote_control_error.rb +9 -0
  60. data/lib/lebowski/runtime/errors/selenium_server_error.rb +9 -0
  61. data/lib/lebowski/runtime/object_encoder.rb +123 -0
  62. data/lib/lebowski/runtime/sprout_core_driver.rb +14 -0
  63. data/lib/lebowski/runtime/sprout_core_extensions.rb +600 -0
  64. data/lib/lebowski/scui.rb +18 -0
  65. data/lib/lebowski/scui/mixins/node_item_view_support.rb +136 -0
  66. data/lib/lebowski/scui/mixins/terminal_view_support.rb +25 -0
  67. data/lib/lebowski/scui/views/combo_box.rb +119 -0
  68. data/lib/lebowski/scui/views/date_picker.rb +148 -0
  69. data/lib/lebowski/scui/views/linkit.rb +36 -0
  70. data/lib/lebowski/spec.rb +17 -0
  71. data/lib/lebowski/spec/core.rb +21 -0
  72. data/lib/lebowski/spec/matchers/be.rb +63 -0
  73. data/lib/lebowski/spec/matchers/has.rb +40 -0
  74. data/lib/lebowski/spec/matchers/match_supporters/has_object_function.rb +67 -0
  75. data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_no_prefix.rb +50 -0
  76. data/lib/lebowski/spec/matchers/match_supporters/has_predicate_with_prefix_has.rb +50 -0
  77. data/lib/lebowski/spec/matchers/match_supporters/match_supporter.rb +29 -0
  78. data/lib/lebowski/spec/matchers/method_missing.rb +24 -0
  79. data/lib/lebowski/spec/operators/operator.rb +20 -0
  80. data/lib/lebowski/spec/operators/that.rb +116 -0
  81. data/lib/lebowski/spec/util.rb +26 -0
  82. data/lib/lebowski/version.rb +17 -0
  83. data/resources/selenium-server.jar +0 -0
  84. data/resources/user-extensions.js +1421 -0
  85. metadata +198 -0
@@ -0,0 +1,670 @@
1
+ # ==========================================================================
2
+ # Project: Lebowski Framework - The SproutCore Test Automation Framework
3
+ # License: Licensed under MIT license (see License.txt)
4
+ # ==========================================================================
5
+
6
+ module Lebowski
7
+ module Foundation
8
+
9
+ #
10
+ # ProxyObject is the root object for all objects that are to proxy an object within a
11
+ # web brower. This provides all of the core functionality allowing you to communicate with
12
+ # a remote object and access other remote objects through the use of relative property paths.
13
+ #
14
+ # In the case where you have created a custom SproutCore object and want to have a proxy for it
15
+ # then your proxy must inherit from the SCObject class that inherits this class.
16
+ #
17
+ class ProxyObject
18
+
19
+ include Lebowski::Foundation
20
+ include Lebowski::Foundation::Mixins::WaitActions
21
+
22
+ attr_reader :parent, # The parent object of this object. Must derive from Lebowski::Foundation::ProxyObject
23
+ :rel_path, # The relative path to the remote object using SC property path notation
24
+ :driver # The SproutCore driver that is used to actually communicate with the remote object
25
+
26
+ attr_accessor :name # A name for this object. Useful for printing out statements and debugging
27
+
28
+ #
29
+ # Creates a new proxy object instance.
30
+ #
31
+ # Try to refrain from overriding this method. Instead, if you wish to perform some operations
32
+ # during the time an object is being initialized, override the init_ext method
33
+ #
34
+ # @param parent {Object} parent object of this object. Must inherit from Lebowski::Foundation::ProxyObject
35
+ # @param rel_path {String} a relative path to the remote object (e.g. 'foo', 'foo.bar')
36
+ # @param driver {Object} used to remotely communicate with the remote object.
37
+ #
38
+ # @see #init_ext
39
+ #
40
+ def initialize(parent=nil, rel_path=nil, driver=nil)
41
+
42
+ if not init_expected_parent_type.nil?
43
+ if not parent.kind_of? init_expected_parent_type
44
+ raise ArgumentInvalidTypeError.new "parent", parent, init_expected_parent_type
45
+ end
46
+ end
47
+
48
+ @parent = parent
49
+ @rel_path = rel_path
50
+ @driver = driver
51
+ @guid = nil
52
+ @defined_paths = {}
53
+ @defined_proxies = {}
54
+ @name = ""
55
+
56
+ init_ext()
57
+ end
58
+
59
+ #
60
+ # Override this method for any initialization procedures during the time the object is being
61
+ # inialized
62
+ #
63
+ # @see #initialize
64
+ #
65
+ def init_ext()
66
+
67
+ end
68
+
69
+ #
70
+ # Use when you wish to represent a proxied object as something else other then the proxy
71
+ # returned by this object when accessed with a relative path using []. This is useful
72
+ # in cases where you have a view that is simply composed of other views but itself is not
73
+ # custom view inherited from SC.View. As an example, it is common in SproutCore to
74
+ # create a complex view that lives only within an SC.Page, like so:
75
+ #
76
+ # MyApp.mainPage = SC.Page.create({
77
+ #
78
+ # composedView: SC.View.design({
79
+ # layout: { top: 0, bottom: 0, left: 0, right: 0 },
80
+ # childViews: 'buttonOne buttonTwo statusLabel'.w(),
81
+ #
82
+ # buttonOne: SC.ButtonView.design({
83
+ # ...
84
+ # }),
85
+ #
86
+ # buttonTwo: SC.ButtonView.design({
87
+ # ...
88
+ # }),
89
+ #
90
+ # statusLabel: SC.LabelView.design({
91
+ # ...
92
+ # })
93
+ # })
94
+ #
95
+ # })
96
+ #
97
+ # Since the root view (composedView) is just a basic SC.View, accessing it using
98
+ # the proxy's [] convention would just give you back a basic View proxy. This then
99
+ # means you have to access the child views explicitly every time you want to interact
100
+ # with the composed view. This can be brittle since the view's structure can change.
101
+ # Instead you can make a proxy to abstract away the interal structure and make it
102
+ # easier to work with the view. Therefore, we could make a proxy as follows:
103
+ #
104
+ # ComposedView < Lebowski::Foundation::Views::View
105
+ #
106
+ # def click_button_one()
107
+ # self['buttonOne'].click
108
+ # end
109
+ #
110
+ # def click_button_two()
111
+ # self['buttonTwo'].click
112
+ # end
113
+ #
114
+ # def status()
115
+ # return self['statusLabel.value']
116
+ # end
117
+ #
118
+ # end
119
+ #
120
+ # With the proxy above, you can then do the following:
121
+ #
122
+ # view = App['mainPage.composedView', View]
123
+ # view = view.represent_as(ComposedView)
124
+ # view.click_button_one
125
+ # status = view.status
126
+ #
127
+ def represent_as(type)
128
+ if not (type.kind_of?(Class) and type.ancestors.member?(ProxyObject))
129
+ raise ArgumentInvalidTypeError.new "type", type, 'class < ProxyObject'
130
+ end
131
+
132
+ obj = type.new @parent, @rel_path, @driver
133
+ return obj
134
+ end
135
+
136
+ #
137
+ # Returns the absolute path of this object based on the parent object heirarchy. As
138
+ # an example, this object's parent has an absolute path of 'foo' and this object has
139
+ # relative path of 'bar', then the absolute path will be 'foo.bar'
140
+ #
141
+ def abs_path()
142
+ if not @abs_path.nil?
143
+ return @abs_path
144
+ end
145
+
146
+ if @parent.nil? or @parent.abs_path.nil?
147
+ return @rel_path
148
+ end
149
+
150
+ @abs_path = "#{@parent.abs_path}.#{rel_path}"
151
+
152
+ return @abs_path
153
+ end
154
+
155
+ #
156
+ # Returns the absolute path given a relative path. Say, for example, that a
157
+ # proxy object has an absolute path of 'mainPage.mainPane.someView'. When
158
+ # given a relative path of 'foo.bar', the returned value would be:
159
+ #
160
+ # 'mainPage.mainPane.someView.foo.bar'
161
+ #
162
+ def abs_path_with(rel_path)
163
+ path = abs_path
164
+ return rel_path if path.nil?
165
+ return "#{path}.#{rel_path}"
166
+ end
167
+
168
+ def define(key, rel_path, expected_type=nil)
169
+ if (not key.kind_of?(String)) or key.empty? or (not key.match(/[\. ]/).nil?)
170
+ raise ArgumentError.raise "key must be a valid string"
171
+ end
172
+
173
+ if (not rel_path.kind_of?(String)) or rel_path.empty?
174
+ raise ArgumentError.raise "rel_path must be a valid string"
175
+ end
176
+
177
+ if @defined_paths.has_key? key
178
+ raise ArgumentError.raise "key '#{key}' already defined as path '#{@defined_paths[key]}'"
179
+ end
180
+
181
+ first_path_part = rel_path_first_part(rel_path)
182
+ sub_path = rel_path_sub_path(rel_path, first_path_part)
183
+
184
+ type = ""
185
+ err_abs_path = abs_path_with(rel_path)
186
+
187
+ if @defined_paths.has_key? first_path_part
188
+ obj = @defined_paths[first_path_part]
189
+ type = obj.sc_type_of(sub_path)
190
+ err_abs_path = obj.abs_path_with(sub_path)
191
+ else
192
+ type = sc_type_of(rel_path)
193
+ end
194
+
195
+ if not (type == SC_T_OBJECT or type == SC_T_HASH)
196
+ err_msg = "Error trying to define key '#{key}'. "
197
+ err_msg << "Relative path '#{rel_path}' does not point to an object. "
198
+ err_msg << "Path is refencing: #{type}. Absolute path = #{err_abs_path}"
199
+ raise ArgumentError.new err_msg
200
+ end
201
+
202
+ obj = self[rel_path, expected_type]
203
+
204
+ @defined_paths[key] = obj
205
+
206
+ return obj
207
+ end
208
+
209
+ def defined()
210
+ return @defined_paths.clone if (not @defined_paths.nil?)
211
+ return {}
212
+ end
213
+
214
+ def proxy(klass, rel_path)
215
+ obj = self[rel_path]
216
+ if not obj.kind_of?(ProxyObject)
217
+ raise ArgumentError.new "rel_path does not point to an object that can be proxied: #{obj} (#{obj.class})"
218
+ end
219
+
220
+ if not (klass.kind_of?(Class) and klass.ancestors.member?(ProxyObject))
221
+ raise ArgumentInvalidTypeError.new "klass", klass, 'class < ProxyObject'
222
+ end
223
+
224
+ @defined_proxies[rel_path] = obj.represent_as(klass)
225
+ end
226
+
227
+ #
228
+ # Given a relative path, unravel it to access an object. Unraveling means to take
229
+ # any defined paths in the given relative path and convert the entire path back
230
+ # into a full relative path without definitions.
231
+ #
232
+ def unravel_relative_path(rel_path)
233
+ if not @defined_proxies[rel_path].nil?
234
+ return @defined_proxies[rel_path]
235
+ end
236
+
237
+ first_path_part = rel_path_first_part(rel_path)
238
+ sub_path = rel_path_sub_path(rel_path, first_path_part)
239
+
240
+ return rel_path if (not @defined_paths.has_key?(first_path_part))
241
+
242
+ obj = @defined_paths[first_path_part]
243
+ return obj if sub_path.empty?
244
+
245
+ result = obj.unravel_relative_path(sub_path)
246
+
247
+ return "#{obj.rel_path}.#{result}" if result.kind_of?(String)
248
+ return result if result.kind_of?(Lebowski::Foundation::SCObject)
249
+
250
+ raise StandardError.new "Unexpected result unreeling rel path: #{result}"
251
+ end
252
+
253
+ #
254
+ # Gets the remote SproutCore GUID for this object
255
+ #
256
+ def sc_guid()
257
+ # We only need to fetch the remote GUID once since it never changes for a given instance
258
+ @guid = @driver.get_sc_guid(abs_path) if @guid.nil?
259
+ return @guid
260
+ end
261
+
262
+ #
263
+ # Gets the remote SC class name for this object
264
+ #
265
+ def sc_class()
266
+ # We only need to fetch the remote SC class name once since it never changes for a given instance
267
+ @class_name = @driver.get_sc_object_class_name(abs_path) if @class_name.nil?
268
+ return @class_name
269
+ end
270
+
271
+ #
272
+ # Gets all the remote SproutCore classes that the proxied object derives from. This will return
273
+ # an array of strings representing the names of the classes. As an example, if the
274
+ # proxy was communicating with an object that was of type SC.ButtonView then the
275
+ # result would be the following:
276
+ #
277
+ # ['SC.ButtonView', 'SC.View', 'SC.Object']
278
+ #
279
+ # The last item in the array is always 'SC.Object' since that is the root object for all
280
+ # SproutCore objects.
281
+ #
282
+ def sc_all_classes()
283
+ @all_class_names = @driver.get_sc_object_class_names(abs_path) if @all_class_names.nil?
284
+ return @all_class_names
285
+ end
286
+
287
+ #
288
+ # Checks if the remote proxied object is a kind of given SC class
289
+ #
290
+ def sc_kind_of?(type)
291
+ if not (type.kind_of?(Class) or type.kind_of?(String))
292
+ raise ArgumentInvalidTypeError.new "type", type, 'class < SCObject', String
293
+ end
294
+
295
+ if type.kind_of?(Class) and type.ancestors.member?(SCObject)
296
+ type = type.represented_sc_class
297
+ end
298
+
299
+ type = type.downcase
300
+ result = sc_all_classes.detect do |val|
301
+ val.downcase == type
302
+ end
303
+ return (not result.nil?)
304
+ end
305
+
306
+ def sc_type_of(rel_path)
307
+ return @driver.get_sc_type_of(abs_path_with(rel_path))
308
+ end
309
+
310
+ def sc_path_defined?(rel_path)
311
+ return (not sc_type_of(rel_path) == SC_T_UNDEFINED)
312
+ end
313
+
314
+ def none?(rel_path)
315
+ type = sc_type_of(rel_path)
316
+ return (type == SC_T_UNDEFINED or type == SC_T_NULL)
317
+ end
318
+
319
+ def object?(rel_path)
320
+ type = sc_type_of(rel_path)
321
+ return (type == SC_T_OBJECT or type == SC_T_HASH)
322
+ end
323
+
324
+ #
325
+ # The primary method used to access a proxied object's properties. Accessing
326
+ # a property is done using a relative property path. The path is a chain of
327
+ # properties connected using dots '.'. The type is automatically determined, but
328
+ # in cases where a particular type is expected, you can optionally supply what the
329
+ # expected type should be.
330
+ #
331
+ # As an example, to access an object's property called 'foo', you can do the
332
+ # following:
333
+ #
334
+ # value = object['foo']
335
+ #
336
+ # If you expect the value's type to be, say, an number, you can do the following:
337
+ #
338
+ # value = object['foo', :number]
339
+ #
340
+ # In the case where you expect the value to be a type of object you can do one
341
+ # of the following:
342
+ #
343
+ # value = object['foo', 'SC.SomeObject']
344
+ #
345
+ # value = object['foo', SomeObject]
346
+ #
347
+ # In the first case, you are supply the object type as a string, in the second case
348
+ # you are supplying the expected type with a proxy class. For the second option to
349
+ # work you must first supply the proxy to the proxy factory.
350
+ #
351
+ # To access a property through a chain of objects, you supply a relative path, like
352
+ # so:
353
+ #
354
+ # value = object['path.to.some.property']
355
+ #
356
+ # Remember that the path is relative to the object you passed the path to. The approach
357
+ # is used to work with how you would normally access properties using the SproutCore
358
+ # framework.
359
+ #
360
+ # The fundamental types detected within the web browser are the following:
361
+ #
362
+ # Null - null in JavaScript
363
+ # Error - SproutCore error object
364
+ # String - string in JavaScript
365
+ # Number - number in JavaScript
366
+ # Boolean - boolean in JavaScript
367
+ # Hash - a JavaScript hash object
368
+ # Object - a SproutCore object
369
+ # Array - a JavaScript array
370
+ # Class - A SproutCore class
371
+ #
372
+ # Based on the value's type within the browser, this method will translate the value as
373
+ # follows:
374
+ #
375
+ # Null -> nil
376
+ # Error -> :error
377
+ # String -> standard string
378
+ # Number -> standard number
379
+ # Boolean -> standard boolean
380
+ # Hash -> a generic proxy object
381
+ # Object -> closest matching proxy object
382
+ # Array -> standard array for basic types; an object array (ObjectArray) for objects
383
+ # Class -> a generic proxy object
384
+ #
385
+ # If the given relative path tries to reference a property that is not defined then :undefined
386
+ # is returned.
387
+ #
388
+ # The two special cases are when the basic type of the relative path is a SproutCore object or
389
+ # an array. In the case of a SproutCore object, the closest matching object type will be returned
390
+ # based on what proxies have been provided to the proxy factory. For instance, let's say you have
391
+ # custom view that derives from SC.View. If no proxy has been made for the custom view then
392
+ # the next closest proxy will be returned, which would be a View proxy that is already part
393
+ # of the lebowski framework. If your require a proxy to interact with the custom view then you
394
+ # need to add that proxy to the proxy framework.
395
+ #
396
+ # When the type is an array, the proxy object will check the content of the array to determine
397
+ # their type. If all the content in the array are of the same type then it will return a
398
+ # corresponding array made up content with that type. So, for example, if an object has
399
+ # a property that is an array of strings then a basic array of string will be returned. In the
400
+ # case where the array contains either hash objects or SproutCore objects then an ObjectArray
401
+ # will be returned.
402
+ #
403
+ def [](rel_path, expected_type=nil)
404
+
405
+ result = unravel_relative_path(rel_path)
406
+
407
+ if (not result.kind_of?(String))
408
+ if (not expected_type.nil?)
409
+ got_expected_type = (expected_type == :object or result.sc_kind_of?(expected_type))
410
+ if (not got_expected_type)
411
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, "object", result)
412
+ end
413
+ end
414
+ return result
415
+ end
416
+
417
+ rel_path = result
418
+ type = sc_type_of(rel_path)
419
+
420
+ case type
421
+ when SC_T_NULL
422
+ return handle_type_null(rel_path, expected_type)
423
+
424
+ when SC_T_UNDEFINED
425
+ return handle_type_undefined(rel_path, expected_type)
426
+
427
+ when SC_T_ERROR
428
+ return handle_type_error(rel_path, expected_type)
429
+
430
+ when SC_T_STRING
431
+ return handle_type_string(rel_path, expected_type)
432
+
433
+ when SC_T_NUMBER
434
+ return handle_type_number(rel_path, expected_type)
435
+
436
+ when SC_T_BOOL
437
+ return handle_type_bool(rel_path, expected_type)
438
+
439
+ when SC_T_ARRAY
440
+ return handle_type_array(rel_path, expected_type)
441
+
442
+ when SC_T_HASH
443
+ return handle_type_hash(rel_path, expected_type)
444
+
445
+ when SC_T_OBJECT
446
+ return handle_type_object(rel_path, expected_type)
447
+
448
+ when SC_T_CLASS
449
+ return handle_type_class(rel_path, expected_type)
450
+
451
+ else
452
+ raise StandardError.new "Unrecognized returned type '#{type}' for path #{abs_path_with(rel_path)}"
453
+ end
454
+ end
455
+
456
+ #
457
+ # Override the == operator so that a proxy object can be compared to another
458
+ # proxy object via their SproutCore GUIDs
459
+ #
460
+ def ==(obj)
461
+ return (self.sc_guid == obj.sc_guid) if obj.kind_of?(ProxyObject)
462
+ return super(obj)
463
+ end
464
+
465
+ alias_method :eql?, :==
466
+
467
+ #
468
+ # Override method_missing so that we can access a proxied object's properties using
469
+ # a more conventional Ruby approach. So instead of accessing an object's property
470
+ # using the [] convention, we can instead do the following:
471
+ #
472
+ # value = proxied_object.foo # compared to proxied_object['foo']
473
+ #
474
+ # This will also translate the name of property into camel case that is normally
475
+ # used in JavaScript. So, if an object in JavaScript has a property with the
476
+ # name 'fooBar', you can access that property using the standard Ruby convention
477
+ # like so:
478
+ #
479
+ # value = proxied_object.foo_bar
480
+ #
481
+ # It will be converted back into 'fooBar'. If the property does not exist
482
+ # on the proxied object then an exception will be thrown. If you want to access
483
+ # property without an exception being thrown then use the [] convention using
484
+ # a relative property path string.
485
+ #
486
+ def method_missing(sym, *args, &block)
487
+ if (not sym.to_s =~ /\?$/) and (args.length == 0)
488
+ camel_case = to_camel_case(sym.to_s)
489
+ return self[camel_case] if sc_path_defined?(camel_case)
490
+ end
491
+ super
492
+ end
493
+
494
+ private
495
+
496
+ def init_expected_parent_type()
497
+ return nil
498
+ end
499
+
500
+ def rel_path_first_part(rel_path)
501
+ first_path_part = rel_path.match(/^((\w|-)*)\./)
502
+ first_path_part = rel_path if first_path_part.nil?
503
+ first_path_part = first_path_part[1] if first_path_part.kind_of?(MatchData)
504
+ return first_path_part
505
+ end
506
+
507
+ def rel_path_sub_path(rel_path, first_path_part)
508
+ sub_path = (first_path_part != rel_path) ? rel_path.sub(/^(\w|-)*\./, "") : ""
509
+ return sub_path
510
+ end
511
+
512
+ def handle_type_null(rel_path, expected_type)
513
+ if (not expected_type.nil?) and not (expected_type == :null)
514
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_NULL, :null)
515
+ end
516
+
517
+ return nil
518
+ end
519
+
520
+ def handle_type_undefined(rel_path, expected_type)
521
+ if (not expected_type.nil?) and not (expected_type == :undefined)
522
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_UNDEFINED, :undefined)
523
+ end
524
+
525
+ return :undefined
526
+ end
527
+
528
+ def handle_type_error(rel_path, expected_type)
529
+ if (not expected_type.nil?) and not (expected_type == :error)
530
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_ERROR, :error)
531
+ end
532
+
533
+ return :error
534
+ end
535
+
536
+ def handle_type_bool(rel_path, expected_type)
537
+ value = @driver.get_sc_path_boolean_value(abs_path_with(rel_path))
538
+
539
+ if (not expected_type.nil?) and not (expected_type == :boolean or expected_type == :bool)
540
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_BOOL, value)
541
+ end
542
+
543
+ return value
544
+ end
545
+
546
+ def handle_type_string(rel_path, expected_type)
547
+ value = @driver.get_sc_path_string_value(abs_path_with(rel_path))
548
+
549
+ if (not expected_type.nil?) and not (expected_type == :string)
550
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_STRING, value)
551
+ end
552
+
553
+ return value
554
+ end
555
+
556
+ def handle_type_number(rel_path, expected_type)
557
+ value = @driver.get_sc_path_number_value(abs_path_with(rel_path))
558
+
559
+ if (not expected_type.nil?) and not (expected_type == :number)
560
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_NUMBER, value)
561
+ end
562
+
563
+ return value
564
+ end
565
+
566
+ def handle_type_class(rel_path, expected_type)
567
+ # TODO: Need to handle this case better
568
+ value = ProxyObject.new self, rel_path, @driver
569
+
570
+ if (not expected_type.nil?) and not (expected_type == :class)
571
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_CLASS, value)
572
+ end
573
+
574
+ return value
575
+ end
576
+
577
+ def handle_type_hash(rel_path, expected_type)
578
+ value = ProxyObject.new self, rel_path, @driver
579
+
580
+ if (not expected_type.nil?) and not (expected_type == :object or expected_type == :hash)
581
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_HASH, value)
582
+ end
583
+
584
+ return value
585
+ end
586
+
587
+ def handle_type_object(rel_path, expected_type)
588
+
589
+ value = nil
590
+
591
+ class_names = @driver.get_sc_object_class_names(abs_path_with(rel_path))
592
+ matching_class = class_names.detect { |name| ProxyFactory.has_key?(name) }
593
+ if matching_class.nil?
594
+ value = ProxyFactory.create_proxy(Lebowski::Foundation::SCObject, self, rel_path)
595
+ else
596
+ value = ProxyFactory.create_proxy(matching_class, self, rel_path)
597
+ end
598
+
599
+ if not expected_type.nil?
600
+ got_expected_type = (expected_type == :object or value.sc_kind_of?(expected_type))
601
+ if not got_expected_type
602
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_OBJECT, value)
603
+ end
604
+ end
605
+
606
+ return value
607
+
608
+ end
609
+
610
+ def handle_type_array(rel_path, expected_type)
611
+ content_type = @driver.get_sc_type_of_array_content(abs_path_with(rel_path))
612
+
613
+ got_expected_type = false
614
+
615
+ # TODO: This needs a better solution
616
+ case content_type
617
+ when SC_T_NUMBER
618
+ got_expected_type = (expected_type == :array or expected_type == :number_array)
619
+ value = @driver.get_sc_path_number_array_value(abs_path_with(rel_path))
620
+ when SC_T_STRING
621
+ got_expected_type = (expected_type == :array or expected_type == :string_array)
622
+ value = @driver.get_sc_path_string_array_value(abs_path_with(rel_path))
623
+ when SC_T_BOOL
624
+ got_expected_type = (expected_type == :array or expected_type == :boolean_array or expected_type == :bool_array)
625
+ value = @driver.get_sc_path_boolean_array_value(abs_path_with(rel_path))
626
+ when SC_T_HASH
627
+ got_expected_type = (expected_type == :array or expected_type == :object_array or expected_type == :hash_array)
628
+ value = ObjectArray.new self, rel_path
629
+ when SC_T_OBJECT
630
+ got_expected_type = (expected_type == :array or expected_type == :object_array)
631
+ value = ObjectArray.new self, rel_path
632
+ when "empty"
633
+ got_expected_type = (expected_type == :array)
634
+ value = []
635
+ else
636
+ got_expected_type = (expected_type == :array)
637
+ # TODO: Replace with correct logic. Temporary for now
638
+ value = []
639
+ end
640
+
641
+ if (not expected_type.nil?) and (not got_expected_type)
642
+ raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_ARRAY, value)
643
+ end
644
+
645
+ return value
646
+ end
647
+
648
+ #
649
+ # Will return a string in camel case format for any value that follows the Ruby
650
+ # variable and method naming convention (e.g. my_variable_name). As an example:
651
+ #
652
+ # Util.to_camel_case(:some_long_name) # => "someLongName"
653
+ # Util.to_camel_case("function_foo_bar") # => "functionFooBar"
654
+ #
655
+ def to_camel_case(value)
656
+ camel_case_str = ""
657
+ word_counter = 1
658
+ words = value.to_s.split('_')
659
+ return words[0] if words.length == 1
660
+ words.each do |word|
661
+ camel_case_str << ((word_counter == 1) ? word : word.sub(/./) { |s| s.upcase })
662
+ word_counter = word_counter.next
663
+ end
664
+ return camel_case_str
665
+ end
666
+
667
+ end
668
+
669
+ end
670
+ end