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.
@@ -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