commander-tool 1.0.0 → 1.0.2
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 +4 -4
- data/lib/commander-tool.rb +247 -81
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc12456920e61c887d63c3224bbabe9b8c8850df925733a917c782da347ad2a6
|
|
4
|
+
data.tar.gz: fc30d88eac1a17c0effd957e5e1fc1cdcada2e83eda493087e2421b568e2ea88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f830e16aad8121ad5a59cf0a2f4ebf9ce8835e0c06ae2589e845137a06f4dc04f0bff5af628c467d25c04da944f11d393e9da67d025166035bb563819eccc24c
|
|
7
|
+
data.tar.gz: 759a7318b4a23153c992a3a5fb2422b3fd31efb6c2632cf38f2b279430aa92e7bf2fa539db240f07cceebe9fa4284d43dceeaf89c9325f51224001cdb49fc521
|
data/lib/commander-tool.rb
CHANGED
|
@@ -1,32 +1,54 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "did_you_mean"
|
|
2
2
|
|
|
3
3
|
class Commander
|
|
4
|
+
VERSION = "1.0.2"
|
|
5
|
+
|
|
4
6
|
class Command
|
|
5
|
-
attr_reader :name, :description, :options, :block
|
|
7
|
+
attr_reader :name, :description, :options, :block, :subcommands
|
|
6
8
|
|
|
7
|
-
def initialize(name, description =
|
|
9
|
+
def initialize(name, description = "", &block)
|
|
8
10
|
@name = name.to_s
|
|
9
|
-
@description = description
|
|
11
|
+
@description = description.to_s
|
|
10
12
|
@options = []
|
|
13
|
+
@subcommands = {}
|
|
11
14
|
@block = block
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
def add_option(switches, description =
|
|
17
|
+
def add_option(switches, description = "")
|
|
15
18
|
@options << Option.new(switches, description)
|
|
16
19
|
self
|
|
17
20
|
end
|
|
21
|
+
|
|
22
|
+
def add_subcommand(name, description = "", &block)
|
|
23
|
+
command = Command.new(name, description, &block)
|
|
24
|
+
@subcommands[command.name] = command
|
|
25
|
+
command
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_subcommands?
|
|
29
|
+
!@subcommands.empty?
|
|
30
|
+
end
|
|
18
31
|
end
|
|
19
32
|
|
|
20
33
|
class Option
|
|
21
34
|
attr_reader :switches, :description, :key
|
|
22
35
|
|
|
23
|
-
def initialize(switches, description =
|
|
24
|
-
@switches = switches
|
|
25
|
-
@description = description
|
|
36
|
+
def initialize(switches, description = "")
|
|
37
|
+
@switches = Array(switches).map(&:to_s)
|
|
38
|
+
@description = description.to_s
|
|
39
|
+
|
|
40
|
+
long =
|
|
41
|
+
@switches.find { |s| s.start_with?("--") }
|
|
42
|
+
|
|
43
|
+
short =
|
|
44
|
+
@switches.find { |s| s.start_with?("-") }
|
|
26
45
|
|
|
27
46
|
@key =
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
if long
|
|
48
|
+
long.sub(/\A--/, "").tr("-", "_")
|
|
49
|
+
elsif short
|
|
50
|
+
short.sub(/\A-/, "").tr("-", "_")
|
|
51
|
+
end
|
|
30
52
|
end
|
|
31
53
|
|
|
32
54
|
def matches?(arg)
|
|
@@ -34,10 +56,9 @@ class Commander
|
|
|
34
56
|
end
|
|
35
57
|
|
|
36
58
|
def value_required?
|
|
37
|
-
description.include?(
|
|
38
|
-
description.
|
|
39
|
-
description.
|
|
40
|
-
description.include?('VALUE')
|
|
59
|
+
@description.include?("<") ||
|
|
60
|
+
@description.match?(/\bargument\b/i) ||
|
|
61
|
+
@description.match?(/\bvalue\b/i)
|
|
41
62
|
end
|
|
42
63
|
end
|
|
43
64
|
|
|
@@ -46,141 +67,270 @@ class Commander
|
|
|
46
67
|
@global_options = []
|
|
47
68
|
end
|
|
48
69
|
|
|
49
|
-
def add_command(name, description =
|
|
50
|
-
|
|
51
|
-
@commands[
|
|
52
|
-
|
|
70
|
+
def add_command(name, description = "", &block)
|
|
71
|
+
command = Command.new(name, description, &block)
|
|
72
|
+
@commands[command.name] = command
|
|
73
|
+
command
|
|
53
74
|
end
|
|
54
75
|
|
|
55
|
-
def add_option(switches, description =
|
|
76
|
+
def add_option(switches, description = "")
|
|
56
77
|
@global_options << Option.new(switches, description)
|
|
57
78
|
self
|
|
58
79
|
end
|
|
59
80
|
|
|
60
81
|
def parse(args = ARGV)
|
|
61
|
-
|
|
62
|
-
|
|
82
|
+
args = Array(args).dup
|
|
83
|
+
|
|
84
|
+
if args.empty?
|
|
85
|
+
print_usage
|
|
86
|
+
return
|
|
87
|
+
end
|
|
63
88
|
|
|
64
|
-
|
|
89
|
+
if args.first == "--version" || args.first == "-v"
|
|
90
|
+
puts "Commander CLI v#{VERSION}"
|
|
91
|
+
return
|
|
92
|
+
end
|
|
65
93
|
|
|
66
|
-
if
|
|
67
|
-
|
|
94
|
+
if args.first == "--help" || args.first == "-h"
|
|
95
|
+
print_usage
|
|
68
96
|
return
|
|
69
97
|
end
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
# Find the first registered top-level command.
|
|
100
|
+
command_index = nil
|
|
101
|
+
command = nil
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
args.each_with_index do |arg, index|
|
|
104
|
+
next if arg.start_with?("-")
|
|
105
|
+
|
|
106
|
+
if @commands.key?(arg)
|
|
107
|
+
command_index = index
|
|
108
|
+
command = @commands[arg]
|
|
109
|
+
break
|
|
110
|
+
end
|
|
111
|
+
end
|
|
76
112
|
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
unless command
|
|
114
|
+
name = args.find { |arg| !arg.start_with?("-") }
|
|
79
115
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
116
|
+
if name
|
|
117
|
+
raise_unknown_command(name)
|
|
118
|
+
else
|
|
119
|
+
raise_unknown_option(args.first, @global_options)
|
|
120
|
+
end
|
|
83
121
|
end
|
|
122
|
+
|
|
123
|
+
# Only arguments BEFORE the command belong to global options.
|
|
124
|
+
global_args = args[0...command_index]
|
|
125
|
+
|
|
126
|
+
# Everything AFTER the command belongs to that command.
|
|
127
|
+
command_args = args[(command_index + 1)..] || []
|
|
128
|
+
|
|
129
|
+
global_options, = parse_options(
|
|
130
|
+
global_args,
|
|
131
|
+
@global_options
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
parse_command(
|
|
135
|
+
command,
|
|
136
|
+
command_args,
|
|
137
|
+
global_options
|
|
138
|
+
)
|
|
84
139
|
end
|
|
85
140
|
|
|
86
141
|
private
|
|
87
142
|
|
|
143
|
+
def parse_command(command, args, inherited_options)
|
|
144
|
+
args = args.dup
|
|
145
|
+
|
|
146
|
+
if command.has_subcommands?
|
|
147
|
+
subcommand_name = args.shift
|
|
148
|
+
|
|
149
|
+
if subcommand_name.nil?
|
|
150
|
+
print_command_help(command)
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if subcommand_name == "--help" || subcommand_name == "-h"
|
|
155
|
+
print_command_help(command)
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
subcommand = command.subcommands[subcommand_name]
|
|
160
|
+
|
|
161
|
+
unless subcommand
|
|
162
|
+
raise_unknown_subcommand(
|
|
163
|
+
subcommand_name,
|
|
164
|
+
command
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
command_options, command_args =
|
|
169
|
+
parse_options(
|
|
170
|
+
args,
|
|
171
|
+
command.options
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
combined_options =
|
|
175
|
+
inherited_options.merge(command_options)
|
|
176
|
+
|
|
177
|
+
if subcommand.has_subcommands?
|
|
178
|
+
parse_command(
|
|
179
|
+
subcommand,
|
|
180
|
+
command_args,
|
|
181
|
+
combined_options
|
|
182
|
+
)
|
|
183
|
+
elsif subcommand.block
|
|
184
|
+
subcommand.block.call(
|
|
185
|
+
combined_options,
|
|
186
|
+
command_args
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
return
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
command_options, command_args =
|
|
194
|
+
parse_options(
|
|
195
|
+
args,
|
|
196
|
+
command.options
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
combined_options =
|
|
200
|
+
inherited_options.merge(command_options)
|
|
201
|
+
|
|
202
|
+
command.block.call(
|
|
203
|
+
combined_options,
|
|
204
|
+
command_args
|
|
205
|
+
) if command.block
|
|
206
|
+
end
|
|
207
|
+
|
|
88
208
|
def parse_options(args, available_options)
|
|
89
209
|
parsed = {}
|
|
90
210
|
remaining = []
|
|
211
|
+
|
|
91
212
|
i = 0
|
|
92
213
|
|
|
93
214
|
while i < args.length
|
|
94
215
|
arg = args[i]
|
|
95
216
|
|
|
96
|
-
if arg
|
|
97
|
-
|
|
217
|
+
if arg == "--"
|
|
218
|
+
remaining.concat(args[(i + 1)..] || [])
|
|
219
|
+
break
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
unless arg.start_with?("-")
|
|
223
|
+
remaining << arg
|
|
224
|
+
i += 1
|
|
225
|
+
next
|
|
226
|
+
end
|
|
98
227
|
|
|
99
|
-
|
|
100
|
-
|
|
228
|
+
option =
|
|
229
|
+
available_options.find do |candidate|
|
|
230
|
+
candidate.matches?(arg)
|
|
101
231
|
end
|
|
102
232
|
|
|
103
|
-
|
|
233
|
+
unless option
|
|
234
|
+
raise_unknown_option(
|
|
235
|
+
arg,
|
|
236
|
+
available_options
|
|
237
|
+
)
|
|
238
|
+
end
|
|
104
239
|
|
|
105
|
-
|
|
106
|
-
if next_arg.nil? || next_arg.start_with?('-')
|
|
107
|
-
raise_missing_argument(arg)
|
|
108
|
-
end
|
|
240
|
+
next_arg = args[i + 1]
|
|
109
241
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
242
|
+
if option.value_required?
|
|
243
|
+
if next_arg.nil? || next_arg.start_with?("-")
|
|
244
|
+
raise_missing_argument(arg)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
parsed[option.key.to_sym] = next_arg
|
|
248
|
+
i += 2
|
|
249
|
+
else
|
|
250
|
+
if next_arg && !next_arg.start_with?("-")
|
|
113
251
|
parsed[option.key.to_sym] = next_arg
|
|
114
252
|
i += 2
|
|
115
253
|
else
|
|
116
254
|
parsed[option.key.to_sym] = true
|
|
117
255
|
i += 1
|
|
118
256
|
end
|
|
119
|
-
else
|
|
120
|
-
remaining << arg
|
|
121
|
-
i += 1
|
|
122
257
|
end
|
|
123
258
|
end
|
|
124
259
|
|
|
125
260
|
[parsed, remaining]
|
|
126
261
|
end
|
|
127
262
|
|
|
128
|
-
def raise_missing_argument(
|
|
129
|
-
abort "error: flag '#{
|
|
263
|
+
def raise_missing_argument(option)
|
|
264
|
+
abort "error: flag '#{option}' needs an argument"
|
|
130
265
|
end
|
|
131
266
|
|
|
132
|
-
def raise_unknown_command(
|
|
133
|
-
corrector =
|
|
134
|
-
|
|
135
|
-
|
|
267
|
+
def raise_unknown_command(name)
|
|
268
|
+
corrector =
|
|
269
|
+
DidYouMean::SpellChecker.new(
|
|
270
|
+
dictionary: @commands.keys
|
|
271
|
+
)
|
|
136
272
|
|
|
137
|
-
suggestions = corrector.correct(
|
|
273
|
+
suggestions = corrector.correct(name)
|
|
138
274
|
|
|
139
|
-
|
|
275
|
+
message =
|
|
276
|
+
"error: unknown command '#{name}'"
|
|
140
277
|
|
|
141
278
|
unless suggestions.empty?
|
|
142
|
-
|
|
143
|
-
|
|
279
|
+
message << "\n\nDid you mean?\n"
|
|
280
|
+
message << suggestions.map { |s| "\t#{s}" }.join("\n")
|
|
144
281
|
end
|
|
145
282
|
|
|
146
|
-
abort
|
|
283
|
+
abort message
|
|
147
284
|
end
|
|
148
285
|
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
286
|
+
def raise_unknown_subcommand(name, command)
|
|
287
|
+
corrector =
|
|
288
|
+
DidYouMean::SpellChecker.new(
|
|
289
|
+
dictionary: command.subcommands.keys
|
|
290
|
+
)
|
|
155
291
|
|
|
156
|
-
suggestions = corrector.correct(
|
|
292
|
+
suggestions = corrector.correct(name)
|
|
157
293
|
|
|
158
|
-
|
|
294
|
+
message =
|
|
295
|
+
"error: unknown command '#{name}'"
|
|
159
296
|
|
|
160
297
|
unless suggestions.empty?
|
|
161
|
-
|
|
162
|
-
|
|
298
|
+
message << "\n\nDid you mean?\n"
|
|
299
|
+
message << suggestions.map { |s| "\t#{s}" }.join("\n")
|
|
163
300
|
end
|
|
164
301
|
|
|
165
|
-
abort
|
|
302
|
+
abort message
|
|
166
303
|
end
|
|
167
304
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
305
|
+
def raise_unknown_option(name, options)
|
|
306
|
+
switches =
|
|
307
|
+
options.flat_map(&:switches)
|
|
308
|
+
|
|
309
|
+
corrector =
|
|
310
|
+
DidYouMean::SpellChecker.new(
|
|
311
|
+
dictionary: switches
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
suggestions = corrector.correct(name)
|
|
315
|
+
|
|
316
|
+
message =
|
|
317
|
+
"error: unknown option '#{name}'"
|
|
318
|
+
|
|
319
|
+
unless suggestions.empty?
|
|
320
|
+
message << "\n\nDid you mean?\n"
|
|
321
|
+
message << suggestions.map { |s| "\t#{s}" }.join("\n")
|
|
172
322
|
end
|
|
173
323
|
|
|
324
|
+
abort message
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def print_usage
|
|
174
328
|
puts "Usage: <command> [options]"
|
|
175
329
|
puts
|
|
176
330
|
puts "Commands:"
|
|
177
331
|
|
|
178
|
-
|
|
179
|
-
puts "
|
|
180
|
-
else
|
|
181
|
-
@commands.each_value do |command|
|
|
182
|
-
puts " #{command.name.ljust(20)} #{command.description}"
|
|
183
|
-
end
|
|
332
|
+
@commands.each_value do |command|
|
|
333
|
+
puts " #{command.name.ljust(20)} #{command.description}"
|
|
184
334
|
end
|
|
185
335
|
|
|
186
336
|
unless @global_options.empty?
|
|
@@ -188,8 +338,24 @@ class Commander
|
|
|
188
338
|
puts "Options:"
|
|
189
339
|
|
|
190
340
|
@global_options.each do |option|
|
|
191
|
-
puts " #{option.switches.join(
|
|
341
|
+
puts " #{option.switches.join(", ").ljust(20)} #{option.description}"
|
|
192
342
|
end
|
|
193
343
|
end
|
|
194
344
|
end
|
|
345
|
+
|
|
346
|
+
def print_command_help(command)
|
|
347
|
+
puts "Usage: #{command.name} <subcommand> [options]"
|
|
348
|
+
puts
|
|
349
|
+
|
|
350
|
+
unless command.description.empty?
|
|
351
|
+
puts command.description
|
|
352
|
+
puts
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
puts "Commands:"
|
|
356
|
+
|
|
357
|
+
command.subcommands.each_value do |subcommand|
|
|
358
|
+
puts " #{subcommand.name.ljust(20)} #{subcommand.description}"
|
|
359
|
+
end
|
|
360
|
+
end
|
|
195
361
|
end
|