vapir-common 1.8.1 → 1.9.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.
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