clive 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,9 @@
1
1
  require 'ast_ast'
2
+ require 'attr_plus'
2
3
 
3
4
  require 'clive/parser'
4
5
  require 'clive/exceptions'
5
6
  require 'clive/tokens'
6
- require 'clive/ext'
7
7
 
8
8
  require 'clive/option'
9
9
  require 'clive/command'
@@ -18,6 +18,8 @@ require 'clive/formatter'
18
18
  #
19
19
  # @example Simple Example
20
20
  #
21
+ # require 'clive'
22
+ #
21
23
  # class CLI
22
24
  # include Clive::Parser
23
25
  #
@@ -9,6 +9,14 @@ module Clive
9
9
  attr_accessor :options, :commands
10
10
  attr_accessor :argv, :base
11
11
  attr_reader :names, :current_desc
12
+ attr_reader :top_klass
13
+
14
+ # Create the base Command instance. Replacement for the #initialize
15
+ # overloading.
16
+ #
17
+ def self.setup(klass, &block)
18
+ new([], "", klass, &block)
19
+ end
12
20
 
13
21
  # Create a new Command instance
14
22
  #
@@ -23,30 +31,23 @@ module Clive
23
31
  #
24
32
  # @yield A block to run, containing switches, flags and commands
25
33
  #
26
- def initialize(*args, &block)
27
- @argv = []
28
- @names = []
29
- @base = false
30
- @commands = Clive::Array.new
31
- @options = Clive::Array.new
34
+ def initialize(names, desc, top_klass, &block)
35
+ @argv = []
36
+ @names = names.map {|i| i.to_s }
37
+ @top_klass = top_klass
38
+ @desc = desc
39
+ @commands = []
40
+ @options = []
41
+ @block = block
42
+ @base = false
32
43
 
33
- @option_missing = Proc.new {|e| raise NoOptionError.new(e)}
34
-
35
- if args.length == 1 && args[0] == true
44
+ if @names == [] && @desc == ""
36
45
  @base = true
37
46
  self.instance_eval(&block) if block_given?
38
- else
39
- args.each do |i|
40
- case i
41
- when ::Array
42
- @names = i.map {|i| i.to_s }
43
- when String
44
- @desc = i
45
- end
46
- end
47
- @block = block
48
47
  end
49
48
 
49
+ @option_missing = Proc.new {|e| raise NoOptionError.new(e)}
50
+
50
51
  # Create basic header "Usage: filename [command] [options]
51
52
  # or "Usage: filename commandname(s) [options]
52
53
  @header = "Usage: #{File.basename($0, '.*')} " <<
@@ -60,19 +61,19 @@ module Clive
60
61
  self.build_help
61
62
  end
62
63
 
63
- # @return [Clive::Array] all bools in this command
64
+ # @return [Array] all bools in this command
64
65
  def bools
65
- Clive::Array.new(@options.find_all {|i| i.class == Bool})
66
+ @options.find_all {|i| i.class == Bool}
66
67
  end
67
68
 
68
- # @return [Clive::Array] all switches in this command
69
+ # @return [Array] all switches in this command
69
70
  def switches
70
- Clive::Array.new(@options.find_all {|i| i.class == Switch})
71
+ @options.find_all {|i| i.class == Switch}
71
72
  end
72
73
 
73
- # @return [Clive::Array] all flags in this command
74
+ # @return [Array] all flags in this command
74
75
  def flags
75
- Clive::Array.new(@options.find_all {|i| i.class == Flag})
76
+ @options.find_all {|i| i.class == Flag}
76
77
  end
77
78
 
78
79
  # Run the block that was passed to find switches, flags, etc.
@@ -87,122 +88,217 @@ module Clive
87
88
  @block = nil
88
89
  end
89
90
 
90
- # Parse the ARGV passed from the command line, and run
91
+ # Gets the type of the option which corresponds with the name given
91
92
  #
92
- # @param [::Array] argv the command line input, usually just +ARGV+
93
- # @return [::Array] any arguments that were present in the input but not used
93
+ # @param name [String]
94
+ # @return [Constant]
94
95
  #
95
- def run(argv=[])
96
- tokens = argv
97
- tokens = tokenize(argv) if @base
98
-
99
- r = []
100
- tokens.each do |i|
101
- k, v = i[0], i[1]
102
- case k
103
- when :command
104
- r << v.run(i[2])
105
- when :switch
106
- v.run
107
- when :flag
108
- args = i[2..-1]
109
- opt_args = v.arg_size(:optional)
110
- nec_args = v.arg_size(:mandatory)
111
- # check for missing args
112
- if args.size < nec_args
113
- raise MissingArgument.new(v.sort_name)
114
- end
115
-
116
- v.run(args)
117
- when :argument
118
- r << v
119
- end
96
+ def type_is?(name)
97
+ find_opt(name).class.name || Clive::Command
98
+ end
99
+
100
+ def opt_type(name)
101
+ case find_opt(name).class.name
102
+ when "Clive::Switch"
103
+ :switch
104
+ when "Clive::Bool"
105
+ :switch
106
+ when "Clive::Flag"
107
+ :flag
108
+ when "Clive::Command"
109
+ :command
110
+ else
111
+ nil
120
112
  end
