clive 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/clive.rb +3 -1
- data/lib/clive/command.rb +228 -131
- data/lib/clive/flag.rb +110 -60
- data/lib/clive/option.rb +4 -0
- data/lib/clive/parser.rb +7 -17
- data/lib/clive/tokens.rb +3 -3
- data/lib/clive/version.rb +1 -1
- data/spec/clive/command_spec.rb +5 -25
- data/spec/clive/flag_spec.rb +59 -29
- data/spec/clive/formatter_spec.rb +9 -9
- metadata +19 -10
- data/Rakefile +0 -9
- data/lib/clive/ext.rb +0 -71
- data/spec/clive/exceptions_spec.rb +0 -1
- data/spec/clive/ext_spec.rb +0 -1
data/lib/clive.rb
CHANGED
@@ -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
|
#
|
data/lib/clive/command.rb
CHANGED
@@ -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(
|
27
|
-
@argv
|
28
|
-
@names
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
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
|
-
@
|
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 [
|
64
|
+
# @return [Array] all bools in this command
|
64
65
|
def bools
|
65
|
-
|
66
|
+
@options.find_all {|i| i.class == Bool}
|
66
67
|
end
|
67
68
|
|
68
|
-
# @return [
|
69
|
+
# @return [Array] all switches in this command
|
69
70
|
def switches
|
70
|
-
|
71
|
+
@options.find_all {|i| i.class == Switch}
|
71
72
|
end
|
72
73
|
|
73
|
-
# @return [
|
74
|
+
# @return [Array] all flags in this command
|
74
75
|
def flags
|
75
|
-
|
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
|
-
#
|
91
|
+
# Gets the type of the option which corresponds with the name given
|
91
92
|
#
|
92
|
-
# @param [
|
93
|
-
# @return [
|
93
|
+
# @param name [String]
|
94
|
+
# @return [Constant]
|
94
95
|
#
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
#
|
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 [
|
128
|
-
# @return [::
|
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
|
-
#
|
133
|
-
# #=> [[:
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
151
|
+
# @param arr [Array]
|
152
|
+
# @return [Array]
|
153
|
+
#
|
154
|
+
def array_to_tokens(arr)
|
155
|
+
result = []
|
141
156
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
167
|
+
result << [:word, a]
|
152
168
|
end
|
153
169
|
end
|
154
|
-
|
155
|
-
|
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
|
-
#
|
168
|
-
#
|
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
|
-
# @
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
218
|
+
tree << [:command, command, command.tokens_to_tree(arr[i+1..-1])]
|
219
|
+
i = l
|
191
220
|
end
|
192
221
|
else
|
193
|
-
|
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
|
-
|
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
|
339
|
+
arg = i[:arg]
|
244
340
|
else
|
245
|
-
arg
|
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 =
|
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
|
-
|
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
|
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
|
data/lib/clive/flag.rb
CHANGED
@@ -5,7 +5,7 @@ module Clive
|
|
5
5
|
# wget -t 10
|
6
6
|
#
|
7
7
|
class Flag < Option
|
8
|
-
|
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
|
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,
|
39
|
-
@names =
|
40
|
-
|
38
|
+
def initialize(names, desc, arguments, &block)
|
39
|
+
@names = names.map(&:to_s)
|
40
|
+
self.args = (arguments || "ARG")
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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 :
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
112
|
-
|
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
|
-
|
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
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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' =>
|
157
|
+
'names' => names_to_strings,
|
142
158
|
'desc' => @desc,
|
143
|
-
'args' =>
|
144
|
-
'options' =>
|
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
|
data/lib/clive/option.rb
CHANGED
data/lib/clive/parser.rb
CHANGED
@@ -5,7 +5,7 @@ module Clive
|
|
5
5
|
#
|
6
6
|
# @example
|
7
7
|
#
|
8
|
-
# require 'clive
|
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.
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
data/lib/clive/tokens.rb
CHANGED
@@ -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 [
|
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 [
|
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 [
|
108
|
+
# @param [Array]
|
109
109
|
# @return [Boolean]
|
110
110
|
#
|
111
111
|
# @example
|
data/lib/clive/version.rb
CHANGED
data/spec/clive/command_spec.rb
CHANGED
@@ -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
|
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 =
|
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
|
-
|
210
|
+
-h, --help \e[90mDisplay help\e[0m
|
231
211
|
EOS
|
232
212
|
|
233
213
|
subject.help.should == help
|
data/spec/clive/flag_spec.rb
CHANGED
@@ -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",
|
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",
|
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 "#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
subject.
|
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
|
62
|
-
it "
|
63
|
-
subject.
|
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
|
68
|
-
it "returns the
|
69
|
-
subject.args = %w(
|
70
|
-
subject.
|
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" =>
|
108
|
+
"names" => %w(-S --say),
|
79
109
|
"desc" => "Say something",
|
80
|
-
"args" =>
|
81
|
-
"options" =>
|
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
|
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",
|
63
|
-
Clive::Flag.new([:choose], "With options", [
|
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
|
-
|
70
|
+
command A command
|
71
71
|
|
72
72
|
Options:
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
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-
|
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
data/lib/clive/ext.rb
DELETED
@@ -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'
|
data/spec/clive/ext_spec.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|