omnijack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +11 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +15 -0
  7. data/NOTICE +5 -0
  8. data/README.md +119 -0
  9. data/Rakefile +23 -0
  10. data/features/list.feature +19 -0
  11. data/features/metadata.feature +43 -0
  12. data/features/platforms.feature +23 -0
  13. data/features/step_definitions/list.rb +12 -0
  14. data/features/step_definitions/metadata.rb +20 -0
  15. data/features/step_definitions/platforms.rb +8 -0
  16. data/features/step_definitions/project.rb +20 -0
  17. data/features/support/env.rb +4 -0
  18. data/lib/omnijack/config.rb +67 -0
  19. data/lib/omnijack/list.rb +94 -0
  20. data/lib/omnijack/metadata.rb +244 -0
  21. data/lib/omnijack/platforms.rb +96 -0
  22. data/lib/omnijack/project/metaprojects.rb +38 -0
  23. data/lib/omnijack/project.rb +63 -0
  24. data/lib/omnijack/version.rb +24 -0
  25. data/lib/omnijack.rb +54 -0
  26. data/omnijack.gemspec +37 -0
  27. data/spec/omnijack/config_spec.rb +55 -0
  28. data/spec/omnijack/list_spec.rb +133 -0
  29. data/spec/omnijack/metadata_spec.rb +577 -0
  30. data/spec/omnijack/platforms_spec.rb +132 -0
  31. data/spec/omnijack/project/angry_chef_spec.rb +55 -0
  32. data/spec/omnijack/project/chef_container_spec.rb +55 -0
  33. data/spec/omnijack/project/chef_dk_spec.rb +55 -0
  34. data/spec/omnijack/project/chef_server_spec.rb +55 -0
  35. data/spec/omnijack/project/chef_spec.rb +55 -0
  36. data/spec/omnijack/project_spec.rb +52 -0
  37. data/spec/omnijack_spec.rb +109 -0
  38. data/spec/spec_helper.rb +38 -0
  39. data/spec/support/real_test_data.json +131 -0
  40. data/vendor/chef/LICENSE +201 -0
  41. data/vendor/chef/NOTICE +21 -0
  42. data/vendor/chef/lib/chef/exceptions.rb +353 -0
  43. data/vendor/chef/lib/chef/mixin/params_validate.rb +242 -0
  44. metadata +276 -0
