Class: Commander::Runner

Inherits:
Object show all
Defined in:
lib/commander/runner.rb

Defined Under Namespace

Classes: CommandError, InvalidCommandError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = ARGV) ⇒ Runner

Initialize a new command runner. Optionally supplying args for mocking, or arbitrary usage.



33
34
35
36
37
38
# File 'lib/commander/runner.rb', line 33

def initialize args = ARGV
  @args, @commands, @aliases, @options = args, {}, {}, []
  @help_formatter_aliases = help_formatter_alias_defaults
  @program = program_defaults
  create_default_commands
end

Instance Attribute Details

#commandsObject (readonly)

Array of commands.



17
18
19
# File 'lib/commander/runner.rb', line 17

def commands
  @commands
end

#help_formatter_aliasesObject (readonly)

Hash of help formatter aliases.



27
28
29
# File 'lib/commander/runner.rb', line 27

def help_formatter_aliases
  @help_formatter_aliases
end

#optionsObject (readonly)

Global options.



22
23
24
# File 'lib/commander/runner.rb', line 22

def options
  @options
end

Class Method Details

.instanceObject

Return singleton Runner instance.



43
44
45
# File 'lib/commander/runner.rb', line 43

def self.instance
  @singleton ||= new
end

.separate_switches_from_description(*args) ⇒ Object

Return switches and description separated from the args passed.



347
348
349
350
351
# File 'lib/commander/runner.rb', line 347

def self.separate_switches_from_description *args
  switches = args.find_all { |arg| arg.to_s =~ /^-/ } 
  description = args.last unless !args.last.is_a? String or args.last.match(/^-/)
  return switches, description
end

.switch_to_sym(switch) ⇒ Object

Attempts to generate a method name symbol from switch. For example:

-h                 # => :h
--trace            # => :trace
--some-switch      # => :some_switch
--[no-]feature     # => :feature
--file FILE        # => :file
--list of,things   # => :list


365
366
367
# File 'lib/commander/runner.rb', line 365

def self.switch_to_sym switch
  switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
end

Instance Method Details

#active_commandObject

Get active command within arguments passed to this runner.



200
201
202
# File 'lib/commander/runner.rb', line 200

def active_command
  @__active_command ||= command(command_name_from_args)
end

#add_command(command) ⇒ Object

Add a command object to this runner.



177
178
179
# File 'lib/commander/runner.rb', line 177

def add_command command
  @commands[command.name] = command
end

#alias?(name) ⇒ Boolean

Check if command name is an alias.

Returns:

  • (Boolean)


184
185
186
# File 'lib/commander/runner.rb', line 184

def alias? name
  @aliases.include? name.to_s
end

#alias_command(alias_name, name, *args) ⇒ Object

Alias command name with alias_name. Optionally args may be passed as if they were being passed straight to the original command via the command-line.



161
162
163
164
# File 'lib/commander/runner.rb', line 161

def alias_command alias_name, name, *args
  @commands[alias_name.to_s] = command name
  @aliases[alias_name.to_s] = args
end

#args_without_command_nameObject

Return arguments without the command name.



230
231
232
233
234
235
236
# File 'lib/commander/runner.rb', line 230

def args_without_command_name
  removed = []
  parts = command_name_from_args.split rescue []
  @args.dup.delete_if do |arg|
    removed << arg if parts.include?(arg) and not removed.include?(arg)
  end
end

#command(name) {|add_command(Commander::Command.new(name))| ... } ⇒ Object

Creates and yields a command instance when a block is passed. Otherwise attempts to return the command, raising InvalidCommandError when it does not exist.

Examples

command :my_command do |c|
  c.when_called do |args|
    # Code
  end
end

Yields:



138
139
140
141
# File 'lib/commander/runner.rb', line 138

def command name, &block
  yield add_command(Commander::Command.new(name)) if block
  @commands[name.to_s]
end

#command_exists?(name) ⇒ Boolean

Check if a command name exists.

Returns:

  • (Boolean)


191
192
193
# File 'lib/commander/runner.rb', line 191

def command_exists? name
  @commands[name.to_s]
end

#command_name_from_argsObject

Attempts to locate a command name from within the arguments. Supports multi-word commands, using the largest possible match.



208
209
210
# File 'lib/commander/runner.rb', line 208

def command_name_from_args
  @__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
end

#create_default_commandsObject

Creates default commands such as ‘help’ which is essentially the same as using the –help switch.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/commander/runner.rb', line 257

def create_default_commands
  command :help do |c|
    c.syntax = 'commander help [command]'
    c.description = 'Display global or [command] help documentation.'
    c.example 'Display global help', 'command help'
    c.example "Display help for 'foo'", 'command help foo'
    c.when_called do |args, options|
      enable_paging
      if args.empty?
        say help_formatter.render 
      else
        command = command args.join(' ')
        require_valid_command command
        say help_formatter.render_command(command)
      end
    end
  end
end

#default_command(name) ⇒ Object

Default command name to be used when no other command is found in the arguments.



170
171
172
# File 'lib/commander/runner.rb', line 170

def default_command name
  @default_command = name
end

#global_option(*args, &block) ⇒ Object

Add a global option; follows the same syntax as Command#option This would be used for switches such as –version, –trace, etc.



147
148
149
150
151
152
153
154
155
# File 'lib/commander/runner.rb', line 147

def global_option *args, &block
  switches, description = Runner.separate_switches_from_description *args
  @options << {
    :args => args,
    :proc => block,
    :switches => switches,
    :description => description,
  }
end

#global_option_proc(switches, &block) ⇒ Object

Returns a proc allowing for commands to inherit global options. This functionality works whether a block is present for the global option or not, so simple switches such as –verbose can be used without a block, and used throughout all commands.