121
- r.flatten
122
113
  end
123
114
 
124
- # Turns the command line input into a series of tokens.
125
- # It will only raise errors if this is the base command instance.
115
+ # Finds the option which has the name given
126
116
  #
127
- # @param [::Array] argv the command line input
128
- # @return [::Array] a series of tokens
117
+ # @param name [String]
118
+ # @return [Clive::Option]
119
+ #
120
+ def find_opt(name)
121
+ options.find {|i| i.names.include?(name)}
122
+ end
123
+
124
+ # Checks whether the string given is the name of a Command or not
125
+ #
126
+ # @param str [String]
127
+ # @return [true, false]
128
+ #
129
+ def is_a_command?(str)
130
+ find_command(str).empty?
131
+ end
132
+
133
+ # Finds the command which has the name given
134
+ #
135
+ # @param name [String]
136
+ # @return [Clive::Command]
137
+ #
138
+ def find_command(str)
139
+ commands.find {|i| i.names.include?(str)}
140
+ end
141
+
142
+ # Converts the array of input from the command line into a string of tokens.
143
+ # It replaces instances of the names of flags, switches and bools with the
144
+ # actual option, but does not affect commands. Instead these are left as +words+.
129
145
  #
130
146
  # @example
131
147
  #
132
- # c.tokenize(["add", "-al", "--verbose"])
133
- # #=> [[:command, #<Clive::Command>, ...args...], [:switch, "a",
134
- # #<Clive::Switch>], [:switch, "l", #<Clive::Switch>], [:switch,
135
- # "verbose", #<Clive::Switch>]]
148
+ # array_to_tokens ['--switch', 'command', '-f', 'arg']
149
+ # #=> [[:option, "switch"], [:word, "command"], [:option, "f"], [:word, "arg"]]
136
150
  #
137
- def tokenize(argv)
138
- self.find
139
- r = []
140
- tokens = Tokens.new(argv)
151
+ # @param arr [Array]
152
+ # @return [Array]
153
+ #
154
+ def array_to_tokens(arr)
155
+ result = []
141
156
 
142
- pre_command = Tokens.new
143
- command = nil
144
- tokens.tokens.each do |i|
145
- k, v = i[0], i[1]
146
- # check if a command
147
- if k == :word && commands[v]
148
- command = v
149
- break
157
+ arr.each do |a|
158
+ if a[0..1] == "--"
159
+ result << [:option, a[2..-1]]
160
+
161
+ elsif a[0] == "-"
162
+ a[1..-1].split('').each do |i|
163
+ result << [:option, i]
164
+ end
165
+
150
166
  else
151
- pre_command << i
167
+ result << [:word, a]
152
168
  end
153
169
  end
154
-
155
- post_command = Tokens.new(tokens.array - pre_command - [command])
156
- pre_command_tokens = parse(pre_command)
157
- r = pre_command_tokens
158
-
159
- if command
160
- t = commands[command].tokenize(post_command)
161
- r << [:command, commands[command], t]
162
- end
163
-
164
- r
170
+
171
+ result
165
172
  end
166
173
 
167
- # This runs through the tokens from Tokens#to_tokens (or similar)
168
- # and creates a new array with the type of object and the object
169
- # itself, possibly with an argument in the case of Flag.
174
+ # Converts the set of tokens returned from #array_to_tokens into a tree.
175
+ # This is where we determine whether a +word+ is an argument or command.
170
176
  #
171
- # @param [Tokens] tokens the tokens to run through
172
- # @return [::Array] of the form
173
- # [[:flag, #<Clive::Flag...>, "word"], [:switch, #<Clive::Switch....
174
- # @raise [InvalidOption] raised if option given can't be found
175
- #
176
- def parse(tokens)
177
- r = []
178
- tokens.tokens.each do |i|
179
- k, v = i[0], i[1]
180
- if switch = switches[v] || switch = bools[v]
181
- r << [:switch, switch]
182
- elsif flag = flags[v]
183
- r << [:flag, flag]
184
- else
185
- if k == :word
186
- # add to last flag?
187
- if r.last && r.last[0] == :flag && r.last.size - 2 < r.last[1].arg_size(:all)
188
- r.last.push(v)
177
+ # @example
178
+ # tokens_to_tree([[:option, "switch"], [:word, "command"],
179
+ # [:option, "f"], [:word, "arg"]])
180
+ # #=> [
181
+ # # [:switch, #<Clive::Switch [switch]>],
182
+ # # [:command, #<Clive::Command [command]>, [
183
+ # # [:flag, #<Clive::Flag [f, flag]>, [
184
+ # # [:arg, 'arg']
185
+ # # ]]
186
+ # # ]]
187
+ # # ]
188
+ #
189
+ # @param arr [Array]
190
+ # @return [Array]
191
+ #
192
+ def tokens_to_tree(arr)
193
+ tree = []
194
+ self.find
195
+
196
+ l = arr.size
197
+ i = 0
198
+ while i < l
199
+ a = arr[i]
200
+
201
+ if a[0] == :word
202
+
203
+ last = tree.last || []
204
+
205
+ if last[0] == :flag
206
+ last[2] ||= []
207
+ end
208
+
209
+ if command = find_command(a[1])
210
+ if last[0] == :flag
211
+ if last[2].size < last[1].arg_size(:mandatory)
212
+ last[2] << [:arg, a[1]]
213
+ else
214
+ tree << [:command, command, command.tokens_to_tree(arr[i+1..-1])]
215
+ i = l
216
+ end
189
217
  else
