cli 0.0.3 → 0.0.4

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