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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/commander-tool.rb +164 -60
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fe0b76d088b96fcd3e9a46d540f79ff38d10bb49d1f35c8ed4d2b1f9bd26ddb
4
- data.tar.gz: 2c349bc20037922113ea90e74a00c6688495f1950bebe67c505d502c6bffff4d
3
+ metadata.gz: 5496f2b9238097da058b066f97a541fff53522c6121de06cc0b1571bdd494e19
4
+ data.tar.gz: 4a3f35031685414b72c956c7bb78e32fd3bace54909e3694ae417159802b0723
5
5
  SHA512:
6
- metadata.gz: 4c20b7c34bcdb2663ee7f4554ac25b1c41c8910fe62d21e9e91b60b47f651c54fdfc7aa0e7f529db11e6a1e27e26d61ba12bc40c5f08ff8ad8fbd7fe8679b4fa
7
- data.tar.gz: 3bd9b6f788e3b2bb33e714520db2b57eb299cce3d95670b15dfb6a203cac16acc233a5c5b908647d1f3a2e5d9e2118fc32a4ca60d79a7f095afade07fe90734b
6
+ metadata.gz: 29c6b2246ef33bf14e01048d7f3a7df42da6d36080c33a00ba9834936a2c91e9d78214d3dc75590706265f066270db42293499245cc1f21460d1afa0871d2022
7
+ data.tar.gz: b4270d0872210a3287049f7a41de8d743859e30ccf2406d26471183dae922daba1809b78abe06856e3154a5ab21c3f88abcbc586bb705a1b1b7b9cdfaa656a2c
@@ -1,32 +1,45 @@
1
- require 'did_you_mean'
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 = '', &block)
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?('--') }&.sub(/^--/, '')&.tr('-', '_') ||
29
- switches.find { |s| s.start_with?('-') }&.sub(/^-/, '')
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?('argument') ||
39
- description.include?('value') ||
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 = '', &block)
50
- cmd = Command.new(name, description, &block)
51
- @commands[cmd.name] = cmd
52
- cmd
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
- global_parsed, remaining_args =
62
- parse_options(args, @global_options)
73
+ global_options, remaining = parse_options(
74
+ args,
75
+ @global_options
76
+ )
63
77
 
64
- command_name = remaining_args.shift
78
+ command_name = remaining.shift
65
79
 
66
80
  if command_name.nil?
67
- handle_no_command(global_parsed)
81
+ handle_no_command(global_options)
68
82
  return
69
83
  end
70
84
 
71
- if @commands.key?(command_name)
72
- command = @commands[command_name]
85
+ command = @commands[command_name]
73
86
 
74
- cmd_parsed, command_args =
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.start_with?('-')
97
- option = available_options.find { |opt| opt.matches?(arg) }
160
+ if arg == "--"
161
+ remaining.concat(args[(i + 1)..] || [])
162
+ break
163
+ end
98
164
 
99
- unless option
100
- raise_unknown_option(arg, available_options)
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? || next_arg.start_with?('-')
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 && !next_arg.start_with?('-')
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(option_name)
129
- abort "error: flag '#{option_name}' needs an argument"
202
+ def raise_missing_argument(option)
203
+ abort "error: flag '#{option}' needs an argument"
130
204
  end
131
205
 
132
- def raise_unknown_command(command_name)
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(command_name)
211
+ suggestions = corrector.correct(name)
138
212
 
139
- msg = "error: unknown command '#{command_name}'"
213
+ message = "error: unknown command '#{name}'"
140
214
 
141
215
  unless suggestions.empty?
142
- msg += "\n\nDid you mean?\n"
143
- msg += suggestions.map { |s| "\t#{s}" }.join("\n")
216
+ message += "\n\nDid you mean?\n"
217
+ message += suggestions.map { |s| "\t#{s}" }.join("\n")
144
218
  end
145
219
 
146
- abort msg
220
+ abort message
147
221
  end
148
222
 
149
- def raise_unknown_option(option_name, available_options)
150
- all_switches = available_options.flat_map(&:switches)
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: all_switches
245
+ dictionary: switches
154
246
  )
155
247
 
156
- suggestions = corrector.correct(option_name)
248
+ suggestions = corrector.correct(name)
157
249
 
158
- msg = "error: unknown option '#{option_name}'"
250
+ message = "error: unknown option '#{name}'"
159
251
 
160
252
  unless suggestions.empty?
161
- msg += "\n\nDid you mean?\n"
162
- msg += suggestions.map { |s| "\t#{s}" }.join("\n")
253
+ message += "\n\nDid you mean?\n"
254
+ message += suggestions.map { |s| "\t#{s}" }.join("\n")
163
255
  end
164
256
 
165
- abort msg
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 v1.0.0"
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
- if @commands.empty?
179
- puts " No commands available."
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(', ').ljust(20)} #{option.description}"
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: commander-tool
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jjjm