vapir-common 1.7.2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +0 -5
- data/lib/vapir-common.rb +16 -4
- data/lib/vapir-common/browser.rb +189 -144
- data/lib/vapir-common/browsers.rb +21 -9
- data/lib/vapir-common/config.rb +341 -0
- data/lib/vapir-common/container.rb +160 -30
- data/lib/vapir-common/element.rb +65 -555
- data/lib/vapir-common/element_class_and_module.rb +378 -0
- data/lib/vapir-common/element_collection.rb +108 -20
- data/lib/vapir-common/elements/elements.rb +243 -67
- data/lib/vapir-common/external/core_extensions.rb +62 -0
- data/lib/vapir-common/handle_options.rb +1 -1
- data/lib/vapir-common/keycodes.rb +135 -0
- data/lib/vapir-common/options.rb +5 -38
- data/lib/vapir-common/page_container.rb +26 -21
- data/lib/vapir-common/specifier.rb +2 -2
- data/lib/vapir-common/version.rb +5 -0
- data/lib/vapir-common/waiter.rb +44 -90
- data/lib/vapir.rb +7 -0
- metadata +12 -27
- data/lib/vapir-common/testcase.rb +0 -89
- data/lib/vapir-common/win_window.rb +0 -1227
data/lib/vapir-common.rb
CHANGED
@@ -1,7 +1,19 @@
|
|
1
|
+
require 'vapir-common/version'
|
2
|
+
require 'vapir-common/external/core_extensions.rb'
|
3
|
+
require 'vapir-common/browser'
|
4
|
+
require 'vapir-common/exceptions'
|
5
|
+
require 'vapir-common/config'
|
1
6
|
module Vapir
|
2
|
-
|
3
|
-
|
7
|
+
def self.require_winwindow
|
8
|
+
begin
|
9
|
+
require 'winwindow'
|
10
|
+
rescue LoadError
|
11
|
+
message = if RUBY_PLATFORM =~ /mswin|windows|mingw32|cygwin/i
|
12
|
+
"This may be resolved by installing the winwindow gem."
|
13
|
+
else
|
14
|
+
"You do not appear to be on Windows - this method is not likely to work."
|
15
|
+
end
|
16
|
+
raise LoadError, $!.message + "\n\n#{message}", $!.backtrace
|
17
|
+
end
|
4
18
|
end
|
5
19
|
end
|
6
|
-
require 'vapir-common/browser'
|
7
|
-
require 'vapir-common/exceptions'
|
data/lib/vapir-common/browser.rb
CHANGED
@@ -1,63 +1,17 @@
|
|
1
1
|
# vapir-common/browser
|
2
|
-
require 'vapir-common/options'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
Watir is a family of open-source drivers for automating web browsers. You
|
8
|
-
can use it to write tests that are easy to read and maintain.
|
9
|
-
|
10
|
-
Watir drives browsers the same way people do. It clicks links, fills in forms,
|
11
|
-
presses buttons. Watir also checks results, such as whether expected text
|
12
|
-
appears on a page.
|
13
|
-
|
14
|
-
The Watir family currently includes support for Internet Explorer (on Windows),
|
15
|
-
Firefox (on Windows, Mac and Linux) and Safari (on Mac).
|
16
|
-
|
17
|
-
Project Homepage: http://wtr.rubyforge.org
|
18
|
-
|
19
|
-
This Browser module provides a generic interface
|
20
|
-
that tests can use to access any browser. The actual browser (and thus
|
21
|
-
the actual Watir driver) is determined at runtime based on configuration
|
22
|
-
settings.
|
23
|
-
|
24
|
-
require 'vapir'
|
25
|
-
browser = Watir::Browser.new
|
26
|
-
browser.goto 'http://google.com'
|
27
|
-
browser.text_field(:name, 'q').set 'pickaxe'
|
28
|
-
browser.button(:name, 'btnG').click
|
29
|
-
if browser.text.include? 'Programming Ruby'
|
30
|
-
puts 'Text was found'
|
31
|
-
else
|
32
|
-
puts 'Text was not found'
|
33
|
-
end
|
34
|
-
|
35
|
-
A comprehensive summary of the Watir API can be found here
|
36
|
-
http://wiki.openqa.org/display/WTR/Methods+supported+by+Element
|
37
|
-
|
38
|
-
There are two ways to configure the browser that will be used by your tests.
|
39
|
-
|
40
|
-
One is to set the +watir_browser+ environment variable to +ie+ or +firefox+.
|
41
|
-
(How you do this depends on your platform.)
|
42
|
-
|
43
|
-
The other is to create a file that looks like this.
|
44
|
-
|
45
|
-
browser: ie
|
46
|
-
|
47
|
-
And then to add this line to your script, after the require statement and
|
48
|
-
before you invoke Browser.new.
|
49
|
-
|
50
|
-
Watir.options_file = 'path/to/the/file/you/just/created'
|
2
|
+
require 'vapir-common/options' # stub; this stuff is deprecated
|
3
|
+
require 'vapir-common/config'
|
4
|
+
require 'vapir-common/version'
|
5
|
+
require 'vapir-common/browsers'
|
51
6
|
|
52
|
-
|
53
|
-
|
7
|
+
module Vapir
|
8
|
+
# The common Browser class from which classes specific to Firefox or IE browsers inherit.
|
9
|
+
#
|
10
|
+
# Calls to this class are delegated to a browser inheriting from this, which is set using config.default_browser
|
54
11
|
class Browser
|
55
|
-
@@browser_classes = {}
|
56
|
-
@@sub_options = {}
|
57
|
-
@@default = nil
|
58
12
|
class << self
|
59
|
-
alias __new__ new
|
60
|
-
def inherited(subclass)
|
13
|
+
alias __new__ new # :nodoc:
|
14
|
+
def inherited(subclass) # :nodoc:
|
61
15
|
class << subclass
|
62
16
|
alias new __new__
|
63
17
|
end
|
@@ -67,105 +21,61 @@ before you invoke Browser.new.
|
|
67
21
|
# configuration settings. (Don't be fooled: this is not actually
|
68
22
|
# an instance of Browser class.)
|
69
23
|
def new *args, &block
|
70
|
-
|
71
|
-
browser=klass.new *args, &block
|
72
|
-
#browser=klass.allocate
|
73
|
-
#browser.send :initialize, *args, &block
|
24
|
+
browser=browser_class.new *args, &block
|
74
25
|
browser
|
75
26
|
end
|
76
|
-
|
77
|
-
# are overridden so that Browser class methods aren't inherited causing infinite loop.
|
78
|
-
def ensure_overridden
|
79
|
-
if self==klass
|
80
|
-
raise NotImplementedError, "This method must be overridden by #{self}!"
|
81
|
-
end
|
82
|
-
end
|
27
|
+
alias new_window new
|
83
28
|
|
84
|
-
# Create a new instance
|
85
|
-
#
|
86
|
-
def start
|
87
|
-
|
88
|
-
|
89
|
-
klass.start url
|
90
|
-
end
|
91
|
-
# Attach to an existing browser.
|
92
|
-
def attach(how, what)
|
93
|
-
ensure_overridden
|
94
|
-
set_sub_options
|
95
|
-
klass.attach(how, what)
|
29
|
+
# Create a new browser instance, starting at the specified url.
|
30
|
+
# If no url is given, start at about:blank.
|
31
|
+
def start(url='about:blank', options={})
|
32
|
+
raise ArgumentError, "URL must be a string; got #{url.inspect}" unless url.is_a?(String)
|
33
|
+
new(options.merge(:goto => url))
|
96
34
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
35
|
+
alias start_window start
|
36
|
+
|
37
|
+
# Attach to an existing browser window. Returns an instance of the current default browser class.
|
38
|
+
#
|
39
|
+
# the window to be attached to can be
|
40
|
+
# referenced by url, title, or window handle ('how' argument)
|
41
|
+
#
|
42
|
+
# The 'what' argument can be either a string or a regular expression, in the
|
43
|
+
# case of of :url or :title.
|
44
|
+
#
|
45
|
+
# Vapir::Browser.attach(:url, 'http://www.google.com')
|
46
|
+
# Vapir::Browser.attach(:title, 'Google')
|
47
|
+
# Vapir::Browser.attach(:hwnd, 528140)
|
48
|
+
#
|
49
|
+
# see the implementing browser's +new+ method for more details on what may be passed.
|
50
|
+
def attach(how, what, options={})
|
51
|
+
new(options.merge(:attach => [how, what]))
|
105
52
|
end
|
53
|
+
alias find attach
|
106
54
|
|
107
|
-
def
|
108
|
-
key = Vapir.
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
namespace.const_get(name_part)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
private :klass
|
116
|
-
# Add support for the browser option, using the specified class,
|
117
|
-
# provided as a string. Optionally, additional options supported by
|
118
|
-
# the class can be specified as an array of symbols. Options specified
|
119
|
-
# by the user and included in this list will be passed (as a hash) to
|
120
|
-
# the set_options class method (if defined) before creating an instance.
|
121
|
-
def support hash_args
|
122
|
-
option = hash_args[:name]
|
123
|
-
class_string = hash_args[:class]
|
124
|
-
additional_options = hash_args[:options]
|
125
|
-
library = hash_args[:library]
|
126
|
-
gem = hash_args[:gem] || library
|
127
|
-
|
128
|
-
@@browser_classes[option] = class_string
|
129
|
-
@@sub_options[option] = additional_options
|
130
|
-
|
131
|
-
autoload class_string, library
|
132
|
-
activate_gem gem, option
|
55
|
+
def browser_class
|
56
|
+
key = Vapir.config.default_browser
|
57
|
+
browser_class=SupportedBrowsers[key.to_sym][:class_name].split('::').inject(Object) do |namespace, name_part|
|
58
|
+
namespace.const_get(name_part) # this triggers autoload if it's not loaded
|
59
|
+
end
|
133
60
|
end
|
61
|
+
private :browser_class
|
134
62
|
|
135
63
|
def default
|
136
|
-
|
64
|
+
# deprecate
|
65
|
+
Vapir.config.default_browser
|
137
66
|
end
|
138
67
|
# Specifies a default browser. Must be specified before options are parsed.
|
139
|
-
def default=
|
140
|
-
|
141
|
-
|
142
|
-
# Returns the names of the browsers that are supported by this module.
|
143
|
-
# These are the options for 'watir_browser' (env var) or 'browser:' (yaml).
|
144
|
-
def browser_names
|
145
|
-
@@browser_classes.keys
|
146
|
-
end
|
147
|
-
|
148
|
-
private
|
149
|
-
def autoload class_string, library
|
150
|
-
mod, klass = class_string.split('::')
|
151
|
-
eval "module ::#{mod}; autoload :#{klass}, '#{library}'; end"
|
152
|
-
end
|
153
|
-
# Activate the gem (if installed). The default browser will be set
|
154
|
-
# to the first gem that activates.
|
155
|
-
def activate_gem gem_name, option
|
156
|
-
begin
|
157
|
-
gem gem_name
|
158
|
-
@@default ||= option
|
159
|
-
rescue Gem::LoadError
|
160
|
-
end
|
161
|
-
end
|
162
|
-
def set_sub_options
|
163
|
-
sub_options = @@sub_options[Vapir.options[:browser]]
|
164
|
-
return if sub_options.nil?
|
165
|
-
specified_options = Vapir.options.reject {|k, v| !sub_options.include? k}
|
166
|
-
self.set_options specified_options
|
68
|
+
def default= default_browser
|
69
|
+
# deprecate
|
70
|
+
Vapir.config.default_browser = default_browser
|
167
71
|
end
|
168
72
|
end
|
73
|
+
|
74
|
+
include Configurable
|
75
|
+
def configuration_parent
|
76
|
+
browser_class.config
|
77
|
+
end
|
78
|
+
|
169
79
|
# locate is used by stuff that uses container. this doesn't actually locate the browser
|
170
80
|
# but checks if it (still) exists.
|
171
81
|
def locate(options={})
|
@@ -177,8 +87,143 @@ before you invoke Browser.new.
|
|
177
87
|
def inspect
|
178
88
|
"#<#{self.class}:0x#{(self.hash*2).to_s(16)} " + (exists? ? "url=#{url.inspect} title=#{title.inspect}" : "exists?=false") + '>'
|
179
89
|
end
|
90
|
+
|
91
|
+
# does the work of #screen_capture when the WinWindow library is being used for that. see #screen_capture documentation (browser-specific)
|
92
|
+
def screen_capture_win_window(filename, options = {})
|
93
|
+
options = handle_options(options, :dc => :window, :format => nil)
|
94
|
+
if options[:format] && !(options[:format].is_a?(String) && options[:format].downcase == 'bmp')
|
95
|
+
raise ArgumentError, ":format was specified as #{options[:format].inspect} but only 'bmp' is supported when :dc is #{options[:dc].inspect}"
|
96
|
+
end
|
97
|
+
if options[:dc] == :desktop
|
98
|
+
win_window.really_set_foreground!
|
99
|
+
screenshot_win=WinWindow.desktop_window
|
100
|
+
options[:dc] = :window
|
101
|
+
else
|
102
|
+
screenshot_win=win_window
|
103
|
+
end
|
104
|
+
screenshot_win.capture_to_bmp_file(filename, :dc => options[:dc])
|
105
|
+
end
|
106
|
+
private :screen_capture_win_window
|
180
107
|
end
|
181
108
|
|
109
|
+
module WatirConfigCompatibility
|
110
|
+
if defined?($FAST_SPEED)
|
111
|
+
if config.warn_deprecated
|
112
|
+
Kernel.warn "WARNING: The $FAST_SPEED global is gone. Please use the new config framework, and unset that global to silence this warning."
|
113
|
+
end
|
114
|
+
Vapir.config.typing_interval=0
|
115
|
+
Vapir.config.type_keys=false
|
116
|
+
end
|
117
|
+
Speeds = {
|
118
|
+
:zippy => {
|
119
|
+
:typing_interval => 0,
|
120
|
+
:type_keys => false,
|
121
|
+
},
|
122
|
+
:fast => {
|
123
|
+
:typing_interval => 0,
|
124
|
+
:type_keys => true,
|
125
|
+
},
|
126
|
+
:slow => {
|
127
|
+
:typing_interval => 0.08,
|
128
|
+
:type_keys => true,
|
129
|
+
},
|
130
|
+
}.freeze
|
131
|
+
module WatirBrowserClassConfigCompatibility
|
132
|
+
OptionsKeys = [:speed, :attach_timeout, :visible]
|
133
|
+
def options
|
134
|
+
if self==Vapir::Browser
|
135
|
+
return browser_class.options
|
136
|
+
end
|
137
|
+
if config.warn_deprecated
|
138
|
+
Kernel.warn_with_caller "WARNING: #options is deprecated; please use the new config framework"
|
139
|
+
end
|
140
|
+
OptionsKeys.inject({}) do |hash, key|
|
141
|
+
respond_to?(key) ? hash.merge(key => self.send(key)) : hash
|
142
|
+
end.freeze
|
143
|
+
end
|
144
|
+
def set_options(options)
|
145
|
+
if self==Vapir::Browser
|
146
|
+
return browser_class.set_options(options)
|
147
|
+
end
|
148
|
+
if config.warn_deprecated
|
149
|
+
Kernel.warn_with_caller "WARNING: #set_options is deprecated; please use the new config framework"
|
150
|
+
end
|
151
|
+
|
152
|
+
unless (unknown_options = options.keys - OptionsKeys.select{|key| respond_to?("#{key}=")}).empty?
|
153
|
+
raise ArgumentError, "unknown options: #{unknown_options.inspect}"
|
154
|
+
end
|
155
|
+
options.each do |key, value|
|
156
|
+
self.send("#{key}=", value)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
def attach_timeout
|
160
|
+
if self==Vapir::Browser
|
161
|
+
return browser_class.attach_timeout
|
162
|
+
end
|
163
|
+
if config.warn_deprecated
|
164
|
+
Kernel.warn_with_caller "WARNING: #attach_timeout is deprecated; please use the new config framework with config.attach_timeout"
|
165
|
+
end
|
166
|
+
config.attach_timeout
|
167
|
+
end
|
168
|
+
def attach_timeout=(timeout)
|
169
|
+
if self==Vapir::Browser
|
170
|
+
return browser_class.attach_timeout=timeout
|
171
|
+
end
|
172
|
+
if config.warn_deprecated
|
173
|
+
Kernel.warn_with_caller "WARNING: #attach_timeout= is deprecated; please use the new config framework with config.attach_timeout="
|
174
|
+
end
|
175
|
+
config.attach_timeout = timeout
|
176
|
+
end
|
177
|
+
end
|
178
|
+
Vapir::Browser.send(:extend, WatirBrowserClassConfigCompatibility)
|
179
|
+
module Speed
|
180
|
+
def speed
|
181
|
+
if self==Vapir::Browser
|
182
|
+
return browser_class.speed
|
183
|
+
end
|
184
|
+
if config.warn_deprecated
|
185
|
+
Kernel.warn_with_caller "WARNING: #speed is deprecated; please use the new config framework with config.typing_interval and config.type_keys"
|
186
|
+
end
|
187
|
+
Speeds.keys.detect do |speed_key|
|
188
|
+
Speeds[speed_key].all? do |config_key, value|
|
189
|
+
config[config_key] == value
|
190
|
+
end
|
191
|
+
end || :other
|
192
|
+
end
|
193
|
+
def speed=(speed_key)
|
194
|
+
if self==Vapir::Browser
|
195
|
+
return browser_class.speed=speed_key
|
196
|
+
end
|
197
|
+
if config.warn_deprecated
|
198
|
+
Kernel.warn_with_caller "WARNING: #speed= is deprecated; please use the new config framework with config.typing_interval= and config.type_keys="
|
199
|
+
end
|
200
|
+
unless Speeds.key?(speed_key)
|
201
|
+
raise ArgumentError, "Invalid speed: #{speed_key}. expected #{Speeds.keys.map{|k| k.inspect }.join(', ')}"
|
202
|
+
end
|
203
|
+
Speeds[speed_key].each do |config_key, value|
|
204
|
+
config[config_key]=value
|
205
|
+
end
|
206
|
+
end
|
207
|
+
def set_slow_speed
|
208
|
+
if self==Vapir::Browser
|
209
|
+
return browser_class.set_slow_speed
|
210
|
+
end
|
211
|
+
if config.warn_deprecated
|
212
|
+
Kernel.warn_with_caller "WARNING: #set_slow_speed is deprecated; please use the new config framework with config.typing_interval= and config.type_keys="
|
213
|
+
end
|
214
|
+
self.speed= :slow
|
215
|
+
end
|
216
|
+
def set_fast_speed
|
217
|
+
if self==Vapir::Browser
|
218
|
+
return browser_class.set_fast_speed
|
219
|
+
end
|
220
|
+
if config.warn_deprecated
|
221
|
+
Kernel.warn_with_caller "WARNING: #set_fast_speed is deprecated; please use the new config framework with config.typing_interval= and config.type_keys="
|
222
|
+
end
|
223
|
+
self.speed= :fast
|
224
|
+
end
|
225
|
+
end
|
226
|
+
Vapir::Browser.send(:extend, Speed)
|
227
|
+
Vapir::Browser.send(:include, Speed)
|
228
|
+
end
|
182
229
|
end
|
183
|
-
|
184
|
-
require 'vapir-common/browsers'
|
@@ -1,9 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
Vapir::
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
module Vapir
|
2
|
+
SupportedBrowsers = {
|
3
|
+
:ie => {:class_name => 'Vapir::IE', :require => 'vapir-ie', :gem => 'vapir-ie'},
|
4
|
+
:firefox => {:class_name => 'Vapir::Firefox', :require => 'vapir-firefox', :gem => 'vapir-firefox'},
|
5
|
+
}
|
6
|
+
SupportedBrowsers.each do |key, browser_hash|
|
7
|
+
# set up autoload
|
8
|
+
split_class = browser_hash[:class_name].split('::')
|
9
|
+
class_namespace = split_class[0..-2].inject(Object) do |namespace, name_part|
|
10
|
+
namespace.const_get(name_part)
|
11
|
+
end
|
12
|
+
class_namespace.autoload(split_class.last, browser_hash[:require])
|
13
|
+
|
14
|
+
# activate the right gem + version
|
15
|
+
begin
|
16
|
+
require 'rubygems'
|
17
|
+
gem browser_hash[:gem], "=#{Vapir::Common::VERSION}"
|
18
|
+
rescue LoadError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,341 @@
|
|
1
|
+
require 'vapir-common/handle_options'
|
2
|
+
module Vapir
|
3
|
+
# represents a entry in a heirarchy of configuration options
|
4
|
+
class Configuration
|
5
|
+
class Error < StandardError; end
|
6
|
+
class BadKeyError < Error; end
|
7
|
+
class NoValueError < Error; end
|
8
|
+
|
9
|
+
# represents a valid option on a Configuration. consists of a key and criteria
|
10
|
+
# for which a value is valid for that key.
|
11
|
+
class Option
|
12
|
+
attr_reader :key, :validator
|
13
|
+
# creates a new option. the options hash (last argument) may specify a
|
14
|
+
# :validator key which will be used to validate any values attempted to be
|
15
|
+
# assigned to the key this option represents.
|
16
|
+
def initialize(key, options={})
|
17
|
+
@key = key
|
18
|
+
options = handle_options(options, {}, [:validator])
|
19
|
+
@validator = options[:validator]
|
20
|
+
end
|
21
|
+
# takes a value and checks that it is valid, if a validator is specified for
|
22
|
+
# this option. the validator may map the value to something different, so the
|
23
|
+
# result of this function call should replace the value being used.
|
24
|
+
def validate!(value)
|
25
|
+
case @validator
|
26
|
+
when nil
|
27
|
+
value
|
28
|
+
when Proc
|
29
|
+
@validator.call value
|
30
|
+
when :boolean
|
31
|
+
case value
|
32
|
+
when 'true', true
|
33
|
+
true
|
34
|
+
when 'false', false
|
35
|
+
false
|
36
|
+
else
|
37
|
+
raise ArgumentError, "value should look like a boolean for key #{key}; instead got #{value.inspect}"
|
38
|
+
end
|
39
|
+
when :numeric
|
40
|
+
case value
|
41
|
+
when Numeric
|
42
|
+
value
|
43
|
+
when String
|
44
|
+
begin
|
45
|
+
Float(value)
|
46
|
+
rescue ArgumentError
|
47
|
+
raise ArgumentError, "value should look like a number for key #{key}; instead got #{value.inspect}"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
raise ArgumentError, "value should look like a number for key #{key}; instead got #{value.inspect}"
|
51
|
+
end
|
52
|
+
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"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# the parent Configuration in the heirarchy. may be nil if there is no parent.
|
59
|
+
attr_reader :parent
|
60
|
+
# creates a new Configuration with the given parent. if a block is given, this
|
61
|
+
# Configuration object will be yielded to it.
|
62
|
+
def initialize(parent, &block)
|
63
|
+
unless parent==nil || parent.is_a?(Configuration)
|
64
|
+
raise TypeError, "expected parent to be a Configuration; got #{parent.inspect}"
|
65
|
+
end
|
66
|
+
@parent=parent
|
67
|
+
@config_hash = {}
|
68
|
+
@recognized_options = {}
|
69
|
+
yield(self) if block_given?
|
70
|
+
end
|
71
|
+
# if the method invoked looks like assignment (ends with an =), calls to #update with
|
72
|
+
# the given method as the key and its argument as the value. otherwise calls #read with
|
73
|
+
# the method as the key.
|
74
|
+
def method_missing(method, *args)
|
75
|
+
method=method.to_s
|
76
|
+
if method =~ /\A([a-z_][a-z0-9_]*)([=?!])?\z/i
|
77
|
+
method = $1
|
78
|
+
special = $2
|
79
|
+
else # don't deal with any special character crap
|
80
|
+
return super
|
81
|
+
end
|
82
|
+
case special
|
83
|
+
when nil
|
84
|
+
raise ArgumentError, "wrong number of arguments retrieving #{method} (#{args.size} for 0)" unless args.size==0
|
85
|
+
read(method)
|
86
|
+
when '='
|
87
|
+
raise ArgumentError, "wrong number of arguments setting #{method} (#{args.size} for 1)" unless args.size==1
|
88
|
+
update(method, *args)
|
89
|
+
#when '?' # no defined behavior for ? or ! at the moment
|
90
|
+
#when '!'
|
91
|
+
else
|
92
|
+
return super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
# alias for #read
|
96
|
+
def [](key)
|
97
|
+
read(key)
|
98
|
+
end
|
99
|
+
# alias for #update
|
100
|
+
def []=(key, value)
|
101
|
+
update(key, value)
|
102
|
+
end
|
103
|
+
# returns an array of
|
104
|
+
def recognized_keys
|
105
|
+
((@parent ? @parent.recognized_keys : [])+@recognized_options.keys).uniq
|
106
|
+
end
|
107
|
+
# returns true if the given key is recognized; false otherwise. may raise BadKeyError
|
108
|
+
# if the given key isn't even a valid format for a key.
|
109
|
+
def recognized_key?(key)
|
110
|
+
key = validate_key_format!(key)
|
111
|
+
recognized_keys.include?(key)
|
112
|
+
end
|
113
|
+
# assert that the given key must be recognized; if it is not recognized, an error
|
114
|
+
# should be raised.
|
115
|
+
def recognize_key!(key)
|
116
|
+
key = validate_key_format!(key)
|
117
|
+
unless recognized_key?(key)
|
118
|
+
raise BadKeyError, "Unrecognized key: #{key}"
|
119
|
+
end
|
120
|
+
key
|
121
|
+
end
|
122
|
+
# returns true if the given key is defined on this Configuration; returns false if not -
|
123
|
+
# note that this returns false if the given key is defined on an ancestor Configuration.
|
124
|
+
def locally_defined_key?(key)
|
125
|
+
key = validate_key_format!(key)
|
126
|
+
@config_hash.key?(key)
|
127
|
+
end
|
128
|
+
# returns true if the given key is defined on this Configuration or any of its ancestors.
|
129
|
+
def defined_key?(key)
|
130
|
+
locally_defined_key?(key) || (parent && parent.defined_key?(key))
|
131
|
+
end
|
132
|
+
# raises an error if the given key is not in an acceptable format. the key should be a string
|
133
|
+
# or symbol consisting of alphanumerics and underscorse, beginning with an alpha or underscore.
|
134
|
+
def validate_key_format!(key)
|
135
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
136
|
+
raise BadKeyError, "key should be a String or Symbol; got #{key.inspect} (#{key.class})"
|
137
|
+
end
|
138
|
+
key=key.to_s.downcase
|
139
|
+
unless key =~ /\A([a-z_][a-z0-9_]*)\z/
|
140
|
+
raise BadKeyError, "key should be all alphanumeric/underscores, not starting with a number"
|
141
|
+
end
|
142
|
+
key
|
143
|
+
end
|
144
|
+
protected
|
145
|
+
# returns a hash of recognized options with the keys being recognized keys and values
|
146
|
+
# being Option instances.
|
147
|
+
def recognized_options
|
148
|
+
(@parent ? @parent.recognized_options : {}).merge(@recognized_options)
|
149
|
+
end
|
150
|
+
public
|
151
|
+
# creates a new key. options are passed to Option.new; see its documentation.
|
152
|
+
def create(key, options={})
|
153
|
+
key=validate_key_format!(key)
|
154
|
+
if recognized_key?(key)
|
155
|
+
raise "already created key #{key}"
|
156
|
+
end
|
157
|
+
@recognized_options[key]= Option.new(key, options)
|
158
|
+
end
|
159
|
+
# reads the value for the given key. if on value is defined, raises NoValueError.
|
160
|
+
def read(key)
|
161
|
+
key = recognize_key! key
|
162
|
+
if @config_hash.key?(key)
|
163
|
+
@config_hash[key]
|
164
|
+
elsif @parent
|
165
|
+
@parent.read(key)
|
166
|
+
else
|
167
|
+
raise NoValueError, "There is no value defined for key #{key}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
# updates the given key with the given value.
|
171
|
+
def update(key, value)
|
172
|
+
key = recognize_key! key
|
173
|
+
value = recognized_options[key].validate! value
|
174
|
+
@config_hash[key]=value
|
175
|
+
end
|
176
|
+
# creates a new key and updates it with the given value. options are passed to Option.new.
|
177
|
+
def create_update(key, value, options={})
|
178
|
+
create(key, options)
|
179
|
+
update(key, value)
|
180
|
+
end
|
181
|
+
# takes a hash of key/value pairs and calls #update on each pair.
|
182
|
+
def update_hash(hash)
|
183
|
+
hash.each do |k,v|
|
184
|
+
update(k,v)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
# deletes the given value from the hash. this does not affect any ancestor Configurations.
|
188
|
+
def delete(key)
|
189
|
+
key = check_key key
|
190
|
+
@config_hash.delete(key)
|
191
|
+
end
|
192
|
+
|
193
|
+
# temporarily set the given keys of this configuration to the given values, yield to the block,
|
194
|
+
# and restore to the original configuration before returning.
|
195
|
+
def with_config(hash, &block)
|
196
|
+
begin
|
197
|
+
orig_config_hash = @config_hash.dup
|
198
|
+
update_hash hash
|
199
|
+
return yield
|
200
|
+
ensure
|
201
|
+
@config_hash = orig_config_hash
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
# module to be included in anything that should have a #config method representing a Configuration.
|
206
|
+
module Configurable
|
207
|
+
# the parent for the Configuration returned from #config
|
208
|
+
attr_accessor :configuration_parent
|
209
|
+
# returns a Configuration object
|
210
|
+
def config
|
211
|
+
@configuration ||= Configuration.new(configuration_parent)
|
212
|
+
end
|
213
|
+
# see Configuration#with_config
|
214
|
+
def with_config(hash, &block)
|
215
|
+
@configuration.with_config(hash, &block)
|
216
|
+
end
|
217
|
+
private
|
218
|
+
# takes a hash of given options, a map of config keys, and a list of other allowed keys.
|
219
|
+
#
|
220
|
+
# the keymap is keyed with keys of the options hash and its values are keys of the Configuration
|
221
|
+
# returned from #config.
|
222
|
+
#
|
223
|
+
# other allowed keys limit what keys are recognized in the given options hash, and ArgumentError
|
224
|
+
# is raised if unrecognized keys are present (this is done by #handle_options; see that method's
|
225
|
+
# documentation).
|
226
|
+
#
|
227
|
+
# returns a hash in which any defined config keys in the keymap which are not already
|
228
|
+
# defined in the given options are set to their config value.
|
229
|
+
def options_from_config(given_options, keymap, other_allowed_keys = [])
|
230
|
+
config_options = (keymap.keys - given_options.keys).inject({}) do |opts, key|
|
231
|
+
if given_options.key?(key)
|
232
|
+
opts
|
233
|
+
elsif config.defined_key?(keymap[key])
|
234
|
+
opts.merge(key => config[keymap[key]])
|
235
|
+
else
|
236
|
+
opts
|
237
|
+
end
|
238
|
+
end
|
239
|
+
handle_options(given_options, config_options, other_allowed_keys + keymap.keys)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class HashConfigurationWithOtherStuff < Configuration
|
244
|
+
attr_accessor :key_prefix
|
245
|
+
def unprefixed_recognized_key(prefixed_key)
|
246
|
+
key_prefix=self.key_prefix || ''
|
247
|
+
if prefixed_key.is_a?(String) || prefixed_key.is_a?(Symbol)
|
248
|
+
prefixed_key=prefixed_key.to_s
|
249
|
+
if prefixed_key[0...key_prefix.length].downcase==key_prefix.downcase
|
250
|
+
key=prefixed_key[key_prefix.length..-1]
|
251
|
+
if recognized_key?(key)
|
252
|
+
return key
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
return nil
|
257
|
+
end
|
258
|
+
def update_from_source
|
259
|
+
config_hash.each do |hash_key, value|
|
260
|
+
if key=unprefixed_recognized_key(hash_key)
|
261
|
+
update(key, value)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
class YamlConfiguration < HashConfigurationWithOtherStuff
|
267
|
+
attr_reader :yaml_file
|
268
|
+
def initialize(parent, yaml_file)
|
269
|
+
super(parent)
|
270
|
+
@yaml_file=yaml_file
|
271
|
+
update_from_source
|
272
|
+
end
|
273
|
+
def config_hash
|
274
|
+
if yaml_file && File.exists?(yaml_file)
|
275
|
+
require 'yaml'
|
276
|
+
@loaded_yaml ||= YAML.load_file(yaml_file) # don't want to reload this every time #update_from_source is called
|
277
|
+
if @loaded_yaml.is_a?(Hash)
|
278
|
+
@loaded_yaml
|
279
|
+
elsif @loaded_yaml == false || @loaded_yaml == nil
|
280
|
+
{}
|
281
|
+
else
|
282
|
+
raise ArgumentError, "Attempted to load Vapir configuration from the YAML file at #{yaml_file.inspect}, but its contents parsed as a #{@loaded_yaml.class}; expected a hash.\nYAML.load_file(#{yaml_file.inspect}) = #{@loaded_yaml.inspect}"
|
283
|
+
end
|
284
|
+
else
|
285
|
+
{}
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
@configurations = []
|
291
|
+
def (@configurations).update_from_source
|
292
|
+
self.each do |c|
|
293
|
+
if c.respond_to?(:update_from_source)
|
294
|
+
c.update_from_source
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
@configurations.push(@base_configuration=Configuration.new(nil) do |config|
|
300
|
+
config.create_update(:attach_timeout, 30, :validator => :numeric)
|
301
|
+
config.create(:default_browser, :validator => proc do |val|
|
302
|
+
require 'vapir-common/browsers'
|
303
|
+
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}"
|
305
|
+
end
|
306
|
+
real_key
|
307
|
+
end)
|
308
|
+
config.create_update(:highlight_color, 'yellow')
|
309
|
+
config.create_update(:wait, true, :validator => :boolean)
|
310
|
+
config.create_update(:type_keys, false, :validator => :boolean)
|
311
|
+
config.create_update(:typing_interval, 0, :validator => :numeric)
|
312
|
+
config.create_update(:warn_deprecated, true, :validator => :boolean)
|
313
|
+
end)
|
314
|
+
|
315
|
+
# adapted from rubygems.rb
|
316
|
+
home_dir = ENV['HOME'] || ENV['USERPROFILE'] || (ENV['HOMEDRIVE'] && ENV['HOMEPATH'] && "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}") || begin
|
317
|
+
File.expand_path("~")
|
318
|
+
rescue
|
319
|
+
if File::ALT_SEPARATOR
|
320
|
+
"C:/"
|
321
|
+
else
|
322
|
+
"/"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
@configurations.push(@home_yaml_configuration = YamlConfiguration.new(@configurations.last, File.join(home_dir, '.vapir_config.yaml')))
|
327
|
+
@configurations.push(@pwd_yaml_configuration = YamlConfiguration.new(@configurations.last, File.join(Dir.pwd, 'vapir_config.yaml')))
|
328
|
+
|
329
|
+
env_config_yaml = (env_yaml_key = ENV.keys.detect{|key| key.downcase == 'vapir_config_yaml' }) && ENV[env_yaml_key]
|
330
|
+
env_config_yaml_file = env_config_yaml && (File.directory?(env_config_yaml) ? File.join(env_config_yaml, 'vapir_config.yaml') : env_config_yaml)
|
331
|
+
@configurations.push(@env_yaml_configuration = YamlConfiguration.new(@configurations.last, env_config_yaml_file))
|
332
|
+
@configurations.push(@env_configuration = HashConfigurationWithOtherStuff.new(@configurations.last))
|
333
|
+
@env_configuration.key_prefix='vapir_'
|
334
|
+
def (@env_configuration).config_hash
|
335
|
+
ENV
|
336
|
+
end
|
337
|
+
@env_configuration.update_from_source
|
338
|
+
|
339
|
+
@configuration_parent = @configurations.last
|
340
|
+
extend Configurable # makes Vapir.config which is the in-process user-configurable one, overriding base, yaml, and env
|
341
|
+
end
|