choice 0.1.1 → 0.1.2

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