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.
@@ -1,5 +0,0 @@
1
- === 0.0.1 / 2008-08-28
2
-
3
- * Created
4
-
5
-
@@ -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
- module Common
3
- VERSION = '1.7.2'
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'
@@ -1,63 +1,17 @@
1
1
  # vapir-common/browser
2
- require 'vapir-common/options'
3
- module Vapir
4
-
5
- =begin rdoc
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
- =end rdoc
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
- #set_sub_options
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
- # makes sure that the class methods of Browser that call to the class methods of klass
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 as with #new and start the browser on the
85
- # specified url.
86
- def start url
87
- ensure_overridden
88
- set_sub_options
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
- def set_options options
98
- #ensure_overridden
99
- unless self==klass
100
- klass.set_options options
101
- end
102
- end
103
- def options
104
- self==klass ? {} : klass.options
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 klass
108
- key = Vapir.options[:browser]
109
- #eval(@@browser_classes[key]) # this triggers the autoload
110
- browser_class_name=@@browser_classes[key]
111
- klass=browser_class_name.split('::').inject(Object) do |namespace, name_part|
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
- @@default
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= option
140
- @@default = option
141
- end
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
- # vapir-common/browsers
2
- # Define browsers supported by Vapir
3
-
4
- Vapir::Browser.support :name => 'ie', :class => 'Vapir::IE',
5
- :library => 'vapir-ie', :gem => 'vapir-ie',
6
- :options => [:speed, :visible]
7
-
8
- Vapir::Browser.support :name => 'firefox', :class => 'Vapir::Firefox',
9
- :library => 'vapir-firefox'
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