optout 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +123 -0
- data/lib/optout.rb +596 -0
- data/spec/optout_spec.rb +380 -0
- metadata +98 -0
data/README.rdoc
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
= Optout
|
2
|
+
|
3
|
+
Optout helps you write code that will call +exec+ and +system+ like functions. It allows you to map hash keys to command line
|
4
|
+
arguments and define validation rules that must be met before the command line arguments are created.
|
5
|
+
|
6
|
+
== Overview
|
7
|
+
|
8
|
+
require "optout"
|
9
|
+
|
10
|
+
# Create options for `gem`
|
11
|
+
optout = Optout.options do
|
12
|
+
on :gem, "install", :required => true
|
13
|
+
on :os, "--platform", %w(mswin cygwin mingw)
|
14
|
+
on :version, "-v", /\A\d+(\.\d+)*\z/
|
15
|
+
on :user, "--user-install"
|
16
|
+
on :location, "-i", Optout::Dir.exists.under(ENV["HOME"])
|
17
|
+
end
|
18
|
+
|
19
|
+
options = {
|
20
|
+
:gem => "rake",
|
21
|
+
:os => "mswin",
|
22
|
+
:version => "0.9.2",
|
23
|
+
:user => true
|
24
|
+
}
|
25
|
+
|
26
|
+
exec "gem", *optout.argv(options)
|
27
|
+
# Returns: ["install", "rake", "--platform", "mswin", "-v", "0.9.2", "--user-install"]
|
28
|
+
|
29
|
+
`gem #{optout.shell(options)}`
|
30
|
+
# Returns: "'install' 'rake' --platform 'mswin' -v '0.9.2' --user-install"
|
31
|
+
|
32
|
+
== Install
|
33
|
+
|
34
|
+
<code>gem install optout</code>
|
35
|
+
|
36
|
+
== Defining Options
|
37
|
+
|
38
|
+
Inorder to turn the incoming option hash into something useful you must tell +Optout+ a bit about your options. This is done by calling <code>Optout#on</code> and passing it the name of a key in the option hash. The simplest case is an option with no switch:
|
39
|
+
|
40
|
+
optout = Optout.options do
|
41
|
+
on :path
|
42
|
+
end
|
43
|
+
|
44
|
+
optout.shell(:path => "/home/sshaw")
|
45
|
+
# Returns: '/home/sshaw'
|
46
|
+
|
47
|
+
Key names can be a +Symbol+ or a +String+, +Optout+ will check for both in the option hash.
|
48
|
+
|
49
|
+
If the option has a switch it can be given after the option's key:
|
50
|
+
|
51
|
+
optout = Optout.options do
|
52
|
+
on :path, "-p"
|
53
|
+
end
|
54
|
+
|
55
|
+
optout.shell(:path => "/home/sshaw")
|
56
|
+
# Returns: -p '/home/sshaw'
|
57
|
+
|
58
|
+
Some programs can be finicky about the space between the switch and the value, or require options
|
59
|
+
in a different format. +Optout+ accepts various configuration options that can remdy this:
|
60
|
+
|
61
|
+
optout = Optout.options do
|
62
|
+
on :path, "-p", :arg_separator => ""
|
63
|
+
end
|
64
|
+
|
65
|
+
optout.shell(:path => "/home/sshaw")
|
66
|
+
# Returns: -p'/home/sshaw'
|
67
|
+
|
68
|
+
optout = Optout.options do
|
69
|
+
on :path, "--path", :arg_separator => "=", :required => true
|
70
|
+
end
|
71
|
+
|
72
|
+
optout.shell(:path => "/home/sshaw")
|
73
|
+
# Returns: --path='/home/sshaw'
|
74
|
+
|
75
|
+
optout.shell({})
|
76
|
+
# Raises: Optout::OptionRequired
|
77
|
+
|
78
|
+
== Validating Options
|
79
|
+
|
80
|
+
+Optout+ can validate your options too. Just specify the validation rule after the option's key or switch:
|
81
|
+
|
82
|
+
optout = Optout.options do
|
83
|
+
# Must match [a-z]
|
84
|
+
on :path, "-p", /[a-z]/
|
85
|
+
end
|
86
|
+
|
87
|
+
optout = Optout.options do
|
88
|
+
# Must be true, false, or nil
|
89
|
+
on :path, "-p", Optout::Boolean
|
90
|
+
end
|
91
|
+
|
92
|
+
optout = Optout.options do
|
93
|
+
# Must be in the given set
|
94
|
+
on :path, %w(/home/sshaw /Users/gatinha /Users/fofinha)
|
95
|
+
end
|
96
|
+
|
97
|
+
optout = Optout.options do
|
98
|
+
# Must be a diretory under "/sshaw" and have user write permission
|
99
|
+
on :path, Optout::Dir.under("/home").permissions("w")
|
100
|
+
end
|
101
|
+
|
102
|
+
optout.shell(:path => "/root")
|
103
|
+
# Raises: Optout::OptionInvalid
|
104
|
+
|
105
|
+
There are plenty of other features, see {the RDoc}[http://rubydoc.info/github/sshaw/optout/frames].
|
106
|
+
|
107
|
+
== More Info
|
108
|
+
|
109
|
+
=== RDoc
|
110
|
+
|
111
|
+
http://rubydoc.info/github/sshaw/optout/frames
|
112
|
+
|
113
|
+
=== Bugs
|
114
|
+
|
115
|
+
https://github.com/sshaw/optout/issues
|
116
|
+
|
117
|
+
== Author
|
118
|
+
|
119
|
+
Skye Shaw [sshaw AT lucas.cis.temple.edu]
|
120
|
+
|
121
|
+
== License
|
122
|
+
|
123
|
+
Released under the MIT License: http://www.opensource.org/licenses/MIT
|
data/lib/optout.rb
ADDED
@@ -0,0 +1,596 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
class Optout
|
5
|
+
VERSION = "0.0.1"
|
6
|
+
|
7
|
+
class OptionError < StandardError
|
8
|
+
attr :key
|
9
|
+
def initialize(key, message)
|
10
|
+
super(message)
|
11
|
+
@key = key
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class OptionRequired < OptionError
|
16
|
+
def initialize(key)
|
17
|
+
super(key, "option required: '#{key}'")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class OptionUnknown < OptionError
|
22
|
+
def initialize(key)
|
23
|
+
super(key, "option unknown: '#{key}'")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class OptionInvalid < OptionError
|
28
|
+
def initialize(key, message)
|
29
|
+
super(key, "option invalid: #{key}; #{message}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
##
|
35
|
+
# Define a set of options to validate and create.
|
36
|
+
#
|
37
|
+
# === Parameters
|
38
|
+
#
|
39
|
+
# [config (Hash)] Configuration options
|
40
|
+
# [definition (Proc)] Option definitions
|
41
|
+
#
|
42
|
+
# === Configuration Options
|
43
|
+
#
|
44
|
+
# [:arg_separator] Set the default for all subsequent options defined via +on+. See Optout#on@Options.
|
45
|
+
# [:check_keys] If +true+ an <code>Optout::OptionUnknown</code> error will be raised when the incoming option hash contains a key that has not been associated with an option.
|
46
|
+
# Defaults to +true+.
|
47
|
+
# [:multiple] Set the default for all subsequent options defined via +on+. See Optout#on@Options.
|
48
|
+
# [:required] Set the default for all subsequent options defined via +on+. See Optout#on@Options.
|
49
|
+
#
|
50
|
+
# === Errors
|
51
|
+
#
|
52
|
+
# [ArgumentError] Calls to +on+ from inside a block can raise an +ArgumentError+.
|
53
|
+
#
|
54
|
+
# === Examples
|
55
|
+
#
|
56
|
+
# optz = Optout.options do
|
57
|
+
# on :all, "-a"
|
58
|
+
# on :size, "-b", /\A\d+\z/, :required => true
|
59
|
+
# on :file, Optout::File.under("/home/sshaw"), :default => "/home/sshaw/tmp"
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# optz.shell(:all => true, :size => 1024, :file => "/home/sshaw/some file")
|
63
|
+
# # Creates: "-a -b '1024' '/home/sshaw/some file'
|
64
|
+
#
|
65
|
+
# optz = Optout.options :required => true, :check_keys => false do
|
66
|
+
# on :lib, :index => 2
|
67
|
+
# on :prefix, "--prefix" , %w{/sshaw/lib /sshaw/usr/lib}, :arg_separator => "="
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# optz.argv(:lib => "libssl2",
|
71
|
+
# :prefix => "/sshaw/usr/lib",
|
72
|
+
# :bad_key => "No error raised because of moi")
|
73
|
+
# # Creates: ["--prefix='/sshaw/usr/lib'", "libssl2"]
|
74
|
+
#
|
75
|
+
|
76
|
+
def options(config = {}, &block)
|
77
|
+
optout = new(config)
|
78
|
+
optout.instance_eval(&block) if block_given?
|
79
|
+
optout
|
80
|
+
end
|
81
|
+
|
82
|
+
alias :keys :options
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(args = {})
|
86
|
+
@options = {}
|
87
|
+
@check_keys = args.include?(:check_keys) ? args[:check_keys] : true
|
88
|
+
#@opt_seperator = args[:opt_seperator]
|
89
|
+
@default_opt_options = {
|
90
|
+
:required => args[:required],
|
91
|
+
:multiple => args[:multiple],
|
92
|
+
:arg_separator => args[:arg_separator]
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Define an option.
|
98
|
+
#
|
99
|
+
# === Parameters
|
100
|
+
#
|
101
|
+
# [key (Symbol)] The key of the option in the option hash that will passed to +shell+ or +argv+.
|
102
|
+
# [switch (String)] Optional. The option's command line switch. If no switch is given only the option's value is output.
|
103
|
+
# [rule (Object)] Optional. Validation rule, see {Validating}[rdoc-ref:#on@Validating].
|
104
|
+
# [options (Hash)] Additional option configuration, see {Options}[rdoc-ref:#on@Options].
|
105
|
+
#
|
106
|
+
# === Options
|
107
|
+
#
|
108
|
+
# [:arg_separator] The +String+ used to separate the option's switch from its value. Defaults to <code>" "</code> (space).
|
109
|
+
# [:default] The option's default value. This will be used if the option is +nil+ or +empty?+.
|
110
|
+
# [:index] The index of the option in the resulting +String+ or +Array+.
|
111
|
+
# [:multiple] If +true+ the option will accept multiple values. If +false+ an <code>Optout::OptionInvalid</code> error will be raised if the option
|
112
|
+
# contains multiple values. If +true+ multiple values are joined on a comma, you can set this to a +String+
|
113
|
+
# to join on that string instead. Defaults to +false+.
|
114
|
+
# [:required] If +true+ the option must contian a value i.e., it must not be +false+ or +nil+ else an <code>Optout::OptionRequired</code> error will be raised.
|
115
|
+
# Defaults to +false+.
|
116
|
+
# [:validator] An additional validation rule, see Validating.
|
117
|
+
#
|
118
|
+
# === Validating
|
119
|
+
#
|
120
|
+
# A Validator will only be applied if there's a value. If the option is required pass <code>:required => true</code>
|
121
|
+
# to +on+ when defining the option. Validation rules can be in one of the following forms:
|
122
|
+
#
|
123
|
+
# [Regular Expresion] A pattern to match the option's value against.
|
124
|
+
# [An Array] Restrict the option's value(s) to item(s) contained in the given array.
|
125
|
+
# [Class] Restrict the option's value to instances of the given class.
|
126
|
+
# [Optout::Boolean] Restrict the option's value to something boolean, i.e., +true+, +false+, or +nil+.
|
127
|
+
# [Optout::File] The option's value must be a file. Note that the file does not have to exist. <code>Optout::File</code> has several methods that can be used to tune validation, see Optout::File.
|
128
|
+
# [Optout::Dir] The option's value must be a directory. <code>Optout::Dir</code> has several methods that can be used to tune validation, see Optout::Dir.
|
129
|
+
#
|
130
|
+
# === Errors
|
131
|
+
#
|
132
|
+
# [ArgumentError] An +ArgumentError+ is raised if +key+ is +nil+ or +key+ has already been defined
|
133
|
+
|
134
|
+
def on(*args)
|
135
|
+
key = args.shift
|
136
|
+
|
137
|
+
# switch is optional, this could be a validation rule
|
138
|
+
switch = args.shift if String === args[0]
|
139
|
+
raise ArgumentError, "option key required" if key.nil?
|
140
|
+
raise ArgumentError, "option already defined: '#{key}'" if @options[key]
|
141
|
+
|
142
|
+
opt_options = Hash === args.last ? @default_opt_options.merge(args.pop) : @default_opt_options.dup
|
143
|
+
opt_options[:index] ||= @options.size
|
144
|
+
opt_options[:validator] = args.shift
|
145
|
+
|
146
|
+
@options[key] = Option.create(key, switch, opt_options)
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Create an argument string that can be to passed to a +system+ like function.
|
151
|
+
#
|
152
|
+
# === Parameters
|
153
|
+
# [options (Hash)] The option hash used to construct the argument string.
|
154
|
+
#
|
155
|
+
# === Returns
|
156
|
+
# [String] The argument string.
|
157
|
+
#
|
158
|
+
# === Errors
|
159
|
+
# See Optout#argv@Errors
|
160
|
+
def shell(options = {})
|
161
|
+
create_options(options).map { |opt| opt.to_s }.join " "
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Create an +argv+ array that can be to passed to an +exec+ like function.
|
166
|
+
#
|
167
|
+
# === Parameters
|
168
|
+
# [options (Hash)] The options hash used to construct the +argv+ array.
|
169
|
+
#
|
170
|
+
# === Returns
|
171
|
+
# [Array] The +argv+ array, each element is a +String+
|
172
|
+
#
|
173
|
+
# === Errors
|
174
|
+
# [Optout::OptionRequired] The option hash is missing a required value.
|
175
|
+
# [Optout::OptionUnknown] The option hash contains an unknown key.
|
176
|
+
# [Optout::OptionInvalid] The option hash contains a value the does not conform to the defined specification.
|
177
|
+
|
178
|
+
def argv(options = {})
|
179
|
+
create_options(options).map { |opt| opt.to_a }.flatten
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def create_options(options = {})
|
184
|
+
argv = []
|
185
|
+
options = options.dup
|
186
|
+
|
187
|
+
@options.each do |key, klass|
|
188
|
+
value = options.delete(key) || options.delete(key.to_s)
|
189
|
+
opt = klass.new(value)
|
190
|
+
opt.validate!
|
191
|
+
argv << opt
|
192
|
+
end
|
193
|
+
|
194
|
+
if @check_keys && options.any?
|
195
|
+
raise OptionUnknown, options.keys[0]
|
196
|
+
end
|
197
|
+
|
198
|
+
argv.select { |opt| !opt.empty? }.
|
199
|
+
sort_by { |opt| opt.index }
|
200
|
+
end
|
201
|
+
|
202
|
+
class Option
|
203
|
+
attr :key
|
204
|
+
attr :value
|
205
|
+
attr :index
|
206
|
+
|
207
|
+
##
|
208
|
+
# Creates a subclass of +Option+
|
209
|
+
#
|
210
|
+
# === Parameters
|
211
|
+
#
|
212
|
+
# [key (Symbol)] The hash key that will be used to lookup and create this option.
|
213
|
+
# [switch (String)] Optional.
|
214
|
+
# [config (Hash)] Describe how to validate and create the option.
|
215
|
+
#
|
216
|
+
# === Examples
|
217
|
+
#
|
218
|
+
# MyOption = Optout::Option.create(:quality, "-q", :arg_separator => "=", :validator => Fixnum)
|
219
|
+
# opt = MyOption.new(75)
|
220
|
+
# opt.empty? # false
|
221
|
+
# opt.validate!
|
222
|
+
# opt.to_s # "-q='75'"
|
223
|
+
#
|
224
|
+
def self.create(key, *args)
|
225
|
+
options = Hash === args.last ? args.pop : {}
|
226
|
+
switch = args.shift
|
227
|
+
|
228
|
+
Class.new(Option) do
|
229
|
+
define_method(:initialize) do |*v|
|
230
|
+
@key = key
|
231
|
+
@switch = switch
|
232
|
+
@value = v.shift || options[:default]
|
233
|
+
@joinon = String === options[:multiple] ? options[:multiple] : ","
|
234
|
+
@index = options[:index].to_i
|
235
|
+
@separator = options[:arg_separator] || " "
|
236
|
+
|
237
|
+
@validators = []
|
238
|
+
@validators << Validator::Required.new(options[:required])
|
239
|
+
@validators << Validator::Multiple.new(options[:multiple])
|
240
|
+
|
241
|
+
# Could be an Array..?
|
242
|
+
@validators << Validator.for(options[:validator]) if options[:validator]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
##
|
248
|
+
# Turn the option into a string that can be to passed to a +system+ like function.
|
249
|
+
# This _does not_ validate the option. You must call <code>validate!</code>.
|
250
|
+
#
|
251
|
+
# === Examples
|
252
|
+
#
|
253
|
+
# MyOption = Optout::Option.create(:level, "-L", %w(fatal info warn debug))
|
254
|
+
# MyOption.new("debug").to_s
|
255
|
+
# # Returns: "-L 'debug'"
|
256
|
+
#
|
257
|
+
def to_s
|
258
|
+
opt = create_opt_array
|
259
|
+
if opt.any?
|
260
|
+
if opt.size == 1
|
261
|
+
opt[0] = quote(opt[0]) unless @switch
|
262
|
+
else
|
263
|
+
opt[1] = quote(opt[1])
|
264
|
+
end
|
265
|
+
end
|
266
|
+
opt.join(@separator)
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# Turn the option into an array that can be passed to an +exec+ like function.
|
271
|
+
# This _does not_ validate the option. You must call <code>validate!</code>.
|
272
|
+
#
|
273
|
+
# === Examples
|
274
|
+
#
|
275
|
+
# MyOption = Optout::Option.create(:level, "-L", %w(fatal info warn debug))
|
276
|
+
# MyOption.new("debug").to_a
|
277
|
+
# # Returns: [ "-L", "debug" ]
|
278
|
+
#
|
279
|
+
def to_a
|
280
|
+
opt = create_opt_array
|
281
|
+
opt = [ opt.join(@separator) ] unless @separator =~ /\A\s+\z/
|
282
|
+
opt
|
283
|
+
end
|
284
|
+
|
285
|
+
##
|
286
|
+
# Check if the option contains a value
|
287
|
+
#
|
288
|
+
# === Returns
|
289
|
+
#
|
290
|
+
# +false+ if the option's value is +false+, +nil+, or an empty +String+, +true+ otherwise.
|
291
|
+
#
|
292
|
+
def empty?
|
293
|
+
!@value || @value.to_s.empty?
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Validate the option
|
298
|
+
#
|
299
|
+
# === Errors
|
300
|
+
#
|
301
|
+
# [OptionRequired] The option is missing a required value
|
302
|
+
# [OptionUnknown] The option contains an unknown key
|
303
|
+
# [OptionInvalid] The option contains a value the does not conform to the defined specification
|
304
|
+
#
|
305
|
+
def validate!
|
306
|
+
@validators.each { |v| v.validate!(self) }
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
def create_opt_array
|
311
|
+
opt = []
|
312
|
+
opt << @switch if @switch && @value
|
313
|
+
opt << normalize(@value) if !empty? && @value != true # Only include @value for non-boolean options
|
314
|
+
opt
|
315
|
+
end
|
316
|
+
|
317
|
+
def quote(value)
|
318
|
+
if unix?
|
319
|
+
sprintf "'%s'", value.gsub("'") { "'\\''" }
|
320
|
+
else
|
321
|
+
# TODO: Real cmd.exe quoting
|
322
|
+
%|"#{value}"|
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def unix?
|
327
|
+
RbConfig::CONFIG["host_os"] !~ /mswin|mingw/i
|
328
|
+
end
|
329
|
+
|
330
|
+
def normalize(value)
|
331
|
+
value.respond_to?(:entries) ? value.entries.join(@joinon) : value.to_s.strip
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
module Validator #:nodoc: all
|
336
|
+
def self.for(setting)
|
337
|
+
if setting.respond_to?(:validate!)
|
338
|
+
setting
|
339
|
+
else
|
340
|
+
# Load validator based on the setting's name or the name of its class
|
341
|
+
validator = setting.class.name
|
342
|
+
if validator == "Class"
|
343
|
+
name = setting.name.split("::", 2)
|
344
|
+
validator = name[1] if name[1] && name[0] == "Optout"
|
345
|
+
end
|
346
|
+
|
347
|
+
# Support 1.8 and 1.9, avoid String/Symbol and const_defined? differences
|
348
|
+
if !constants.include?(validator) && !constants.include?(validator.to_sym)
|
349
|
+
raise ArgumentError, "don't know how to validate with #{setting}"
|
350
|
+
end
|
351
|
+
|
352
|
+
const_get(validator).new(setting)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
Base = Struct.new :setting
|
357
|
+
|
358
|
+
# Check for multiple values
|
359
|
+
class Multiple < Base
|
360
|
+
def validate!(opt)
|
361
|
+
if !opt.empty? && opt.value.respond_to?(:entries) && opt.value.entries.size > 1 && !multiple_values_allowed?
|
362
|
+
raise OptionInvalid.new(opt.key, "multiple values are not allowed")
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
def multiple_values_allowed?
|
368
|
+
!!setting
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
class Required < Base
|
373
|
+
def validate!(opt)
|
374
|
+
if opt.empty? && option_required?
|
375
|
+
raise OptionRequired, opt.key
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
private
|
380
|
+
def option_required?
|
381
|
+
!!setting
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class Array < Base
|
386
|
+
def validate!(opt)
|
387
|
+
values = [opt.value].flatten
|
388
|
+
values.each do |e|
|
389
|
+
if !setting.include?(e)
|
390
|
+
raise OptionInvalid.new(opt.key, "value '#{e}' must be one of (#{setting.join(", ")})")
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
class Regexp < Base
|
397
|
+
def validate!(opt)
|
398
|
+
if !opt.empty? && opt.value.to_s !~ setting
|
399
|
+
raise OptionInvalid.new(opt.key, "value '#{opt.value}' does not match pattern #{setting}")
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
class Class < Base
|
405
|
+
def validate!(opt)
|
406
|
+
if !(setting === opt.value)
|
407
|
+
raise OptionInvalid.new(opt.key, "value '#{opt.value}' must be type #{setting}")
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
class Boolean < Base
|
413
|
+
def validate!(opt)
|
414
|
+
if !(opt.value == true || opt.value == false || opt.value.nil?)
|
415
|
+
raise OptionInvalid.new(opt.key, "does not accept an argument")
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
class File < Base
|
421
|
+
RULES = %w|under named permissions|;
|
422
|
+
MODES = { "x" => :executable?, "r" => :readable?, "w" => :writable? }
|
423
|
+
|
424
|
+
RULES.each do |r|
|
425
|
+
define_method(r) do |arg|
|
426
|
+
instance_variable_set("@#{r}", arg)
|
427
|
+
self
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def exists(wanted = true)
|
432
|
+
@exists = wanted
|
433
|
+
self
|
434
|
+
end
|
435
|
+
|
436
|
+
def validate!(opt)
|
437
|
+
return if opt.empty?
|
438
|
+
|
439
|
+
@file = Pathname.new(opt.value.to_s)
|
440
|
+
what = self.class.name.split("::")[-1].downcase
|
441
|
+
error = case
|
442
|
+
when !under?
|
443
|
+
"#{what} must be under '#{@under}'"
|
444
|
+
when !named?
|
445
|
+
"#{what} name must match '#{@named}'"
|
446
|
+
when !permissions?
|
447
|
+
"#{what} must have user permission of #{@permissions}"
|
448
|
+
when !exists?
|
449
|
+
"#{what} '#{@file}' does not exist"
|
450
|
+
when !creatable?
|
451
|
+
# TODO: Why can't it be created!?
|
452
|
+
"can't create a #{what} at '#{@file}'"
|
453
|
+
end
|
454
|
+
raise OptionInvalid.new(opt.key, error) if error
|
455
|
+
end
|
456
|
+
|
457
|
+
protected
|
458
|
+
def correct_type?
|
459
|
+
@file.file?
|
460
|
+
end
|
461
|
+
|
462
|
+
def permissions?
|
463
|
+
!@permissions ||
|
464
|
+
exists? &&
|
465
|
+
@permissions.split(//).inject(true) { |can, m| can && MODES[m] && @file.send(MODES[m]) }
|
466
|
+
end
|
467
|
+
|
468
|
+
def exists?
|
469
|
+
!@exists || @file.exist? && correct_type?
|
470
|
+
end
|
471
|
+
|
472
|
+
def named?
|
473
|
+
basename = @file.basename.to_s
|
474
|
+
!@named ||
|
475
|
+
(::Regexp === @named ?
|
476
|
+
basename =~ @named :
|
477
|
+
basename == @named)
|
478
|
+
end
|
479
|
+
|
480
|
+
def under?
|
481
|
+
!@under ||
|
482
|
+
exists? &&
|
483
|
+
(::Regexp === @under ?
|
484
|
+
@file.parent.to_s =~ @under :
|
485
|
+
@file.parent.expand_path.to_s == ::File.expand_path(@under))
|
486
|
+
end
|
487
|
+
|
488
|
+
def creatable?
|
489
|
+
@file.exist? && correct_type? ||
|
490
|
+
!@file.exist? && @file.parent.exist? && @file.parent.writable?
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
class Dir < File
|
495
|
+
protected
|
496
|
+
def correct_type?
|
497
|
+
@file.directory?
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
|
503
|
+
#
|
504
|
+
# These are shortcuts and/or marker classes used by the public interface so Validator.for()
|
505
|
+
# can load the equivalent validation class
|
506
|
+
#
|
507
|
+
|
508
|
+
##
|
509
|
+
# <code>Optout::File</code> is a validaton rule that can be used to check that an option's value is a path to a file.
|
510
|
+
# By default <code>Optout::File</code> *does* *not* *check* that the file exists. Instead, it checks that the file's parent directory
|
511
|
+
# exists. This is done so that you can validate a path that _will_ be created by the program the options are for.
|
512
|
+
# If you _do_ want the file to exist just call the +exists+ method.
|
513
|
+
#
|
514
|
+
# Validation rules can be combined:
|
515
|
+
#
|
516
|
+
# Optout.options do
|
517
|
+
# on :path, "--path", Optout::File.exists.under("/home").named(/\.txt$/)
|
518
|
+
# end
|
519
|
+
#
|
520
|
+
class File
|
521
|
+
class << self
|
522
|
+
Validator::File::RULES.each do |r|
|
523
|
+
define_method(r) { |arg| proxy_for.new.send(r, arg) }
|
524
|
+
end
|
525
|
+
|
526
|
+
##
|
527
|
+
# :singleton-method: under
|
528
|
+
# :call-seq:
|
529
|
+
# under(path)
|
530
|
+
# under(Regexp)
|
531
|
+
#
|
532
|
+
# The option must be under the given path.
|
533
|
+
#
|
534
|
+
# === Parameters
|
535
|
+
#
|
536
|
+
# This can be a +String+ denoting the parent directory or a +Regexp+ to match the parent directory against.
|
537
|
+
|
538
|
+
|
539
|
+
##
|
540
|
+
# :singleton-method: named
|
541
|
+
# :call-seq:
|
542
|
+
# named(basename)
|
543
|
+
# named(Regexp)
|
544
|
+
#
|
545
|
+
# The option's basename must match the given basename.
|
546
|
+
#
|
547
|
+
# === Parameters
|
548
|
+
#
|
549
|
+
# A +String+ denoting the basename or a +Regexp+ to match the basename against.
|
550
|
+
|
551
|
+
|
552
|
+
##
|
553
|
+
# :singleton-method: permissions
|
554
|
+
# :call-seq:
|
555
|
+
# permissions(symbolic_mode)
|
556
|
+
#
|
557
|
+
# The option's user permissions must match the given permission(s).
|
558
|
+
#
|
559
|
+
# === Parameters
|
560
|
+
#
|
561
|
+
# A +String+ denoting the desired permission. Any combination of <code>"r"</code>, <code>"w"</code> and <code>"x"</code> is supported.
|
562
|
+
|
563
|
+
##
|
564
|
+
#
|
565
|
+
# If +wanted+ is true the file must exist.
|
566
|
+
#
|
567
|
+
def exists(wanted = true)
|
568
|
+
proxy_for.new.exists(wanted)
|
569
|
+
end
|
570
|
+
|
571
|
+
def proxy_for #:nodoc:
|
572
|
+
Validator::File
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
##
|
578
|
+
# <code>Optout::Dir</code> is a validaton rule that can be used to check that an option's value is a path to a directory.
|
579
|
+
# Validation rules can be combined:
|
580
|
+
#
|
581
|
+
# Optout.options do
|
582
|
+
# on :path, "--path", Optout::Dir.exists.under("/tmp").named(/\d$/)
|
583
|
+
# end
|
584
|
+
#
|
585
|
+
# See Optout::File for a list of methods.
|
586
|
+
#
|
587
|
+
class Dir < File
|
588
|
+
def self.proxy_for #:nodoc:
|
589
|
+
Validator::Dir
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
class Boolean #:nodoc:
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
data/spec/optout_spec.rb
ADDED
@@ -0,0 +1,380 @@
|
|
1
|
+
require "optout"
|
2
|
+
require "tempfile"
|
3
|
+
require "fileutils"
|
4
|
+
require "rbconfig"
|
5
|
+
|
6
|
+
def create_optout(options = {})
|
7
|
+
Optout.options(options) do
|
8
|
+
on :x, "-x"
|
9
|
+
on :y, "-y"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def optout_option(*options)
|
14
|
+
Optout.options { on :x, "-x", *options }
|
15
|
+
end
|
16
|
+
|
17
|
+
class Optout
|
18
|
+
class Option
|
19
|
+
def unix?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
shared_examples_for "something that validates files" do
|
26
|
+
before(:all) { @tmpdir = Dir.mktmpdir }
|
27
|
+
after(:all) { FileUtils.rm_rf(@tmpdir) }
|
28
|
+
|
29
|
+
def options
|
30
|
+
{ :x => @file }
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should not raise an exception if a file does not exist but its directory does" do
|
34
|
+
file = File.join(@tmpdir, "__bad__")
|
35
|
+
optout = optout_option(@validator)
|
36
|
+
proc { optout.argv(:x => file) }.should_not raise_exception
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "permissions" do
|
40
|
+
# Only some chmod() modes work on Win
|
41
|
+
if RbConfig::CONFIG["host_os"] !~ /mswin|mingw/i
|
42
|
+
it "should raise an exception when user permissions don't match" do
|
43
|
+
FileUtils.chmod(0100, @file)
|
44
|
+
optout = optout_option(@validator.permissions("r"))
|
45
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionInvalid, /user permission/)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not raise an exception when user permissions match" do
|
49
|
+
checker = proc do |validator|
|
50
|
+
proc { optout_option(validator).argv(options) }.should_not raise_exception
|
51
|
+
end
|
52
|
+
|
53
|
+
FileUtils.chmod(0100, @file)
|
54
|
+
checker.call(@validator.permissions("x"))
|
55
|
+
|
56
|
+
FileUtils.chmod(0200, @file)
|
57
|
+
checker.call(@validator.permissions("w"))
|
58
|
+
|
59
|
+
FileUtils.chmod(0400, @file)
|
60
|
+
checker.call(@validator.permissions("r"))
|
61
|
+
|
62
|
+
FileUtils.chmod(0700, @file)
|
63
|
+
checker.call(@validator.permissions("rwx"))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "exists" do
|
69
|
+
it "should raise an exception if the file does not exist" do
|
70
|
+
optout = optout_option(@validator.exists)
|
71
|
+
proc { optout.argv(:x => @file + "no_file") }.should raise_exception(Optout::OptionInvalid, /does not exist/)
|
72
|
+
proc { optout.argv(options) }.should_not raise_exception
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "under a directory" do
|
77
|
+
it "should raise an exception if not under the given directory" do
|
78
|
+
optout = optout_option(@validator.under(File.join("wrong", "path")))
|
79
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionInvalid, /must be under/)
|
80
|
+
|
81
|
+
optout = optout_option(@validator.under(@tmpdir))
|
82
|
+
proc { optout.argv(options) }.should_not raise_exception
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should raise an exception if the parent directory does not match the given pattern" do
|
86
|
+
# We need to respect the @file's type to ensure other validation rules implicitly applied by the @validator pass.
|
87
|
+
# First create parent dirs to validate against
|
88
|
+
tmp = File.join(@tmpdir, "a1", "b1")
|
89
|
+
FileUtils.mkdir_p(tmp)
|
90
|
+
|
91
|
+
# Then copy the target of the validation (file or directory) under the parent dir.
|
92
|
+
FileUtils.cp_r(@file, tmp)
|
93
|
+
|
94
|
+
# And create the option's value
|
95
|
+
tmp = File.join(tmp, File.basename(@file))
|
96
|
+
options = { :x => tmp }
|
97
|
+
|
98
|
+
optout = optout_option(@validator.under(/X$/))
|
99
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionInvalid, /must be under/)
|
100
|
+
|
101
|
+
[ %r|(/[a-z]\d){2}|, %r|[a-z]\d$| ].each do |r|
|
102
|
+
optout = optout_option(@validator.under(r))
|
103
|
+
proc { optout.argv(options) }.should_not raise_exception
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "basename" do
|
109
|
+
it "should raise an exception if it does not equal the given value" do
|
110
|
+
optout = optout_option(@validator.named("__bad__"))
|
111
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionInvalid, /name must match/)
|
112
|
+
|
113
|
+
optout = optout_option(@validator.named(File.basename(@file)))
|
114
|
+
proc { optout.argv(options) }.should_not raise_exception
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should raise an exception if it does not match the given pattern" do
|
118
|
+
optout = optout_option(@validator.named(/\A-_-_-_/))
|
119
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionInvalid, /name must match/)
|
120
|
+
|
121
|
+
ends_with = File.basename(@file)[/.{2}\z/]
|
122
|
+
optout = optout_option(@validator.named(/#{Regexp.quote(ends_with)}\z/))
|
123
|
+
proc { optout.argv(options) }.should_not raise_exception
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe Optout do
|
129
|
+
describe "defining options" do
|
130
|
+
before(:each) { @optout = Optout.new }
|
131
|
+
|
132
|
+
it "should require the option's key" do
|
133
|
+
proc { @optout.on }.should raise_exception(ArgumentError, /option key required/)
|
134
|
+
proc { Optout.options { on } }.should raise_exception(ArgumentError, /option key required/)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should not allow an option to be defined twice" do
|
138
|
+
@optout.on :x
|
139
|
+
proc { @optout.on :x }.should raise_exception(ArgumentError, /already defined/)
|
140
|
+
proc do
|
141
|
+
Optout.options do
|
142
|
+
on :x
|
143
|
+
on :x
|
144
|
+
end
|
145
|
+
end.should raise_exception(ArgumentError, /already defined/)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "creating options" do
|
150
|
+
before(:each) { @optout = create_optout }
|
151
|
+
|
152
|
+
context "as a string" do
|
153
|
+
it "should only output the option's value if there's no switch" do
|
154
|
+
optout = Optout.options { on :x }
|
155
|
+
optout.shell(:x => "x").should eql("'x'")
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should output an empty string if the option hash is empty" do
|
159
|
+
@optout.shell({}).should be_empty
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should only output the option's switch if its value if true" do
|
163
|
+
@optout.shell(:x => true, :y => true).should eql("-x -y")
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should not output the option if its value is false" do
|
167
|
+
@optout.shell(:x => false, :y => true).should eql("-y")
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should only output the options that have a value" do
|
171
|
+
@optout.shell(:x => "x", :y => nil).should eql("-x 'x'")
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should output all of the options" do
|
175
|
+
@optout.shell(:x => "x", :y => "y").should eql("-x 'x' -y 'y'")
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should escape the single quote char" do
|
179
|
+
@optout.shell(:x => "' a'b'c '").should eql(%q|-x ''\'' a'\''b'\''c '\'''|)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should not separate switches from their value" do
|
183
|
+
optout = create_optout(:arg_separator => "")
|
184
|
+
optout.shell(:x => "x", :y => "y").should eql("-x'x' -y'y'")
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should seperate all switches from their value with a '='" do
|
188
|
+
optout = create_optout(:arg_separator => "=")
|
189
|
+
optout.shell(:x => "x", :y => "y").should eql("-x='x' -y='y'")
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should join all options with multiple values on a delimiter" do
|
193
|
+
optout = create_optout(:multiple => true)
|
194
|
+
optout.shell(:x => %w|a b c|, :y => "y").should eql("-x 'a,b,c' -y 'y'")
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should join all options with multiple values on a ':'" do
|
198
|
+
optout = create_optout(:multiple => ":")
|
199
|
+
optout.shell(:x => %w|a b c|, :y => "y").should eql("-x 'a:b:c' -y 'y'")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "as an array" do
|
204
|
+
it "should only output the option's value if there's no switch" do
|
205
|
+
optout = Optout.options { on :x }
|
206
|
+
optout.argv(:x => "x").should eql(["x"])
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should output an empty array if the option hash is empty" do
|
210
|
+
@optout.argv({}).should be_empty
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should only output the option's switch if its value if true" do
|
214
|
+
@optout.argv(:x => true, :y => true).should eql(["-x", "-y"])
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should not output the option if its value is false" do
|
218
|
+
@optout.argv(:x => false, :y => true).should eql(["-y"])
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should only output the options that have a value" do
|
222
|
+
@optout.argv(:x => "x", :y => nil).should eql(["-x", "x"])
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should output all of the options" do
|
226
|
+
@optout.argv(:x => "x", :y => "y").should eql(["-x", "x", "-y", "y"])
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should not escape the single quote char" do
|
230
|
+
@optout.argv(:x => "' a'b'c '").should eql(["-x", "' a'b'c '"])
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should not separate switches from their value" do
|
234
|
+
optout = create_optout(:arg_separator => "")
|
235
|
+
optout.argv(:x => "x", :y => "y").should eql(["-xx", "-yy"])
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should seperate all of switches from their value with a '='" do
|
239
|
+
optout = create_optout(:arg_separator => "=")
|
240
|
+
optout.argv(:x => "x", :y => "y").should eql(["-x=x", "-y=y"])
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should join all options with multiple values on a delimiter" do
|
244
|
+
optout = create_optout(:multiple => true)
|
245
|
+
optout.argv(:x => %w|a b c|, :y => "y").should eql(["-x", "a,b,c", "-y", "y"])
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should join all options with multiple values on a ':'" do
|
249
|
+
optout = create_optout(:multiple => ":")
|
250
|
+
optout.argv(:x => %w|a b c|, :y => "y").should eql(["-x", "a:b:c", "-y", "y"])
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# TODO: Check exception.key
|
256
|
+
describe "validation rules" do
|
257
|
+
it "should raise an exception if the option hash contains an unknown key" do
|
258
|
+
optout = create_optout
|
259
|
+
proc { optout.argv(:bad => 123) }.should raise_exception(Optout::OptionUnknown)
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should not raise an exception if the option hash contains an unknown key" do
|
263
|
+
optout = create_optout(:check_keys => false)
|
264
|
+
proc { optout.argv(:bad => 123) }.should_not raise_exception
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should raise an exception if an option is missing" do
|
268
|
+
optout = create_optout(:required => true)
|
269
|
+
proc { optout.argv(:x => 123) }.should raise_exception(Optout::OptionRequired, /'y'/)
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should raise an exception if a required option is missing" do
|
273
|
+
optout = Optout.options do
|
274
|
+
on :x
|
275
|
+
on :y, :required => true
|
276
|
+
end
|
277
|
+
|
278
|
+
[ { :x => 123 }, { :x => 123, :y => false } ].each do |options|
|
279
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionRequired, /'y'/)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should raise an exception if any option contains multiple values" do
|
284
|
+
optout = create_optout(:multiple => false)
|
285
|
+
|
286
|
+
[ { :x => 123, :y => %w|a b c| },
|
287
|
+
{ :x => 123, :y => { :a => "b", :b => "c" }} ].each do |options|
|
288
|
+
proc { optout.argv(options) }.should raise_exception(Optout::OptionInvalid)
|
289
|
+
end
|
290
|
+
|
291
|
+
# An Array with 1 value is OK
|
292
|
+
proc { optout.argv(:x => 123, :y => %w|a|) }.should_not raise_exception(Optout::OptionInvalid)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should raise an exception if a single value option contains multiple values" do
|
296
|
+
optout = Optout.options do
|
297
|
+
on :x
|
298
|
+
on :y, :multiple => false
|
299
|
+
end
|
300
|
+
|
301
|
+
proc { optout.argv(:x => "x", :y => %w|a b c|) }.should raise_exception(Optout::OptionInvalid, /\by\b/)
|
302
|
+
end
|
303
|
+
|
304
|
+
it "should check the option's type" do
|
305
|
+
optout = optout_option(Float)
|
306
|
+
proc { optout.argv(:x => 123) }.should raise_exception(Optout::OptionInvalid, /type Float/)
|
307
|
+
proc { optout.argv(:x => 123.0) }.should_not raise_exception(Optout::OptionInvalid)
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should raise an exception if the option's value is not in the given set" do
|
311
|
+
optout = optout_option(%w|sshaw skye|, :multiple => true)
|
312
|
+
|
313
|
+
[ "bob", [ "jack", "jill" ] ].each do |v|
|
314
|
+
proc { optout.argv(:x => v) }.should raise_exception(Optout::OptionInvalid)
|
315
|
+
end
|
316
|
+
|
317
|
+
[ "sshaw", [ "sshaw", "skye" ] ].each do |v|
|
318
|
+
proc { optout.argv(:x => v) }.should_not raise_exception
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
it "should raise an exception if the option's value does not match the given pattern" do
|
323
|
+
optout = optout_option(/X\d{2}/)
|
324
|
+
proc { optout.argv(:x => "X7") }.should raise_exception(Optout::OptionInvalid, /match pattern/)
|
325
|
+
proc { optout.argv(:x => "X21") }.should_not raise_exception
|
326
|
+
end
|
327
|
+
|
328
|
+
it "should raise an exception if the option has a non-boolean value" do
|
329
|
+
optout = optout_option(Optout::Boolean)
|
330
|
+
proc { optout.argv(:x => "x") }.should raise_exception(Optout::OptionInvalid, /does not accept/)
|
331
|
+
[ false, true, nil ].each do |v|
|
332
|
+
proc { optout.argv(:x => v) }.should_not raise_exception
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
it "should call a custom validator" do
|
337
|
+
klass = Class.new do
|
338
|
+
def validate!(opt)
|
339
|
+
raise "raise up!"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
optout = optout_option(klass.new)
|
344
|
+
proc { optout.argv(:x => "x") }.should raise_exception(RuntimeError, "raise up!")
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should raise an exception if an unknown validation rule is used" do
|
348
|
+
optout = optout_option("whaaaaa")
|
349
|
+
proc { optout.argv(:x => "x") }.should raise_exception(ArgumentError, /don't know how to validate/)
|
350
|
+
end
|
351
|
+
|
352
|
+
context "when validating a file" do
|
353
|
+
it_should_behave_like "something that validates files"
|
354
|
+
|
355
|
+
before(:all) do
|
356
|
+
@file = Tempfile.new("", @tmpdir).path
|
357
|
+
@validator = Optout::File
|
358
|
+
end
|
359
|
+
|
360
|
+
it "should raise an exception if it's not a file" do
|
361
|
+
optout = optout_option(@validator)
|
362
|
+
proc { optout.argv(:x => @tmpdir) }.should raise_exception(Optout::OptionInvalid, /can't create a file/)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context "when validating a directory" do
|
367
|
+
it_should_behave_like "something that validates files"
|
368
|
+
|
369
|
+
before(:all) do
|
370
|
+
@file = Dir.mktmpdir(nil, @tmpdir)
|
371
|
+
@validator = Optout::Dir
|
372
|
+
end
|
373
|
+
|
374
|
+
it "should raise an exception if it's not a directory" do
|
375
|
+
optout = optout_option(@validator)
|
376
|
+
proc { optout.argv(:x => Tempfile.new("", @tmpdir).path) }.should raise_exception(Optout::OptionInvalid)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: optout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Skye Shaw
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-01-04 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 59
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 9
|
32
|
+
- 0
|
33
|
+
version: 0.9.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 27
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
version: 1.3.0
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
description: " Optout helps you write code that will call exec() and system() like functions. It allows you to map hash keys to command line \n arguments and define validation rules that must be me before the command line options are created. \n"
|
53
|
+
email: sshaw@lucas.cis.temple.edu
|
54
|
+
executables: []
|
55
|
+
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files:
|
59
|
+
- README.rdoc
|
60
|
+
files:
|
61
|
+
- lib/optout.rb
|
62
|
+
- spec/optout_spec.rb
|
63
|
+
- README.rdoc
|
64
|
+
homepage: http://github.com/sshaw/optout
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.10
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: "The opposite of getopt(): validate an option hash and turn it into something appropriate for exec() and system() like functions"
|
97
|
+
test_files:
|
98
|
+
- spec/optout_spec.rb
|