cli 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
data/cli.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "cli"
8
- s.version = "0.0.3"
8
+ s.version = "0.0.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jakub Pastuszek"]
12
- s.date = "2011-12-01"
12
+ s.date = "2011-12-15"
13
13
  s.description = "Provides DSL for command-line options, switches and arguments parser and stdin handling with generated usage printer"
14
14
  s.email = "jpastuszek@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -30,6 +30,10 @@ Gem::Specification.new do |s|
30
30
  "features/step_definitions/cli_steps.rb",
31
31
  "features/support/env.rb",
32
32
  "lib/cli.rb",
33
+ "lib/cli/arguments.rb",
34
+ "lib/cli/dsl.rb",
35
+ "lib/cli/options.rb",
36
+ "lib/cli/switches.rb",
33
37
  "spec/cli_spec.rb",
34
38
  "spec/spec_helper.rb"
35
39
  ]
data/lib/cli.rb CHANGED
@@ -2,136 +2,102 @@ require 'ostruct'
2
2
  require 'stringio'
3
3
  require 'yaml'
4
4
 
5
- class CLI
6
- class ParsingError < ArgumentError
7
- end
8
-
9
- class Parsed < OpenStruct
10
- def value(argument, value)
11
- send((argument.name.to_s + '=').to_sym, argument.cast(value))
12
- end
13
-
14
- def set(argument)
15
- send((argument.name.to_s + '=').to_sym, true)
16
- end
17
- end
18
-
19
- module Options
20
- class Base
21
- def initialize(name, options = {})
22
- @name = name
23
- @options = options
24
- end
5
+ require 'cli/dsl'
6
+ require 'cli/arguments'
7
+ require 'cli/switches'
8
+ require 'cli/options'
25
9
 
26
- attr_reader :name
27
- end
28
-
29
- module Cast
30
- def cast(value)
31
- begin
32
- cast_class = @options[:cast]
33
- if cast_class == nil
34
- value
35
- elsif cast_class == Integer
36
- value.to_i
37
- elsif cast_class == Float
38
- value.to_f
39
- elsif cast_class == YAML
40
- YAML.load(value)
41
- else
42
- cast_class.new(value)
43
- end
44
- rescue => e
45
- raise ParsingError, "failed to cast: #{@name} to type: #{@options[:cast].name}: #{e}"
46
- end
10
+ class CLI
11
+ class ParserError < ArgumentError
12
+ class NameArgumetNotSymbolError < ParserError
13
+ def initialize(type, arg)
14
+ super("#{type} name has to be of type Symbol, got #{arg.class.name}")
47
15
  end
48
16
  end
49
17
 
50
- module Description
51
- def description?
52
- @options.member? :description
53
- end
54
-
55
- def description
56
- @options[:description]
18
+ class OptionsArgumentNotHashError < ParserError
19
+ def initialize(type, arg)
20
+ super("#{type} options has to be of type Hash, got #{arg.class.name}")
57
21
  end
58
22
  end
59
23
 
60
- module Value
61
- def default
62
- @options[:default]
24
+ class ArgumentNameSpecifiedTwice < ParserError
25
+ def initialize(arg)
26
+ super("argument #{arg} specified twice")
63
27
  end
28
+ end
64
29
 
65
- def has_default?
66
- @options.member? :default
30
+ class LongNameSpecifiedTwiceError < ParserError
31
+ def initialize(what, name)
32
+ super("#{what} #{name} specified twice")
67
33
  end
34
+ end
68
35
 
69
- def optional?
70
- has_default?
36
+ class ShortNameSpecifiedTwiceError < ParserError
37
+ def initialize(what, name)
38
+ super("short #{what} #{name} specified twice")
71
39
  end
72
40
  end
73
- end
74
-
75
- class STDINHandling < Options::Base
76
- include Options::Cast
77
- include Options::Description
78
41
 
79
- def to_s
80
- (@name or @options[:cast] or 'data').to_s.tr('_', '-')
42
+ class ShortNameNotSymbolError < ParserError
43
+ def initialize(short)
44
+ super("short name has to be of type Symbol, got #{short.class.name}")
45
+ end
81
46
  end
82
- end
83
-
84
- class Argument < Options::Base
85
- include Options::Value
86
- include Options::Cast
87
- include Options::Description
88
47
 
