commander-fastlane 4.4.3

Sign up to get free protection for your applications and to get access to all the features.
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