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