190
- r << [:argument, v]
218
+ tree << [:command, command, command.tokens_to_tree(arr[i+1..-1])]
219
+ i = l
191
220
  end
192
221
  else
193
- @option_missing.call(v)
222
+ if last[0] == :flag && last[2].size < last[1].arg_size(:all)
223
+ last[2] << [:arg, a[1]]
224
+ else
225
+ tree << [:arg, a[1]]
226
+ end
227
+ end
228
+ else
229
+ tree << [opt_type(a[1]), find_opt(a[1])]
230
+ end
231
+
232
+ i += 1
233
+ end
234
+
235
+ tree
236
+ end
237
+
238
+ # Traverses the tree created by #tokens_to_tree and runs the correct options.
239
+ #
240
+ # @param tree [Array]
241
+ # @return [Array]
242
+ # Any unused arguments.
243
+ #
244
+ def run_tree(tree)
245
+ i = 0
246
+ l = tree.size
247
+ r = []
248
+
249
+ while i < l
250
+ curr = tree[i]
251
+
252
+ case curr[0]
253
+ when :command
254
+ r << curr[1].run(curr[2])
255
+
256
+ when :switch
257
+ curr[1].run
258
+
259
+ when :flag
260
+ args = curr[2].map {|i| i[1] }
261
+ if args.size < curr[1].arg_size(:mandatory)
262
+ raise MissingArgument.new(curr[1].sort_name)
194
263
  end
264
+ curr[1].run(args)
265
+
266
+ when :arg
267
+ r << curr[1]
195
268
  end
269
+
270
+ i += 1
271
+ end
272
+ r.flatten
273
+ end
274
+
275
+
276
+ # Parse the ARGV passed from the command line, and run
277
+ #
278
+ # @param [Array] argv the command line input, usually just +ARGV+
279
+ # @return [Array] any arguments that were present in the input but not used
280
+ #
281
+ def run(argv=[])
282
+ to_run = argv
283
+ if @base # if not base we will have been passed the parsed tree already
284
+ to_run = tokens_to_tree( array_to_tokens(argv) )
196
285
  end
197
- r
286
+ run_tree(to_run)
198
287
  end
199
288
 
289
+
200
290
  def to_h
201
291
  {
202
292
  'names' => @names,
203
293
  'desc' => @desc
204
294
  }
205
295
  end
296
+
297
+ def method_missing(sym, *args, &block)
298
+ if @top_klass.respond_to?(sym)
299
+ @top_klass.send(sym, *args)
300
+ end
301
+ end
206
302
 
207
303
 
208
304
 
@@ -219,7 +315,7 @@ module Clive
219
315
  # and flags
220
316
  #
221
317
  def command(*args, &block)
222
- @commands << Command.new(args, @current_desc, &block)
318
+ @commands << Command.new(args, @current_desc, @top_klass, &block)
223
319
  @current_desc = ""
224
320
  end
225
321
 
@@ -234,15 +330,15 @@ module Clive
234
330
  # @see Flag#initialize
235
331
  def flag(*args, &block)
236
332
  names = []
237
- arg = []
333
+ arg = nil
238
334
  args.each do |i|
239
335
  if i.is_a? Symbol
240
336
  names << i
241
337
  else
242
338
  if i[:arg]
243
- arg << i[:arg]
339
+ arg = i[:arg]
244
340
  else
245
- arg << i[:args]
341
+ arg = i[:args]
246
342
  end
247
343
  end
248
344
  end
@@ -369,7 +465,7 @@ module Clive
369
465
  def help_formatter(*args, &block)
370
466
  if block_given?
371
467
  width = 30
372
- prepend = 5
468
+ prepend = 4
373
469
 
374
470
  unless args.empty?
375
471
  args[0].each do |k,v|
@@ -388,26 +484,27 @@ module Clive
388
484
  else
389
485
  case args[0]
390
486
  when :default
391
- help_formatter do |h|
392
- h.switch "{prepend}{names.join(', ')} {spaces}{desc.grey}"
393
- h.bool "{prepend}{names.join(', ')} {spaces}{desc.grey}"
394
- h.flag "{prepend}{names.join(', ')} {args.join(' ')} {spaces}" <<
395
- "{desc.grey} {options.join('(', ', ', ')').blue.bold}"
396
- h.command "{prepend}{names.join(', ')} {spaces}{desc.grey}"
397
- end
398
-
487
+ help_formatter(&HELP_FORMATTERS[:default])
399
488
  when :white
400
- help_formatter do |h|
401
- h.switch "{prepend}{names.join(', ')} {spaces}{desc}"
402
- h.bool "{prepend}{names.join(', ')} {spaces}{desc}"
403
- h.flag "{prepend}{names.join(', ')} {args.join(' ')} {spaces}" <<
404
- "{desc} {options.join('(', ', ', ')').bold}"
405
- h.command "{prepend}{names.join(', ')} {spaces}{desc}"
406
- end
407
-
489
+ help_formatter(&HELP_FORMATTERS[:white])
408
490
  end
