vapir-common 1.8.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/vapir-common.rb CHANGED
@@ -4,6 +4,8 @@ require 'vapir-common/browser'
4
4
  require 'vapir-common/exceptions'
5
5
  require 'vapir-common/config'
6
6
  module Vapir
7
+ # requires the winwindow library, raising a more informative error message than the default
8
+ # on failure.
7
9
  def self.require_winwindow
8
10
  begin
9
11
  require 'winwindow'
@@ -5,6 +5,7 @@ module Vapir
5
5
  class Error < StandardError; end
6
6
  class BadKeyError < Error; end
7
7
  class NoValueError < Error; end
8
+ class InvalidValueError < Error; end
8
9
 
9
10
  # represents a valid option on a Configuration. consists of a key and criteria
10
11
  # for which a value is valid for that key.
@@ -34,7 +35,7 @@ module Vapir
34
35
  when 'false', false
35
36
  false
36
37
  else
37
- raise ArgumentError, "value should look like a boolean for key #{key}; instead got #{value.inspect}"
38
+ raise InvalidValueError, "value should look like a boolean for key #{key}; instead got #{value.inspect}"
38
39
  end
39
40
  when :numeric
40
41
  case value
@@ -44,13 +45,21 @@ module Vapir
44
45
  begin
45
46
  Float(value)
46
47
  rescue ArgumentError
47
- raise ArgumentError, "value should look like a number for key #{key}; instead got #{value.inspect}"
48
+ raise InvalidValueError, "value should look like a number for key #{key}; instead got #{value.inspect}"
48
49
  end
49
50
  else
50
- raise ArgumentError, "value should look like a number for key #{key}; instead got #{value.inspect}"
51
+ raise InvalidValueError, "value should look like a number for key #{key}; instead got #{value.inspect}"
52
+ end
53
+ when :positive_integer
54
+ if value.is_a?(Integer) && value > 0
55
+ value
56
+ elsif value.is_a?(String) && value.strip =~ /\A\d+\z/ && value.to_i > 0
57
+ value.to_i
58
+ else
59
+ raise InvalidValueError, "value should be a positive integer; got #{value.inspect}"
51
60
  end
52
61
  else
53
- raise ArgumentError, "invalid validator given: #{@validotor.inspect}\nvalidator should be nil for unspecified, a Proc, or a symbol indicating a known validator type"
62
+ raise ArgumentError, "invalid validator given: #{@validator.inspect}\nvalidator should be nil for unspecified, a Proc, or a symbol indicating a known validator type"
54
63
  end
55
64
  end
56
65
  end
@@ -129,6 +138,14 @@ module Vapir
129
138
  def defined_key?(key)
130
139
  locally_defined_key?(key) || (parent && parent.defined_key?(key))
131
140
  end
141
+ # returns an array of keys defined on the current configuration
142
+ def defined_keys
143
+ recognized_keys.select{|key| defined_key?(key) }
144
+ end
145
+ # returns a hash of currently defined configuration keys and values
146
+ def defined_hash
147
+ (@parent ? @parent.defined_hash : {}).merge(@config_hash).freeze
148
+ end
132
149
  # raises an error if the given key is not in an acceptable format. the key should be a string
133
150
  # or symbol consisting of alphanumerics and underscorse, beginning with an alpha or underscore.
134
151
  def validate_key_format!(key)
@@ -212,7 +229,7 @@ module Vapir
212
229
  end
213
230
  # see Configuration#with_config
214
231
  def with_config(hash, &block)
215
- @configuration.with_config(hash, &block)
232
+ config.with_config(hash, &block)
216
233
  end
217
234
  private
218
235
  # takes a hash of given options, a map of config keys, and a list of other allowed keys.
@@ -287,8 +304,9 @@ module Vapir
287
304
  end
288
305
  end
289
306
 
307
+ # :stopdoc:
290
308
  @configurations = []
291
- def (@configurations).update_from_source
309
+ def (@configurations).update_from_source # :nodoc:
292
310
  self.each do |c|
293
311
  if c.respond_to?(:update_from_source)
294
312
  c.update_from_source
@@ -298,15 +316,18 @@ module Vapir
298
316
 