89
- def to_s
90
- name.to_s.tr('_', '-')
48
+ class ShortNameIsInvalidError < ParserError
49
+ def initialize(short)
50
+ super("short name has to be one letter symbol, got #{short}")
51
+ end
91
52
  end
92
53
  end
93
54
 
94
- class Switch < Options::Base
95
- include Options::Description
96
-
97
- def has_short?
98
- @options.member? :short
55
+ class ParsingError < ArgumentError
56
+ class MissingOptionValueError < ParsingError
57
+ def initialize(option)
58
+ super("missing value for option #{option.switch}")
59
+ end
99
60
  end
100
61
 
101
- def short
102
- @options[:short]
62
+ class UnknownSwitchError < ParsingError
63
+ def initialize(arg)
64
+ super("unknown switch #{arg}")
65
+ end
103
66
  end
104
67
 
105
- def switch
106
- '--' + name.to_s.tr('_', '-')
68
+ class MandatoryOptionsNotSpecifiedError < ParsingError
69
+ def initialize(options)
70
+ super("mandatory options not specified: #{options.map{|o| o.switch}.sort.join(', ')}")
71
+ end
107
72
  end
108
73
 
109
- def switch_short
110
- '-' + short.to_s
74
+ class MandatoryArgumentNotSpecifiedError < ParsingError
75
+ def initialize(arg)
76
+ super("mandatory argument #{arg} not given")
77
+ end
111
78
  end
112
79
 
113
- def to_s
114
- switch
80
+ class CastError < ParsingError
81
+ def initialize(arg, cast_name, error)
82
+ super("failed to cast: #{arg} to type: #{cast_name}: #{error}")
83
+ end
115
84
  end
116
85
  end
117
86
 
118
- class Option < Switch
119
- include Options::Value
120
- include Options::Cast
87
+ class Values < OpenStruct
88
+ def value(argument, value)
89
+ send((argument.name.to_s + '=').to_sym, value)
90
+ end
121
91
 
122
- def optional?
123
- has_default? or not @options[:required]
92
+ def set(argument)
93
+ value(argument, true)
124
94
  end
125
95
  end
126
96
 
127
97
  def initialize(&block)
128
- #TODO: optoins should be in own class?
129
- @switches = []
130
- @switches_long = {}
131
- @switches_short = {}
132
- @options_default = []
133
- @options_required = []
134
- @arguments = []
98
+ @arguments = Arguments.new
99
+ @switches = Switches.new
100
+ @options = Options.new
135
101
  instance_eval(&block) if block_given?
136
102
  end
137
103
 
@@ -139,70 +105,74 @@ class CLI
139
105
  @description = desc
140
106
  end
141
107
 
142
- def stdin(name = nil, options = {})
143
- @stdin_handling = STDINHandling.new(name, options)
108
+ def stdin(name = :data, options = {})
109
+ @stdin = DSL::Input.new(name, options)
144
110
  end
145
111
 
146
112
  def argument(name, options = {})
147
- raise ArgumentError, "expected argument options of type Hash, got: #{options.class.name}" unless options.is_a? Hash
148
- @arguments << Argument.new(name, options)
113
+ argument_dsl = DSL::Argument.new(name, options)
114
+
115
+ raise ParserError::ArgumentNameSpecifiedTwice.new(argument_dsl.name) if @arguments.has?(argument_dsl)
116
+
117
+ @arguments << argument_dsl
149
118
  end
150
119
 
151
120
  def switch(name, options = {})
152
- o = Switch.new(name, options)
153
- @switches << o
154
- @switches_long[name] = o
155
- @switches_short[o.short] = o if o.has_short?
121
+ switch_dsl = DSL::Switch.new(name, options)
122
+
123
+ raise ParserError::LongNameSpecifiedTwiceError.new('switch', switch_dsl.name) if @switches.has_long?(switch_dsl)
124
+ raise ParserError::LongNameSpecifiedTwiceError.new('option and switch', switch_dsl.name) if @options.has_long?(switch_dsl)
125
+ raise ParserError::ShortNameSpecifiedTwiceError.new('switch', switch_dsl.short) if @switches.has_short?(switch_dsl)
126
+ raise ParserError::ShortNameSpecifiedTwiceError.new('option and switch', switch_dsl.short) if @options.has_short?(switch_dsl)
127
+
128
+ @switches << switch_dsl
156
129
  end
157
130
 
158
131
  def option(name, options = {})
