choice 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ 0.1.2:
2
+ - Made validate directive accept block and validate against its boolean value.
3
+ - Created shorthand format for defining options directly with a hash.
4
+
1
5
  0.1.1:
2
6
  - Fixed test_option.rb to be self sufficient.
3
7
  - Fix so that long argument with equals sign in it will parse correctly [Justin Bailey]
data/README CHANGED
@@ -5,9 +5,8 @@ works awesomely with Highline[http://highline.rubyforge.org/] or other command
5
5
  line interface libraries.
6
6
 
7
7
  Choice was written by Chris Wanstrath as an exercise in test driving development
8
- of a DSL. We hope you find it useful. If you don't, we'd love to hear why.
9
- This project is still an infant: bugs are expected and tattling on them is
10
- appreciated.
8
+ of a DSL. This project is still an infant: bugs are expected and tattling on them
9
+ is appreciated.
11
10
 
12
11
  Installing is easy, with RubyGems. Give it a shot:
13
12
  $ gem install choice
@@ -400,6 +399,25 @@ here, works like the other options above.
400
399
 
401
400
  footer "That's all there is to it!"
402
401
 
402
+ == Shorthand
403
+
404
+ Now that you've gone through all the hard stuff, here's the easy stuff: Choice
405
+ options can be defined with a simple hash if you'd like. Here's an example,
406
+ from the tests:
407
+
408
+ Choice.options do
409
+ header "Tell me about yourself?"
410
+ header ""
411
+ options :band => { :short => "-b", :long => "--band=BAND", :cast => String, :desc => "Your favorite band.",
412
+ :validate => /\w+/ },
413
+ :animal => { :short => "-a", :long => "--animal=ANIMAL", :cast => String, :desc => "Your favorite animal." }
414
+
415
+ footer ""
416
+ footer "--help This message"
417
+ end
418
+
419
+ How's that tickle you? Real nice.
420
+
403
421
  == It looks like poetry
404
422
 
405
423
  That's it. Not much, I know. Maybe this will make handling your command
@@ -8,122 +8,132 @@ require 'choice/lazyhash'
8
8
  # Usage of this module is lovingly detailed in the README file.
9
9
  #
10
10
  module Choice
11
- class <<self
12
- # The main method, which defines the options
13
- def options(&block)
14
- # Setup all instance variables
15
- @@args ||= ARGV
16
- @@banner ||= false
17
- @@header ||= Array.new
18
- @@options ||= Array.new
19
- @@footer ||= Array.new
20
-
21
- # Eval the passed block to define the options.
22
- instance_eval(&block)
11
+ extend self
23
12
 
24
- # Parse what we've got.
25
- parse unless parsed?
13
+ # The main method, which defines the options
14
+ def options(hash = {}, &block)
15
+ # if we are passing in a hash to define our options, use that straight
16
+ options_from_hash(hash) unless hash.empty?
17
+
18
+ # Setup all instance variables
19
+ reset! if hash.empty?
20
+ @@args ||= ARGV
21
+
22
+ # Eval the passed block to define the options.
23
+ instance_eval(&block) if block_given?
24
+
25
+ # Parse what we've got.
26
+ parse unless parsed?
27
+ end
28
+
29
+ # Set options from a hash, shorthand style
30
+ def options_from_hash(options_hash)
31
+ options_hash.each do |name, definition|
32
+ option = Option.new
33
+ definition.each do |key, value|
34
+ Array(value).each { |hit| option.send(key, hit) }
35
+ end
36
+ @@options << [name.to_s, option]
26
37
  end
38
+ end
39
+
40
+ # Returns a hash representing options passed in via the command line.
41
+ def choices
42
+ @@choices
43
+ end
44
+
45
+ # Defines an option.
46
+ def option(opt, options = {}, &block)
47
+ # Notice: options is maintained as an array of arrays, the first element
48
+ # the option name and the second the option object.
49
+ @@options << [opt.to_s, Option.new(options, &block)]
50
+ end
27
51
 
28
- # Returns a hash representing options passed in via the command line.
29
- def choices
30
- @@choices
31
- end
52
+ # Separators are text displayed by --help within the options block.
53
+ def separator(str)
54
+ # We store separators as simple strings in the options array to maintain
55
+ # order. They are ignored by the parser.
56
+ @@options << str
57
+ end
32
58
 
33
- # Defines an option.
34
- def option(opt, options = {}, &block)
35
- # Notice: options is maintained as an array of arrays, the first element
36
- # the option name and the second the option object.
37
- @@options << [opt.to_s, Option.new(options, &block)]
38
- end
39
-
40
- # Separators are text displayed by --help within the options block.
41
- def separator(str)
42
- # We store separators as simple strings in the options array to maintain
43
- # order. They are ignored by the parser.
44
- @@options << str
45
- end
46
-
47
- # Define the banner, header, footer methods. All are just getters/setters
48
- # of class variables.
49
- %w[banner header footer].each do |method|
50
- define_method(method) do |string|
51
- variable = "@@#{method}"
52
- return class_variable_get(variable) if string.nil?
53
- val = class_variable_get(variable) || ''
54
- class_variable_set(variable, val << string)
55
- end
59
+ # Define the banner, header, footer methods. All are just getters/setters
60
+ # of class variables.
61
+ %w[banner header footer].each do |method|
62
+ define_method(method) do |string|
63
+ variable = "@@#{method}"
64
+ return class_variable_get(variable) if string.nil?
65
+ val = class_variable_get(variable) || ''
66
+ class_variable_set(variable, val << string)
56
67
  end
68
+ end
57
69
 
58
-
59
- # Parse the provided args against the defined options.
60
- def parse #:nodoc:
61
- # Do nothing if options are not defined.
62
- return unless @@options.size > 0
70
+
71
+ # Parse the provided args against the defined options.
72
+ def parse #:nodoc:
73
+ # Do nothing if options are not defined.
74
+ return unless @@options.size > 0
63
75
 
64
- # Show help if it's anywhere in the argument list.
65
- if @@args.include?('--help')
66
- self.help
67
- else
68
- begin
69
- # Delegate parsing to our parser class, passing it our defined
70
- # options and the passed arguments.
71
- @@choices = LazyHash.new(Parser.parse(@@options, @@args))
72
- rescue Choice::Parser::ParseError
73
- # If we get an expected exception, show the help file.
74
- self.help
75
- end
76
+ # Show help if it's anywhere in the argument list.
77
+ if @@args.include?('--help')
78
+ help
79
+ else
80
+ begin
81
+ # Delegate parsing to our parser class, passing it our defined
82
+ # options and the passed arguments.
83
+ @@choices = LazyHash.new(Parser.parse(@@options, @@args))
84
+ rescue Choice::Parser::ParseError
85
+ # If we get an expected exception, show the help file.
86
+ help
76
87
  end
77
88
  end
78
-
79
- # Did we already parse the arguments?
80
- def parsed? #:nodoc:
81
- @@choices ||= false
82
- end
83
-
84
- # Print the help screen by calling our Writer object
85
- def help #:nodoc:
86
- Writer.help( { :banner => @@banner, :header => @@header,
87
- :options => @@options, :footer => @@footer },
88
- output_to, exit_on_help? )
89
- end
90
-
91
- # Set the args, potentially to something other than ARGV.
92
- def args=(args) #:nodoc:
93
- @@args = args.dup.map { |a| a + '' }
94
- parse if parsed?
95
- end
89
+ end
96
90
 
97
- # Return the args.
98
- def args #:nodoc:
99
- @@args
100
- end
101
-
102
- # You can choose to not kill the script after the help screen is printed.
103
- def dont_exit_on_help=(val) #:nodoc:
104
- @@exit = true
105
- end
106
-
107
- # Do we want to exit on help?
108
- def exit_on_help? #:nodoc:
109
- @@exit rescue false
110
- end
111
-
112
- # If we want to write to somewhere other than STDOUT.
113
- def output_to(target = nil) #:nodoc:
114
- @@output_to ||= STDOUT
115
- return @@output_to if target.nil?
116
- @@output_to = target
117
- end
118
-
119
- # Reset all the class variables.
120
- def reset #:nodoc:
121
- @@args = false
122
- @@banner = false
123
- @@header = Array.new
124
- @@options = Array.new
125
- @@footer = Array.new
126
- end
91
+ # Did we already parse the arguments?
92
+ def parsed? #:nodoc:
93
+ @@choices ||= false
127
94
  end
128
95
 
96
+ # Print the help screen by calling our Writer object
97
+ def help #:nodoc:
98
+ Writer.help( { :banner => @@banner, :header => @@header,
99
+ :options => @@options, :footer => @@footer },
100
+ output_to, exit_on_help? )
101
+ end
102
+
103
+ # Set the args, potentially to something other than ARGV.
104
+ def args=(args) #:nodoc:
105
+ @@args = args.dup.map { |a| a + '' }
106
+ parse if parsed?
107
+ end
108
+
109
+ # Return the args.
110
+ def args #:nodoc:
111
+ @@args
112
+ end
113
+
114
+ # You can choose to not kill the script after the help screen is printed.
115
+ def dont_exit_on_help=(val) #:nodoc:
116
+ @@exit = true
117
+ end
118
+
119
+ # Do we want to exit on help?
120
+ def exit_on_help? #:nodoc:
121
+ @@exit rescue false
122
+ end
123
+
124
+ # If we want to write to somewhere other than STDOUT.
125
+ def output_to(target = nil) #:nodoc:
126
+ @@output_to ||= STDOUT
127
+ return @@output_to if target.nil?
128
+ @@output_to = target
129
+ end
130
+
131
+ # Reset all the class variables.
132
+ def reset! #:nodoc:
133
+ @@args = false
134
+ @@banner = false
135
+ @@header = Array.new
136
+ @@options = Array.new
137
+ @@footer = Array.new
138
+ end
129
139
  end
@@ -17,7 +17,7 @@ module Choice
17
17
  @choices = []
18
18
 
19
19
  # If we got a block, eval it and set everything up.
20
- self.instance_eval(&block) if block_given?
20
+ instance_eval(&block) if block_given?
21
21
 
22
22
  # Is this option required?
23
23
  @required = options[:required] || false
@@ -28,17 +28,16 @@ module Choice
28
28
  # It also gives us choice? methods.
29
29
  def method_missing(method, *args, &block)
30
30
  # Get the name of the choice we want, as a class variable string.
31
- var = "@#{method.to_s.sub(/\?/,'')}"
31
+ var = "@#{method.to_s.sub('?','')}"
32
32
 
33
33
  # To string, for regex purposes.
34
34
  method = method.to_s
35
35
 
36
36
  # Don't let in any choices not defined in our white list array.
37
- raise ParseError, "I don't know '#{method}'" unless CHOICES.include? method.sub(/\?/,'')
37
+ raise ParseError, "I don't know `#{method}'" unless CHOICES.include? method.sub('?','')
38
38
 
39
39
  # If we're asking a question, give an answer. Like 'short?'.
40
- return true if method =~ /\?/ && instance_variable_get(var)
41
- return false if method =~ /\?/
40
+ return !!instance_variable_get(var) if method =~ /\?/
42
41
 
43
42
  # If we were called with no arguments, we want a get.
44
43
  return instance_variable_get(var) unless args[0] || block_given?
@@ -66,27 +65,22 @@ module Choice
66
65
  end
67
66
 
68
67
  # Simple, desc question method.
69
- def desc?
70
- return false if @desc.nil?
71
- true
72
- end
68
+ def desc?() !!@desc end
73
69
 
74
70
  # Returns Option converted to an array.
75
71
  def to_a
76
- array = []
77
- @choices.each do |choice|
78
- array << instance_variable_get("@#{choice}") if @choices.include? choice
72
+ @choices.inject([]) do |array, choice|
73
+ return array unless @choices.include? choice
74
+ array + [instance_variable_get("@#{choice}")]
79
75
  end
80
- array
81
76
  end
82
77
 
83
78
  # Returns Option converted to a hash.
84
79
  def to_h
85
- hash = {}
86
- @choices.each do |choice|
87
- hash[choice] = instance_variable_get("@#{choice}") if @choices.include? choice
80
+ @choices.inject({}) do |hash, choice|
81
+ return hash unless @choices.include? choice
82
+ hash.merge choice => instance_variable_get("@#{choice}")
88
83
  end
89
- hash
90
84
  end
91
85
 
92
86
  # In case someone tries to use a method we don't know about in their
@@ -3,6 +3,7 @@ module Choice
3
3
  # The parser takes our option definitions and our arguments and produces
4
4
  # a hash of values.
5
5
  module Parser #:nodoc: all
6
+ extend self
6
7
 
7
8
  # What method to call on an object for each given 'cast' value.
8
9
  CAST_METHODS = { Integer => :to_i, String => :to_s, Float => :to_f,
@@ -13,17 +14,15 @@ module Choice
13
14
  # the option's name and the second element being a hash of the option's
14
15
  # info. You also pass in your current arguments, so it knows what to
15
16
  # check against.
16
- def self.parse(options, args)
17
+ def parse(options, args)
17
18
  # Return empty hash if the parsing adventure would be fruitless.
18
19
  return {} if options.nil? || !options || args.nil? || !args.is_a?(Array)
19
20
 
20
21
  # If we are passed an array, make the best of it by converting it
21
22
  # to a hash.
22
- if options.is_a?(Array)
23
- new_options = {}
24
- options.each { |o| new_options[o[0]] = o[1] if o.is_a?(Array) }
25
- options = new_options
26
- end
23
+ options = options.inject({}) do |hash, value|
24
+ value.is_a?(Array) ? hash.merge(value.first => value[1]) : hash
25
+ end if options.is_a? Array
27
26
 
28
27
  # Define local hashes we're going to use. choices is where we store
29
28
  # the actual values we've pulled from the argument list.
@@ -39,11 +38,8 @@ module Choice
39
38
  name = name.to_s
40
39
 
41
40
  # Only take hashes or hash-like duck objects.
42
- if obj.respond_to?(:to_h)
43
- obj = obj.to_h
44
- else
45
- raise HashExpectedForOption
46
- end
41
+ raise HashExpectedForOption unless obj.respond_to? :to_h
42
+ obj = obj.to_h
47
43
 
48
44
  # Is this option required?
49
45
  hard_required[name] = true if obj['required']
@@ -53,11 +49,11 @@ module Choice
53
49
 
54
50
  # If there is a validate statement, save it as a regex.
55
51
  # If it's present but can't pull off a to_s (wtf?), raise an error.
56
- if obj['validate'] && obj['validate'].respond_to?(:to_s)
57
- validators[name] = Regexp.new(obj['validate'].to_s)
58
- elsif obj['validate']
59
- raise ValidateExpectsRegexp
60
- end
52
+ validators[name] = case obj['validate']
53
+ when Proc then obj['validate']
54
+ when Regexp, String then Regexp.new(obj['validate'].to_s)
55
+ else raise ValidateExpectsRegexpOrBlock
56
+ end if obj['validate']
61
57
 
62
58
  # Parse the long option. If it contains a =, figure out if the
63
59
  # argument is required or optional. Optional arguments are formed
@@ -149,7 +145,6 @@ module Choice
149
145
  else
150
146
  # If we're here, we have no idea what the passed argument is. Die.
151
147
  raise UnknownOption if arg =~ /^-/
152
-
153
148
  end
154
149
  end
155
150
 
@@ -159,8 +154,13 @@ module Choice
159
154
  # Check to make sure we have all the required arguments.
160
155
  raise ArgumentRequired if required[name] && value === true
161
156
 
162
- # Validate the argument if we need to.
163
- raise ArgumentValidationFails if validators[name] && validators[name] !~ value
157
+ # Validate the argument if we need to, against a regexp or a block.
158
+ if validators[name]
159
+ if validators[name].is_a?(Regexp) && validators[name] =~ value
160
+ elsif validators[name].is_a?(Proc) && validators[name].call(value)
161
+ else raise ArgumentValidationFails
162
+ end
163
+ end
164
164
 
165
165
  # Make sure the argument is valid
166
166
  raise InvalidArgument unless value.to_a.all? { |v| hashes['valids'][name].include?(v) } if hashes['valids'][name]
@@ -197,7 +197,7 @@ module Choice
197
197
 
198
198
  private
199
199
  # Turns trailing command line arguments into an array for an arrayed value
200
- def self.arrayize_arguments(name, args)
200
+ def arrayize_arguments(name, args)
201
201
  # Go through trailing arguments and suck them in if they don't seem
202
202
  # to have an owner.
203
203
  array = []
@@ -213,7 +213,7 @@ module Choice
213
213
  class HashExpectedForOption < Exception; end
214
214
  class UnknownOption < ParseError; end
215
215
  class ArgumentRequired < ParseError; end
216
- class ValidateExpectsRegexp < ParseError; end
216
+ class ValidateExpectsRegexpOrBlock < ParseError; end
217
217
  class ArgumentValidationFails < ParseError; end
218
218
  class InvalidArgument < ParseError; end
219
219
  class ArgumentRequiredWithValid < ParseError; end
@@ -2,7 +2,7 @@ module Choice
2
2
  module Version #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- TINY = 1
5
+ TINY = 2
6
6
  STRING = [MAJOR, MINOR, TINY] * '.'
7
7
  end
8
8
  end
@@ -7,7 +7,7 @@ $VERBOSE = nil
7
7
  class TestChoice < Test::Unit::TestCase
8
8
 
9
9
  def setup
10
- Choice.reset
10
+ Choice.reset!
11
11
  Choice.dont_exit_on_help = true
12
12
  Choice.send(:class_variable_set, '@@choices', true)
13
13
  end
@@ -156,4 +156,28 @@ HELP
156
156
 
157
157
  assert_equal help_string, REQUIRED_STRING
158
158
  end
159
+
160
+ def test_shorthand_choices
161
+ Choice.options do
162
+ header "Tell me about yourself?"
163
+ header ""
164
+ options :band => { :short => "-b", :long => "--band=BAND", :cast => String, :desc => ["Your favorite band.", "Something cool."],
165
+ :validate => /\w+/ },
166
+ :animal => { :short => "-a", :long => "--animal=ANIMAL", :cast => String, :desc => "Your favorite animal." }
167
+
168
+ footer ""
169
+ footer "--help This message"
170
+ end
171
+
172
+ band = 'LedZeppelin'
173
+ animal = 'Reindeer'
174
+
175
+ args = ['-b', band, "--animal=#{animal}"]
176
+ Choice.args = args
177
+
178
+ assert_equal band, Choice.choices['band']
179
+ assert_equal animal, Choice.choices[:animal]
180
+ assert_equal ["Tell me about yourself?", ""], Choice.header
181
+ assert_equal ["", "--help This message"], Choice.footer
182
+ end
159
183
  end
@@ -5,7 +5,7 @@ require 'choice/option'
5
5
 
6
6
  class TestOption < Test::Unit::TestCase
7
7
  def setup
8
- Choice.reset
8
+ Choice.reset!
9
9
  @option = Choice::Option.new
10
10
  end
11
11
 
@@ -72,6 +72,13 @@ class TestOption < Test::Unit::TestCase
72
72
  end
73
73
 
74
74
  assert_equal /^\w+$/, @option.validate
75
+
76
+ block = proc { |f| File.exists? f }
77
+ @option = Choice::Option.new do
78
+ validate &block
79
+ end
80
+
81
+ assert_equal block, @option.validate
75
82
  end
76
83
 
77
84
  def test_dsl
@@ -108,10 +115,10 @@ class TestOption < Test::Unit::TestCase
108
115
 
109
116
  assert_equal Choice::Option, @option.class
110
117
  assert_equal Array, array.class
111
- assert array.include?([desc])
112
- assert array.include?(short)
113
- assert array.include?(long)
114
- assert array.include?(default)
118
+ assert array.include? [desc]
119
+ assert array.include? short
120
+ assert array.include? long
121
+ assert array.include? default
115
122
  assert_equal proc { 1 + 1 }.call, array.select { |a| a.is_a? Proc }.first.call
116
123
  end
117
124
 
@@ -163,7 +163,7 @@ class TestParser < Test::Unit::TestCase
163
163
  assert choices['chunky']
164
164
  end
165
165
 
166
- def test_validate
166
+ def test_validate_regexp
167
167
  @options['email'] = Choice::Option.new do
168
168
  short '-e'
169
169
  long '--email=EMAIL'
@@ -185,6 +185,30 @@ class TestParser < Test::Unit::TestCase
185
185
  assert_equal email_good, choices['email']
186
186
  end
187
187
 
188
+ def test_validate_block
189
+ @options['file'] = Choice::Option.new do
190
+ short '-f'
191
+ long '--file=FILE'
192
+ desc 'Your valid email addy.'
193
+ validate do |arg|
194
+ File.exists? arg
195
+ end
196
+ end
197
+
198
+ file_bad = 'not_a_file.rb'
199
+ file_good = __FILE__
200
+
201
+ args = ['-f', file_bad]
202
+ assert_raise(Choice::Parser::ArgumentValidationFails) do
203
+ choices = Choice::Parser.parse(@options, args)
204
+ end
205
+
206
+ args = ['-f', file_good]
207
+ choices = Choice::Parser.parse(@options, args)
208
+
209
+ assert_equal file_good, choices['file']
210
+ end
211
+
188
212
  def test_unknown_argument
189
213
  @options['cd'] = Choice::Option.new do
190
214
  short '-c'
@@ -5,7 +5,7 @@ require 'choice'
5
5
  class TestWriter < Test::Unit::TestCase
6
6
 
7
7
  def setup
8
- Choice.reset
8
+ Choice.reset!
9
9
  end
10
10
 
11
11
  HELP_OUT = ''
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: choice
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2006-07-22 00:00:00 -07:00
6
+ version: 0.1.2
7
+ date: 2006-11-05 00:00:00 -08:00
8
8
  summary: Choice is a command line option parser.
9
9
  require_paths:
10
10
  - lib