commander-tool 1.0.0 → 1.0.1
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 +164 -60
- 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: 5496f2b9238097da058b066f97a541fff53522c6121de06cc0b1571bdd494e19
|
|
4
|
+
data.tar.gz: 4a3f35031685414b72c956c7bb78e32fd3bace54909e3694ae417159802b0723
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29c6b2246ef33bf14e01048d7f3a7df42da6d36080c33a00ba9834936a2c91e9d78214d3dc75590706265f066270db42293499245cc1f21460d1afa0871d2022
|
|
7
|
+
data.tar.gz: b4270d0872210a3287049f7a41de8d743859e30ccf2406d26471183dae922daba1809b78abe06856e3154a5ab21c3f88abcbc586bb705a1b1b7b9cdfaa656a2c
|
data/lib/commander-tool.rb
CHANGED
|
@@ -1,32 +1,45 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "did_you_mean"
|
|
2
2
|
|
|
3
3
|
class Commander
|
|
4
|
+
VERSION = "1.0.1"
|
|
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
11
|
@description = description
|
|
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 =
|
|
36
|
+
def initialize(switches, description = "")
|
|
24
37
|
@switches = switches
|
|
25
38
|
@description = description
|
|
26
39
|
|
|
27
40
|
@key =
|
|
28
|
-
switches.find { |s| s.start_with?(
|
|
29
|
-
switches.find { |s| s.start_with?(
|
|
41
|
+
switches.find { |s| s.start_with?("--") }&.sub(/^--/, "")&.tr("-", "_") ||
|
|
42
|
+
switches.find { |s| s.start_with?("-") }&.sub(/^-/, "")
|
|
30
43
|
end
|
|
31
44
|
|
|
32
45
|
def matches?(arg)
|
|
@@ -34,10 +47,9 @@ class Commander
|
|
|
34
47
|
end
|
|
35
48
|
|
|
36
49
|
def value_required?
|
|
37
|
-
description.include?(
|
|
38
|
-
description.include?(
|
|
39
|
-
description.include?(
|
|
40
|
-
description.include?('VALUE')
|
|
50
|
+
@description.include?("<") ||
|
|
51
|
+
@description.include?("argument") ||
|
|
52
|
+
@description.include?("value")
|
|
41
53
|
end
|
|
42
54
|
end
|
|
43
55
|
|
|
@@ -46,45 +58,97 @@ class Commander
|
|
|
46
58
|
@global_options = []
|
|
47
59
|
end
|
|
48
60
|
|
|
49
|
-
def add_command(name, description =
|
|
50
|
-
|
|
51
|
-
@commands[
|
|
52
|
-
|
|
61
|
+
def add_command(name, description = "", &block)
|
|
62
|
+
command = Command.new(name, description, &block)
|
|
63
|
+
@commands[command.name] = command
|
|
64
|
+
command
|
|
53
65
|
end
|
|
54
66
|
|
|
55
|
-
def add_option(switches, description =
|
|
67
|
+
def add_option(switches, description = "")
|
|
56
68
|
@global_options << Option.new(switches, description)
|
|
57
69
|
self
|
|
58
70
|
end
|
|
59
71
|
|
|
60
72
|
def parse(args = ARGV)
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
global_options, remaining = parse_options(
|
|
74
|
+
args,
|
|
75
|
+
@global_options
|
|
76
|
+
)
|
|
63
77
|
|
|
64
|
-
command_name =
|
|
78
|
+
command_name = remaining.shift
|
|
65
79
|
|
|
66
80
|
if command_name.nil?
|
|
67
|
-
handle_no_command(
|
|
81
|
+
handle_no_command(global_options)
|
|
68
82
|
return
|
|
69
83
|
end
|
|
70
84
|
|
|
71
|
-
|
|
72
|
-
command = @commands[command_name]
|
|
85
|
+
command = @commands[command_name]
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
parse_options(remaining_args, command.options)
|
|
76
|
-
|
|
77
|
-
# Merge global and command options for execution
|
|
78
|
-
combined_options = global_parsed.merge(cmd_parsed)
|
|
79
|
-
|
|
80
|
-
command.block.call(combined_options, command_args) if command.block
|
|
81
|
-
else
|
|
87
|
+
unless command
|
|
82
88
|
raise_unknown_command(command_name)
|
|
83
89
|
end
|
|
90
|
+
|
|
91
|
+
parse_command(
|
|
92
|
+
command,
|
|
93
|
+
remaining,
|
|
94
|
+
global_options
|
|
95
|
+
)
|
|
84
96
|
end
|
|
85
97
|
|
|
86
98
|
private
|
|
87
99
|
|
|
100
|
+
def parse_command(command, args, inherited_options)
|
|
101
|
+
if command.has_subcommands?
|
|
102
|
+
subcommand_name = args.shift
|
|
103
|
+
|
|
104
|
+
if subcommand_name.nil?
|
|
105
|
+
print_command_help(command)
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
subcommand = command.subcommands[subcommand_name]
|
|
110
|
+
|
|
111
|
+
unless subcommand
|
|
112
|
+
raise_unknown_subcommand(
|
|
113
|
+
subcommand_name,
|
|
114
|
+
command
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
command_options, command_args =
|
|
119
|
+
parse_options(args, command.options)
|
|
120
|
+
|
|
121
|
+
combined_options =
|
|
122
|
+
inherited_options.merge(command_options)
|
|
123
|
+
|
|
124
|
+
if subcommand.has_subcommands?
|
|
125
|
+
parse_command(
|
|
126
|
+
subcommand,
|
|
127
|
+
command_args,
|
|
128
|
+
combined_options
|
|
129
|
+
)
|
|
130
|
+
elsif subcommand.block
|
|
131
|
+
subcommand.block.call(
|
|
132
|
+
combined_options,
|
|
133
|
+
command_args
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
command_options, command_args =
|
|
141
|
+
parse_options(args, command.options)
|
|
142
|
+
|
|
143
|
+
combined_options =
|
|
144
|
+
inherited_options.merge(command_options)
|
|
145
|
+
|
|
146
|
+
command.block.call(
|
|
147
|
+
combined_options,
|
|
148
|
+
command_args
|
|
149
|
+
) if command.block
|
|
150
|
+
end
|
|
151
|
+
|
|
88
152
|
def parse_options(args, available_options)
|
|
89
153
|
parsed = {}
|
|
90
154
|
remaining = []
|
|
@@ -93,23 +157,33 @@ class Commander
|
|
|
93
157
|
while i < args.length
|
|
94
158
|
arg = args[i]
|
|
95
159
|
|
|
96
|
-
if arg
|
|
97
|
-
|
|
160
|
+
if arg == "--"
|
|
161
|
+
remaining.concat(args[(i + 1)..] || [])
|
|
162
|
+
break
|
|
163
|
+
end
|
|
98
164
|
|
|
99
|
-
|
|
100
|
-
|
|
165
|
+
if arg.start_with?("-")
|
|
166
|
+
option = available_options.find do |opt|
|
|
167
|
+
opt.matches?(arg)
|
|
101
168
|
end
|
|
102
169
|
|
|
170
|
+
raise_unknown_option(
|
|
171
|
+
arg,
|
|
172
|
+
available_options
|
|
173
|
+
) unless option
|
|
174
|
+
|
|
103
175
|
next_arg = args[i + 1]
|
|
104
176
|
|
|
105
177
|
if option.value_required?
|
|
106
|
-
if next_arg.nil? ||
|
|
178
|
+
if next_arg.nil? ||
|
|
179
|
+
next_arg.start_with?("-")
|
|
107
180
|
raise_missing_argument(arg)
|
|
108
181
|
end
|
|
109
182
|
|
|
110
183
|
parsed[option.key.to_sym] = next_arg
|
|
111
184
|
i += 2
|
|
112
|
-
elsif next_arg &&
|
|
185
|
+
elsif next_arg &&
|
|
186
|
+
!next_arg.start_with?("-")
|
|
113
187
|
parsed[option.key.to_sym] = next_arg
|
|
114
188
|
i += 2
|
|
115
189
|
else
|
|
@@ -125,62 +199,80 @@ class Commander
|
|
|
125
199
|
[parsed, remaining]
|
|
126
200
|
end
|
|
127
201
|
|
|
128
|
-
def raise_missing_argument(
|
|
129
|
-
abort "error: flag '#{
|
|
202
|
+
def raise_missing_argument(option)
|
|
203
|
+
abort "error: flag '#{option}' needs an argument"
|
|
130
204
|
end
|
|
131
205
|
|
|
132
|
-
def raise_unknown_command(
|
|
206
|
+
def raise_unknown_command(name)
|
|
133
207
|
corrector = DidYouMean::SpellChecker.new(
|
|
134
208
|
dictionary: @commands.keys
|
|
135
209
|
)
|
|
136
210
|
|
|
137
|
-
suggestions = corrector.correct(
|
|
211
|
+
suggestions = corrector.correct(name)
|
|
138
212
|
|
|
139
|
-
|
|
213
|
+
message = "error: unknown command '#{name}'"
|
|
140
214
|
|
|
141
215
|
unless suggestions.empty?
|
|
142
|
-
|
|
143
|
-
|
|
216
|
+
message += "\n\nDid you mean?\n"
|
|
217
|
+
message += suggestions.map { |s| "\t#{s}" }.join("\n")
|
|
144
218
|
end
|
|
145
219
|
|
|
146
|
-
abort
|
|
220
|
+
abort message
|
|
147
221
|
end
|
|
148
222
|
|
|
149
|
-
def
|
|
150
|
-
|
|
223
|
+
def raise_unknown_subcommand(name, command)
|
|
224
|
+
corrector = DidYouMean::SpellChecker.new(
|
|
225
|
+
dictionary: command.subcommands.keys
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
suggestions = corrector.correct(name)
|
|
229
|
+
|
|
230
|
+
message =
|
|
231
|
+
"error: unknown command '#{name}'"
|
|
232
|
+
|
|
233
|
+
unless suggestions.empty?
|
|
234
|
+
message += "\n\nDid you mean?\n"
|
|
235
|
+
message += suggestions.map { |s| "\t#{s}" }.join("\n")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
abort message
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def raise_unknown_option(name, options)
|
|
242
|
+
switches = options.flat_map(&:switches)
|
|
151
243
|
|
|
152
244
|
corrector = DidYouMean::SpellChecker.new(
|
|
153
|
-
dictionary:
|
|
245
|
+
dictionary: switches
|
|
154
246
|
)
|
|
155
247
|
|
|
156
|
-
suggestions = corrector.correct(
|
|
248
|
+
suggestions = corrector.correct(name)
|
|
157
249
|
|
|
158
|
-
|
|
250
|
+
message = "error: unknown option '#{name}'"
|
|
159
251
|
|
|
160
252
|
unless suggestions.empty?
|
|
161
|
-
|
|
162
|
-
|
|
253
|
+
message += "\n\nDid you mean?\n"
|
|
254
|
+
message += suggestions.map { |s| "\t#{s}" }.join("\n")
|
|
163
255
|
end
|
|
164
256
|
|
|
165
|
-
abort
|
|
257
|
+
abort message
|
|
166
258
|
end
|
|
167
259
|
|
|
168
260
|
def handle_no_command(options)
|
|
169
261
|
if options[:version] || options[:v]
|
|
170
|
-
puts "Commander CLI
|
|
262
|
+
puts "Commander CLI v#{VERSION}"
|
|
171
263
|
return
|
|
172
264
|
end
|
|
173
265
|
|
|
266
|
+
print_usage
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def print_usage
|
|
174
270
|
puts "Usage: <command> [options]"
|
|
175
271
|
puts
|
|
176
272
|
puts "Commands:"
|
|
177
273
|
|
|
178
|
-
|
|
179
|
-
puts "
|
|
180
|
-
else
|
|
181
|
-
@commands.each_value do |command|
|
|
182
|
-
puts " #{command.name.ljust(20)} #{command.description}"
|
|
183
|
-
end
|
|
274
|
+
@commands.each_value do |command|
|
|
275
|
+
puts " #{command.name.ljust(20)} #{command.description}"
|
|
184
276
|
end
|
|
185
277
|
|
|
186
278
|
unless @global_options.empty?
|
|
@@ -188,8 +280,20 @@ class Commander
|
|
|
188
280
|
puts "Options:"
|
|
189
281
|
|
|
190
282
|
@global_options.each do |option|
|
|
191
|
-
puts " #{option.switches.join(
|
|
283
|
+
puts " #{option.switches.join(", ").ljust(20)} #{option.description}"
|
|
192
284
|
end
|
|
193
285
|
end
|
|
194
286
|
end
|
|
287
|
+
|
|
288
|
+
def print_command_help(command)
|
|
289
|
+
puts "Usage: #{command.name} <subcommand> [options]"
|
|
290
|
+
puts
|
|
291
|
+
puts command.description unless command.description.empty?
|
|
292
|
+
puts
|
|
293
|
+
puts "Commands:"
|
|
294
|
+
|
|
295
|
+
command.subcommands.each_value do |subcommand|
|
|
296
|
+
puts " #{subcommand.name.ljust(20)} #{subcommand.description}"
|
|
297
|
+
end
|
|
298
|
+
end
|
|
195
299
|
end
|