409
491
  end
410
492
  end
411
493
 
494
+ HELP_FORMATTERS = {
495
+ :default => lambda do |h|
496
+ h.switch "{prepend}{names.join(', ')} {spaces}{desc.grey}"
497
+ h.bool "{prepend}{names.join(', ')} {spaces}{desc.grey}"
498
+ h.flag "{prepend}{names.join(', ')} {args} {spaces}{desc.grey} {options.blue.bold}"
499
+ h.command "{prepend}{names.join(', ')} {spaces}{desc.grey}"
500
+ end,
501
+ :white => lambda do |h|
502
+ h.switch "{prepend}{names.join(', ')} {spaces}{desc}"
503
+ h.bool "{prepend}{names.join(', ')} {spaces}{desc}"
504
+ h.flag "{prepend}{names.join(', ')} {args} {spaces}{desc} {options.bold}"
505
+ h.command "{prepend}{names.join(', ')} {spaces}{desc}"
506
+ end
507
+ }
508
+
412
509
  end
413
510
  end
@@ -5,7 +5,7 @@ module Clive
5
5
  # wget -t 10
6
6
  #
7
7
  class Flag < Option
8
- attr_accessor :args
8
+ attr_reader :args
9
9
 
10
10
  # Creates a new Flag instance. A flag is a switch that can take one or more
11
11
  # arguments.
@@ -17,7 +17,7 @@ module Clive
17
17
  # @param desc [String]
18
18
  # A description of the flag.
19
19
  #
20
- # @param args [String, Array, Range]
20
+ # @param arguments [String, Array, Range]
21
21
  # Either, a string showing the arguments to be given, eg.
22
22
  #
23
23
  # "FROM" # single argument required, or
@@ -35,34 +35,37 @@ module Clive
35
35
  # @yield [String]
36
36
  # A block to be run if switch is triggered, will always be passed a string
37
37
  #
38
- def initialize(names, desc, args, &block)
39
- @names = Clive::Array.new(names.map(&:to_s))
40
- @args = Clive::Array.new
38
+ def initialize(names, desc, arguments, &block)
39
+ @names = names.map(&:to_s)
40
+ self.args = (arguments || "ARG")
41
41
 
42
- # Need to be able to make each arg_name optional or not
43
- # and allow for type in future
44
- args.each do |i|
45
- case i
46
- when String
47
- i.split(' ').each do |arg|
42
+ @desc = desc
43
+ @block = block
44
+ end
45
+
46
+ def args=(val)
47
+ case val
48
+ when String
49
+ if val[-3..-1] == "..."
50
+ @args = {:type => :splat, :base_name => val[0..-4]}
51
+
52
+ else
53
+ @args = {:type => :list, :arguments => []}
54
+ val.split(' ').each do |arg|
48
55
  optional = false
49
56
  if arg[0] == "["
50
57
  optional = true
51
58
  arg = arg[1..-2]
52
59
  end
53
- @args << {:name => arg, :optional => optional}
60
+
61
+ @args[:arguments] << {:name => arg, :optional => optional}
54
62
  end
55
- else
56
- @args = i
57
63
  end
64
+ when Range
65
+ @args = {:type => :range, :range => val}
66
+ when Array
67
+ @args = {:type => :choice, :items => val}
58
68
  end
59
-
60
- if @args.empty?
61
- @args = [{:name => "ARG", :optional => false}]
62
- end
63
-
64
- @desc = desc
65
- @block = block
66
69
  end
67
70
 
68
71
  # Runs the block that was given with an argument
@@ -71,12 +74,19 @@ module Clive
71
74
  # @raise [InvalidArgument] only if +args+ is an array of acceptable inputs
72
75
  # and a match is not found.
73
76
  def run(args)
74
- if @args.is_a?(Array) && @args[0].is_a?(Hash)
75
- args = Clive::Array.new(@args.collect {|i| !i[:optional]}).optimise_fill(args)
76
- else # list
77
- unless @args.to_a.map(&:to_s).include? args[0]
77
+ case @args[:type]
78
+ when :list
79
+ args = optimise_fill(args, @args[:arguments].map {|i| !i[:optional] })
80
+ when :choice
81
+ unless @args[:items].map{|i| i.to_s}.include?(args[0])
82
+ raise InvalidArgument.new(args)
83
+ end
84
+ when :range
85
+ unless @args[:range].to_a.map {|i| i.to_s}.include?(args[0])
78
86
  raise InvalidArgument.new(args)
79
87
  end
88
+ when :splat
89
+ args = [args]
80
90
  end
81
91
  @block.call(*args)
82
92
  end
@@ -86,64 +96,104 @@ module Clive
86
96
  # Can be passed three things; :all, returns size of all arguments; :optional
87
97
  # returns all optional arguments; :mandatory, returns size of mandatory arguments.
88
98
  def arg_size(type=:all)
