require 'optparse' # A little class for building command-line programs. Based on OptionParser. # # You define a subclass of Ordu with the `option`, `command` and `action` # directives. `option` looks a lot like `OptionParser#on`, but can also accept # a Symbol or a String (allowing you to specify an instance method as the # option handler). `command` takes a block which is used as the body of the # command (or, again, a String/Symbol, or an Ordu class). `action` is another # block (or Symbol/String) which is executed if no subcommands are run. # # When the class is defined, these options, commands and action are simply # remembered. Then when the class is initialized, the instance is created and # the options applied. # # When `parse!` is called, the command-line switches are parsed. The next # leftover argument is checked to see if it is a command. If so, then that # command is built into an Ordu, and `parse!`ed with the next arguments. # Everything left is passed into the `action` method (which instance_execs the # block specified in the class definition). class Ordu < OptionParser # Make a new Ordu instance. It will be set up with the options, commands and # action specified in its class definition. def initialize(options: nil, commands: nil, action: nil) super() options ||= self.class.options commands ||= self.class.commands action ||= self.class.action options.each { |o| option!(*o) } commands.each { |c| command!(*c) } @action = action end # Parse options in order, and call commands for non-option arguments (if the # command exists). This is how programs are exected. def parse!(args) args = order!(args) unless args.empty? if commands.key?(args.first) return commands[args.shift].parse!(args) end end run(*args) end # Parses a new instance. Note that this must be the top-level, since you # can't supply a name. This is convenient for starting the parser from # scripts. def self.parse!(args) new.parse!(args) end private def program_name name || super end def name @name ||= self.class.name end def self.name(name = nil) name ? @name = name : @name end def self.desc(desc = nil) desc ? @desc = desc : @desc end # Runs the action for this Ordu instance. def run(*args) return instance_exec(*args, &@action) if @action end # Gets the hash of subcommands def commands @commands ||= {} end # Applies a new option (using OptionParser#on). The last argument must be a # block, and will be instance_exec'ed in this instance. def option!(*args) b = args.pop on(*args) { |*x| instance_exec(*x, &b) } end # Applies a new command (i.e., a new Ordu which may be run if the command is # given). name and desc are set on the new class. def command!(name, desc, block) command = Class.new(self.class, &block) command.name [program_name, name].join(' ') command.desc desc commands[name] = command end # Defines a new option for instances of this Ordu class. def self.option(*args, &block) args.push(block) if block_given? options.push(args) end # An array of the options to be applied def self.options @options ||= [] end # Defines a new command for instances of this Ordu class. The block will be # pushed in as the last element of the command array. def self.command(*args, &block) args.push(block) if block_given? commands.push(args) end # An array of the commands available to instances of this Ordu class. def self.commands @commands ||= [] end # Defines the action to be run once parsing is finished. def self.action(&block) @action = block if block_given? @action ||= lambda { } end end