159
- o = Option.new(name, options)
160
- @switches << o
161
- @switches_long[name] = o
162
- @switches_short[o.short] = o if o.has_short?
163
- @options_default << o if o.has_default?
164
- @options_required << o unless o.optional?
132
+ option_dsl = DSL::Option.new(name, options)
133
+
134
+ raise ParserError::LongNameSpecifiedTwiceError.new('option', option_dsl.name) if @options.has_long?(option_dsl)
135
+ raise ParserError::LongNameSpecifiedTwiceError.new('switch and option', option_dsl.name) if @switches.has_long?(option_dsl)
136
+ raise ParserError::ShortNameSpecifiedTwiceError.new('option', option_dsl.short) if @options.has_short?(option_dsl)
137
+ raise ParserError::ShortNameSpecifiedTwiceError.new('switch and option', option_dsl.short) if @switches.has_short?(option_dsl)
138
+
139
+ @options << option_dsl
165
140
  end
166
141
 
167
142
  def parse(_argv = ARGV, stdin = STDIN, stderr = STDERR)
168
- parsed = Parsed.new
169
-
143
+ values = Values.new
170
144
  argv = _argv.dup
171
145
 
172
146
  # check help
173
147
  if argv.include? '-h' or argv.include? '--help'
174
- parsed.help = usage
175
- return parsed
148
+ values.help = usage
149
+ return values
176
150
  end
177
151
 
178
152
  # set defaults
179
- @options_default.each do |o|
180
- parsed.value(o, o.default)
153
+ @options.defaults.each do |o|
154
+ values.value(o, o.cast(o.default))
181
155
  end
182
156
 
183
157
  # process switches
184
- options_required = @options_required.dup
185
- while argv.first =~ /^-/
186
- arg = argv.shift
187
- switch = if arg =~ /^--/
188
- @switches_long[arg.sub(/^--/, '').tr('-', '_').to_sym]
189
- else
190
- @switches_short[arg.sub(/^-/, '').tr('-', '_').to_sym]
191
- end
158
+ mandatory_options = @options.mandatory.dup
192
159
 
193
- raise ParsingError, "unknonw switch: #{arg}" unless switch
160
+ while Switches.is_switch?(argv.first)
161
+ arg = argv.shift
194
162
 
195
- if switch.kind_of? Option
196
- value = argv.shift or raise ParsingError, "missing option argument: #{switch}"
197
- parsed.value(switch, value)
198
- options_required.delete(switch)
163
+ if switch = @switches.find(arg)
164
+ values.set(switch)
165
+ elsif option = @options.find(arg)
166
+ value = argv.shift or raise ParsingError::MissingOptionValueError.new(option)
167
+ values.value(option, option.cast(value))
168
+ mandatory_options.delete(option)
199
169
  else
200
- parsed.set(switch)
170
+ raise ParsingError::UnknownSwitchError.new(arg) unless switch
201
171
  end
202
172
  end
203
173
 
204
- # check required
205
- raise ParsingError, "following options are required but were not specified: #{options_required.map{|o| o.switch}.join(', ')}" unless options_required.empty?
174
+ # check mandatory options
175
+ raise ParsingError::MandatoryOptionsNotSpecifiedError.new(mandatory_options) unless mandatory_options.empty?
206
176
 
207
177
  # process arguments
208
178
  arguments = @arguments.dup
@@ -210,16 +180,16 @@ class CLI
210
180
  value = if argv.length < arguments.length + 1 and argument.optional?
211
181
  argument.default # not enough arguments, try to skip optional if possible
212
182
  else
213
- argv.shift or raise ParsingError, "missing argument: #{argument}"
183
+ argv.shift or raise ParsingError::MandatoryArgumentNotSpecifiedError.new(argument)
214
184
  end
215
185
 
216
- parsed.value(argument, value)
186
+ values.value(argument, argument.cast(value))
217
187
  end
218
188
 
219
189
  # process stdin
220
- parsed.stdin = @stdin_handling.cast(stdin) if @stdin_handling
190
+ values.stdin = @stdin.cast(stdin) if @stdin
221
191
 
222
- parsed
192
+ values
223
193
  end
224
194
 
225
195
  def parse!(argv = ARGV, stdin = STDIN, stderr = STDERR, stdout = STDOUT)
@@ -236,29 +206,26 @@ class CLI
236
206
  end
237
207
 
238
208
  def usage(msg = nil)
