class InvalidCommand < Exception end module RCoLi module CommandContainer attr_accessor :parent def action(&block) @action = block end def get_action @action end def command(name, &block) obj = Command.new(name) obj.parent = self block.call(obj) commands << obj end def commands (@commands ||= []) end def options (@options ||= []) end def switch(names, &block) obj = Switch.new(names) block.call(obj) if block_given? options << obj end def flag(names, &block) obj = Flag.new(names) block.call(obj) if block_given? options << obj end def parse_args(args, result) return if args.empty? arg = args.delete_at(0) if (is_option? arg) if (option = find_option(arg)) if (option.is_a? Flag) raise InvalidCommand, "Flag #{arg} is missing a value" if args.empty? value = args.delete_at(0) else value = true end target = self.parent ? :options : :global_options option.keys.each{|key| result.send(target)[key] = value} else raise InvalidCommand, "#'{arg}' is not a valid option" end else if (cmd = find_command(arg)) result.command = cmd cmd.put_default_values(result) cmd.parse_args(args, result) elsif (commands.empty?) result.arguments << arg else raise InvalidCommand, "'#{arg}' is not a valid #{@name} command" end end parse_args(args, result) end def put_default_values(result) options.find_all{|option| option.respond_to? :value_of_default_value and option.value_of_default_value}.each do |option| target = self.parent ? :options : :global_options option.keys.each{|key| result.send(target)[key] = option.value_of_default_value} end end def find_command(name) commands.find{|command| command.value_of_name.eql? name} end private def find_option(name) options.find{|opt| opt.correspond?(name)} end def is_option?(value) value.start_with?('-') end end module Option setter :description def initialize(names) @s_name = names[:short] @l_name = names[:long] end def keys [@s_name, @l_name].compact end def help_keys result = [] result << "-#{@s_name}" if @s_name result << "--#{@l_name}" if @l_name result end def correspond?(value) return (value.sub('-','').eql? @s_name or value.sub('--','').eql? @l_name) end end class Switch include Option end class Flag include Option setter :default_value setter :arg_name end class Command setter :name setter :summary setter :description setter :syntax def solitaire @solitaire = true end def solitaire? return true == @solitaire end def initialize(name) @name = name end include CommandContainer def full_command command = self result = [] while(command.parent) do result << command.value_of_name command = command.parent end return result.reverse.join(' ') end end class Program setter :name setter :author setter :version setter :description include Help include CommandContainer def execute(args, context) result = ParsedArgs.new put_default_values(result) parse_args(args, result) if result.command action = result.command.get_action raise "Invalid configuration. Missing action block." unless action context.instance_exec(result.global_options, result.options, result.arguments, &@pre_action) if (@pre_action and !result.command.solitaire?) context.instance_exec(result.global_options, result.options, result.arguments, &action) context.instance_exec(result.global_options, result.options, result.arguments, &@post_action) if (@post_action and !result.command.solitaire?) else say "Display UI" end end def pre(&block) @pre_action = block end def post(&block) @post_action = block end end class ParsedArgs attr_reader :global_options attr_reader :options attr_reader :arguments attr_accessor :command def initialize @global_options = {} @options = {} @arguments = [] end def no_command? return @command.nil? end end end