299
317
  @configurations.push(@base_configuration=Configuration.new(nil) do |config|
300
318
  config.create_update(:attach_timeout, 30, :validator => :numeric)
319
+ config.create_update(:close_timeout, 16, :validator => :numeric)
320
+ config.create_update(:quit_timeout, 8, :validator => :numeric)
301
321
  config.create(:default_browser, :validator => proc do |val|
302
322
  require 'vapir-common/browsers'
303
323
  unless (val.is_a?(String) || val.is_a?(Symbol)) && (real_key = Vapir::SupportedBrowsers.keys.detect{|key| key.to_s==val.to_s })
304
- raise ArgumentError, "default_browser should be a string or symbol matching a supported browser - one of: #{Vapir::SupportedBrowsers.keys.join(', ')}. instead got #{value.inspect}"
324
+ raise Vapir::Configuration::InvalidValueError, "default_browser should be a string or symbol matching a supported browser - one of: #{Vapir::SupportedBrowsers.keys.join(', ')}. instead got #{val.inspect}"
305
325
  end
306
326
  real_key
307
327
  end)
308
328
  config.create_update(:highlight_color, 'yellow')
309
329
  config.create_update(:wait, true, :validator => :boolean)
330
+ config.create_update(:wait_timeout, 120, :validator => :numeric)
310
331
  config.create_update(:type_keys, false, :validator => :boolean)
311
332
  config.create_update(:typing_interval, 0, :validator => :numeric)
312
333
  config.create_update(:warn_deprecated, true, :validator => :boolean)
@@ -337,5 +358,6 @@ module Vapir
337
358
  @env_configuration.update_from_source
338
359
 
339
360
  @configuration_parent = @configurations.last
361
+ # :startdoc:
340
362
  extend Configurable # makes Vapir.config which is the in-process user-configurable one, overriding base, yaml, and env
341
363
  end
@@ -180,63 +180,8 @@ module Vapir
180
180
  # returns an array of text nodes below this element in the DOM heirarchy which are visible -
181
181
  # that is, their parent element is visible.
182
182
  def visible_text_nodes
183
- # TODO: needs tests
184
183
  assert_exists do
185
- recurse_text_nodes=ycomb do |recurse|
186
- proc do |node, parent_visibility|
187
- case node.nodeType
188
- when 1, 9 # TODO: name a constant ELEMENT_NODE, rather than magic number
189
- style= node.nodeType==1 ? base_element_class.element_object_style(node, document_object) : nil
190
- our_visibility = style && (visibility=style.invoke('visibility'))
191
- unless our_visibility && ['hidden', 'collapse', 'visible'].include?(our_visibility=our_visibility.strip.downcase)
192
- our_visibility = parent_visibility
193
- end
194
- display = style && style.invoke('display')
195
- if display && display.strip.downcase=='none'
196
- []
197
- else
198
- Vapir::Element.object_collection_to_enumerable(node.childNodes).inject([]) do |result, child_node|
199
- result + recurse.call(child_node, our_visibility)
200
- end
201
- end
202
- when 3 # TODO: name a constant TEXT_NODE, rather than magic number
203
- if parent_visibility && ['hidden','collapse'].include?(parent_visibility.downcase)
204
- []
205
- else
206
- [node.data]
207
- end
208
- else
209
- #Kernel.warn("ignoring node of type #{node.nodeType}")
210
- []
211
- end
212
- end
213
- end
214
-
215
- # determine the current visibility and display. TODO: this is copied/adapted from #visible?; should DRY
216
- element_to_check=containing_object
217
- real_visibility=nil
218
- while element_to_check #&& !element_to_check.instanceof(nsIDOMDocument)
219
- if (style=base_element_class.element_object_style(element_to_check, document_object))
220
- # only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
221
- # or 'visible'. ignore 'inherit'; keep looking upward.
222
- # this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
223
- # this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
224
- if real_visibility==nil && (visibility=style.invoke('visibility'))
225
- visibility=visibility.strip.downcase
226
- if ['hidden', 'collapse', 'visible'].include?(visibility)
227
- real_visibility=visibility
228
- end
229
- end
230
- # check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
231
- display=style.invoke('display')
232
- if display && (display=display.strip.downcase)=='none'
233
- # if display is none, then this element is not visible, and thus has no visible text nodes underneath.
234
- return []
235
- end
236
- end
237
- element_to_check=element_to_check.parentNode
238
- end
239
- recurse_text_nodes.call(containing_object, real_visibility)
184
+ visible_text_nodes_method.call(containing_object, document_object)
240
185
  end
241
186
  end
242
187
  # returns an visible text inside this element by concatenating text nodes below this element in the DOM heirarchy which are visible.
@@ -286,6 +231,128 @@ module Vapir
286
231
  element_class
287
232
  end
288
233
 
234
+ # takes one argument, a proc.
235
+ # this will be yielded successive dom nodes, and should return true if the node matches whatever
236
+ # criteria you care to match; false otherwise.
237
+ #
238
+ # returns an ElementCollection consisting of the deepest elements within the dom heirarchy
239
+ # which match the given match_proc_or_function.
240
+ def base_innermost_by_node(match_proc)
241
+ ElementCollection.new(self, base_element_class, extra_for_contained.merge(:candidates => proc do |container|
242
+ ycomb do |innermost_matching_nodes|
243
+ proc do |container_node|
244
+ child_nodes = Vapir::Element.object_collection_to_enumerable(container_node.childNodes)
245
+ matched_child_elements=child_nodes.select do |node|
246
+ node.nodeType==1 && match_proc.call(node)
247
+ end
248
+ if matched_child_elements.empty?
249
+ [container_node]
250
+ else
251
+ matched_child_elements.map do |matched_child_element|
252
+ innermost_matching_nodes.call(matched_child_element)
253
+ end.inject([], &:+)
254
+ end
255
+ end
256
+ end.call(container.containing_object)
257
+ end))
258
+ end
259
+ alias innermost_by_node base_innermost_by_node
260
+ # takes text or regexp, and returns an ElementCollection consisting of deepest (innermost) elements in the dom heirarchy whose visible text
261
+ # matches what's given (by substring for text; by regexp match for regexp)
262
+ def base_innermost_matching_visible_text(text_or_regexp)
263
+ innermost_by_node(proc do |node|
264
+ visible_text_nodes_method.call(node, document_object).join('')[text_or_regexp] # String#[] works with either text or regexp - returns the matched substring or nil
265
+ end)
266
+ end
267
+ alias innermost_matching_visible_text base_innermost_matching_visible_text
268
+
269
+ private
270
+ # returns a proc that takes a node and a document object, and returns
271
+ # true if the element's display property will allow it to be displayed; false if not.
272
+ def element_displayed_method
273
+ @element_displayed_method ||= proc do |node, document_object|
274
+ style= node.nodeType==1 ? base_element_class.element_object_style(node, document_object) : nil
275
+ display = style && style.invoke('display')
276
+ displayed = display ? display.strip.downcase!='none' : true
277
+ displayed
278
+ end
279
+ end
280
+
281
+ # returns a proc that takes a node and a document object, and returns
282
+ # the visibility of that node, obtained by ascending the dom until an explicit
283
+ # definition for visibility is found.
284
+ def element_real_visibility_method
285
+ @element_real_visibility_method ||= proc do |element_to_check, document_object|
286
+ real_visibility=nil
287
+ while element_to_check && real_visibility==nil
288
+ style = base_element_class.element_object_style(element_to_check, document_object)
289
+ if style
290
+ # only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
291
+ # or 'visible'. ignore 'inherit'; keep looking upward.
292
+ # this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
293
+ # this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
294
+ if style.invoke('visibility')
295
+ visibility=style.invoke('visibility').strip.downcase
296
+ if ['hidden', 'collapse', 'visible'].include?(visibility)
297
+ real_visibility=visibility
298
+ end
299
+ end
300
+ end
301
+ element_to_check=element_to_check.parentNode
302
+ end
303
+ real_visibility
304
+ end
305
+ end
306
+
307
+ # returns a proc that takes a node and a document object, and returns
308
+ # an Array of strings, each of which is the data of a text node beneath the given node which
309
+ # is visible.
310
+ def visible_text_nodes_method
311
+ @visible_text_nodes_method ||= proc do |element_object, document_object|
312
+ recurse_text_nodes=ycomb do |recurse|
313
+ proc do |node, parent_visibility|
314
+ case node.nodeType
315
+ when 1, 9 # TODO: name a constant ELEMENT_NODE, rather than magic number
316
+ style= node.nodeType==1 ? base_element_class.element_object_style(node, document_object) : nil
317
+ our_visibility = style && (visibility=style.invoke('visibility'))
318
+ unless our_visibility && ['hidden', 'collapse', 'visible'].include?(our_visibility=our_visibility.strip.downcase)
319
+ our_visibility = parent_visibility
320
+ end
321
+ if !element_displayed_method.call(node, document_object)
322
+ []
323
+ else
324
+ Vapir::Element.object_collection_to_enumerable(node.childNodes).inject([]) do |result, child_node|
325
+ result + recurse.call(child_node, our_visibility)
326
+ end
327
+ end
328
+ when 3 # TODO: name a constant TEXT_NODE, rather than magic number
329
+ if parent_visibility && ['hidden','collapse'].include?(parent_visibility.downcase)
330
+ []
331
+ else
332
+ [node.data]
333
+ end
334
+ else
335
+ #Kernel.warn("ignoring node of type #{node.nodeType}")
336
+ []
337
+ end
338
+ end
339
+ end
340
+
341
+ # determine the current visibility and display.
342
+ element_to_check=element_object
343
+ while element_to_check
344
+ if !element_displayed_method.call(element_to_check, document_object)
345
+ # check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
346
+ # if display is none, then this element is not visible, and thus has no visible text nodes underneath.
347
+ return []
348
+ end
349
+ element_to_check=element_to_check.parentNode
350
+ end
351
+ recurse_text_nodes.call(element_object, element_real_visibility_method.call(element_object, document_object))
352
+ end
353
+ end
354
+
355
+ public
289
356
  # shows the available objects on the current container.
290
357
  # This is usually only used for debugging or writing new test scripts.
291
358
  # This is a nice feature to help find out what HTML objects are on a page
@@ -333,3 +400,4 @@ module Vapir
333
400
  end
334
401
  end
335
402
  end
403
+
@@ -449,7 +449,7 @@ module Vapir
449
449
  def visible?
450
450
  assert_exists do
451
451
  element_to_check=element_object
452
- #nsIDOMDocument=jssh_socket.Components.interfaces.nsIDOMDocument
452
+ #nsIDOMDocument=firefox_socket.Components.interfaces.nsIDOMDocument
453
453
  really_visible=nil
454
454
  while element_to_check #&& !element_to_check.instanceof(nsIDOMDocument)
455
455
  if (style=element_object_style(element_to_check, document_object))
@@ -579,22 +579,28 @@ module Vapir
579
579
  @container
580
580
  end
581
581
 
582
+ # the Vapir::Browser this element is on
582
583
  attr_reader :browser
584
+ # the Vapir::PageContainer containing this element (a Browser, Frame, or ModalDialogDocument)
583
585
  attr_reader :page_container
584
586
 
585
587
  def document_object
586
588
  assert_container
587
589
  @container.document_object
588
590
  end
591
+ # returns the content window object of the current page on the browser (this is the 'window'
592
+ # object in javascript).
589
593
  def content_window_object
590
594
  assert_container
591
595
  @container.content_window_object
592
596
  end
597
+ # returns the underlying object representing the browser.
593
598
  def browser_window_object
594
599
  assert_container
595
600
  @container.browser_window_object
596
601
  end
597
602
 
603
+ # used by inspect, to_s, and pretty_print to determine what to show
598
604
  def attributes_for_stringifying
599
605
  attributes_to_inspect=self.class.attributes_to_inspect
600
606
  unless exists?
@@ -621,6 +627,9 @@ module Vapir
621
627
  " "+attr.first+'='+attr.last.inspect
622
628
  end.join('') + ">"
623
629
  end
630
+ # returns a string representation of this element with each attribute on its own line. this
631
+ # returns the same information as #inspect, but formatted somewhat more readably. you might
632
+ # also be interested in pretty-printing the element; see the pp library.
624
633
  def to_s
625
634
  attrs=attributes_for_stringifying
626
635
  longest_label=attrs.inject(0) {|max, attr| [max, attr.first.size].max }
@@ -647,7 +656,7 @@ module Vapir
647
656
  def object_collection_to_enumerable(object)
648
657
  if object.is_a?(Enumerable)
649
658
  object
650
- elsif Object.const_defined?('JsshObject') && object.is_a?(JsshObject)
659
+ elsif Object.const_defined?('JavascriptObject') && object.is_a?(JavascriptObject)
651
660
  object.to_array
652
661
  elsif Object.const_defined?('WIN32OLE') && object.is_a?(WIN32OLE)
653
662
  array=[]
@@ -4,7 +4,7 @@ module Vapir
4
4
  # this module is for methods that should go on both common element modules (ie, TextField) as well
5
5
  # as browser-specific element classes (ie, Firefox::TextField).
6
6
  module ElementClassAndModuleMethods
7
- # takes an element_object (JsshObject or WIN32OLE), and finds the most specific class
7
+ # takes an element_object (JavascriptObject or WIN32OLE), and finds the most specific class
8
8
  # that is < self whose specifiers match it. Returns an instance of that class using the given
9
9
  # element_object.
10
10
  #
@@ -178,6 +178,7 @@ module Vapir
178
178
  # Raises ObjectDisabledException if the object is disabled
179
179
  # Raises ObjectReadOnlyException if the object is read only
180
180
  def append(value, options={})
181
+ raise ArgumentError, "Text field value must be a string! Got #{value.inspect}" unless value.is_a?(String)
181
182
  options={:blur => true, :change => true, :select => true, :focus => true}.merge(options)
182
183
  assert_enabled
183
184
  assert_not_readonly
@@ -34,13 +34,23 @@ unless :to_proc.respond_to?(:to_proc)
34
34
  end
35
35
  end
36
36
 
37
+ class Hash
38
+ # returns a hash whose keys are the intersection of the keys of this hash and the keys given
39
+ # as arguments to this function. values are the same as in this hash.
40
+ def select_keys(*keys)
41
+ keys.inject(self.class.new) do |hash,key|
42
+ self.key?(key) ? hash.merge(key => self[key]) : hash
43
+ end
44
+ end
45
+ end
46
+
37
47
  module Kernel
38
48
  # this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
39
49
  # to define a recursive function to return the length of an array:
40
50
  #
41
- # length = ycomb do |len|
42
- # proc{|list| list == [] ? 0 : len.call(list[1..-1]) }
43
- # end
51
+ # length = ycomb do |len|
52
+ # proc{|list| list == [] ? 0 : len.call(list[1..-1]) }
53
+ # end
44
54
  #
45
55
  # see https://secure.wikimedia.org/wikipedia/en/wiki/Fixed_point_combinator#Y_combinator
46
56
  # and chapter 9 of the little schemer, available as the sample chapter at http://www.ccs.neu.edu/home/matthias/BTLS/
@@ -50,7 +60,7 @@ module Kernel
50
60
  module_function :ycomb
51
61
 
52
62
  def warn_with_caller(message)
53
- Kernel.warn "#{message}\ncalled from: #{caller[1..-1].map{|c|"\n\t"+c}}"
63
+ Kernel.warn "#{message}\ncalled from: #{caller[1..-1].map{|c|"\n\t"+c}.join('')}"
54
64
  end
55
65
  module_function :warn_with_caller
56
66
  end
@@ -109,6 +109,16 @@ module Vapir
109
109
  names.first.is_a?(String) &&
110
110
  container.containing_object.object_respond_to?(:getElementsByName) &&
111
111
  specifiers.all?{|specifier| specifier[:tagName].is_a?(String) && %w(BUTTON TEXTAREA APPLET SELECT FORM FRAME IFRAME IMG A INPUT OBJECT MAP PARAM META).include?(specifier[:tagName].upcase) }
112
+ # thing to determine if object_respond_to?(:getElementsByClassName) is lying. returns
113
+ # true if the thing is full of lies.
114
+ getElementsByClassName_lies = proc do
115
+ Object.const_defined?('WIN32OLERuntimeError') && begin
116
+ container.containing_object.getElementsByClassName('dummy')
117
+ false
118
+ rescue WIN32OLERuntimeError, NoMethodError
119
+ true
120
+ end
121
+ end
112
122
  if can_use_getElementById
113
123
  candidates= if by_id=container.containing_object.getElementById(ids.first)
114
124
  [by_id]
@@ -117,7 +127,7 @@ module Vapir
117
127
  end
118
128
  elsif can_use_getElementsByName
119
129
  candidates=container.containing_object.getElementsByName(names.first)#.to_array
120
- elsif classNames.size==1 && classNames.first.is_a?(String) && container.containing_object.object_respond_to?(:getElementsByClassName)
130
+ elsif classNames.size==1 && classNames.first.is_a?(String) && container.containing_object.object_respond_to?(:getElementsByClassName) && !getElementsByClassName_lies.call
121
131
  candidates=container.containing_object.getElementsByClassName(classNames.first)
122
132
  elsif tags.size==1 && tags.first.is_a?(String)
123
133
  candidates=container.containing_object.getElementsByTagName(tags.first)
@@ -127,7 +137,7 @@ module Vapir
127
137
  # return:
128
138
  if candidates.is_a?(Array)
129
139
  candidates
130
- elsif Object.const_defined?('JsshObject') && candidates.is_a?(JsshObject)
140
+ elsif Object.const_defined?('JavascriptObject') && candidates.is_a?(JavascriptObject)
131
141
  candidates.to_array
132
142
  elsif Object.const_defined?('WIN32OLE') && candidates.is_a?(WIN32OLE)
133
143
  candidates.send :extend, Enumerable
@@ -141,15 +151,15 @@ module Vapir
141
151
  unless specifiers_list.is_a?(Enumerable) && specifiers_list.all?{|spec| spec.is_a?(Hash)}
142
152
  raise ArgumentError, "specifiers_list should be a list of Hashes!"
143
153
  end
144
- if Object.const_defined?('JsshObject') && (candidates.is_a?(JsshObject) || (candidates.length != 0 && candidates.all?{|c| c.is_a?(JsshObject)}))
145
- # optimize for JSSH by moving code to the other side of the socket, rather than talking across it a whole lot
154
+ if Object.const_defined?('JavascriptObject') && (candidates.is_a?(JavascriptObject) || (candidates.length != 0 && candidates.all?{|c| c.is_a?(JavascriptObject)}))
155
+ # optimize for firefox by moving code to the other side of the socket, rather than talking across it a whole lot
146
156
  # this javascript should be exactly the same as the ruby in the else (minus WIN32OLE optimization) -
147
157
  # just written in javascript instead of ruby.
148
158
  #
149
159
  # Note that the else block works perfectly fine, but is much much slower due to the amount of
150
160
  # socket activity.
151
- jssh_socket= candidates.is_a?(JsshObject) ? candidates.jssh_socket : candidates.first.jssh_socket
152
- match_candidates_js=JsshObject.new("
161
+ firefox_socket= candidates.is_a?(JavascriptObject) ? candidates.firefox_socket : candidates.first.firefox_socket
162
+ match_candidates_js=JavascriptObject.new("
153
163
  (function(candidates, specifiers_list, aliases)
154
164
  { candidates=$A(candidates);
155
165
  specifiers_list=$A(specifiers_list);
@@ -214,7 +224,7 @@ module Vapir
214
224
  });
215
225
  return matched_candidates;
216
226
  })
217
- ", jssh_socket, :debug_name => 'match_candidates_function')
227
+ ", firefox_socket, :debug_name => 'match_candidates_function')
218
228
  matched_candidates=match_candidates_js.call(candidates, specifiers_list, aliases)
219
229
  if block_given?
220
230
  matched_candidates.to_array.each do |matched_candidate|
@@ -301,6 +311,9 @@ module Vapir
301
311
 
302
312
  # This is on the Vapir module itself because it's used in a number of other places, should be in a broad namespace.
303
313
  module_function
314
+ # fuzzily matches the given attribute with the given 'what'. if 'what' is a regexp, matches it
315
+ # against attr; if it's a string, downcases and strips before comparing; tries a couple other
316
+ # things; falls back to normal equality-checking. read the source for more information.
304
317
  def fuzzy_match(attr, what)
305
318
  # IF YOU CHANGE THIS, CHANGE THE JAVASCRIPT REIMPLEMENTATION IN match_candidates
306
319
  case what
@@ -1,5 +1,5 @@
1
1
  module Vapir
2
2
  module Common
3
- VERSION = '1.8.1'
3
+ VERSION = '1.9.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vapir-common
3
3
  version: !ruby/object:Gem::Version
4
- hash: 53
4
+ hash: 51
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 8
9
- - 1
10
- version: 1.8.1
8
+ - 9
9
+ - 0
10
+ version: 1.9.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ethan
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-17 00:00:00 -04:00
18
+ date: 2011-08-04 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies: []
21
21