239
- switches = @switches.select{|s| s.class == Switch}
240
- options = @switches.select{|s| s.class == Option}
241
-
242
209
  out = StringIO.new
243
210
  out.puts msg if msg
244
211
  out.print "Usage: #{File.basename $0}"
245
- out.print ' [switches|options]' if not switches.empty? and not options.empty?
246
- out.print ' [switches]' if not switches.empty? and options.empty?
247
- out.print ' [options]' if switches.empty? and not options.empty?
212
+ out.print ' [switches|options]' if not @switches.empty? and not @options.empty?
213
+ out.print ' [switches]' if not @switches.empty? and @options.empty?
214
+ out.print ' [options]' if @switches.empty? and not @options.empty?
248
215
  out.print ' ' + @arguments.map{|a| a.to_s}.join(' ') unless @arguments.empty?
249
- out.print " < #{@stdin_handling}" if @stdin_handling
216
+ out.print " < #{@stdin}" if @stdin
250
217
 
251
218
  out.puts
252
219
  out.puts @description if @description
253
220
 
254
- if @stdin_handling and @stdin_handling.description?
221
+ if @stdin and @stdin.description?
255
222
  out.puts "Input:"
256
- out.puts " #{@stdin_handling} - #{@stdin_handling.description}"
223
+ out.puts " #{@stdin} - #{@stdin.description}"
257
224
  end
258
225
 
259
- unless switches.empty?
226
+ unless @switches.empty?
260
227
  out.puts "Switches:"
261
- switches.each do |s|
228
+ @switches.each do |s|
262
229
  out.print ' '
263
230
  out.print s.switch
264
231
  out.print " (#{s.switch_short})" if s.has_short?
@@ -267,9 +234,9 @@ class CLI
267
234
  end
268
235
  end
269
236
 
270
- unless options.empty?
237
+ unless @options.empty?
271
238
  out.puts "Options:"
272
- options.each do |o|
239
+ @options.each do |o|
273
240
  out.print ' '
274
241
  out.print o.switch
275
242
  out.print " (#{o.switch_short})" if o.has_short?
