clive 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +129 -126
- data/lib/clive.rb +30 -119
- data/lib/clive/bool.rb +32 -34
- data/lib/clive/command.rb +171 -54
- data/lib/clive/exceptions.rb +2 -2
- data/lib/clive/ext.rb +21 -3
- data/lib/clive/flag.rb +80 -67
- data/lib/clive/formatter.rb +180 -0
- data/lib/clive/option.rb +41 -25
- data/lib/clive/output.rb +14 -18
- data/lib/clive/parser.rb +79 -16
- data/lib/clive/switch.rb +8 -17
- data/lib/clive/tokens.rb +1 -1
- data/lib/clive/version.rb +2 -2
- data/spec/clive/bool_spec.rb +54 -0
- data/spec/clive/command_spec.rb +260 -0
- data/spec/clive/exceptions_spec.rb +1 -0
- data/spec/clive/ext_spec.rb +1 -0
- data/spec/clive/flag_spec.rb +84 -0
- data/spec/clive/formatter_spec.rb +108 -0
- data/spec/clive/option_spec.rb +34 -0
- data/spec/clive/output_spec.rb +5 -0
- data/spec/clive/parser_spec.rb +106 -0
- data/spec/clive/switch_spec.rb +14 -0
- data/spec/clive/tokens_spec.rb +38 -0
- data/spec/shared_specs.rb +16 -0
- data/spec/spec_helper.rb +12 -0
- metadata +34 -8
data/lib/clive/bool.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Clive
|
2
|
+
|
3
3
|
# A switch which can be triggered with either --no-[name] and --[name].
|
4
|
-
# The '
|
4
|
+
# The 'truth' of this is then passed to the block.
|
5
|
+
#
|
5
6
|
class Bool < Option
|
6
7
|
attr_accessor :truth
|
7
8
|
|
@@ -13,27 +14,27 @@ class Clive
|
|
13
14
|
# +short+ and/or +desc+ can be omitted when creating a Boolean, all
|
14
15
|
# other arguments must be present.
|
15
16
|
#
|
16
|
-
# @
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
17
|
+
# @param names [Array[Symbol]]
|
18
|
+
# Names that the boolean switch can be called with, must include
|
19
|
+
# a long name (eg. 2 or more characters) so that the --no- can
|
20
|
+
# be prefixed.
|
21
|
+
#
|
22
|
+
# @param desc [String]
|
23
|
+
# A description of the bool.
|
24
|
+
#
|
25
|
+
# @param truth [true, false]
|
26
|
+
# Truth of the switch to create.
|
21
27
|
#
|
22
|
-
# @yield [
|
23
|
-
# @raise [MissingLongName]
|
28
|
+
# @yield [true, false] A block to be run when the switch is triggered
|
29
|
+
# @raise [MissingLongName] Raised when a long name is not given
|
24
30
|
#
|
25
|
-
def initialize(
|
31
|
+
def initialize(names, desc, truth, &block)
|
26
32
|
@names = []
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
else
|
33
|
-
@names << "no-#{i.to_s}" if i.length > 1
|
34
|
-
end
|
35
|
-
when String
|
36
|
-
@desc = i
|
33
|
+
names.each do |i|
|
34
|
+
if truth
|
35
|
+
@names << i.to_s
|
36
|
+
else
|
37
|
+
@names << "no-#{i.to_s}" if i.length > 1
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
@@ -42,27 +43,24 @@ class Clive
|
|
42
43
|
raise MissingLongName, @names[0]
|
43
44
|
end
|
44
45
|
|
46
|
+
@desc = desc
|
45
47
|
@truth = truth
|
46
48
|
@block = block
|
47
49
|
end
|
48
50
|
|
49
|
-
# Run the block with
|
51
|
+
# Run the block with the switches truth.
|
50
52
|
def run
|
51
53
|
@block.call(@truth)
|
52
54
|
end
|
53
55
|
|
54
|
-
#
|
55
|
-
# @
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
spaces = 1 if spaces < 1
|
63
|
-
s = spaces(spaces)
|
64
|
-
p = spaces(prepend)
|
65
|
-
"#{p}#{n}#{s}#{@desc}"
|
56
|
+
# Should only return a hash when this is the 'true' switch.
|
57
|
+
# @see Clive::Option#to_h
|
58
|
+
def to_h
|
59
|
+
if @truth
|
60
|
+
{'names' => names_to_strings(true), 'desc' => @desc}
|
61
|
+
else
|
62
|
+
nil
|
63
|
+
end
|
66
64
|
end
|
67
65
|
|
68
66
|
end
|
data/lib/clive/command.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
module Clive
|
2
2
|
|
3
3
|
# A string which describes the command to execute
|
4
4
|
# eg. git add
|
@@ -8,18 +8,18 @@ class Clive
|
|
8
8
|
|
9
9
|
attr_accessor :options, :commands
|
10
10
|
attr_accessor :argv, :base
|
11
|
-
|
11
|
+
attr_reader :names, :current_desc
|
12
12
|
|
13
13
|
# Create a new Command instance
|
14
14
|
#
|
15
15
|
# @overload initialize(base, &block)
|
16
16
|
# Creates a new base Command to house everything else
|
17
|
-
# @param [Boolean]
|
17
|
+
# @param base [Boolean] whether the command is the base
|
18
18
|
#
|
19
|
-
# @overload initialize(
|
19
|
+
# @overload initialize(names, desc, &block)
|
20
20
|
# Creates a new Command as part of the base Command
|
21
|
-
# @param [Symbol]
|
22
|
-
# @param [String]
|
21
|
+
# @param names [Symbol] the name of the command
|
22
|
+
# @param desc [String] the description of the command
|
23
23
|
#
|
24
24
|
# @yield A block to run, containing switches, flags and commands
|
25
25
|
#
|
@@ -47,10 +47,15 @@ class Clive
|
|
47
47
|
@block = block
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
@header
|
50
|
+
# Create basic header "Usage: filename [command] [options]
|
51
|
+
# or "Usage: filename commandname(s) [options]
|
52
|
+
@header = "Usage: #{File.basename($0, '.*')} " <<
|
53
|
+
(@base ? "[command]" : @names.join(', ')) <<
|
54
|
+
" [options]"
|
55
|
+
|
53
56
|
@footer = nil
|
57
|
+
@current_desc = ""
|
58
|
+
help_formatter :default
|
54
59
|
|
55
60
|
self.build_help
|
56
61
|
end
|
@@ -146,7 +151,7 @@ class Clive
|
|
146
151
|
pre_command << i
|
147
152
|
end
|
148
153
|
end
|
149
|
-
|
154
|
+
|
150
155
|
post_command = Tokens.new(tokens.array - pre_command - [command])
|
151
156
|
pre_command_tokens = parse(pre_command)
|
152
157
|
r = pre_command_tokens
|
@@ -179,7 +184,7 @@ class Clive
|
|
179
184
|
else
|
180
185
|
if k == :word
|
181
186
|
# add to last flag?
|
182
|
-
if r.last && r.last[0] == :flag && r.last.size - 2 < r.last[1].
|
187
|
+
if r.last && r.last[0] == :flag && r.last.size - 2 < r.last[1].arg_size
|
183
188
|
r.last.push(v)
|
184
189
|
else
|
185
190
|
r << [:argument, v]
|
@@ -192,13 +197,16 @@ class Clive
|
|
192
197
|
r
|
193
198
|
end
|
194
199
|
|
195
|
-
|
200
|
+
def to_h
|
201
|
+
{
|
202
|
+
'names' => @names,
|
203
|
+
'desc' => @desc
|
204
|
+
}
|
205
|
+
end
|
196
206
|
|
197
|
-
|
198
|
-
@option_missing = block
|
199
|
-
end
|
207
|
+
|
200
208
|
|
201
|
-
# @group
|
209
|
+
# @group DSL
|
202
210
|
|
203
211
|
# Add a new command to +@commands+
|
204
212
|
#
|
@@ -211,19 +219,35 @@ class Clive
|
|
211
219
|
# and flags
|
212
220
|
#
|
213
221
|
def command(*args, &block)
|
214
|
-
@commands << Command.new(*args, &block)
|
222
|
+
@commands << Command.new(*args, @current_desc, &block)
|
223
|
+
@current_desc = ""
|
215
224
|
end
|
216
225
|
|
217
226
|
# Add a new switch to +@switches+
|
218
227
|
# @see Switch#initialize
|
219
228
|
def switch(*args, &block)
|
220
|
-
@options << Switch.new(
|
229
|
+
@options << Switch.new(args, @current_desc, &block)
|
230
|
+
@current_desc = ""
|
221
231
|
end
|
222
232
|
|
223
233
|
# Adds a new flag to +@flags+
|
224
234
|
# @see Flag#initialize
|
225
235
|
def flag(*args, &block)
|
226
|
-
|
236
|
+
names = []
|
237
|
+
arg = []
|
238
|
+
args.each do |i|
|
239
|
+
if i.is_a? Symbol
|
240
|
+
names << i
|
241
|
+
else
|
242
|
+
if i[:arg]
|
243
|
+
arg << i[:arg]
|
244
|
+
else
|
245
|
+
arg << i[:args]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
@options << Flag.new(names, @current_desc, arg, &block)
|
250
|
+
@current_desc = ""
|
227
251
|
end
|
228
252
|
|
229
253
|
# Creates a boolean switch. This is done by adding two switches of
|
@@ -232,21 +256,48 @@ class Clive
|
|
232
256
|
#
|
233
257
|
# @see Bool#initialize
|
234
258
|
def bool(*args, &block)
|
235
|
-
@options << Bool.new(
|
236
|
-
@options << Bool.new(
|
259
|
+
@options << Bool.new(args, @current_desc, true, &block)
|
260
|
+
@options << Bool.new(args, @current_desc, false, &block)
|
261
|
+
@current_desc= ""
|
237
262
|
end
|
238
263
|
|
239
|
-
|
240
|
-
|
241
|
-
#
|
242
|
-
#
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
264
|
+
# Add a description for the next option in the class. Or acts as an
|
265
|
+
# accessor for @desc.
|
266
|
+
#
|
267
|
+
# @example
|
268
|
+
#
|
269
|
+
# class CLI
|
270
|
+
# include Clive::Parser
|
271
|
+
#
|
272
|
+
# desc 'Force build docs'
|
273
|
+
# switch :force do
|
274
|
+
# # code
|
275
|
+
# end
|
276
|
+
# end
|
277
|
+
#
|
278
|
+
def desc(str=nil)
|
279
|
+
if str
|
280
|
+
@current_desc = str
|
281
|
+
else
|
282
|
+
@desc
|
247
283
|
end
|
248
284
|
end
|
249
285
|
|
286
|
+
# Define a block to execute when the option to execute cannot be found.
|
287
|
+
#
|
288
|
+
# @example
|
289
|
+
#
|
290
|
+
# class CLI
|
291
|
+
# include Clive::Parser
|
292
|
+
#
|
293
|
+
# option_missing do |name|
|
294
|
+
# puts "#{name} couldn't be found"
|
295
|
+
# end
|
296
|
+
#
|
297
|
+
def option_missing(&block)
|
298
|
+
@option_missing = block
|
299
|
+
end
|
300
|
+
|
250
301
|
# Set the header
|
251
302
|
def header(val)
|
252
303
|
@header = val
|
@@ -257,40 +308,106 @@ class Clive
|
|
257
308
|
@footer = val
|
258
309
|
end
|
259
310
|
|
260
|
-
|
261
|
-
a = @names.sort.join(', ')
|
262
|
-
b = @desc
|
263
|
-
s = spaces(width-a.length)
|
264
|
-
p = spaces(prepend)
|
265
|
-
"#{p}#{a}#{s}#{b}"
|
266
|
-
end
|
311
|
+
# @group Help
|
267
312
|
|
313
|
+
# This actually creates a switch with "-h" and "--help" that controls
|
314
|
+
# the help on this command.
|
315
|
+
def build_help
|
316
|
+
@options << Switch.new([:h, :help], "Display help") do
|
317
|
+
puts self.help
|
318
|
+
exit 0
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
268
322
|
# Generate the summary for help, show all flags and switches, but do not
|
269
323
|
# show the flags and switches within each command. Should also prepend the
|
270
324
|
# header and append the footer if set.
|
271
|
-
def help
|
272
|
-
|
325
|
+
def help
|
326
|
+
@formatter.format(@header, @footer, @commands, @options)
|
327
|
+
end
|
328
|
+
|
329
|
+
# This allows you to define how the output from #help looks.
|
330
|
+
#
|
331
|
+
# For this you have access to several tokens which are evaluated in an object
|
332
|
+
# with the correct values, this means you are able to use #join on arrays or
|
333
|
+
# prepend, etc. The variables (tokens) are:
|
334
|
+
#
|
335
|
+
# * prepend - a string of spaces as specified when #help_formatter is called
|
336
|
+
# * names - an array of names for the option
|
337
|
+
# * spaces - a string of spaces to align the descriptions properly
|
338
|
+
# * desc - a string of the description for the option
|
339
|
+
#
|
340
|
+
# And for flags you have access to:
|
341
|
+
#
|
342
|
+
# * args - an array of arguments for the flag
|
343
|
+
# * options - an array of options to choose from
|
344
|
+
#
|
345
|
+
#
|
346
|
+
# @overload help_formatter(args, &block)
|
347
|
+
# Create a new help formatter to use.
|
348
|
+
# @param args [Hash]
|
349
|
+
# @option args [Integer] :width Width before flexible spaces
|
350
|
+
# @option args [Integer] :prepend Width of spaces to prepend with
|
351
|
+
#
|
352
|
+
# @overload help_formatter(name)
|
353
|
+
# Use an existing help formatter.
|
354
|
+
# @param name [Symbol] name of the formatter (either +:default+ or +:white+)
|
355
|
+
#
|
356
|
+
#
|
357
|
+
# @example
|
358
|
+
#
|
359
|
+
# CLI.help_formatter do |h|
|
360
|
+
#
|
361
|
+
# h.switch "{prepend}{names.join(', ')} {spaces}{desc.grey}"
|
362
|
+
# h.bool "{prepend}{names.join(', ')} {spaces}{desc.grey}"
|
363
|
+
# h.flag "{prepend}{names.join(', ')} {args.join(' ')} {spaces}{desc.grey}"
|
364
|
+
# h.command "{prepend}{names.join(', ')} {spaces}{desc.grey}"
|
365
|
+
#
|
366
|
+
# end
|
367
|
+
#
|
368
|
+
#
|
369
|
+
def help_formatter(*args, &block)
|
370
|
+
if block_given?
|
371
|
+
width = 30
|
372
|
+
prepend = 5
|
273
373
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
374
|
+
unless args.empty?
|
375
|
+
args[0].each do |k,v|
|
376
|
+
case k
|
377
|
+
when :width
|
378
|
+
width = v
|
379
|
+
when :prepend
|
380
|
+
prepend = v
|
381
|
+
end
|
382
|
+
end
|
279
383
|
end
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
384
|
+
|
385
|
+
@formatter = Formatter.new(width, prepend)
|
386
|
+
block.call(@formatter)
|
387
|
+
@formatter
|
388
|
+
else
|
389
|
+
case args[0]
|
390
|
+
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
|
+
|
399
|
+
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
|
+
|
286
408
|
end
|
287
409
|
end
|
288
|
-
|
289
|
-
summary << "\n#{@footer}\n" if @footer
|
290
|
-
|
291
|
-
summary
|
292
410
|
end
|
293
411
|
|
294
|
-
|
295
412
|
end
|
296
413
|
end
|
data/lib/clive/exceptions.rb
CHANGED
data/lib/clive/ext.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Clive
|
3
2
|
class Array < ::Array
|
4
3
|
|
5
4
|
# If passed a Symbol or String will get the option or command with that name.
|
@@ -49,5 +48,24 @@ class Clive
|
|
49
48
|
result
|
50
49
|
end
|
51
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
|
+
|
52
70
|
end
|
53
|
-
end
|
71
|
+
end
|