89
- case type
90
- when :all
91
- if @args.is_a?(Array) && @args[0].is_a?(Hash)
92
- @args.size
93
- else
94
- 1
95
- end
96
- when :optional
97
- if @args.is_a?(Array) && @args[0].is_a?(Hash)
98
- @args.find_all {|i| i[:optional] == true }.size
99
- else
100
- 0
99
+ case @args[:type]
100
+ when :list
101
+ case type
102
+ when :all
103
+ @args[:arguments].size
104
+ when :optional
105
+ @args[:arguments].find_all {|i| i[:optional] == true }.size
106
+ when :mandatory
107
+ @args[:arguments].find_all {|i| i[:optional] == false }.size
101
108
  end
102
- when :mandatory
103
- if @args.is_a?(Array) && @args[0].is_a?(Hash)
104
- @args.find_all {|i| i[:optional] == false }.size
105
- else
109
+
110
+ when :choice, :range
111
+ (type == :optional) ? 0 : 1
112
+
113
+ when :splat
114
+ case type
115
+ when :all
116
+ 1.0/0 # Infinity!
117
+ when :optional
118
+ 1.0/0 # Infinity!
119
+ when :mandatory
106
120
  1
107
121
  end
108
122
  end
109
123
  end
110
124
 
111
- def args_to_strings
112
- if @args.is_a? Range
113
- [""]
114
- elsif @args[0].is_a? Hash
125
+ def args_to_string
126
+ case @args[:type]
127
+ when :list
115
128
  r = []
116
- @args.each do |arg|
129
+ @args[:arguments].each do |arg|
117
130
  if arg[:optional]
118
131
  r << "[" + arg[:name] + "]"
119
132
  else
120
- r << arg[:name]
133
+ r << "<" + arg[:name] + ">"
121
134
  end
122
135
  end
123
- r
124
- else
125
- [""]
136
+ r.join(' ')
137
+ when :choice, :range
138
+ ""
139
+ when :splat
140
+ "<#{@args[:base_name]}1> ..."
126
141
  end
127
142
  end
128
143
 
129
- def options_to_strings
130
- if @args.is_a? Range
131
- [@args.to_s]
132
- elsif @args[0].is_a? Hash
133
- ['']
134
- else
135
- @args
144
+ def options_to_string
145
+ case @args[:type]
146
+ when :list, :splat
147
+ ''
148
+ when :choice
149
+ '(' + @args[:items].join(', ') + ')'
150
+ when :range
151
+ '(' + @args[:range].to_s + ')'
136
152
  end
137
153
  end
138
154
 
139
155
  def to_h
140
156
  {
141
- 'names' => Clive::Array.new(names_to_strings),
157
+ 'names' => names_to_strings,
142
158
  'desc' => @desc,
143
- 'args' => Clive::Array.new(args_to_strings),
144
- 'options' => Clive::Array.new(options_to_strings)
159
+ 'args' => args_to_string,
160
+ 'options' => options_to_string
145
161
  }
146
162
  end
147
163
 
164
+
165
+ # Attempts to fill +self+ with values from +input+, giving priority to
166
+ # true, then false. If insufficient input to fill all false will use nil.
167
+ #
168
+ # @param [Array] input array of values to fill +self+ with
169
+ # @return [Array] filled array
170
+ #
171
+ # @example
172
+ #
173
+ # [true, false, false, true].optimise_fill(["a", "b", "c"])
174
+ # #=> ["a", "b", nil, "c"]
175
+ #
176
+ #
177
+ def optimise_fill(input, match)
178
+ diff = input.size - match.reject{|i| i == false}.size
179
+
180
+ result = []
181
+ match.each_index do |i|
182
+ curr_item = match[i]
183
+ if curr_item == true
184
+ result << input.shift
185
+ else
186
+ if diff > 0
187
+ result << input.shift
188
+ diff -= 1
189
+ else
190
+ result << nil
191
+ end
192
+ end
193
+ end
194
+ result
195
+ end
196
+
197
+
148
198
  end
149
199
  end
@@ -91,6 +91,10 @@ module Clive
91
91
  "desc" => @desc
92
92
  }
93
93
  end
94
+
95
+ def inspect
96
+ "#<#{self.class.name} [#{@names.join(', ')}]>"
97
+ end
94
98
 
95
99
  end
96
100
  end
@@ -5,7 +5,7 @@ module Clive
5
5
  #
6
6
  # @example
7
7
  #
8
- # require 'clive/parser'
8
+ # require 'clive'
9
9
  #
10
10
  # class CLI
11
11
  # include Clive::Parser
@@ -29,7 +29,7 @@ module Clive
29
29
  def self.included(klass)
30
30
  klass.instance_variable_set("@klass", klass)
31
31
  klass.extend(self)
32
- klass.instance_variable_set "@base", Clive::Command.new(true)
32
+ klass.instance_variable_set "@base", Clive::Command.setup(klass)
33
33
  end
34
34
 
35
35
  # @return [Clive::Command]
@@ -72,23 +72,13 @@ module Clive
72
72
  base.help_formatter(*args, &block)
73
73
  end
74
74
 
75
-
76
- # @see http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
77
- # or because that doesn't exist anymore from this mirror
78
- # http://viewsourcecode.org/why/hacking/seeingMetaclassesClearly.html
79
- #
80
- def meta_def(name, &blk)
81
- (class << self; self; end).instance_eval { define_method(name, &blk) }
82
- end
83
-
75
+ # This is a bit nicer, I think, for defining CLIs.
84
76
  def option_var(name, value=nil)
