Class: Console::Command
- Defined in:
- lib/qualitysmith_extensions/console/command.rb,
lib/qualitysmith_extensions/console/command.facets.1.8.51.rb,
lib/qualitysmith_extensions/console/command.facets.1.8.54.rb
Overview
Console Command
Console::Command provides a clean and easy way to create a command line interface for your program. The unique technique utlizes a Commandline to Object Mapping (COM) to make it quick and easy.
Synopsis
Let’s make an executable called ‘mycmd’.
#!/usr/bin/env ruby
require 'facets'
require 'command'
MyCmd << Console::Command
def _v
$VERBOSE = true
end
def jump
if $VERBOSE
puts "JUMP! JUMP! JUMP!"
else
puts "Jump"
end
end
end
MyCmd.execute
Then on the command line:
% mycmd jump
Jump
% mycmd -v jump
JUMP! JUMP! JUMP!
Subcommands
Commands can take subcommand and suboptions. To do this simply add a module to your class with the same name as the subcommand, in which the suboption methods are defined.
MyCmd << Console::Command
def initialize
@height = 1
end
def _v
$VERBOSE = true
end
def jump
if $VERBOSE
puts "JUMP!" * @height
else
puts "Jump" * @height
end
end
module Jump
def __height(h)
@height = h.to_i
end
end
end
MyCmd.start
Then on the command line:
% mycmd jump -h 2
Jump Jump
% mycmd -v jump -h 3
JUMP! JUMP! JUMP!
Another thing to notice about this example is that #start is an alias for #execute.
Missing Subcommands
You can use #method_missing to catch missing subcommand calls.
Main and Default
If your command does not take subcommands then simply define a #main method to dispatch action. All options will be treated globablly in this case and any remaining comman-line arguments will be passed to #main.
If on the other hand your command does take subcommands but none is given, the #default method will be called, if defined. If not defined an error will be raised (but only reported if $DEBUG is true).
Global Options
You can define global options which are options that will be processed no matter where they occur in the command line. In the above examples only the options occuring before the subcommand are processed globally. Anything occuring after the subcommand belonds strictly to the subcommand. For instance, if we had added the following to the above example:
global_option :_v
Then -v could appear anywhere in the command line, even on the end, and still work as expected.
% mycmd jump -h 3 -v
Missing Options
You can use #option_missing to catch any options that are not explicility defined.
The method signature should look like:
option_missing(option_name, args)
Example:
def option_missing(option_name, args)
p args if $debug
case option_name
when 'p'
@a = args[0].to_i
@b = args[1].to_i
2
else
raise InvalidOptionError(option_name, args)
end
end
Its return value should be the effective “arity” of that options – that is, how many arguments it consumed (“-p a b”, for example, would consume 2 args: “a” and “b”). An arity of 1 is assumed if nil or false is returned.
Be aware that when using subcommand modules, the same option_missing method will catch missing options for global options and subcommand options too unless an option_missing method is also defined in the subcommand module.
–
Help Documentation
You can also add help information quite easily. If the following code is saved as ‘foo’ for instance.
MyCmd << Console::Command
help "Dispays the word JUMP!"
def jump
if $VERBOSE
puts "JUMP! JUMP! JUMP!"
else
puts "Jump"
end
end
end
MyCmd.execute
then by running ‘foo help’ on the command line, standard help information will be displayed.
foo
jump Displays the word JUMP!
++
Defined Under Namespace
Classes: UnknownOptionError
Class Method Summary collapse
- .alias_subcommand(hash) ⇒ Object
-
.execute(*args) ⇒ Object
Starts the command execution.
-
.global_option(*names) ⇒ Object
Change the option mode.
- .global_options ⇒ Object
-
.pass_through(options, mod) ⇒ Object
This is to be called from your subcommand module to specify which options should simply be “passed on” to some wrapped command that you will later call.
-
.start ⇒ Object
Starts the command execution.
Instance Method Summary collapse
-
#execute(line = nil) ⇒ Object
Execute the command.
-
#initialize(global_options = []) ⇒ Command
constructor
Do not let this pass through to any included module.
Constructor Details
#initialize(global_options = []) ⇒ Command
Do not let this pass through to any included module.
329 330 331 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 329 def initialize(=[]) @global_options = end |
Class Method Details
.alias_subcommand(hash) ⇒ Object
316 317 318 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 316 def alias_subcommand(hash) (@subcommand_aliases ||= {}).merge! hash end |
.execute(*args) ⇒ Object
Starts the command execution.
249 250 251 252 253 254 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 249 def execute( *args ) cmd = new() cmd.instance_variable_set("@global_options", ) cmd.instance_variable_set("@subcommand_aliases", @subcommand_aliases || {}) cmd.execute( *args ) end |
.global_option(*names) ⇒ Object
Change the option mode.
260 261 262 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 260 def global_option( *names ) names.each{ |name| << name.to_sym } end |
.global_options ⇒ Object
264 265 266 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 264 def @global_options ||= [] end |
.pass_through(options, mod) ⇒ Object
This is to be called from your subcommand module to specify which options should simply be “passed on” to some wrapped command that you will later call. Options that are collected by the option methods that this generates will be stored in @passthrough_options (so remember to append that array to your wrapped command!).
module Status
Console::Command.pass_through({
[:_q, :__quiet] => 0,
[:_N, :__non_recursive] => 0,
[:__no_ignore] => 0,
}, self)
end
Development notes:
-
Currently requires you to pass the subcommand module’s “self” to this method. I didn’t know of a better way to cause it to create the instance methods in that module rather than here in Console::Command.
-
Possible alternatives:
-
Binding.of_caller() (facets.rubyforge.org/src/doc/rdoc/core/classes/Binding.html) – wary of using it if it depends on Continuations, which I understand are deprecated
-
copy the pass_through class method to each subcommand module so that calls will be in the module’s context…
-
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 284 def pass_through(, mod) .each do |method_names, arity| method_names.each do |method_name| if method_name == :_u #puts "Defining method #{method_name}(with arity #{arity}) in #{mod.name}" #puts "#{mod.name} has #{(mod.methods - Object.methods).inspect}" end option_name = method_name.to_s.option_demethodize mod.send(:define_method, method_name.to_sym) do |*args| @passthrough_options << option_name args_for_current_option = Escape.shell_command(args.slice(0, arity)) @passthrough_options << args_for_current_option unless args_for_current_option == '' #p args_for_current_option #puts "in #{method_name}: Passing through #{arity} options: #{@passthrough_options.inspect}" #(why does @passthrough_options show up as nil? even when later on it's *not* nil...) arity end # mod.instance_eval %Q{ # def #{method_name}(*args) # @passthrough_options << '#{option_name}' # args_for_current_option = Escape.shell_command(args.slice(0, #{arity})) # @passthrough_options << args_for_current_option unless args_for_current_option == '' # #p args_for_current_option # #puts "in #{method_name}: Passing through #{arity} options: #{@passthrough_options.inspect}" #(why does @passthrough_options show up as nil? even when later on it's *not* nil...) # #{arity} # end # } end end end |
.start ⇒ Object
Starts the command execution.
257 258 259 260 261 262 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 257 def execute( *args ) cmd = new() cmd.instance_variable_set("@global_options", ) cmd.instance_variable_set("@subcommand_aliases", @subcommand_aliases || {}) cmd.execute( *args ) end |
Instance Method Details
#execute(line = nil) ⇒ Object
Execute the command.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/qualitysmith_extensions/console/command.rb', line 335 def execute( line=nil ) begin case line when String arguments = Shellwords.shellwords(line) when Array arguments = line else arguments = ARGV end # Duplicate arguments to work on them in-place. argv = arguments.dup # Split single letter option groupings into separate options. # ie. -xyz => -x -y -z argv = argv.collect { |arg| if md = /^-(\w{2,})/.match( arg ) md[1].split(//).collect { |c| "-#{c}" } else arg end }.flatten # Process global options .each do |name| o = name.to_s.option_demethodize m = method(name) c = m.arity while i = argv.index(o) args = argv.slice!(i,c+1) args.shift m.call(*args) end end # Does this command take subcommands? takes_subcommands = !respond_to?(:main) # Process primary options argv = ( argv, takes_subcommands ) # If this command doesn't take subcommands, then the remaining arguments are arguments for main(). return send(:main, *argv) unless takes_subcommands # What to do if there is nothing else? if argv.empty? if respond_to?(:default) return __send__(:default) else $stderr << "Nothing to do." puts '' # :fix: This seems to be necessary or else I don't see the $stderr output at all! --Tyler return end end # Remaining arguments are subcommand and suboptions. @subcommand = argv.shift.gsub('-','_') @subcommand = (subcommand_aliases[@subcommand.to_sym] || @subcommand).to_s puts "@subcommand = #{@subcommand}" if $debug # Extend subcommand option module #subconst = subcommand.gsub(/\W/,'_').capitalize subconst = @subcommand.modulize #p self.class.constants if $debug if self.class.const_defined?(subconst) puts "Extending self (#{self.class}) with subcommand module #{subconst}" if $debug submod = self.class.const_get(subconst) #puts "... which has these **module** methods (should be instance methods): #{(submod.methods - submod.instance_methods - Object.methods).sort.inspect}" self.extend submod #puts "... and now self has: #{(self.methods - Object.methods).sort.inspect}" end # Is the subcommand defined? # This is a little tricky. The method has to be defined by a *subclass*. @subcommand_is_defined = self.respond_to?( @subcommand ) and !Console::Command.public_instance_methods.include?( @subcommand.to_s ) # The rest of the args will be interpreted as options for this particular subcommand options. argv = ( argv, false ) # Actually call the subcommand (or method_missing if the subcommand method isn't defined) if @subcommand_is_defined puts "Calling #{@subcommand}(#{argv.inspect})" if $debug __send__(@subcommand, *argv) else #begin puts "Calling method_missing with #{@subcommand}, #{argv.inspect}" if $debug method_missing(@subcommand, *argv) #rescue NoMethodError => e #if self.private_methods.include?( "no_command_error" ) # no_command_error( *args ) #else # $stderr << "Non-applicable command -- #{argv.join(' ')}\n" # exit -1 #end #end end rescue UnknownOptionError => exception $stderr << exception. << "\n" exit -1 end # rescue => err # if $DEBUG # raise err # else # msg = err.message.chomp('.') + '.' # msg[0,1] = msg[0,1].capitalize # msg << " (#{err.class})" if $VERBOSE # $stderr << msg # end end |