omnijack 0.1.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.
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
+