claide 0.2.0 → 0.3.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.
- checksums.yaml +7 -0
- data/lib/claide.rb +6 -704
- metadata +6 -8
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d9085e38c36d1128b294f24e832a757683f3c9a2
|
4
|
+
data.tar.gz: 8d85b685edcf81f078b1961f164e34df77981573
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: df44b05b8e3e0fce7a7f9109155cf4e37b91c26ce7a48d1694f9df81165d1345e099eae6367bee698ebac620a43961a59d48199cc912b2b1151b33ce58024121
|
7
|
+
data.tar.gz: 8f78e80a5f12aa9a135698319bd5ca173798655784c2a79e63066f8f0768447768fe5b55bdf4690aac330f3f33cf850d489161199214bd357d16f98eb2759695
|
data/lib/claide.rb
CHANGED
@@ -4,714 +4,16 @@
|
|
4
4
|
# {CLAide::InformativeError}
|
5
5
|
#
|
6
6
|
module CLAide
|
7
|
+
|
7
8
|
# @return [String]
|
8
9
|
#
|
9
10
|
# CLAide’s version, following [semver](http://semver.org).
|
10
11
|
#
|
11
|
-
VERSION = '0.
|
12
|
-
|
13
|
-
# This class is responsible for parsing the parameters specified by the user,
|
14
|
-
# accessing individual parameters, and keep state by removing handled
|
15
|
-
# parameters.
|
16
|
-
#
|
17
|
-
class ARGV
|
18
|
-
|
19
|
-
# @param [Array<String>] argv
|
20
|
-
#
|
21
|
-
# A list of parameters. Each entry is ensured to be a string by calling
|
22
|
-
# `#to_s` on it.
|
23
|
-
#
|
24
|
-
def initialize(argv)
|
25
|
-
@entries = self.class.parse(argv)
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [Boolean]
|
29
|
-
#
|
30
|
-
# Returns wether or not there are any remaining unhandled parameters.
|
31
|
-
#
|
32
|
-
def empty?
|
33
|
-
@entries.empty?
|
34
|
-
end
|
35
|
-
|
36
|
-
# @return [Array<String>]
|
37
|
-
#
|
38
|
-
# A list of the remaining unhandled parameters, in the same format a user
|
39
|
-
# specifies it in.
|
40
|
-
#
|
41
|
-
# @example
|
42
|
-
#
|
43
|
-
# argv = CLAide::ARGV.new(['tea', '--no-milk', '--sweetner=honey'])
|
44
|
-
# argv.shift_argument # => 'tea'
|
45
|
-
# argv.remainder # => ['--no-milk', '--sweetner=honey']
|
46
|
-
#
|
47
|
-
def remainder
|
48
|
-
@entries.map do |type, (key, value)|
|
49
|
-
case type
|
50
|
-
when :arg
|
51
|
-
key
|
52
|
-
when :flag
|
53
|
-
"--#{'no-' if value == false}#{key}"
|
54
|
-
when :option
|
55
|
-
"--#{key}=#{value}"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# @return [Hash]
|
61
|
-
#
|
62
|
-
# A hash that consists of the remaining flags and options and their
|
63
|
-
# values.
|
64
|
-
#
|
65
|
-
# @example
|
66
|
-
#
|
67
|
-
# argv = CLAide::ARGV.new(['tea', '--no-milk', '--sweetner=honey'])
|
68
|
-
# argv.options # => { 'milk' => false, 'sweetner' => 'honey' }
|
69
|
-
#
|
70
|
-
def options
|
71
|
-
options = {}
|
72
|
-
@entries.each do |type, (key, value)|
|
73
|
-
options[key] = value unless type == :arg
|
74
|
-
end
|
75
|
-
options
|
76
|
-
end
|
77
|
-
|
78
|
-
# @return [Array<String>]
|
79
|
-
#
|
80
|
-
# A list of the remaining arguments.
|
81
|
-
#
|
82
|
-
# @example
|
83
|
-
#
|
84
|
-
# argv = CLAide::ARGV.new(['tea', 'white', '--no-milk', 'biscuit'])
|
85
|
-
# argv.shift_argument # => 'tea'
|
86
|
-
# argv.arguments # => ['white', 'biscuit']
|
87
|
-
#
|
88
|
-
def arguments
|
89
|
-
@entries.map { |type, value| value if type == :arg }.compact
|
90
|
-
end
|
91
|
-
|
92
|
-
# @return [Array<String>]
|
93
|
-
#
|
94
|
-
# A list of the remaining arguments.
|
95
|
-
#
|
96
|
-
# @note
|
97
|
-
#
|
98
|
-
# This version also removes the arguments from the remaining parameters.
|
99
|
-
#
|
100
|
-
# @example
|
101
|
-
#
|
102
|
-
# argv = CLAide::ARGV.new(['tea', 'white', '--no-milk', 'biscuit'])
|
103
|
-
# argv.arguments # => ['tea', 'white', 'biscuit']
|
104
|
-
# argv.arguments! # => ['tea', 'white', 'biscuit']
|
105
|
-
# argv.arguments # => []
|
106
|
-
#
|
107
|
-
def arguments!
|
108
|
-
arguments = []
|
109
|
-
while arg = shift_argument
|
110
|
-
arguments << arg
|
111
|
-
end
|
112
|
-
arguments
|
113
|
-
end
|
114
|
-
|
115
|
-
# @return [String]
|
116
|
-
#
|
117
|
-
# The first argument in the remaining parameters.
|
118
|
-
#
|
119
|
-
# @note
|
120
|
-
#
|
121
|
-
# This will remove the argument from the remaining parameters.
|
122
|
-
#
|
123
|
-
# @example
|
124
|
-
#
|
125
|
-
# argv = CLAide::ARGV.new(['tea', 'white'])
|
126
|
-
# argv.shift_argument # => 'tea'
|
127
|
-
# argv.arguments # => ['white']
|
128
|
-
#
|
129
|
-
def shift_argument
|
130
|
-
if index = @entries.find_index { |type, _| type == :arg }
|
131
|
-
entry = @entries[index]
|
132
|
-
@entries.delete_at(index)
|
133
|
-
entry.last
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# @return [Boolean, nil]
|
138
|
-
#
|
139
|
-
# Returns `true` if the flag by the specified `name` is among the
|
140
|
-
# remaining parameters and is not negated.
|
141
|
-
#
|
142
|
-
# @param [String] name
|
143
|
-
#
|
144
|
-
# The name of the flag to look for among the remaining parameters.
|
145
|
-
#
|
146
|
-
# @param [Boolean] default
|
147
|
-
#
|
148
|
-
# The value that is returned in case the flag is not among the remaining
|
149
|
-
# parameters.
|
150
|
-
#
|
151
|
-
# @note
|
152
|
-
#
|
153
|
-
# This will remove the flag from the remaining parameters.
|
154
|
-
#
|
155
|
-
# @example
|
156
|
-
#
|
157
|
-
# argv = CLAide::ARGV.new(['tea', '--no-milk', '--sweetner=honey'])
|
158
|
-
# argv.flag?('milk') # => false
|
159
|
-
# argv.flag?('milk') # => nil
|
160
|
-
# argv.flag?('milk', true) # => true
|
161
|
-
# argv.remainder # => ['tea', '--sweetner=honey']
|
162
|
-
#
|
163
|
-
def flag?(name, default = nil)
|
164
|
-
delete_entry(:flag, name, default)
|
165
|
-
end
|
166
|
-
|
167
|
-
# @return [String, nil]
|
168
|
-
#
|
169
|
-
# Returns the value of the option by the specified `name` is among the
|
170
|
-
# remaining parameters.
|
171
|
-
#
|
172
|
-
# @param [String] name
|
173
|
-
#
|
174
|
-
# The name of the option to look for among the remaining parameters.
|
175
|
-
#
|
176
|
-
# @param [String] default
|
177
|
-
#
|
178
|
-
# The value that is returned in case the option is not among the
|
179
|
-
# remaining parameters.
|
180
|
-
#
|
181
|
-
# @note
|
182
|
-
#
|
183
|
-
# This will remove the option from the remaining parameters.
|
184
|
-
#
|
185
|
-
# @example
|
186
|
-
#
|
187
|
-
# argv = CLAide::ARGV.new(['tea', '--no-milk', '--sweetner=honey'])
|
188
|
-
# argv.option('sweetner') # => 'honey'
|
189
|
-
# argv.option('sweetner') # => nil
|
190
|
-
# argv.option('sweetner', 'sugar') # => 'sugar'
|
191
|
-
# argv.remainder # => ['tea', '--no-milk']
|
192
|
-
#
|
193
|
-
def option(name, default = nil)
|
194
|
-
delete_entry(:option, name, default)
|
195
|
-
end
|
196
|
-
|
197
|
-
private
|
198
|
-
|
199
|
-
def delete_entry(requested_type, requested_key, default)
|
200
|
-
result = nil
|
201
|
-
@entries.delete_if do |type, (key, value)|
|
202
|
-
if requested_key == key && requested_type == type
|
203
|
-
result = value
|
204
|
-
true
|
205
|
-
end
|
206
|
-
end
|
207
|
-
result.nil? ? default : result
|
208
|
-
end
|
209
|
-
|
210
|
-
# @return [Array<Array>]
|
211
|
-
#
|
212
|
-
# A list of tuples for each parameter, where the first entry is the
|
213
|
-
# `type` and the second entry the actual parsed parameter.
|
214
|
-
#
|
215
|
-
# @example
|
216
|
-
#
|
217
|
-
# list = parse(['tea', '--no-milk', '--sweetner=honey'])
|
218
|
-
# list # => [[:arg, "tea"],
|
219
|
-
# [:flag, ["milk", false]],
|
220
|
-
# [:option, ["sweetner", "honey"]]]
|
221
|
-
#
|
222
|
-
def self.parse(argv)
|
223
|
-
entries = []
|
224
|
-
copy = argv.map(&:to_s)
|
225
|
-
while x = copy.shift
|
226
|
-
type = key = value = nil
|
227
|
-
if is_arg?(x)
|
228
|
-
# A regular argument (e.g. a command)
|
229
|
-
type, value = :arg, x
|
230
|
-
else
|
231
|
-
key = x[2..-1]
|
232
|
-
if key.include?('=')
|
233
|
-
# An option with a value
|
234
|
-
type = :option
|
235
|
-
key, value = key.split('=', 2)
|
236
|
-
else
|
237
|
-
# A boolean flag
|
238
|
-
type = :flag
|
239
|
-
value = true
|
240
|
-
if key[0,3] == 'no-'
|
241
|
-
# A negated boolean flag
|
242
|
-
key = key[3..-1]
|
243
|
-
value = false
|
244
|
-
end
|
245
|
-
end
|
246
|
-
value = [key, value]
|
247
|
-
end
|
248
|
-
entries << [type, value]
|
249
|
-
end
|
250
|
-
entries
|
251
|
-
end
|
252
|
-
|
253
|
-
def self.is_arg?(x)
|
254
|
-
x[0,2] != '--'
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
# Including this module into an exception class will ensure that when raised,
|
259
|
-
# while running {Command.run}, only the message of the exception will be
|
260
|
-
# shown to the user. Unless disabled with the `--verbose` flag.
|
261
|
-
#
|
262
|
-
# In addition, the message will be colored red, if {Command.colorize_output}
|
263
|
-
# is set to `true`.
|
264
|
-
#
|
265
|
-
module InformativeError
|
266
|
-
attr_writer :exit_status
|
267
|
-
|
268
|
-
# @return [Numeric]
|
269
|
-
#
|
270
|
-
# The exist status code that should be used to terminate the program with.
|
271
|
-
#
|
272
|
-
# Defaults to `1`.
|
273
|
-
#
|
274
|
-
def exit_status
|
275
|
-
@exit_status ||= 1
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# The exception class that is raised to indicate a help banner should be
|
280
|
-
# shown while running {Command.run}.
|
281
|
-
#
|
282
|
-
class Help < StandardError
|
283
|
-
include InformativeError
|
284
|
-
|
285
|
-
# @return [Command]
|
286
|
-
#
|
287
|
-
# The command instance for which a help banner should be shown.
|
288
|
-
#
|
289
|
-
attr_reader :command
|
290
|
-
|
291
|
-
# @return [String]
|
292
|
-
#
|
293
|
-
# The optional error message that will be shown before the help banner.
|
294
|
-
#
|
295
|
-
attr_reader :error_message
|
296
|
-
|
297
|
-
# @param [Command] command
|
298
|
-
#
|
299
|
-
# An instance of a command class for which a help banner should be shown.
|
300
|
-
#
|
301
|
-
# @param [String] error_message
|
302
|
-
#
|
303
|
-
# An optional error message that will be shown before the help banner.
|
304
|
-
# If specified, the exit status, used to terminate the program with, will
|
305
|
-
# be set to `1`, otherwise a {Help} exception is treated as not being a
|
306
|
-
# real error and exits with `0`.
|
307
|
-
#
|
308
|
-
def initialize(command, error_message = nil)
|
309
|
-
@command, @error_message = command, error_message
|
310
|
-
@exit_status = @error_message.nil? ? 0 : 1
|
311
|
-
end
|
312
|
-
|
313
|
-
# @return [String]
|
314
|
-
#
|
315
|
-
# The optional error message, colored in red if {Command.colorize_output}
|
316
|
-
# is set to `true`.
|
317
|
-
#
|
318
|
-
def formatted_error_message
|
319
|
-
if @error_message
|
320
|
-
message = "[!] #{@error_message}"
|
321
|
-
@command.colorize_output? ? message.red : message
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
# @return [String]
|
326
|
-
#
|
327
|
-
# The optional error message, combined with the help banner of the
|
328
|
-
# command.
|
329
|
-
#
|
330
|
-
def message
|
331
|
-
[formatted_error_message, @command.formatted_banner].compact.join("\n\n")
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
# This class is used to build a command-line interface
|
336
|
-
#
|
337
|
-
# Each command is represented by a subclass of this class, which may be
|
338
|
-
# nested to create more granular commands.
|
339
|
-
#
|
340
|
-
# Following is an overview of the types of commands and what they should do.
|
341
|
-
#
|
342
|
-
# ### Any command type
|
343
|
-
#
|
344
|
-
# * Inherit from the command class under which the command should be nested.
|
345
|
-
# * Set {Command.summary} to a brief description of the command.
|
346
|
-
# * Override {Command.options} to return the options it handles and their
|
347
|
-
# descriptions and prepending them to the results of calling `super`.
|
348
|
-
# * Override {Command#initialize} if it handles any parameters.
|
349
|
-
# * Override {Command#validate!} to check if the required parameters the
|
350
|
-
# command handles are valid, or call {Command#help!} in case they’re not.
|
351
|
-
#
|
352
|
-
# ### Abstract command
|
353
|
-
#
|
354
|
-
# The following is needed for an abstract command:
|
355
|
-
#
|
356
|
-
# * Set {Command.abstract_command} to `true`.
|
357
|
-
# * Subclass the command.
|
358
|
-
#
|
359
|
-
# When the optional {Command.description} is specified, it will be shown at
|
360
|
-
# the top of the command’s help banner.
|
361
|
-
#
|
362
|
-
# ### Normal command
|
363
|
-
#
|
364
|
-
# The following is needed for a normal command:
|
365
|
-
#
|
366
|
-
# * Set {Command.arguments} to the description of the arguments this command
|
367
|
-
# handles.
|
368
|
-
# * Override {Command#run} to perform the actual work.
|
369
|
-
#
|
370
|
-
# When the optional {Command.description} is specified, it will be shown
|
371
|
-
# underneath the usage section of the command’s help banner. Otherwise this
|
372
|
-
# defaults to {Command.summary}.
|
373
|
-
#
|
374
|
-
class Command
|
375
|
-
class << self
|
376
|
-
# @return [Boolean]
|
377
|
-
#
|
378
|
-
# Indicates wether or not this command can actually perform work of
|
379
|
-
# itself, or that it only contains subcommands.
|
380
|
-
#
|
381
|
-
attr_accessor :abstract_command
|
382
|
-
alias_method :abstract_command?, :abstract_command
|
383
|
-
|
384
|
-
# @return [String]
|
385
|
-
#
|
386
|
-
# A brief description of the command, which is shown next to the
|
387
|
-
# command in the help banner of a parent command.
|
388
|
-
#
|
389
|
-
attr_accessor :summary
|
390
|
-
|
391
|
-
# @return [String]
|
392
|
-
#
|
393
|
-
# A longer description of the command, which is shown underneath the
|
394
|
-
# usage section of the command’s help banner. Any indentation in this
|
395
|
-
# value will be ignored.
|
396
|
-
#
|
397
|
-
attr_accessor :description
|
398
|
-
|
399
|
-
# @return [String]
|
400
|
-
#
|
401
|
-
# A list of arguments the command handles. This is shown in the usage
|
402
|
-
# section of the command’s help banner.
|
403
|
-
#
|
404
|
-
attr_accessor :arguments
|
405
|
-
|
406
|
-
# @return [Boolean]
|
407
|
-
#
|
408
|
-
# The default value for {Command#colorize_output}. This defaults to
|
409
|
-
# `true` if `String` has the instance methods `#green` and `#red`.
|
410
|
-
# Which are defined by, for instance, the
|
411
|
-
# [colored](https://github.com/defunkt/colored) gem.
|
412
|
-
#
|
413
|
-
def colorize_output
|
414
|
-
if @colorize_output.nil?
|
415
|
-
@colorize_output = String.method_defined?(:red) &&
|
416
|
-
String.method_defined?(:green)
|
417
|
-
end
|
418
|
-
@colorize_output
|
419
|
-
end
|
420
|
-
attr_writer :colorize_output
|
421
|
-
alias_method :colorize_output?, :colorize_output
|
422
|
-
|
423
|
-
# @return [String]
|
424
|
-
#
|
425
|
-
# The name of the command. Defaults to a snake-cased version of the
|
426
|
-
# class’ name.
|
427
|
-
#
|
428
|
-
def command
|
429
|
-
@command ||= name.split('::').last.gsub(/[A-Z]+[a-z]*/) do |part|
|
430
|
-
part.downcase << '-'
|
431
|
-
end[0..-2]
|
432
|
-
end
|
433
|
-
attr_writer :command
|
434
|
-
|
435
|
-
# @return [String]
|
436
|
-
#
|
437
|
-
# The full command up-to this command.
|
438
|
-
#
|
439
|
-
# @example
|
440
|
-
#
|
441
|
-
# BevarageMaker::Tea.full_command # => "beverage-maker tea"
|
442
|
-
#
|
443
|
-
def full_command
|
444
|
-
if superclass == Command
|
445
|
-
"#{command}"
|
446
|
-
else
|
447
|
-
"#{superclass.full_command} #{command}"
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
451
|
-
# @return [Array<Command>]
|
452
|
-
#
|
453
|
-
# A list of command classes that are nested under this command.
|
454
|
-
#
|
455
|
-
def subcommands
|
456
|
-
@subcommands ||= []
|
457
|
-
end
|
458
|
-
|
459
|
-
# @visibility private
|
460
|
-
#
|
461
|
-
# Automatically registers a subclass as a subcommand.
|
462
|
-
#
|
463
|
-
def inherited(subcommand)
|
464
|
-
subcommands << subcommand
|
465
|
-
end
|
466
|
-
|
467
|
-
# Should be overriden by a subclass if it handles any options.
|
468
|
-
#
|
469
|
-
# The subclass has to combine the result of calling `super` and its own
|
470
|
-
# list of options. The recommended way of doing this is by concatenating
|
471
|
-
# concatening to this classes’ own options.
|
472
|
-
#
|
473
|
-
# @return [Array<Array>]
|
474
|
-
#
|
475
|
-
# A list of option name and description tuples.
|
476
|
-
#
|
477
|
-
# @example
|
478
|
-
#
|
479
|
-
# def self.options
|
480
|
-
# [
|
481
|
-
# ['--verbose', 'Print more info'],
|
482
|
-
# ['--help', 'Print help banner'],
|
483
|
-
# ].concat(super)
|
484
|
-
# end
|
485
|
-
#
|
486
|
-
def options
|
487
|
-
options = [
|
488
|
-
['--verbose', 'Show more debugging information'],
|
489
|
-
['--help', 'Show help banner of specified command'],
|
490
|
-
]
|
491
|
-
if Command.colorize_output?
|
492
|
-
options.unshift(['--no-color', 'Show output without color'])
|
493
|
-
end
|
494
|
-
options
|
495
|
-
end
|
496
|
-
|
497
|
-
# @param [Array, ARGV] argv
|
498
|
-
#
|
499
|
-
# A list of (remaining) parameters.
|
500
|
-
#
|
501
|
-
# @return [Command]
|
502
|
-
#
|
503
|
-
# An instance of the command class that was matched by going through
|
504
|
-
# the arguments in the parameters and drilling down command classes.
|
505
|
-
#
|
506
|
-
def parse(argv)
|
507
|
-
argv = ARGV.new(argv) unless argv.is_a?(ARGV)
|
508
|
-
cmd = argv.arguments.first
|
509
|
-
if cmd && subcommand = subcommands.find { |sc| sc.command == cmd }
|
510
|
-
argv.shift_argument
|
511
|
-
subcommand.parse(argv)
|
512
|
-
else
|
513
|
-
new(argv)
|
514
|
-
end
|
515
|
-
end
|
516
|
-
|
517
|
-
# Instantiates the command class matching the parameters through
|
518
|
-
# {Command.parse}, validates it through {Command#validate!}, and runs it
|
519
|
-
# through {Command#run}.
|
520
|
-
#
|
521
|
-
# @note
|
522
|
-
#
|
523
|
-
# You should normally call this on
|
524
|
-
#
|
525
|
-
# @param [Array, ARGV] argv
|
526
|
-
#
|
527
|
-
# A list of parameters. For instance, the standard `ARGV` constant,
|
528
|
-
# which contains the parameters passed to the program.
|
529
|
-
#
|
530
|
-
# @return [void]
|
531
|
-
#
|
532
|
-
def run(argv)
|
533
|
-
command = parse(argv)
|
534
|
-
command.validate!
|
535
|
-
command.run
|
536
|
-
rescue Exception => exception
|
537
|
-
if exception.is_a?(InformativeError)
|
538
|
-
puts exception.message
|
539
|
-
if command.verbose?
|
540
|
-
puts
|
541
|
-
puts *exception.backtrace
|
542
|
-
end
|
543
|
-
exit exception.exit_status
|
544
|
-
else
|
545
|
-
report_error(exception)
|
546
|
-
end
|
547
|
-
end
|
548
|
-
|
549
|
-
# Allows the application to perform custom error reporting, by overriding
|
550
|
-
# this method.
|
551
|
-
#
|
552
|
-
# @param [Exception] exception
|
553
|
-
#
|
554
|
-
# An exception that occurred while running a command through
|
555
|
-
# {Command.run}.
|
556
|
-
#
|
557
|
-
# @raise
|
558
|
-
#
|
559
|
-
# By default re-raises the specified exception.
|
560
|
-
#
|
561
|
-
# @return [void]
|
562
|
-
#
|
563
|
-
def report_error(exception)
|
564
|
-
raise exception
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
|
-
# Set to `true` if the user specifies the `--verbose` option.
|
569
|
-
#
|
570
|
-
# @note
|
571
|
-
#
|
572
|
-
# If you want to make use of this value for your own configuration, you
|
573
|
-
# should check the value _after_ calling the `super` {Command#initialize}
|
574
|
-
# implementation.
|
575
|
-
#
|
576
|
-
# @return [Boolean]
|
577
|
-
#
|
578
|
-
# Wether or not backtraces should be included when presenting the user an
|
579
|
-
# exception that includes the {InformativeError} module.
|
580
|
-
#
|
581
|
-
attr_accessor :verbose
|
582
|
-
alias_method :verbose?, :verbose
|
583
|
-
|
584
|
-
# Set to `true` if {Command.colorize_output} returns `true` and the user
|
585
|
-
# did **not** specify the `--no-color` option.
|
586
|
-
#
|
587
|
-
# @note (see #verbose)
|
588
|
-
#
|
589
|
-
# @return [Boolean]
|
590
|
-
#
|
591
|
-
# Wether or not to color {InformativeError} exception messages red and
|
592
|
-
# subcommands in help banners green.
|
593
|
-
#
|
594
|
-
attr_accessor :colorize_output
|
595
|
-
alias_method :colorize_output?, :colorize_output
|
596
|
-
|
597
|
-
# Subclasses should override this method to remove the arguments/options
|
598
|
-
# they support from `argv` _before_ calling `super`.
|
599
|
-
#
|
600
|
-
# The `super` implementation sets the {#verbose} attribute based on wether
|
601
|
-
# or not the `--verbose` option is specified; and the {#colorize_output}
|
602
|
-
# attribute to `false` if {Command.colorize_output} returns `true`, but the
|
603
|
-
# user specified the `--no-color` option.
|
604
|
-
#
|
605
|
-
# @param [ARGV] argv
|
606
|
-
#
|
607
|
-
# A list of (user-supplied) params that should be handled.
|
608
|
-
#
|
609
|
-
def initialize(argv)
|
610
|
-
@verbose = argv.flag?('verbose')
|
611
|
-
@colorize_output = argv.flag?('color', Command.colorize_output?)
|
612
|
-
@argv = argv
|
613
|
-
end
|
614
|
-
|
615
|
-
# Raises a Help exception if the `--help` option is specified, if `argv`
|
616
|
-
# still contains remaining arguments/options by the time it reaches this
|
617
|
-
# implementation, or when called on an ‘abstract command’.
|
618
|
-
#
|
619
|
-
# Subclasses should call `super` _before_ doing their own validation. This
|
620
|
-
# way when the user specifies the `--help` flag a help banner is shown,
|
621
|
-
# instead of possible actual validation errors.
|
622
|
-
#
|
623
|
-
# @raise [Help]
|
624
|
-
#
|
625
|
-
# @return [void]
|
626
|
-
#
|
627
|
-
def validate!
|
628
|
-
help! if @argv.flag?('help')
|
629
|
-
help! "Unknown arguments: #{@argv.remainder.join(' ')}" if !@argv.empty?
|
630
|
-
help! if self.class.abstract_command?
|
631
|
-
end
|
632
|
-
|
633
|
-
# This method should be overriden by the command class to perform its work.
|
634
|
-
#
|
635
|
-
# @return [void
|
636
|
-
#
|
637
|
-
def run
|
638
|
-
raise "A subclass should override the Command#run method to actually " \
|
639
|
-
"perform some work."
|
640
|
-
end
|
641
|
-
|
642
|
-
# @visibility private
|
643
|
-
def formatted_options_description
|
644
|
-
opts = self.class.options
|
645
|
-
size = opts.map { |opt| opt.first.size }.max
|
646
|
-
opts.map { |key, desc| " #{key.ljust(size)} #{desc}" }.join("\n")
|
647
|
-
end
|
648
|
-
|
649
|
-
# @visibility private
|
650
|
-
def formatted_usage_description
|
651
|
-
if message = self.class.description || self.class.summary
|
652
|
-
message = strip_heredoc(message)
|
653
|
-
message = message.split("\n").map { |line| " #{line}" }.join("\n")
|
654
|
-
args = " #{self.class.arguments}" if self.class.arguments
|
655
|
-
" $ #{self.class.full_command}#{args}\n\n#{message}"
|
656
|
-
end
|
657
|
-
end
|
658
|
-
|
659
|
-
# @visibility private
|
660
|
-
def formatted_subcommand_summaries
|
661
|
-
subcommands = self.class.subcommands.reject do |subcommand|
|
662
|
-
subcommand.summary.nil?
|
663
|
-
end.sort_by(&:command)
|
664
|
-
unless subcommands.empty?
|
665
|
-
command_size = subcommands.map { |cmd| cmd.command.size }.max
|
666
|
-
subcommands.map do |subcommand|
|
667
|
-
command = subcommand.command.ljust(command_size)
|
668
|
-
command = command.green if colorize_output?
|
669
|
-
" * #{command} #{subcommand.summary}"
|
670
|
-
end.join("\n")
|
671
|
-
end
|
672
|
-
end
|
673
|
-
|
674
|
-
# @visibility private
|
675
|
-
def formatted_banner
|
676
|
-
banner = []
|
677
|
-
if self.class.abstract_command?
|
678
|
-
banner << self.class.description if self.class.description
|
679
|
-
elsif usage = formatted_usage_description
|
680
|
-
banner << 'Usage:'
|
681
|
-
banner << usage
|
682
|
-
end
|
683
|
-
if commands = formatted_subcommand_summaries
|
684
|
-
banner << 'Commands:'
|
685
|
-
banner << commands
|
686
|
-
end
|
687
|
-
banner << 'Options:'
|
688
|
-
banner << formatted_options_description
|
689
|
-
banner.join("\n\n")
|
690
|
-
end
|
691
|
-
|
692
|
-
protected
|
693
|
-
|
694
|
-
# @raise [Help]
|
695
|
-
#
|
696
|
-
# Signals CLAide that a help banner for this command should be shown,
|
697
|
-
# with an optional error message.
|
698
|
-
#
|
699
|
-
# @return [void]
|
700
|
-
#
|
701
|
-
def help!(error_message = nil)
|
702
|
-
raise Help.new(self, error_message)
|
703
|
-
end
|
704
|
-
|
705
|
-
private
|
12
|
+
VERSION = '0.3.0'
|
706
13
|
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
else
|
712
|
-
string
|
713
|
-
end
|
714
|
-
end
|
715
|
-
end
|
14
|
+
require 'claide/argv.rb'
|
15
|
+
require 'claide/command.rb'
|
16
|
+
require 'claide/help.rb'
|
17
|
+
require 'claide/informative_error.rb'
|
716
18
|
|
717
19
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: claide
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Eloy Duran
|
@@ -10,7 +9,7 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2013-05-23 00:00:00.000000000 Z
|
14
13
|
dependencies: []
|
15
14
|
description:
|
16
15
|
email:
|
@@ -26,25 +25,24 @@ files:
|
|
26
25
|
homepage: https://github.com/CocoaPods/CLAide
|
27
26
|
licenses:
|
28
27
|
- MIT
|
28
|
+
metadata: {}
|
29
29
|
post_install_message:
|
30
30
|
rdoc_options: []
|
31
31
|
require_paths:
|
32
32
|
- lib
|
33
33
|
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
34
|
requirements:
|
36
|
-
- -
|
35
|
+
- - '>='
|
37
36
|
- !ruby/object:Gem::Version
|
38
37
|
version: '0'
|
39
38
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
-
none: false
|
41
39
|
requirements:
|
42
|
-
- -
|
40
|
+
- - '>='
|
43
41
|
- !ruby/object:Gem::Version
|
44
42
|
version: '0'
|
45
43
|
requirements: []
|
46
44
|
rubyforge_project:
|
47
|
-
rubygems_version:
|
45
|
+
rubygems_version: 2.0.3
|
48
46
|
signing_key:
|
49
47
|
specification_version: 3
|
50
48
|
summary: A small command-line interface framework.
|