clive 0.6.2 → 0.7.0
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/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
|