@@ -0,0 +1,6 @@
1
+ class CLI::Arguments < Array
2
+ def has?(argument_dsl)
3
+ self.find{|a| a.name == argument_dsl.name}
4
+ end
5
+ end
6
+
data/lib/cli/dsl.rb ADDED
@@ -0,0 +1,121 @@
1
+ class CLI
2
+ module DSL
3
+ class Base
4
+ def initialize(name, options = {})
5
+ class_name = self.class.name.gsub(/.*:/, '').downcase
6
+ raise ParserError::NameArgumetNotSymbolError.new(class_name, name) unless name.is_a? Symbol
7
+ raise ParserError::OptionsArgumentNotHashError.new(class_name, options) unless options.is_a? Hash
8
+ @name = name
9
+ @options = options
10
+ end
11
+
12
+ attr_reader :name
13
+ end
14
+
15
+ module Cast
16
+ def cast(value)
17
+ begin
18
+ cast_class = @options[:cast]
19
+ if cast_class == nil
20
+ value
21
+ elsif cast_class == Integer
22
+ value.to_i
23
+ elsif cast_class == Float
24
+ value.to_f
25
+ elsif cast_class == YAML
26
+ YAML.load(value)
27
+ else
28
+ cast_class.new(value)
29
+ end
30
+ rescue => e
31
+ raise ParsingError::CastError.new(@name, @options[:cast].name, e)
32
+ end
33
+ end
34
+ end
35
+
36
+ module Description
37
+ def description?
38
+ @options.member? :description
39
+ end
40
+
41
+ def description
42
+ @options[:description]
43
+ end
44
+ end
45
+
46
+ module Value
47
+ def default
48
+ @options[:default]
49
+ end
50
+
51
+ def has_default?
52
+ @options.member? :default
53
+ end
54
+
55
+ def optional?
56
+ has_default?
57
+ end
58
+ end
59
+
60
+ class Input < DSL::Base
61
+ include DSL::Cast
62
+ include DSL::Description
63
+
64
+ def to_s
65
+ (@name or @options[:cast] or 'data').to_s.tr('_', '-')
66
+ end
67
+ end
68
+
69
+ class Argument < DSL::Base
70
+ include DSL::Value
71
+ include DSL::Cast
72
+ include DSL::Description
73
+
74
+ def to_s
75
+ name.to_s.tr('_', '-')
76
+ end
77
+ end
78
+
79
+ class Switch < DSL::Base
80
+ def initialize(name, options = {})
81
+ super(name, options)
82
+ if short = options[:short]
83
+ raise ParserError::ShortNameNotSymbolError.new(short) if not short.is_a? Symbol
84
+ raise ParserError::ShortNameIsInvalidError.new(short) if short.to_s.length > 1
85
+ end
86
+ end
87
+
88
+ include DSL::Description
89
+
90
+ def has_short?
91
+ @options.member? :short
92
+ end
93
+
94
+ def short
95
+ @options[:short]
96
+ end
97
+
98
+ def switch
99
+ '--' + name.to_s.tr('_', '-')
100
+ end
101
+
102
+ def switch_short
103
+ '-' + short.to_s
104
+ end
105
+
106
+ def to_s
107
+ switch
108
+ end
109
+ end
110
+
111
+ class Option < Switch
112
+ include DSL::Value
113
+ include DSL::Cast
114
+
115
+ def optional?
116
+ has_default? or not @options[:required]
117
+ end
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,19 @@
1
+ require 'cli/switches'
2
+
3
+ class CLI::Options < CLI::Switches
4
+ def initialize
5
+ super
6
+ @defaults = []
7
+ @mandatory = []
8
+ end
9
+
10
+ def <<(option_dsl)
11
+ super option_dsl
12
+ @defaults << option_dsl if option_dsl.has_default?
13
+ @mandatory << option_dsl unless option_dsl.optional?
14
+ end
15
+
16
+ attr_reader :defaults
17
+ attr_reader :mandatory
18
+ end
19
+
@@ -0,0 +1,41 @@
1
+ class CLI::Switches < Array
2
+ def initialize
3
+ @long = {}
4
+ @short = {}
5
+ end
6
+
7
+ def <<(switch_dsl)
8
+ super(switch_dsl)
9
+ @long[switch_dsl.name] = switch_dsl
10
+ @short[switch_dsl.short] = switch_dsl if switch_dsl.has_short?
11
+ end
12
+
13
+ def self.is_switch?(arg)
14
+ arg =~ /^-/
15
+ end
16
+
17
+ def find(arg)
18
+ if arg =~ /^--/
19
+ find_long(arg)
20
+ else
21
+ find_short(arg)
22
+ end
23
+ end
24
+
25
+ def find_long(arg)
26
+ @long[arg.sub(/^--/, '').tr('-', '_').to_sym]
27
+ end
28
+
29
+ def find_short(arg)
30
+ @short[arg.sub(/^-/, '').tr('-', '_').to_sym]
31
+ end
32
+
33
+ def has_long?(switch_dsl)
34
+ @long.member?(switch_dsl.name)
35
+ end
36
+
37
+ def has_short?(switch_dsl)
38
+ @short.member?(switch_dsl.short) if switch_dsl.has_short?
39
+ end
40
+ end
41
+
data/spec/cli_spec.rb CHANGED
@@ -94,12 +94,35 @@ EOF
94
94
  ps.test.should == 'hello'
95
95
  end
96
96
 
97
+ it "should raise error if not symbol and optional hash is passed" do
98
+ lambda {
99
+ ps = CLI.new do
100
+ argument 'number'
101
+ end
102
+ }.should raise_error CLI::ParserError::NameArgumetNotSymbolError, "argument name has to be of type Symbol, got String"
103
+
104
+ lambda {
105
+ ps = CLI.new do
106
+ argument :number, :test
107
+ end
108
+ }.should raise_error CLI::ParserError::OptionsArgumentNotHashError, "argument options has to be of type Hash, got Symbol"
109
+ end
110
+
111
+ it "should raise error if artument name is specified twice" do
112
+ lambda {
113
+ ps = CLI.new do
114
+ argument :number
115
+ argument :number
116
+ end
117
+ }.should raise_error CLI::ParserError::ArgumentNameSpecifiedTwice, 'argument number specified twice'
118
+ end
119
+
97
120
  it "should raise error if not given" do
98
121
  lambda {
99
122
  ps = CLI.new do
100
123
  argument :log
101
124
  end.parse([])
102
- }.should raise_error CLI::ParsingError
125
+ }.should raise_error CLI::ParsingError::MandatoryArgumentNotSpecifiedError, 'mandatory argument log not given'
103
126
  end
104
127
 
105
128
  it "should raise error if casting fail" do
