lib/executable.rb in executable-1.0.0 vs lib/executable.rb in executable-1.1.0

- old
+ new

@@ -1,92 +1,73 @@ # = Executable Mixin # -# The Executable mixin is a very quick and and easy -# way to make almost any class usable via a command -# line interface. It simply uses writer methods as -# option setters, and the first command line argument -# as the method to call, with the subsequent arguments -# passed to the method. +# The Executable mixin is a very quick and and easy way to make almost +# any class usable via a command line interface. It simply uses writer +# methods as option setters, and the first command line argument as a +# method to call with the subsequent arguments passed to the method. # -# The only limitation of this approach (besides the weak -# control of the process) is that required options must -# be specified with the key=value notation. +# The only limitation of this approach is that non-boolean options must +# be specified with `key=value` notation. # -# class X +# class Example # include Executable # # attr_accessor :quiet # # def bread(*args) -# ["BREAD", quiet, *args] +# ["bread", quiet, *args] # end # # def butter(*args) -# ["BUTTER", quiet, *args] +# ["butter", quiet, *args] # end # end # -# x = X.new +# ex = Example.new # -# x.execute_command("butter yum") -# => ["BUTTER", nil, "yum"] +# ex.execute!("butter yum") +# => ["butter", nil, "yum"] # -# x.execute_command("bread --quiet") -# => ["BUTTER", true] +# ex.execute!("bread --quiet") +# => ["butter", true] # -# Executable also defines #command_missing and #option_missing, -# which you can override to provide suitable results. +# Executable also provides #option_missing, which you can overriden to provide +# suitable results when a given command line option has no corresponding +# writer method. # -# TODO: Maybe command_missing is redundant, and method_missing would suffice? -# module Executable - class NoCommandError < NameError + # Used the #excute! method to invoke the command. + def execute!(argv=ARGV) + Executable.execute(self, argv) end - class NoOptionError < ArgumentError - end + ## When no attribute write exists for an option that has been given on + ## the command line #option_missing is called. Override #option_missing + ## to handle these cases, if needed. Otherwise a NoMethodArgument will be + ## raised. This callback method receives the name and value of the option. + #def option_missing(opt, arg) + # raise NoMethodError, "undefined option `#{opt}=' for #{self}" + #end - # Used to invoke the command. - def execute_command(argv=ARGV) - Executable.run(self, argv) - end - - # This is the fallback subcommand. Override this to provide - # a fallback when no command is given on the commandline. - def command_missing - raise NoCommandError - end - - # Override option_missing if needed. - # This receives the name of the option and - # the remaining arguments list. It must consume - # any argument it uses from the (begining of) - # the list. - def option_missing(opt, *argv) - raise NoOptionError, opt - end - class << self - def run(obj, argv=ARGV) - args = parse(obj, argv) - subcmd = args.shift + # Process the arguments as an exectuable against the given object. + def execute(obj, argv=ARGV) + args = parse(obj, argv) + subcmd = args.first if subcmd && !obj.respond_to?("#{subcmd}=") - obj.send(subcmd, *args) + obj.send(*args) else - obj.command_missing + obj.method_missing(*args) end end - #def run(obj) - # methname, args = *parse(obj) - # meth = obj.method(methname) - # meth.call(*args) - #end + # The original name for #execute. + alias_method :run, :execute - # + # Parse command line with respect to +obj+. def parse(obj, argv) case argv when String require 'shellwords' argv = Shellwords.shellwords(argv) @@ -109,86 +90,54 @@ end end return args end - # + # Parse a setting option. def parse_equal(obj, opt, argv) if md = /^[-]*(.*?)=(.*?)$/.match(opt) x, v = md[1], md[2] else raise ArgumentError, "#{x}" end - if obj.respond_to?("#{x}=") - # TODO: to_b if 'true' or 'false' ? - obj.send("#{x}=",v) - else - obj.option_missing(x, v) # argv? - end + # TODO: to_b if 'true' or 'false' ? + #if obj.respond_to?("#{x}=") + obj.send("#{x}=", v) + #else + # obj.option_missing(x, v) + #end end - # + # Parse a named boolean option. def parse_option(obj, opt, argv) - x = opt[2..-1] - if obj.respond_to?("#{x}=") - obj.send("#{x}=",true) - else - obj.option_missing(x, argv) - end + x = opt.sub(/^--/, '') + #if obj.respond_to?("#{x}=") + obj.send("#{x}=", true) + #else + # obj.option_missing(x, true) + #end end + # Parse flags. Each character of a flag set is treated as a separate option. + # For example: # + # $ foo -abc + # + # Would be parsed the same as: + # + # $ foo -a -b -c + # def parse_flags(obj, opt, args) - x = opt[1..-1] - c = 0 + x = opt.sub(/^-/, '') + #c = 0 x.split(//).each do |k| - if obj.respond_to?("#{k}=") - obj.send("#{k}=",true) - else - obj.option_missing(x, argv) - end + #if obj.respond_to?("#{k}=") + obj.send("#{k}=", true) + #else + # obj.option_missing(x, true) + #end end end end #class << self end - - -=begin SPEC - -class X - include Executable - - attr_accessor :file - attr_accessor :quiet - - attr :cmd - - # - def bread(*args) - @cmd = "BREAD" - end - - # - def butter(*args) - @cmd = "BUTTER" - end -end - -Respect.spec "Executable" do - - it "first command runs" do - x = X.new - x.execute_command("bread") - x.cmd.assert == "BREAD" - end - - it "second command runs" do - x = X.new - x.execute_command("butter") - x.cmd.assert == "BUTTER" - end -end - -=end -