@@ -0,0 +1,353 @@
1
+ #
2
+ # Author:: Adam Jacob (<adam@opscode.com>)
3
+ # Author:: Seth Falcon (<seth@opscode.com>)
4
+ # Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
5
+ # Copyright:: Copyright 2008-2010 Opscode, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+ class Chef
21
+ # == Chef::Exceptions
22
+ # Chef's custom exceptions are all contained within the Chef::Exceptions
23
+ # namespace.
24
+ class Exceptions
25
+
26
+ # Backcompat with Chef::ShellOut code:
27
+ require 'mixlib/shellout/exceptions'
28
+
29
+ def self.const_missing(const_name)
30
+ if const_name == :ShellCommandFailed
31
+ Chef::Log.warn("Chef::Exceptions::ShellCommandFailed is deprecated, use Mixlib::ShellOut::ShellCommandFailed")
32
+ called_from = caller[0..3].inject("Called from:\n") {|msg, trace_line| msg << " #{trace_line}\n" }
33
+ Chef::Log.warn(called_from)
34
+ Mixlib::ShellOut::ShellCommandFailed
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ class Application < RuntimeError; end
41
+ class Cron < RuntimeError; end
42
+ class Env < RuntimeError; end
43
+ class Exec < RuntimeError; end
44
+ class ErlCall < RuntimeError; end
45
+ class FileNotFound < RuntimeError; end
46
+ class Package < RuntimeError; end
47
+ class Service < RuntimeError; end
48
+ class Route < RuntimeError; end
49
+ class SearchIndex < RuntimeError; end
50
+ class Override < RuntimeError; end
51
+ class UnsupportedAction < RuntimeError; end
52
+ class MissingLibrary < RuntimeError; end
53
+
54
+ class CannotDetermineNodeName < RuntimeError
55
+ def initialize
56
+ super "Unable to determine node name: configure node_name or configure the system's hostname and fqdn"
57
+ end
58
+ end
59
+
60
+ class User < RuntimeError; end
61
+ class Group < RuntimeError; end
62
+ class Link < RuntimeError; end
63
+ class Mount < RuntimeError; end
64
+ class PrivateKeyMissing < RuntimeError; end
65
+ class CannotWritePrivateKey < RuntimeError; end
66
+ class RoleNotFound < RuntimeError; end
67
+ class DuplicateRole < RuntimeError; end
68
+ class ValidationFailed < ArgumentError; end
69
+ class InvalidPrivateKey < ArgumentError; end
70
+ class ConfigurationError < ArgumentError; end
71
+ class RedirectLimitExceeded < RuntimeError; end
72
+ class AmbiguousRunlistSpecification < ArgumentError; end
73
+ class CookbookFrozen < ArgumentError; end
74
+ class CookbookNotFound < RuntimeError; end
75
+ # Cookbook loader used to raise an argument error when cookbook not found.
76
+ # for back compat, need to raise an error that inherits from ArgumentError
77
+ class CookbookNotFoundInRepo < ArgumentError; end
78
+ class RecipeNotFound < ArgumentError; end
79
+ class AttributeNotFound < RuntimeError; end
80
+ class MissingCookbookDependency < StandardError; end # CHEF-5120
81
+ class InvalidCommandOption < RuntimeError; end
82
+ class CommandTimeout < RuntimeError; end
83
+ class RequestedUIDUnavailable < RuntimeError; end
84
+ class InvalidHomeDirectory < ArgumentError; end
85
+ class DsclCommandFailed < RuntimeError; end
86
+ class PlistUtilCommandFailed < RuntimeError; end
87
+ class UserIDNotFound < ArgumentError; end
88
+ class GroupIDNotFound < ArgumentError; end
89
+ class ConflictingMembersInGroup < ArgumentError; end
90
+ class InvalidResourceReference < RuntimeError; end
91
+ class ResourceNotFound < RuntimeError; end
92
+
93
+ # Can't find a Resource of this type that is valid on this platform.
94
+ class NoSuchResourceType < NameError; end
95
+
96
+ class InvalidResourceSpecification < ArgumentError; end
97
+ class SolrConnectionError < RuntimeError; end
98
+ class IllegalChecksumRevert < RuntimeError; end
99
+ class CookbookVersionNameMismatch < ArgumentError; end
100
+ class MissingParentDirectory < RuntimeError; end
101
+ class UnresolvableGitReference < RuntimeError; end
102
+ class InvalidRemoteGitReference < RuntimeError; end
103
+ class InvalidEnvironmentRunListSpecification < ArgumentError; end
104
+ class InvalidDataBagItemID < ArgumentError; end
105
+ class InvalidDataBagName < ArgumentError; end
106
+ class EnclosingDirectoryDoesNotExist < ArgumentError; end
107
+ # Errors originating from calls to the Win32 API
108
+ class Win32APIError < RuntimeError; end
109
+ # Thrown when Win32 API layer binds to non-existent Win32 function. Occurs
110
+ # when older versions of Windows don't support newer Win32 API functions.
111
+ class Win32APIFunctionNotImplemented < NotImplementedError; end
112
+ # Attempting to run windows code on a not-windows node
113
+ class Win32NotWindows < RuntimeError; end
114
+ class WindowsNotAdmin < RuntimeError; end
115
+ # Attempting to access a 64-bit only resource on a 32-bit Windows system
116
+ class Win32ArchitectureIncorrect < RuntimeError; end
117
+ class ObsoleteDependencySyntax < ArgumentError; end
118
+ class InvalidDataBagPath < ArgumentError; end
119
+ class DuplicateDataBagItem < RuntimeError; end
120
+
121
+ # A different version of a cookbook was added to a
122
+ # VersionedRecipeList than the one already there.
123
+ class CookbookVersionConflict < ArgumentError ; end
124
+
125
+ # does not follow X.Y.Z format. ArgumentError?
126
+ class InvalidPlatformVersion < ArgumentError; end
127
+ class InvalidCookbookVersion < ArgumentError; end
128
+
129
+ # version constraint should be a string or array, or it doesn't
130
+ # match OP VERSION. ArgumentError?
131
+ class InvalidVersionConstraint < ArgumentError; end
132
+
133
+ # Version constraints are not allowed in chef-solo
134
+ class IllegalVersionConstraint < NotImplementedError; end
135
+
136
+ class MetadataNotValid < StandardError; end
137
+
138
+ # File operation attempted but no permissions to perform it
139
+ class InsufficientPermissions < RuntimeError; end
140
+
141
+ # Ifconfig failed
142
+ class Ifconfig < RuntimeError; end
143
+
144
+ # Invalid "source" parameter to a remote_file resource
145
+ class InvalidRemoteFileURI < ArgumentError; end
146
+
147
+ # Node::Attribute computes the merged version of of attributes
148
+ # and makes it read-only. Attempting to modify a read-only
149
+ # attribute will cause this error.
150
+ class ImmutableAttributeModification < NoMethodError; end
151
+
152
+ # Merged node attributes are invalidated when the component
153
+ # attributes are updated. Attempting to read from a stale copy
154
+ # of merged attributes will trigger this error.
155
+ class StaleAttributeRead < StandardError; end
156
+
157
+ # Registry Helper throws the following errors
158
+ class Win32RegArchitectureIncorrect < Win32ArchitectureIncorrect; end
159
+ class Win32RegHiveMissing < ArgumentError; end
160
+ class Win32RegKeyMissing < RuntimeError; end
161
+ class Win32RegValueMissing < RuntimeError; end
162
+ class Win32RegDataMissing < RuntimeError; end
163
+ class Win32RegValueExists < RuntimeError; end
164
+ class Win32RegNoRecursive < ArgumentError; end
165
+ class Win32RegTypeDoesNotExist < ArgumentError; end
166
+ class Win32RegBadType < ArgumentError; end
167
+ class Win32RegBadValueSize < ArgumentError; end
168
+ class Win32RegTypesMismatch < ArgumentError; end
169
+
170
+ class InvalidEnvironmentPath < ArgumentError; end
171
+ class EnvironmentNotFound < RuntimeError; end
172
+
173
+ # File-like resource found a non-file (socket, pipe, directory, etc) at its destination
174
+ class FileTypeMismatch < RuntimeError; end
175
+
176
+ # File (or descendent) resource configured to manage symlink source, but
177
+ # the symlink that is there either loops or points to a nonexistent file
178
+ class InvalidSymlink < RuntimeError; end
179
+
180
+ class ChildConvergeError < RuntimeError; end
181
+
182
+ class MissingRole < RuntimeError
183
+ NULL = Object.new
184
+
185
+ attr_reader :expansion
186
+
187
+ def initialize(message_or_expansion=NULL)
188
+ @expansion = nil
189
+ case message_or_expansion
190
+ when NULL
191
+ super()
192
+ when String
193
+ super
194
+ when RunList::RunListExpansion
195
+ @expansion = message_or_expansion
196
+ missing_roles = @expansion.errors.join(', ')
197
+ super("The expanded run list includes nonexistent roles: #{missing_roles}")
198
+ end
199
+ end
200
+
201
+ end
202
+ # Exception class for collecting multiple failures. Used when running
203
+ # delayed notifications so that chef can process each delayed
204
+ # notification even if chef client or other notifications fail.
205
+ class MultipleFailures < StandardError
206
+ def initialize(*args)
207
+ super
208
+ @all_failures = []
209
+ end
210
+
211
+ def message
212
+ base = "Multiple failures occurred:\n"
213
+ @all_failures.inject(base) do |message, (location, error)|
214
+ message << "* #{error.class} occurred in #{location}: #{error.message}\n"
215
+ end
216
+ end
217
+
218
+ def client_run_failure(exception)
219
+ set_backtrace(exception.backtrace)
220
+ @all_failures << [ "chef run", exception ]
221
+ end
222
+
223
+ def notification_failure(exception)
224
+ @all_failures << [ "delayed notification", exception ]
225
+ end
226
+
227
+ def raise!
228
+ unless empty?
229
+ raise self.for_raise
230
+ end
231
+ end
232
+
233
+ def empty?
234
+ @all_failures.empty?
235
+ end
236
+
237
+ def for_raise
238
+ if @all_failures.size == 1
239
+ @all_failures[0][1]
240
+ else
241
+ self
242
+ end
243
+ end
244
+ end
245
+
246
+ class CookbookVersionSelection
247
+
248
+ # Compound exception: In run_list expansion and resolution,
249
+ # run_list items referred to cookbooks that don't exist and/or
250
+ # have no versions available.
251
+ class InvalidRunListItems < StandardError
252
+ attr_reader :non_existent_cookbooks
253
+ attr_reader :cookbooks_with_no_matching_versions
254
+
255
+ def initialize(message, non_existent_cookbooks, cookbooks_with_no_matching_versions)
256
+ super(message)
257
+
258
+ @non_existent_cookbooks = non_existent_cookbooks
259
+ @cookbooks_with_no_matching_versions = cookbooks_with_no_matching_versions
260
+ end
261
+
262
+ def to_json(*a)
263
+ result = {
264
+ "message" => message,
265
+ "non_existent_cookbooks" => non_existent_cookbooks,
266
+ "cookbooks_with_no_versions" => cookbooks_with_no_matching_versions
267
+ }
268
+ Chef::JSONCompat.to_json(result, *a)
269
+ end
270
+ end
271
+
272
+ # In run_list expansion and resolution, a constraint was
273
+ # unsatisfiable.
274
+ #
275
+ # This exception may not be the complete error report. If you
276
+ # resolve the misconfiguration represented by this exception and
277
+ # re-solve, you may get another exception
278
+ class UnsatisfiableRunListItem < StandardError
279
+ attr_reader :run_list_item
280
+ attr_reader :non_existent_cookbooks, :most_constrained_cookbooks
281
+
282
+ # most_constrained_cookbooks: if I were to remove constraints
283
+ # regarding these cookbooks, I would get a solution or move on
284
+ # to the next error (deeper in the graph). An item in this list
285
+ # may be unsatisfiable, but when resolved may also reveal
286
+ # further unsatisfiable constraints; this condition would not be
287
+ # reported.
288
+ def initialize(message, run_list_item, non_existent_cookbooks, most_constrained_cookbooks)
289
+ super(message)
290
+
291
+ @run_list_item = run_list_item
292
+ @non_existent_cookbooks = non_existent_cookbooks
293
+ @most_constrained_cookbooks = most_constrained_cookbooks
294
+ end
295
+
296
+ def to_json(*a)
297
+ result = {
298
+ "message" => message,
299
+ "unsatisfiable_run_list_item" => run_list_item,
300
+ "non_existent_cookbooks" => non_existent_cookbooks,
301
+ "most_constrained_cookbooks" => most_constrained_cookbooks
302
+ }
303
+ Chef::JSONCompat.to_json(result, *a)
304
+ end
305
+ end
306
+
307
+ end # CookbookVersionSelection
308
+
309
+ # When the server sends a redirect, RFC 2616 states a user-agent should
310
+ # not follow it with a method other than GET or HEAD, unless a specific
311
+ # action is taken by the user. A redirect received as response to a
312
+ # non-GET and non-HEAD request will thus raise an InvalidRedirect.
313
+ class InvalidRedirect < StandardError; end
314
+
315
+ # Raised when the content length of a download does not match the content
316
+ # length declared in the http response.
317
+ class ContentLengthMismatch < RuntimeError
318
+ def initialize(response_length, content_length)
319
+ super "Response body length #{response_length} does not match HTTP Content-Length header #{content_length}."
320
+ end
321
+ end
322
+
323
+ class UnsupportedPlatform < RuntimeError
324
+ def initialize(platform)
325
+ super "This functionality is not supported on platform #{platform}."
326
+ end
327
+ end
328
+
329
+ # Raised when Chef::Config[:run_lock_timeout] is set and some other client run fails
330
+ # to release the run lock becure Chef::Config[:run_lock_timeout] seconds pass.
331
+ class RunLockTimeout < RuntimeError
332
+ def initialize(duration, blocking_pid)
333
+ super "Unable to acquire lock. Waited #{duration} seconds for #{blocking_pid} to release."
334
+ end
335
+ end
336
+
337
+ class ChecksumMismatch < RuntimeError
338
+ def initialize(res_cksum, cont_cksum)
339
+ super "Checksum on resource (#{res_cksum}) does not match checksum on content (#{cont_cksum})"
340
+ end
341
+ end
342
+
343
+ class BadProxyURI < RuntimeError; end
344
+
345
+ # Raised by Chef::JSONCompat
346
+ class JSON
347
+ class EncodeError < RuntimeError; end
348
+ class ParseError < RuntimeError; end
349
+ end
350
+
351
+ class InvalidSearchQuery < ArgumentError; end
352
+ end
353
+ end
@@ -0,0 +1,242 @@
1
+ #
2
+ # Author:: Adam Jacob (<adam@opscode.com>)
3
+ # Copyright:: Copyright (c) 2008 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ class Chef
19
+ class DelayedEvaluator < Proc
20
+ end
21
+ module Mixin
22
+ module ParamsValidate
23
+
24
+ # Takes a hash of options, along with a map to validate them. Returns the original
25
+ # options hash, plus any changes that might have been made (through things like setting
26
+ # default values in the validation map)
27
+ #
28
+ # For example:
29
+ #
30
+ # validate({ :one => "neat" }, { :one => { :kind_of => String }})
31
+ #
32
+ # Would raise an exception if the value of :one above is not a kind_of? string. Valid
33
+ # map options are:
34
+ #
35
+ # :default:: Sets the default value for this parameter.
36
+ # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
37
+ # The key will be inserted into the error message if the Proc does not return true:
38
+ # "Option #{key}'s value #{value} #{message}!"
39
+ # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
40
+ # that the value is one of those types.
41
+ # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
42
+ # method names.
43
+ # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
44
+ # by default, options are not required.
45
+ # :regex:: Match the value of the paramater against a regular expression.
46
+ # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any
47
+ # of the values.
48
+ def validate(opts, map)
49
+ #--
50
+ # validate works by taking the keys in the validation map, assuming it's a hash, and
51
+ # looking for _pv_:symbol as methods. Assuming it find them, it calls the right
52
+ # one.
53
+ #++
54
+ raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash)
55
+ raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash)
56
+
57
+ map.each do |key, validation|
58
+ unless key.kind_of?(Symbol) || key.kind_of?(String)
59
+ raise ArgumentError, "Validation map keys must be symbols or strings!"
60
+ end
61
+ case validation
62
+ when true
63
+ _pv_required(opts, key)
64
+ when false
65
+ true
66
+ when Hash
67
+ validation.each do |check, carg|
68
+ check_method = "_pv_#{check.to_s}"
69
+ if self.respond_to?(check_method, true)
70
+ self.send(check_method, opts, key, carg)
71
+ else
72
+ raise ArgumentError, "Validation map has unknown check: #{check}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ opts
78
+ end
79
+
80
+ def lazy(&block)
81
+ DelayedEvaluator.new(&block)
82
+ end
83
+
84
+ def set_or_return(symbol, arg, validation)
85
+ iv_symbol = "@#{symbol.to_s}".to_sym
86
+ if arg == nil && self.instance_variable_defined?(iv_symbol) == true
87
+ ivar = self.instance_variable_get(iv_symbol)
88
+ if(ivar.is_a?(DelayedEvaluator))
89
+ validate({ symbol => ivar.call }, { symbol => validation })[symbol]
90
+ else
91
+ ivar
92
+ end
93
+ else
94
+ if(arg.is_a?(DelayedEvaluator))
95
+ val = arg
96
+ else
97
+ val = validate({ symbol => arg }, { symbol => validation })[symbol]
98
+
99
+ # Handle the case where the "default" was a DelayedEvaluator. In
100
+ # this case, the block yields an optional parameter of +self+,
101
+ # which is the equivalent of "new_resource"
102
+ if val.is_a?(DelayedEvaluator)
103
+ val = val.call(self)
104
+ end
105
+ end
106
+ self.instance_variable_set(iv_symbol, val)
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ # Return the value of a parameter, or nil if it doesn't exist.
113
+ def _pv_opts_lookup(opts, key)
114
+ if opts.has_key?(key.to_s)
115
+ opts[key.to_s]
116
+ elsif opts.has_key?(key.to_sym)
117
+ opts[key.to_sym]
118
+ else
119
+ nil
120
+ end
121
+ end
122
+
123
+ # Raise an exception if the parameter is not found.
124
+ def _pv_required(opts, key, is_required=true)
125
+ if is_required
126
+ if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
127
+ (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
128
+ true
129
+ else
130
+ raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
131
+ end
132
+ end
133
+ end
134
+
135
+ def _pv_equal_to(opts, key, to_be)
136
+ value = _pv_opts_lookup(opts, key)
137
+ unless value.nil?
138
+ passes = false
139
+ Array(to_be).each do |tb|
140
+ passes = true if value == tb
141
+ end
142
+ unless passes
143
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
144
+ end
145
+ end
146
+ end
147
+
148
+ # Raise an exception if the parameter is not a kind_of?(to_be)
149
+ def _pv_kind_of(opts, key, to_be)
150
+ value = _pv_opts_lookup(opts, key)
151
+ unless value.nil?
152
+ passes = false
153
+ Array(to_be).each do |tb|
154
+ passes = true if value.kind_of?(tb)
155
+ end
156
+ unless passes
157
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
158
+ end
159
+ end
160
+ end
161
+
162
+ # Raise an exception if the parameter does not respond to a given set of methods.
163
+ def _pv_respond_to(opts, key, method_name_list)
164
+ value = _pv_opts_lookup(opts, key)
165
+ unless value.nil?
166
+ Array(method_name_list).each do |method_name|
167
+ unless value.respond_to?(method_name)
168
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ # Assert that parameter returns false when passed a predicate method.
175
+ # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
176
+ # error value.blank? returns a 'truthy' (not nil or false) value.
177
+ #
178
+ # Note, this will *PASS* if the object doesn't respond to the method.
179
+ # So, to make sure a value is not nil and not blank, you need to do
180
+ # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
181
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
182
+ value = _pv_opts_lookup(opts, key)
183
+ predicate_method = (predicate_method_base_name.to_s + "?").to_sym
184
+
185
+ if value.respond_to?(predicate_method)
186
+ if value.send(predicate_method)
187
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
188
+ end
189
+ end
190
+ end
191
+
192
+ # Assign a default value to a parameter.
193
+ def _pv_default(opts, key, default_value)
194
+ value = _pv_opts_lookup(opts, key)
195
+ if value == nil
196
+ opts[key] = default_value
197
+ end
198
+ end
199
+
200
+ # Check a parameter against a regular expression.
201
+ def _pv_regex(opts, key, regex)
202
+ value = _pv_opts_lookup(opts, key)
203
+ if value != nil
204
+ passes = false
205
+ [ regex ].flatten.each do |r|
206
+ if value != nil
207
+ if r.match(value.to_s)
208
+ passes = true
209
+ end
210
+ end
211
+ end
212
+ unless passes
213
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
214
+ end
215
+ end
216
+ end
217
+
218
+ # Check a parameter against a hash of proc's.
219
+ def _pv_callbacks(opts, key, callbacks)
220
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
221
+ value = _pv_opts_lookup(opts, key)
222
+ if value != nil
223
+ callbacks.each do |message, zeproc|
224
+ if zeproc.call(value) != true
225
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ # Allow a parameter to default to @name
232
+ def _pv_name_attribute(opts, key, is_name_attribute=true)
233
+ if is_name_attribute
234
+ if opts[key] == nil
235
+ opts[key] = self.instance_variable_get("@name")
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+