vapir-common 1.7.2 → 1.8.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/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
|