85
- @klass.meta_def(name) do
86
- instance_variable_get("@#{name}")
87
- end
88
- @klass.meta_def("#{name}=") do |val|
89
- instance_variable_set("@#{name}", val)
77
+ if value
78
+ @klass.class_attr_accessor name => value
79
+ else
80
+ @klass.class_attr_accessor name
90
81
  end
91
- instance_variable_set("@#{name}", value)
92
82
  end
93
83
 
94
84
  # Create a new hash which is accessible to the options in the new class
@@ -36,7 +36,7 @@ module Clive
36
36
  # Turn +@tokens+ into an array, this ensures that shorts are split
37
37
  # as is expected
38
38
  #
39
- # @return [::Array] array representation of tokens held
39
+ # @return [Array] array representation of tokens held
40
40
  def array
41
41
  return [] unless self.tokens
42
42
  arr = []
@@ -60,7 +60,7 @@ module Clive
60
60
  # Strings beginning with --, eg. --verbose become [:long, "verbose"].
61
61
  # Strings which begin with neither become [:word, "value"].
62
62
  #
63
- # @return [::Array] the tokens that are held
63
+ # @return [Array] the tokens that are held
64
64
  def tokens
65
65
  t = []
66
66
  self.each do |i|
@@ -105,7 +105,7 @@ module Clive
105
105
 
106
106
  # Test whether an array is a token
107
107
  #
108
- # @param [::Array]
108
+ # @param [Array]
109
109
  # @return [Boolean]
110
110
  #
111
111
  # @example
@@ -1,3 +1,3 @@
1
1
  module Clive
2
- VERSION = '0.7.2'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -3,11 +3,11 @@ require 'spec_helper'
3
3
  describe Clive::Command do
4
4
 
5
5
  context "when creating a base command" do
6
- subject { Clive::Command.new(true) {} }
6
+ subject { Clive::Command.setup(Class.new) {} }
7
7
  end
8
8
 
9
9
  subject {
10
- Clive::Command.new([:co, :comm], "A command") do
10
+ Clive::Command.new([:co, :comm], "A command", Class.new) do
11
11
  bool(:boo) {}
12
12
  switch(:swi) {}
13
13
  flag(:fla) {}
@@ -19,7 +19,7 @@ describe Clive::Command do
19
19
 
20
20
  describe "#initialize" do
21
21
  subject {
22
- Clive::Command.new([:com], "A command") do
22
+ Clive::Command.new([:com], "A command", Class.new) do
23
23
  flag(:test)
24
24
  end
25
25
  }
@@ -36,11 +36,6 @@ describe Clive::Command do
36
36
  }.should raise_error Clive::NoOptionError
37
37
  end
38
38
 
39
- it "sets the help formatter to :default" do
40
- formatter = subject.instance_variable_get("@help_formatter")
41
- formatter.should == subject.help_formatter(:default)
42
- end
43
-
44
39
  it "doesn't run the block given" do
45
40
  subject.flags.size.should == 0
46
41
  end
@@ -97,21 +92,6 @@ describe Clive::Command do
97
92
  end
98
93
  end
99
94
 
100
- describe "#tokenize" do
101
- it "returns an array of tokens" do
102
- subject.find
103
- res = [
104
- [:switch, subject.bools.find {|i| i.names == ["boo"] }],
105
- [:argument, "what"]
106
- ]
107
- subject.tokenize(%w(--boo what)).should == res
108
- end
109
- end
110
-
111
- describe "#parse" do
112
- it "returns an array of tokens"
113
- end
114
-
115
95
  describe "#to_h" do
116
96
  it "returns a hash of data for help formatting" do
117
97
  hsh = {'names' => subject.names, 'desc' => subject.desc}
@@ -214,7 +194,7 @@ describe Clive::Command do
214
194
 
215
195
  describe "#build_help" do
216
196
  it "adds a switch for help" do
217
- subject.options = Clive::Array.new
197
+ subject.options = []
218
198
  subject.options.should be_empty
219
199
  subject.build_help
220
200
  subject.options.map(&:names).should include ['h', 'help']
@@ -227,7 +207,7 @@ describe Clive::Command do
227
207
  Usage: rspec co, comm [options]
228
208
 
229
209
  Options:
230
- -h, --help \e[90mDisplay help\e[0m
210
+ -h, --help \e[90mDisplay help\e[0m
231
211
  EOS
232
212
 
233
213
  subject.help.should == help
@@ -2,15 +2,9 @@ require 'spec_helper'
2
2
 
3
3
  describe Clive::Flag do
4
4
 
5
- subject { Clive::Flag.new([:S, :say], "Say something", ["WORD(S)"]) {|i| $stdout.puts i } }
5
+ subject { Clive::Flag.new([:S, :say], "Say something", "WORD(S)") {|i| $stdout.puts i } }
6
6
 
7
7
  it_behaves_like "an option"
8
-
9
- describe "#args" do
10
- it "returns a hash with argument(s)" do
11
- subject.args.should == [{:name => "WORD(S)", :optional => false}]
12
- end
13
- end
14
8
 
15
9
  describe "#run" do
16
10
  it "calls the block with the argument" do
@@ -19,12 +13,6 @@ describe Clive::Flag do
19
13
  end
20
14
  end
21
15
 
22
- describe "#args_to_strings" do
23
- it "converts the arguments to strings" do
24
- subject.args_to_strings.should == ["WORD(S)"]
25
- end
26
- end
27
-
28
16
  describe "#arg_size" do
29
17
  context "when choice is available" do
30
18
  it "returns 1" do
@@ -34,7 +22,7 @@ describe Clive::Flag do
34
22
  end
35
23
 
36
24
  context "when arguments are required" do
37
- subject { Clive::Flag.new([:n], "Description", ["REQ [OPT] REQ2 [OPT2] [OPT3]"]) }
25
+ subject { Clive::Flag.new([:n], "Description", "REQ [OPT] REQ2 [OPT2] [OPT3]") }
38
26
 
39
27
  it "returns the number of all arguments" do
40
28
  subject.arg_size(:all).should == 5
@@ -50,35 +38,77 @@ describe Clive::Flag do
50
38
  end
51
39
  end
52
40
 
53
- describe "#options_to_strings" do
54
- context "when @args is a range" do
55
- it "returns array with range as string" do
56
- subject.args = 1..4
57
- subject.options_to_strings.should == ["1..4"]
41
+ describe "#args_to_string" do
42
+
43
+ context "when a list of options" do
44
+ it "returns the arguments as a string" do
45
+ subject.args = "first [second]"
46
+ subject.args_to_string.should == "<first> [second]"
47
+ end
48
+ end
49
+
50
+ context "when a splat as option" do
51
+ it "returns the argument and ellipsis" do
52
+ subject.args = "arg..."
53
+ subject.args_to_string.should == "<arg1> ..."
54
+ end
55
+ end
56
+
57
+ context "when a choice of options" do
58
+ it "returns an empty string" do
59
+ subject.args = %w(a b c)
60
+ subject.args_to_string.should == ""
61
+ end
62
+ end
63
+
64
+ context "when a range of options" do
65
+ it "returns an empty string" do
66
+ subject.args = 1..5
67
+ subject.args_to_string.should == ""
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ describe "#options_to_string" do
74
+
75
+ context "when a list of options" do
76
+ it "returns an empty string" do
77
+ subject.args = "first [second]"
78
+ subject.options_to_string.should == ""
58
79
  end
59
80
  end
60
81
 
61
- context "when @args is a hash" do
62
- it "converts the options to strings" do
63
- subject.options_to_strings.should == [""]
82
+ context "when a splat as option" do
83
+ it "returns an empty string" do
84
+ subject.args = "arg..."
85
+ subject.options_to_string.should == ""
64
86
  end
65
87
  end
66
88
 
67
- context "when @args is an array" do
68
- it "returns the array" do
69
- subject.args = %w(1 2 3 4)
70
- subject.options_to_strings.should == %w(1 2 3 4)
89
+ context "when a choice of options" do
90
+ it "returns the choices joined" do
91
+ subject.args = %w(a b c)
92
+ subject.options_to_string.should == "(a, b, c)"
71
93
  end
72
94
  end
95
+
96
+ context "when a range of options" do
97
+ it "returns a string representation of the range" do
98
+ subject.args = 1..5
99
+ subject.options_to_string.should == "(1..5)"
100
+ end
101
+ end
102
+
73
103
  end
74
104
 
75
105
  describe "#to_h" do
76
106
  it "returns a hash" do
77
107
  hsh = {
78
- "names" => Clive::Array.new(%w(-S --say)),
108
+ "names" => %w(-S --say),
79
109
  "desc" => "Say something",
80
- "args" => Clive::Array.new(["WORD(S)"]),
81
- "options" => Clive::Array.new([""])
110
+ "args" => "<WORD(S)>",
111
+ "options" => ""
82
112
  }
83
113
  subject.to_h.should == hsh
84
114
  end
@@ -54,26 +54,26 @@ describe Clive::Formatter do
54
54
 
55
55
  describe "#format" do
56
56
  it "generates the help" do
57
- formatter = Clive::Command.new(true).help_formatter(:white)
57
+ formatter = Clive::Command.setup(Class.new).help_formatter(:white)
58
58
  options = [
59
59
  Clive::Switch.new([:t, :test], "A test switch"),
60
60
  Clive::Bool.new([:boolean], "A bool", true),
61
61
  Clive::Bool.new([:boolean], "A bool", false),
62
- Clive::Flag.new([:args], "With args", ["ARG [OPT]"]),
63
- Clive::Flag.new([:choose], "With options", [["a", "b", "c"]])
62
+ Clive::Flag.new([:args], "With args", "ARG [OPT]"),
63
+ Clive::Flag.new([:choose], "With options", ["a", "b", "c"])
64
64
  ]
65
- command = Clive::Command.new([:command], "A command")
65
+ command = Clive::Command.new([:command], "A command", Class.new)
66
66
  result = <<EOS
67
67
  head
68
68
 
69
69
  Commands:
70
- command A command
70
+ command A command
71
71
 
72
72
  Options:
73
- -t, --test A test switch
74
- --[no-]boolean A bool
75
- --args ARG [OPT] With args \e[1m\e[0m
76
- --choose With options \e[1m(a, b, c)\e[0m
73
+ -t, --test A test switch
74
+ --[no-]boolean A bool
75
+ --args <ARG> [OPT] With args \e[1m\e[0m
76
+ --choose With options \e[1m(a, b, c)\e[0m
77
77
 
78
78
  foot
79
79
  EOS
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 7
8
- - 2
9
- version: 0.7.2
7
+ - 8
8
+ - 0
9
+ version: 0.8.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Joshua Hawxwell
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-07 00:00:00 +00:00
17
+ date: 2011-02-24 00:00:00 +00:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -32,6 +32,21 @@ dependencies:
32
32
  version: 0.2.1
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: attr_plus
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ - 2
46
+ - 2
47
+ version: 0.2.2
48
+ type: :runtime
49
+ version_requirements: *id002
35
50
  description: " Clive provides a DSL for building command line interfaces. It allows \n you to define commands, switches, flags (switches with options) and \n boolean switches, it then parses the input and runs the correct blocks.\n"
36
51
  email: m@hawx.me
37
52
  executables: []
@@ -42,12 +57,10 @@ extra_rdoc_files: []
42
57
 
43
58
  files:
44
59
  - README.md
45
- - Rakefile
46
60
  - LICENSE
47
61
  - lib/clive/bool.rb
48
62
  - lib/clive/command.rb
49
63
  - lib/clive/exceptions.rb
50
- - lib/clive/ext.rb
51
64
  - lib/clive/flag.rb
52
65
  - lib/clive/formatter.rb
53
66
  - lib/clive/option.rb
@@ -59,8 +72,6 @@ files:
59
72
  - lib/clive.rb
60
73
  - spec/clive/bool_spec.rb
61
74
  - spec/clive/command_spec.rb
62
- - spec/clive/exceptions_spec.rb
63
- - spec/clive/ext_spec.rb
64
75
  - spec/clive/flag_spec.rb
65
76
  - spec/clive/formatter_spec.rb
66
77
  - spec/clive/option_spec.rb
@@ -105,8 +116,6 @@ summary: A DSL for building command line interfaces.
105
116
  test_files:
106
117
  - spec/clive/bool_spec.rb
107
118
  - spec/clive/command_spec.rb
108
- - spec/clive/exceptions_spec.rb
109
- - spec/clive/ext_spec.rb
110
119
  - spec/clive/flag_spec.rb
111
120
  - spec/clive/formatter_spec.rb
112
121
  - spec/clive/option_spec.rb
data/Rakefile DELETED
@@ -1,9 +0,0 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- require 'rake/testtask'
5
- Rake::TestTask.new(:test) do |test|
6
- test.libs << 'lib' << 'test'
7
- test.pattern = 'test/**/test_*.rb'
8
- test.verbose = true
9
- end
@@ -1,71 +0,0 @@
1
- module Clive
2
- class Array < ::Array
3
-
4
- # If passed a Symbol or String will get the option or command with that name.
5
- # Otherwise does what you expect of an Array (see ::Array#[])
6
- #
7
- # @param [Symbol, String, Integer, Range] val name or index of item to return
8
- # @return the item that has been found
9
- def [](val)
10
- val = val.to_s if val.is_a? Symbol
11
- if val.is_a? String
12
- self.find_all {|i| i.names.include?(val)}[0]
13
- else
14
- super
15
- end
16
- end
17
-
18
- # Attempts to fill +self+ with values from +input+, giving priority to
19
- # true, then false. If insufficient input to fill all false will use nil.
20
- #
21
- # @param [Array] input array of values to fill +self+ with
22
- # @return [Array] filled array
23
- #
24
- # @example
25
- #
26
- # [true, false, false, true].optimise_fill(["a", "b", "c"])
27
- # #=> ["a", "b", nil, "c"]
28
- #
29
- #
30
- def optimise_fill(input)
31
- match = self
32
- diff = input.size - match.reject{|i| i == false}.size
33
-
34
- result = []
35
- match.each_index do |i|
36
- curr_item = match[i]
37
- if curr_item == true
38
- result << input.shift
39
- else
40
- if diff > 0
41
- result << input.shift
42
- diff -= 1
43
- else
44
- result << nil
45
- end
46
- end
47
- end
48
- result
49
- end
50
-
51
- alias_method :_join, :join
52
-
53
- def join(*args)
54
- case args.size
55
- when 1
56
- self._join(args[0])
57
-
58
- when 2 # use second for last eg. 1, 2 and 3
59
- self[0..-2]._join(args[0]) << args[1] << self[-1]
60
-
61
- when 3 # prepend and append 1st and 3rd eg. (1, 2, 3)
62
- if self[0] != ""
63
- args[0] << self._join(args[1]) << args[2]
64
- else
65
- ""
66
- end
67
- end
68
- end
69
-
70
- end
71
- end
@@ -1 +0,0 @@
1
- require 'spec_helper'
@@ -1 +0,0 @@
1
- require 'spec_helper'