commander-fastlane 4.4.3

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +37 -0
  5. data/.rubocop_todo.yml +77 -0
  6. data/.travis.yml +12 -0
  7. data/DEVELOPMENT +15 -0
  8. data/Gemfile +3 -0
  9. data/History.rdoc +428 -0
  10. data/LICENSE +22 -0
  11. data/Manifest +38 -0
  12. data/README.md +460 -0
  13. data/Rakefile +13 -0
  14. data/bin/commander +104 -0
  15. data/commander.gemspec +31 -0
  16. data/lib/commander.rb +35 -0
  17. data/lib/commander/blank.rb +7 -0
  18. data/lib/commander/command.rb +210 -0
  19. data/lib/commander/configure.rb +14 -0
  20. data/lib/commander/core_ext.rb +2 -0
  21. data/lib/commander/core_ext/array.rb +24 -0
  22. data/lib/commander/core_ext/object.rb +8 -0
  23. data/lib/commander/delegates.rb +25 -0
  24. data/lib/commander/help_formatters.rb +49 -0
  25. data/lib/commander/help_formatters/base.rb +24 -0
  26. data/lib/commander/help_formatters/terminal.rb +19 -0
  27. data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
  28. data/lib/commander/help_formatters/terminal/help.erb +44 -0
  29. data/lib/commander/help_formatters/terminal_compact.rb +11 -0
  30. data/lib/commander/help_formatters/terminal_compact/command_help.erb +27 -0
  31. data/lib/commander/help_formatters/terminal_compact/help.erb +35 -0
  32. data/lib/commander/import.rb +5 -0
  33. data/lib/commander/methods.rb +11 -0
  34. data/lib/commander/platform.rb +7 -0
  35. data/lib/commander/runner.rb +484 -0
  36. data/lib/commander/user_interaction.rb +528 -0
  37. data/lib/commander/version.rb +3 -0
  38. data/spec/command_spec.rb +157 -0
  39. data/spec/configure_spec.rb +37 -0
  40. data/spec/core_ext/array_spec.rb +18 -0
  41. data/spec/core_ext/object_spec.rb +19 -0
  42. data/spec/help_formatters/terminal_compact_spec.rb +195 -0
  43. data/spec/help_formatters/terminal_spec.rb +190 -0
  44. data/spec/methods_spec.rb +20 -0
  45. data/spec/runner_spec.rb +646 -0
  46. data/spec/spec_helper.rb +78 -0
  47. data/spec/ui_spec.rb +30 -0
  48. metadata +175 -0