@@ -108,7 +131,7 @@ EOF
108
131
  ps = CLI.new do
109
132
  argument :log, :cast => IP
110
133
  end.parse(['abc'])
111
- }.should raise_error CLI::ParsingError, 'failed to cast: log to type: IP: invalid address'
134
+ }.should raise_error CLI::ParsingError::CastError, 'failed to cast: log to type: IP: invalid address'
112
135
  end
113
136
 
114
137
  describe "with defaults" do
@@ -168,6 +191,34 @@ EOF
168
191
  ps.unset.should be_nil
169
192
  end
170
193
 
194
+ it "should raise error if not symbol and optional hash is passed" do
195
+ lambda {
196
+ ps = CLI.new do
197
+ switch 'number'
198
+ end.parse([])
199
+ }.should raise_error CLI::ParserError::NameArgumetNotSymbolError, "switch name has to be of type Symbol, got String"
200
+
201
+ lambda {
202
+ ps = CLI.new do
203
+ switch :number, :test
204
+ end
205
+ }.should raise_error CLI::ParserError::OptionsArgumentNotHashError, "switch options has to be of type Hash, got Symbol"
206
+ end
207
+
208
+ it "shoud raise error is short name is invalid" do
209
+ lambda {
210
+ ps = CLI.new do
211
+ switch :location, :short => "l"
212
+ end
213
+ }.should raise_error CLI::ParserError::ShortNameNotSymbolError, 'short name has to be of type Symbol, got String'
214
+
215
+ lambda {
216
+ ps = CLI.new do
217
+ switch :location, :short => :abc
218
+ end
219
+ }.should raise_error CLI::ParserError::ShortNameIsInvalidError, 'short name has to be one letter symbol, got abc'
220
+ end
221
+
171
222
  it "should raise error on unrecognized switch" do
172
223
  ps = CLI.new do
173
224
  option :location
@@ -175,7 +226,7 @@ EOF
175
226
 
176
227
  lambda {
177
228
  ps.parse(['--xxx'])
178
- }.should raise_error CLI::ParsingError
229
+ }.should raise_error CLI::ParsingError::UnknownSwitchError, 'unknown switch --xxx'
179
230
  end
180
231
  end
181
232
 
@@ -240,6 +291,20 @@ EOF
240
291
  ps.gold.should be_nil
241
292
  end
242
293
 
294
+ it "should raise error if not symbol and optional hash is passed" do
295
+ lambda {
296
+ ps = CLI.new do
297
+ option 'number'
298
+ end
299
+ }.should raise_error CLI::ParserError::NameArgumetNotSymbolError, "option name has to be of type Symbol, got String"
300
+
301
+ lambda {
302
+ ps = CLI.new do
303
+ option :number, :test
304
+ end
305
+ }.should raise_error CLI::ParserError::OptionsArgumentNotHashError, "option options has to be of type Hash, got Symbol"
306
+ end
307
+
243
308
  it "should raise error on missing option argument" do
244
309
  ps = CLI.new do
245
310
  option :location
@@ -247,12 +312,13 @@ EOF
247
312
 
248
313
  lambda {
249
314
  ps.parse(['--location'])
250
- }.should raise_error CLI::ParsingError
315
+ }.should raise_error CLI::ParsingError::MissingOptionValueError, 'missing value for option --location'
251
316
  end
252
317
 
253
- it "should raise error on missing required option" do
318
+ it "should raise error on missing mandatory option" do
254
319
  ps = CLI.new do
255
320
  option :location
321
+ option :weight, :required => true
256
322
  option :size, :required => true
257
323
  option :group, :default => 'red'
258
324
  option :speed, :short => :s, :cast => Integer
@@ -260,7 +326,7 @@ EOF
260
326
 
261
327
  lambda {
262
328
  ps.parse(['--location', 'singapore'])
263
- }.should raise_error CLI::ParsingError, "following options are required but were not specified: --size"
329
+ }.should raise_error CLI::ParsingError::MandatoryOptionsNotSpecifiedError, "mandatory options not specified: --size, --weight"
264
330
  end
265
331
  end
266
332
 
@@ -291,6 +357,70 @@ EOF
291
357
  ps.debug.should be_true
292
358
  end
293
359
 