326
327
328
329
330
331
332
333
# File 'lib/commander/runner.rb', line 326

def global_option_proc switches, &block
  lambda do |value|
    unless active_command.nil?
      active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
    end
    yield value if block and !value.nil?
  end
end

#help_formatterObject

Help formatter instance.



223
224
225
# File 'lib/commander/runner.rb', line 223

def help_formatter
  @__help_formatter ||= program(:help_formatter).new self
end

#help_formatter_alias_defaultsObject

Returns hash of help formatter alias defaults.



241
242
243
# File 'lib/commander/runner.rb', line 241

def help_formatter_alias_defaults
  return :compact => HelpFormatter::TerminalCompact
end

#parse_global_optionsObject

Parse global command options.



311
312
313
314
315
316
317
318
# File 'lib/commander/runner.rb', line 311

def parse_global_options
  options.inject OptionParser.new do |options, option|
    options.on *option[:args], &global_option_proc(option[:switches], &option[:proc])
  end.parse! @args.dup
rescue OptionParser::InvalidOption
  # Ignore invalid options since options will be further 
  # parsed by our sub commands.
end

#program(key, *args) ⇒ Object

Assign program information.

Examples

# Set data
program :name, 'Commander'
program :version, Commander::VERSION
program :description, 'Commander utility program.'
program :help, 'Copyright', '2008 TJ Holowaychuk'
program :help, 'Anything', 'You want'
program :int_message 'Bye bye!'
program :help_formatter, :compact
program :help_formatter, Commander::HelpFormatter::TerminalCompact

# Get data
program :name # => 'Commander'

Keys

:version         (required) Program version triple, ex: '0.0.1'
:description     (required) Program description
:name            Program name, defaults to basename of executable
:help_formatter  Defaults to Commander::HelpFormatter::Terminal
:help            Allows addition of arbitrary global help blocks
:int_message     Message to display when interrupted (CTRL + C)


112
113
114
115
116
117
118
119
120
121
122
# File 'lib/commander/runner.rb', line 112

def program key, *args
  if key == :help and !args.empty?
    @program[:help] ||= {}
    @program[:help][args.first] = args.at(1)
  elsif key == :help_formatter && !args.empty?
    @program[key] = (@help_formatter_aliases[args.first] || args.first)
  else
    @program[key] = *args unless args.empty?
    @program[key]
  end
end

#program_defaultsObject

Returns hash of program defaults.



248
249
250
251
# File 'lib/commander/runner.rb', line 248

def program_defaults
  return :help_formatter => HelpFormatter::Terminal, 
         :name => File.basename($0)
end

#remove_global_options(options, args) ⇒ Object

Removes global options from args. This prevents an invalid option error from occurring when options are parsed again for the command.



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/commander/runner.rb', line 288

def remove_global_options options, args
  # TODO: refactor with flipflop, please TJ ! have time to refactor me !
  options.each do |option|
    switches = option[:switches]
    past_switch, arg_removed = false, false
    args.delete_if do |arg|
      # TODO: clean this up, no rescuing ;)
      if switches.any? { |switch| switch.match(/^#{arg}/) rescue false }
        past_switch, arg_removed = true, false
        true
      elsif past_switch && !arg_removed && arg !~ /^-/ 
        arg_removed = true
      else
        arg_removed = true
        false
      end
    end
  end
end

#require_program(*keys) ⇒ Object

Raises a CommandError when the program any of the keys are not present, or empty.



338
339
340
341
342
# File 'lib/commander/runner.rb', line 338

def require_program *keys
  keys.each do |key|
    raise CommandError, "program #{key} required" if program(key).nil? or program(key).empty?
  end
end

#require_valid_command(command = active_command) ⇒ Object

Raises InvalidCommandError when a command is not found.



279
280
281
# File 'lib/commander/runner.rb', line 279

def require_valid_command command = active_command
  raise InvalidCommandError, 'invalid command', caller if command.nil?
end

#run!Object

Run command parsing and execution process.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/commander/runner.rb', line 50

def run!
  trace = false
  require_program :version, :description
  trap('INT') { abort program(:int_message) } if program(:int_message)
  global_option('-h', '--help', 'Display help documentation') { command(:help).run *@args[1..-1]; return }
  global_option('-v', '--version', 'Display version information') { say version; return } 
  global_option('-t', '--trace', 'Display backtrace when an error occurs') { trace = true }
  parse_global_options
  remove_global_options options, @args
  unless trace
    begin
      run_active_command
    rescue InvalidCommandError => e
      abort "#{e}. Use --help for more information"
    rescue \
      OptionParser::InvalidOption, 
      OptionParser::InvalidArgument,
      OptionParser::MissingArgument => e
      abort e
    rescue => e
      abort "error: #{e}. Use --trace to view backtrace"
    end
  else
    run_active_command
  end
end

#run_active_commandObject

Run the active command.



372
373
374
375
376
377
378
379
# File 'lib/commander/runner.rb', line 372

def run_active_command
  require_valid_command
  if alias? command_name_from_args
    active_command.run *(@aliases[command_name_from_args.to_s] + args_without_command_name)
  else
    active_command.run *args_without_command_name
  end      
end

#say(*args) ⇒ Object

:nodoc:



381
382
383
# File 'lib/commander/runner.rb', line 381

def say *args #:nodoc: 
  $terminal.say *args
end

#valid_command_names_from(*args) ⇒ Object

Returns array of valid command names found within args.



215
216
217
218
# File 'lib/commander/runner.rb', line 215

def valid_command_names_from *args
  arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
  commands.keys.find_all { |name| name if /^#{name}/.match arg_string }
end

#versionObject

Return program version.



80
81
82
# File 'lib/commander/runner.rb', line 80

def version
  '%s %s' % [program(:name), program(:version)]
end