@@ -0,0 +1,8 @@
1
+ class Object
2
+ ##
3
+ # Return the current binding.
4
+
5
+ def get_binding
6
+ binding
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ module Commander
2
+ module Delegates
3
+ %w(
4
+ add_command
5
+ command
6
+ program
7
+ run!
8
+ global_option
9
+ alias_command
10
+ default_command
11
+ always_trace!
12
+ never_trace!
13
+ ).each do |meth|
14
+ eval <<-END, binding, __FILE__, __LINE__
15
+ def #{meth}(*args, &block)
16
+ ::Commander::Runner.instance.#{meth}(*args, &block)
17
+ end
18
+ END
19
+ end
20
+
21
+ def defined_commands(*args, &block)
22
+ ::Commander::Runner.instance.commands(*args, &block)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module Commander
2
+ module HelpFormatter
3
+ autoload :Base, 'commander/help_formatters/base'
4
+ autoload :Terminal, 'commander/help_formatters/terminal'
5
+ autoload :TerminalCompact, 'commander/help_formatters/terminal_compact'
6
+
7
+ class Context
8
+ def initialize(target)
9
+ @target = target
10
+ end
11
+
12
+ def get_binding
13
+ @target.instance_eval { binding }.tap do |bind|
14
+ decorate_binding(bind)
15
+ end
16
+ end
17
+
18
+ # No-op, override in subclasses.
19
+ def decorate_binding(_bind)
20
+ end
21
+ end
22
+
23
+ class ProgramContext < Context
24
+ def decorate_binding(bind)
25
+ bind.eval("max_command_length = #{max_command_length(bind)}")
26
+ bind.eval("max_aliases_length = #{max_aliases_length(bind)}")
27
+ end
28
+
29
+ def max_command_length(bind)
30
+ max_key_length(bind.eval('@commands'))
31
+ end
32
+
33
+ def max_aliases_length(bind)
34
+ max_key_length(bind.eval('@aliases'))
35
+ end
36
+
37
+ def max_key_length(hash, default = 20)
38
+ longest = hash.keys.max_by(&:size)
39
+ longest ? longest.size : default
40
+ end
41
+ end
42
+
43
+ module_function
44
+
45
+ def indent(amount, text)
46
+ text.to_s.gsub("\n", "\n" + (' ' * amount))
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ module Commander
2
+ ##
3
+ # = Help Formatter
4
+ #
5
+ # Commander's help formatters control the output when
6
+ # either the help command, or --help switch are called.
7
+ # The default formatter is Commander::HelpFormatter::Terminal.
8
+
9
+ module HelpFormatter
10
+ class Base
11
+ def initialize(runner)
12
+ @runner = runner
13
+ end
14
+
15
+ def render
16
+ 'Implement global help here'
17
+ end
18
+
19
+ def render_command(command)
20
+ "Implement help for #{command.name} here"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require 'erb'
2
+
3
+ module Commander
4
+ module HelpFormatter
5
+ class Terminal < Base
6
+ def render
7
+ template(:help).result(ProgramContext.new(@runner).get_binding)
8
+ end
9
+
10
+ def render_command(command)
11
+ template(:command_help).result(Context.new(command).get_binding)
12
+ end
13
+
14
+ def template(name)
15
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal', "#{name}.erb")), nil, '-')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+
2
+ <%= $terminal.color "NAME", :bold %>:
3
+
4
+ <%= @name %>
5
+ <% if @syntax -%>
6
+
7
+ <%= $terminal.color "SYNOPSIS", :bold %>:
8
+
9
+ <%= @syntax -%>
10
+
11
+ <% end -%>
12
+
13
+ <%= $terminal.color "DESCRIPTION", :bold %>:
14
+
15
+ <%= Commander::HelpFormatter.indent 4, (@description || @summary || 'No description.') -%>
16
+
17
+ <% unless @examples.empty? -%>
18
+
19
+ <%= $terminal.color "EXAMPLES", :bold %>:
20
+ <% for description, command in @examples -%>
21
+
22
+ # <%= description %>
23
+ <%= command %>
24
+ <% end -%>
25
+ <% end -%>
26
+ <% unless @options.empty? -%>
27
+
28
+ <%= $terminal.color "OPTIONS", :bold %>:
29
+ <% for option in @options -%>
30
+
31
+ <%= option[:switches].join ', ' %>
32
+ <%= Commander::HelpFormatter.indent 8, option[:description] %>
33
+ <% end -%>
34
+ <% end -%>
35
+
@@ -0,0 +1,44 @@
1
+ <%= $terminal.color "NAME", :bold %>:
2
+
3
+ <%= program :name %>
4
+
5
+ <%= $terminal.color "DESCRIPTION", :bold %>:
6
+
7
+ <%= Commander::HelpFormatter.indent 4, program(:description) %>
8
+
9
+ <%= "#{$terminal.color "COMMANDS", :bold}:#{default_command_description}" %>
10
+ <% for name, command in @commands.sort -%>
11
+ <% unless alias? name %>
12
+ <%= "%-#{max_command_length}s #{summary_prefix(command.name)}%s" % [command.name, command.summary || command.description] -%>
13
+ <% end -%>
14
+ <% end %>
15
+ <% unless @aliases.empty? %>
16
+ <%= $terminal.color "ALIASES", :bold %>:
17
+ <% for alias_name, args in @aliases.sort %>
18
+ <%= "%-#{max_aliases_length}s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] -%>
19
+ <% end %>
20
+ <% end %>
21
+ <% unless @options.empty? -%>
22
+ <%= $terminal.color "GLOBAL OPTIONS", :bold %>:
23
+ <% for option in @options -%>
24
+
25
+ <%= option[:switches].join ', ' %>
26
+ <%= option[:description] %>
27
+ <% end -%>
28
+ <% end -%>
29
+ <% unless default_command_options.empty? %>
30
+ <%= $terminal.color "OPTIONS for #{@default_command}", :bold %>:
31
+ <% for option in @default_command_options -%>
32
+
33
+ <%= option[:switches].join ', ' %>
34
+ <%= option[:description] %>
35
+ <% end -%>
36
+ <% end -%>
37
+ <% if program :help -%>
38
+ <% for title, body in program(:help) %>
39
+ <%= $terminal.color title.to_s.upcase, :bold %>:
40
+
41
+ <%= body %>
42
+ <% end -%>
43
+ <% end -%>
44
+
@@ -0,0 +1,11 @@
1
+ require 'erb'
2
+
3
+ module Commander
4
+ module HelpFormatter
5
+ class TerminalCompact < Terminal
6
+ def template(name)
7
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal_compact', "#{name}.erb")), nil, '-')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+
2
+ <%= @name %>
3
+ <% if @syntax -%>
4
+
5
+ Usage: <%= @syntax %>
6
+ <% end -%>
7
+ <% if @description || @summary -%>
8
+
9
+ <%= @description || @summary %>
10
+ <% end -%>
11
+ <% unless @examples.empty? -%>
12
+
13
+ Examples:
14
+ <% for description, command in @examples -%>
15
+
16
+ # <%= description %>
17
+ <%= command %>
18
+ <% end -%>
19
+ <% end -%>
20
+ <% unless @options.empty? -%>
21
+
22
+ Options:
23
+ <% for option in @options -%>
24
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] %>
25
+ <% end -%>
26
+ <% end -%>
27
+
@@ -0,0 +1,35 @@
1
+ <%= program :name %>
2
+
3
+ <%= program :description %>
4
+
5
+ <%= "Commands:#{default_command_description}" %>
6
+ <% for name, command in @commands.sort -%>
7
+ <% unless alias? name -%>
8
+ <%= "%-#{max_command_length}s #{summary_prefix(command.name)}%s" % [command.name, command.summary || command.description] %>
9
+ <% end -%>
10
+ <% end -%>
11
+ <% unless @aliases.empty? %>
12
+ Aliases:
13
+ <% for alias_name, args in @aliases.sort -%>
14
+ <%= "%-#{max_aliases_length}s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] %>
15
+ <% end -%>
16
+ <% end %>
17
+ <% unless @options.empty? -%>
18
+ Global Options:
19
+ <% for option in @options -%>
20
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] -%>
21
+ <% end -%>
22
+ <% end -%>
23
+ <% unless default_command_options.empty? %>
24
+ <%= "Options for #{@default_command}:" %>
25
+ <% for option in @default_command_options -%>
26
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] %>
27
+ <% end -%>
28
+ <% end -%>
29
+ <% if program :help -%>
30
+ <% for title, body in program(:help) %>
31
+ <%= title %>:
32
+ <%= body %>
33
+ <% end %>
34
+ <% end -%>
35
+
@@ -0,0 +1,5 @@
1
+ require 'commander'
2
+
3
+ include Commander::Methods
4
+
5
+ at_exit { run! }
@@ -0,0 +1,11 @@
1
+ module Commander
2
+ module Methods
3
+ include Commander::UI
4
+ include Commander::UI::AskForClass
5
+ include Commander::Delegates
6
+
7
+ if $stdin.tty? && (cols = $terminal.output_cols) >= 40
8
+ $terminal.wrap_at = cols - 5
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Commander
2
+ module Platform
3
+ def self.jruby?
4
+ defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'jruby')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,484 @@
1
+ require 'optparse'
2
+
3
+ module Commander
4
+ class Runner
5
+ #--
6
+ # Exceptions
7
+ #++
8
+
9
+ class CommandError < StandardError; end
10
+ class InvalidCommandError < CommandError; end
11
+
12
+ ##
13
+ # Array of commands.
14
+
15
+ attr_reader :commands
16
+
17
+ ##
18
+ # Global options.
19
+
20
+ attr_reader :options
21
+
22
+ ##
23
+ # Hash of help formatter aliases.
24
+
25
+ attr_reader :help_formatter_aliases
26
+
27
+ ##
28
+ # Initialize a new command runner. Optionally
29
+ # supplying _args_ for mocking, or arbitrary usage.
30
+
31
+ def initialize(args = ARGV)
32
+ @args, @commands, @aliases, @options,
33
+ @default_command_options = args, {}, {}, [], []
34
+ @help_formatter_aliases = help_formatter_alias_defaults
35
+ @program = program_defaults
36
+ @always_trace = false
37
+ @never_trace = false
38
+ create_default_commands
39
+ end
40
+
41
+ ##
42
+ # Return singleton Runner instance.
43
+
44
+ def self.instance
45
+ @singleton ||= new
46
+ end
47
+
48
+ ##
49
+ # Run command parsing and execution process.
50
+
51
+ def run!
52
+ trace = @always_trace || false
53
+ require_program :version, :description
54
+ trap('INT') { abort program(:int_message) } if program(:int_message)
55
+ trap('INT') { program(:int_block).call } if program(:int_block)
56
+ global_option('-h', '--help', 'Display help documentation') do
57
+ args = @args - %w(-h --help)
58
+ command(:help).run(*args)
59
+ return
60
+ end
61
+ global_option('-v', '--version', 'Display version information') do
62
+ say version
63
+ return
64
+ end
65
+ global_option('-t', '--trace', 'Display backtrace when an error occurs') { trace = true } unless @never_trace || @always_trace
66
+ parse_global_options
67
+ remove_global_options options, @args
68
+ if trace
69
+ run_active_command
70
+ else
71
+ begin
72
+ run_active_command
73
+ rescue InvalidCommandError => e
74
+ abort "#{e}. Use --help for more information"
75
+ rescue \
76
+ OptionParser::InvalidOption,
77
+ OptionParser::InvalidArgument,
78
+ OptionParser::MissingArgument => e
79
+ abort e.to_s
80
+ rescue => e
81
+ if @never_trace
82
+ abort "error: #{e}."
83
+ else
84
+ abort "error: #{e}. Use --trace to view backtrace"
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Return program version.
92
+
93
+ def version
94
+ format('%s %s', program(:name), program(:version))
95
+ end
96
+
97
+ ##
98
+ # Enable tracing on all executions (bypasses --trace)
99
+
100
+ def always_trace!
101
+ @always_trace = true
102
+ @never_trace = false
103
+ end
104
+
105
+ ##
106
+ # Hide the trace option from the help menus and don't add it as a global option
107
+
108
+ def never_trace!
109
+ @never_trace = true
110
+ @always_trace = false
111
+ end
112
+
113
+ ##
114
+ # Assign program information.
115
+ #
116
+ # === Examples
117
+ #
118
+ # # Set data
119
+ # program :name, 'Commander'
120
+ # program :version, Commander::VERSION
121
+ # program :description, 'Commander utility program.'
122
+ # program :help, 'Copyright', '2008 TJ Holowaychuk'
123
+ # program :help, 'Anything', 'You want'
124
+ # program :int_message 'Bye bye!'
125
+ # program :help_formatter, :compact
126
+ # program :help_formatter, Commander::HelpFormatter::TerminalCompact
127
+ #
128
+ # # Get data
129
+ # program :name # => 'Commander'
130
+ #
131
+ # === Keys
132
+ #
133
+ # :version (required) Program version triple, ex: '0.0.1'
134
+ # :description (required) Program description
135
+ # :name Program name, defaults to basename of executable
136
+ # :help_formatter Defaults to Commander::HelpFormatter::Terminal
137
+ # :help Allows addition of arbitrary global help blocks
138
+ # :help_paging Flag for toggling help paging
139
+ # :int_message Message to display when interrupted (CTRL + C)
140
+ #
141
+
142
+ def program(key, *args, &block)
143
+ if key == :help && !args.empty?
144
+ @program[:help] ||= {}
145
+ @program[:help][args.first] = args.at(1)
146
+ elsif key == :help_formatter && !args.empty?
147
+ @program[key] = (@help_formatter_aliases[args.first] || args.first)
148
+ elsif block
149
+ @program[key] = block
150
+ else
151
+ unless args.empty?
152
+ @program[key] = args.count == 1 ? args[0] : args
153
+ end
154
+ @program[key]
155
+ end
156
+ end
157
+
158
+ ##
159
+ # Creates and yields a command instance when a block is passed.
160
+ # Otherwise attempts to return the command, raising InvalidCommandError when
161
+ # it does not exist.
162
+ #
163
+ # === Examples
164
+ #
165
+ # command :my_command do |c|
166
+ # c.when_called do |args|
167
+ # # Code
168
+ # end
169
+ # end
170
+ #
171
+
172
+ def command(name, &block)
173
+ yield add_command(Commander::Command.new(name)) if block
174
+ @commands[name.to_s]
175
+ end
176
+
177
+ ##
178
+ # Add a global option; follows the same syntax as Command#option
179
+ # This would be used for switches such as --version, --trace, etc.
180
+
181
+ def global_option(*args, &block)
182
+ switches, description = Runner.separate_switches_from_description(*args)
183
+ @options << {
184
+ args: args,
185
+ proc: block,
186
+ switches: switches,
187
+ description: description,
188
+ }
189
+ end
190
+
191
+ ##
192
+ # Alias command _name_ with _alias_name_. Optionally _args_ may be passed
193
+ # as if they were being passed straight to the original command via the command-line.
194
+
195
+ def alias_command(alias_name, name, *args)
196
+ @commands[alias_name.to_s] = command name
197
+ @aliases[alias_name.to_s] = args
198
+ end
199
+
200
+ ##
201
+ # Default command _name_ to be used when no other
202
+ # command is found in the arguments.
203
+
204
+ def default_command(name)
205
+ @default_command = name
206
+ end
207
+
208
+ ##
209
+ # Description about mark for default_command
210
+
211
+ def default_command_description
212
+ return ' (* default)' if @default_command
213
+ ''
214
+ end
215
+
216
+ ##
217
+ # Prefix for summary to identify default_command easily.
218
+
219
+ def summary_prefix(name)
220
+ return '' if @default_command.nil?
221
+ return '* ' if name == @default_command.to_s
222
+ ' '
223
+ end
224
+
225
+ ##
226
+ # Options of default_command.
227
+
228
+ def default_command_options
229
+ return [] if @commands.empty?
230
+
231
+ default_command = @commands.values.find { |command| command.name == @default_command.to_s }
232
+ return [] if default_command.nil?
233
+
234
+ @default_command_options = default_command.options
235
+ end
236
+
237
+ ##
238
+ # Add a command object to this runner.
239
+
240
+ def add_command(command)
241
+ @commands[command.name] = command
242
+ end
243
+
244
+ ##
245
+ # Check if command _name_ is an alias.
246
+
247
+ def alias?(name)
248
+ @aliases.include? name.to_s
249
+ end
250
+
251
+ ##
252
+ # Check if a command _name_ exists.
253
+
254
+ def command_exists?(name)
255
+ @commands[name.to_s]
256
+ end
257
+
258
+ #:stopdoc:
259
+
260
+ ##
261
+ # Get active command within arguments passed to this runner.
262
+
263
+ def active_command
264
+ @__active_command ||= command(command_name_from_args)
265
+ end
266
+
267
+ ##
268
+ # Attempts to locate a command name from within the arguments.
269
+ # Supports multi-word commands, using the largest possible match.
270
+
271
+ def command_name_from_args
272
+ @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
273
+ end
274
+
275
+ ##
276
+ # Returns array of valid command names found within _args_.
277
+
278
+ def valid_command_names_from(*args)
279
+ arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
280
+ commands.keys.find_all { |name| name if arg_string =~ /^#{name}\b/ }
281
+ end
282
+
283
+ ##
284
+ # Help formatter instance.
285
+
286
+ def help_formatter
287
+ @__help_formatter ||= program(:help_formatter).new self
288
+ end
289
+
290
+ ##
291
+ # Return arguments without the command name.
292
+
293
+ def args_without_command_name
294
+ removed = []
295
+ parts = command_name_from_args.split rescue []
296
+ @args.dup.delete_if do |arg|
297
+ removed << arg if parts.include?(arg) && !removed.include?(arg)
298
+ end
299
+ end
300
+
301
+ ##
302
+ # Returns hash of help formatter alias defaults.
303
+
304
+ def help_formatter_alias_defaults
305
+ {
306
+ compact: HelpFormatter::TerminalCompact,
307
+ }
308
+ end
309
+
310
+ ##
311
+ # Returns hash of program defaults.
312
+
313
+ def program_defaults
314
+ {
315
+ help_formatter: HelpFormatter::Terminal,
316
+ name: File.basename($PROGRAM_NAME),
317
+ help_paging: true,
318
+ }
319
+ end
320
+
321
+ ##
322
+ # Creates default commands such as 'help' which is
323
+ # essentially the same as using the --help switch.
324
+
325
+ def create_default_commands
326
+ command :help do |c|
327
+ c.syntax = 'commander help [command]'
328
+ c.description = 'Display global or [command] help documentation'
329
+ c.example 'Display global help', 'command help'
330
+ c.example "Display help for 'foo'", 'command help foo'
331
+ c.when_called do |args, _options|
332
+ UI.enable_paging if program(:help_paging)
333
+ if args.empty?
334
+ say help_formatter.render
335
+ else
336
+ command = command args.join(' ')
337
+ begin
338
+ require_valid_command command
339
+ rescue InvalidCommandError => e
340
+ abort "#{e}. Use --help for more information"
341
+ end
342
+ say help_formatter.render_command(command)
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ ##
349
+ # Raises InvalidCommandError when a _command_ is not found.
350
+
351
+ def require_valid_command(command = active_command)
352
+ fail InvalidCommandError, 'invalid command', caller if command.nil?
353
+ end
354
+
355
+ ##
356
+ # Removes global _options_ from _args_. This prevents an invalid
357
+ # option error from occurring when options are parsed
358
+ # again for the command.
359
+
360
+ def remove_global_options(options, args)
361
+ # TODO: refactor with flipflop, please TJ ! have time to refactor me !
362
+ options.each do |option|
363
+ switches = option[:switches].dup
364
+ next if switches.empty?
365
+
366
+ if (switch_has_arg = switches.any? { |s| s =~ /[ =]/ })
367
+ switches.map! { |s| s[0, s.index('=') || s.index(' ') || s.length] }
368
+ end
369
+
370
+ switches = expand_optionally_negative_switches(switches)
371
+
372
+ past_switch, arg_removed = false, false
373
+ args.delete_if do |arg|
374
+ if switches.any? { |s| s[0, arg.length] == arg }
375
+ arg_removed = !switch_has_arg
376
+ past_switch = true
377
+ elsif past_switch && !arg_removed && arg !~ /^-/
378
+ arg_removed = true
379
+ else
380
+ arg_removed = true
381
+ false
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ # expand switches of the style '--[no-]blah' into both their
388
+ # '--blah' and '--no-blah' variants, so that they can be
389
+ # properly detected and removed
390
+ def expand_optionally_negative_switches(switches)
391
+ switches.reduce([]) do |memo, val|
392
+ if val =~ /\[no-\]/
393
+ memo << val.gsub(/\[no-\]/, '')
394
+ memo << val.gsub(/\[no-\]/, 'no-')
395
+ else
396
+ memo << val
397
+ end
398
+ end
399
+ end
400
+
401
+ ##
402
+ # Parse global command options.
403
+
404
+ def parse_global_options
405
+ parser = options.inject(OptionParser.new) do |options, option|
406
+ options.on(*option[:args], &global_option_proc(option[:switches], &option[:proc]))
407
+ end
408
+
409
+ options = @args.dup
410
+ begin
411
+ parser.parse!(options)
412
+ rescue OptionParser::InvalidOption => e
413
+ # Remove the offending args and retry.
414
+ options = options.reject { |o| e.args.include?(o) }
415
+ retry
416
+ end
417
+ end
418
+
419
+ ##
420
+ # Returns a proc allowing for commands to inherit global options.
421
+ # This functionality works whether a block is present for the global
422
+ # option or not, so simple switches such as --verbose can be used
423
+ # without a block, and used throughout all commands.
424
+
425
+ def global_option_proc(switches, &block)
426
+ lambda do |value|
427
+ unless active_command.nil?
428
+ active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
429
+ end
430
+ yield value if block && !value.nil?
431
+ end
432
+ end
433
+
434
+ ##
435
+ # Raises a CommandError when the program any of the _keys_ are not present, or empty.
436
+
437
+ def require_program(*keys)
438
+ keys.each do |key|
439
+ fail CommandError, "program #{key} required" if program(key).nil? || program(key).empty?
440
+ end
441
+ end
442
+
443
+ ##
444
+ # Return switches and description separated from the _args_ passed.
445
+
446
+ def self.separate_switches_from_description(*args)
447
+ switches = args.find_all { |arg| arg.to_s =~ /^-/ }
448
+ description = args.last if args.last.is_a?(String) && !args.last.match(/^-/)
449
+ [switches, description]
450
+ end
451
+
452
+ ##
453
+ # Attempts to generate a method name symbol from +switch+.
454
+ # For example:
455
+ #
456
+ # -h # => :h
457
+ # --trace # => :trace
458
+ # --some-switch # => :some_switch
459
+ # --[no-]feature # => :feature
460
+ # --file FILE # => :file
461
+ # --list of,things # => :list
462
+ #
463
+
464
+ def self.switch_to_sym(switch)
465
+ switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
466
+ end
467
+
468
+ ##
469
+ # Run the active command.
470
+
471
+ def run_active_command
472
+ require_valid_command
473
+ if alias? command_name_from_args
474
+ active_command.run(*(@aliases[command_name_from_args.to_s] + args_without_command_name))
475
+ else
476
+ active_command.run(*args_without_command_name)
477
+ end
478
+ end
479
+
480
+ def say(*args) #:nodoc:
481
+ $terminal.say(*args)
482
+ end
483
+ end
484
+ end