360
+ describe "name conflict reporting" do
361
+ it "raise error when long names configlict" do
362
+ lambda {
363
+ ps = CLI.new do
364
+ switch :location
365
+ switch :location
366
+ end
367
+ }.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'switch location specified twice'
368
+
369
+ lambda {
370
+ ps = CLI.new do
371
+ option :location
372
+ option :location
373
+ end
374
+ }.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'option location specified twice'
375
+
376
+ lambda {
377
+ ps = CLI.new do
378
+ switch :location
379
+ option :location
380
+ end
381
+ }.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'switch and option location specified twice'
382
+
383
+ lambda {
384
+ ps = CLI.new do
385
+ option :location
386
+ switch :location
387
+ end
388
+ }.should raise_error CLI::ParserError::LongNameSpecifiedTwiceError, 'option and switch location specified twice'
389
+ end
390
+ end
391
+
392
+ describe "short name conflict reporting" do
393
+ it "raise error when short names configlict" do
394
+ lambda {
395
+ ps = CLI.new do
396
+ switch :location, :short => :l
397
+ switch :location2, :short => :l
398
+ end
399
+ }.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short switch l specified twice'
400
+
401
+ lambda {
402
+ ps = CLI.new do
403
+ option :location, :short => :l
404
+ option :location2, :short => :l
405
+ end
406
+ }.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short option l specified twice'
407
+
408
+ lambda {
409
+ ps = CLI.new do
410
+ switch :location, :short => :l
411
+ option :location2, :short => :l
412
+ end
413
+ }.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short switch and option l specified twice'
414
+
415
+ lambda {
416
+ ps = CLI.new do
417
+ option :location2, :short => :l
418
+ switch :location, :short => :l
419
+ end
420
+ }.should raise_error CLI::ParserError::ShortNameSpecifiedTwiceError, 'short option and switch l specified twice'
421
+ end
422
+ end
423
+
294
424
  describe "usage and description" do
295
425
  it "parse should set help variable if -h or --help specified in the argument list and not parse the input" do
296
426
  ss = CLI.new do
@@ -359,6 +489,31 @@ EOF
359
489
  ss.usage.should include("log file to process")
360
490
  end
361
491
 
492
+ it "should suggest that switches can be used in usage line" do
493
+ ss = CLI.new do
494
+ switch :location, :short => :l
495
+ end
496
+
497
+ ss.usage.first.should == "Usage: rspec [switches]\n"
498
+ end
499
+
500
+ it "should suggest that options can be used in usage line" do
501
+ ss = CLI.new do
502
+ option :location, :short => :l
503
+ end
504
+
505
+ ss.usage.first.should == "Usage: rspec [options]\n"
506
+ end
507
+
508
+ it "should suggest that switches or options can be used in usage line" do
509
+ ss = CLI.new do
510
+ switch :location, :short => :l
511
+ option :size, :short => :s
512
+ end
513
+
514
+ ss.usage.first.should == "Usage: rspec [switches|options]\n"
515
+ end
516
+
362
517
  it "should allow describing whole script" do
363
518
  ss = CLI.new do
364
519
  description 'Log file processor'
@@ -398,7 +553,7 @@ EOF
398
553
  switch :debug, :short => :d, :description => "enable debugging"
399
554
  switch :logging, :short => :l
400
555
  switch :run
401
- option :location, :short => :l, :description => "place where server is located"
556
+ option :location, :short => :r, :description => "place where server is located"
402
557
  option :group, :default => 'red'
403
558
  option :power_up, :short => :p
404
559
  option :speed, :short => :s, :cast => Integer
@@ -425,7 +580,7 @@ Switches:
425
580
  --logging (-l)
426
581
  --run
427
582
  Options:
428
- --location (-l) - place where server is located
583
+ --location (-r) - place where server is located
429
584
  --group [red]
430
585
  --power-up (-p)
431
586
  --speed (-s)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cli
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
9
+ - 4
10
+ version: 0.0.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jakub Pastuszek
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-12-01 00:00:00 Z
18
+ date: 2011-12-15 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  type: :development
@@ -146,6 +146,10 @@ files:
146
146
  - features/step_definitions/cli_steps.rb
147
147
  - features/support/env.rb
148
148
  - lib/cli.rb
149
+ - lib/cli/arguments.rb
150
+ - lib/cli/dsl.rb
151
+ - lib/cli/options.rb
152
+ - lib/cli/switches.rb
149
153
  - spec/cli_spec.rb
150
154
  - spec/spec_helper.rb
151
155
  homepage: http://github.com/jpastuszek/cli