# == CLI::Command # # Here is an example of usage: # # # General Options # # module GeneralOptions # attr_accessor :dryrun ; alias_accessor :n, :noharm, :dryrun # attr_accessor :quiet ; alias_accessor :q, :quiet # attr_accessor :force # attr_accessor :trace # end # # # Build Subcommand # # class BuildCommand < CLI::Command # include GeneralOptions # # # metadata files # attr_accessor :file ; alias_accessor :f, :file # attr_accessor :manifest ; alias_accessor :m, :manifest # # def call # # do stuf here # end # end # # # Box Master Command # # class BoxCommand < CLI::Command # subcommand :build, BuildCommand # end # # BoxCommand.start # # == Authors # # * Trans # # == Todo # # * Move helpers into core. # * Add global options to master command, or "all are master options" flag (?) # * Add usage/help/documentation/man features (?) # # == Copying # # Copyright (c) 2005,2008 Thomas Sawyer # # Ruby License # # This module is free software. You may use, modify, and/or # redistribute this software under the same terms as Ruby. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. require 'facets/arguments' require 'facets/consoleutils' require 'facets/array/not_empty' module CLI # For CommandOptions, but defined external to it, so # that it is easy to access from user defined commands. # (This lookup issue should be fixed in Ruby 1.9+, and then # the class can be moved back into Command namespace.) class NoOptionError < NoMethodError def initialize(name, *arg) super("unknown option -- #{name}", name, *args) end end class NoCommandError < NoMethodError def initialize(name, *arg) super("unknown subcommand -- #{name}", name, *args) end end # Here is an example of usage: # # # General Options # # module GeneralOptions # attr_writer :dryrun ; alias_writer :n, :noharm, :dryrun # attr_writer :quiet ; alias_writer :q, :quiet # attr_writer :force # attr_writer :trace # end # # # Build Subcommand # # class BuildCommand < Command # include GeneralOptions # # # metadata files # attr_writer :file ; alias_writer :f, :file # attr_writer :manifest ; alias_writer :m, :manifest # # def call # # do stuf here # end # end # # # Box Master Command # # class BoxCommand < CLI::Command # subcommand :build, BuildCommand # end # # BoxCommand.start class Command # Include this in your dispatch command if you want # all options to be traeted the same. module UniversalOptions end # def self.option_arity(arity_hash=nil) if arity_hash (@option_arity ||= {}).merge!(arity_hash) end @option_arity end # def self.start(line=nil) cargs = Arguments.new(line || ARGV, option_arity) pre = cargs.preoptions if instance_method(:call).arity == 0 #is_a?(SingleCommand) args, opts = *cargs.parameters new(args, opts).call else subc, args, opts = *cargs.subcommand if self < UniversalOptions new(pre, opts).call(subc, args, opts) else new(pre).call(subc, args, opts) end end end # Command Arguments (for single commands). attr :arguments # Command options. For dispatch commands these are the pre-options. attr :options # For dispatchers, this is a convenience method for creating subcommands. def self.subcommand(name, command_class, options=nil) options ||= {} if options[:no_merge] file, line = __FILE__, __LINE__+1 code = %{ def #{name}(args, opts) #{command_class}.new(args, opts).call end } else file, line = __FILE__, __LINE__+1 code = %{ def #{name}(args, opts) opts.merge(options) #{command_class}.new(args, opts).call end } end class_eval(code, file, line) end private # def initialize(*args) @arguments = [] @options = {} opts, args = *args.partition{ |e| Hash===e } #TEST("options should all be hashes"){ ! opts.all?{ |e| Hash===e } initialize_arguments(*args) initialize_options(*opts) end # def initialize_arguments(*arguments) @arguments.concat(arguments) end # def initialize_options(*options) options = options.inject{ |h,o| h.merge(o) } begin opt, val = nil, nil options.each do |opt, val| opt = opt.gsub('-','_') send("#{opt}=", val) end rescue NoMethodError option_missing(opt, val) end @options.update(options) end public # For a single command (ie. a subcommand) override #call with arity=0. def call(cmd=nil, *args) opts = Hash==args.last ? args.pop : {} #TEST("options should all be hashes"){ ! opts.all?{ |e| Hash===e } #cmd = :default if cmd.nil? if cmd.nil? default else begin # FIXME: rename call to [] ? raise NameError if cmd == 'call' raise NameError unless commands.include?(cmd.to_sym) subcommand = method(cmd) parameters = [args, opts] rescue NameError subcommand = method(:command_missing) parameters = [cmd, args, opts] end if subcommand.arity < 0 subcommand.call(*parameters[0..subcommand.arity]) else subcommand.call(*parameters[0,subcommand.arity]) end end end # Display help message. # The one provided is just a very limited dummy routine. # def help # puts "USAGE #{File.basename($0)} [options]" # puts "\nOptions:" # options = self.class.instance_methods(false) # options = options - Command.instance_methods(true) # options = options.select{ |m| m.to_s =~ /=$/ } # options.each do |opt| # puts " --#{opt.to_s.chomp('=')}" # end # end private # Override default to provide non-subcommand functionality. def default; end # def commands @_commands ||= ( cmds = self.class.instance_methods(true) - Command.instance_methods(true) cmds.select{ |c| c !~ /\W/ } cmds.collect{ |c| c.to_sym } ) end # def command_missing(cmd, args, opt) raise NoCommandError.new(cmd, args << opt) end # def option_missing(opt, arg=nil) raise NoOptionError.new(opt) end end # Temporary backward compatability. MasterCommand = Command end module Console #:nodoc: # For backward compatibility. Command = CLI::Command end # SCRAP CODE FOR REFERENCE TO POSSIBLE ADD FUTURE FEATURES =begin # We include a module here so you can define your own help # command and call #super to utilize this one. module Help def help opts = help_options s = "" s << "#{File.basename($0)}\n\n" unless opts.empty? s << "OPTIONS\n" s << help_options s << "\n" end s << "COMMANDS\n" s << help_commands puts s end private def help_commands help = self.class.help bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 lines = [] help.each { |cmd, str| cmd = cmd.to_s if cmd !~ /^_/ lines << " " + cmd + (" " * (bufs - cmd.size)) + str end } lines.join("\n") end def help_options help = self.class.help bufs = help.keys.collect{ |a| a.to_s.size }.max + 3 lines = [] help.each { |cmd, str| cmd = cmd.to_s if cmd =~ /^_/ lines << " " + cmd.gsub(/_/,'-') + (" " * (bufs - cmd.size)) + str end } lines.join("\n") end module ClassMethods def help( str=nil ) return (@help ||= {}) unless str @current_help = str end def method_added( meth ) if @current_help @help ||= {} @help[meth] = @current_help @current_help = nil end end end end include Help extend Help::ClassMethods =end =begin # Provides a very basic usage help string. # # TODO Add support for __options. def usage str = [] public_methods(false).sort.each do |meth| meth = meth.to_s case meth when /^_/ opt = meth.sub(/^_+/, '') meth = method(meth) if meth.arity == 0 str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") elsif meth.arity == 1 str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") elsif meth.arity > 0 v = []; meth.arity.times{ |i| v << 'value' + (i + 1).to_s } str << (opt.size > 1 ? "[--#{opt} #{v.join(' ')}]" : "[-#{opt} #{v.join(' ')}]") else str << (opt.size > 1 ? "[--#{opt} *values]" : "[-#{opt} *values]") end when /=$/ opt = meth.chomp('=') str << (opt.size > 1 ? "[--#{opt} value]" : "[-#{opt} value]") when /!$/ opt = meth.chomp('!') str << (opt.size > 1 ? "[--#{opt}]" : "[-#{opt}]") end end return str.join(" ") end # def self.usage_class(usage) c = Class.new(self) argv = Shellwords.shellwords(usage) argv.each_with_index do |name, i| if name =~ /^-/ if argv[i+1] =~ /^[(.*?)]/ c.class_eval %{ attr_accessor :#{name} } else c.class_eval %{ attr_reader :#{name} def #{name}! ; @#{name} = true ; end } end end end return c end end =end