Module: Executable

Defined in:
lib/executable.rb

Overview

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 only limitation of this approach (besides the weak control of the process) is that required options must be specified with the key=value notation.

class X
  include Executable

  attr_accessor :quiet

  def bread(*args)
    ["BREAD", quiet, *args]
  end

  def butter(*args)
    ["BUTTER", quiet, *args]
  end
end

x = X.new

x.execute_command("butter yum")
=> ["BUTTER", nil, "yum"]

x.execute_command("bread --quiet")
=> ["BUTTER", true]

Executable also defines #command_missing and #option_missing, which you can override to provide suitable results.

TODO: Maybe command_missing is redundant, and method_missing would suffice?

Defined Under Namespace

Classes: NoCommandError, NoOptionError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.parse(obj, argv) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/executable.rb', line 88

def parse(obj, argv)
  case argv
  when String
    require 'shellwords'
    argv = Shellwords.shellwords(argv)
  else
    argv = argv.dup
  end

  argv = argv.dup
  args, opts, i = [], {}, 0
  while argv.size > 0
    case opt = argv.shift
    when /=/
      parse_equal(obj, opt, argv)
    when /^--/
      parse_option(obj, opt, argv)
    when /^-/
      parse_flags(obj, opt, argv)
    else
      args << opt
    end
  end
  return args
end

.parse_equal(obj, opt, argv) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/executable.rb', line 115

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
end

.parse_flags(obj, opt, args) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/executable.rb', line 140

def parse_flags(obj, opt, args)
  x = opt[1..-1]
  c = 0
  x.split(//).each do |k|
    if obj.respond_to?("#{k}=")
      obj.send("#{k}=",true)
    else
      obj.option_missing(x, argv)
    end
  end
end

.parse_option(obj, opt, argv) ⇒ Object



130
131
132
133
134
135
136
137
# File 'lib/executable.rb', line 130

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
end

.run(obj, argv = ARGV) ⇒ Object



71
72
73
74
75
76
77
78
79
# File 'lib/executable.rb', line 71

def run(obj, argv=ARGV)
  args = parse(obj, argv)
  subcmd = args.shift
  if subcmd && !obj.respond_to?("#{subcmd}=")
    obj.send(subcmd, *args)
  else
    obj.command_missing
  end
end

Instance Method Details

#command_missingObject

This is the fallback subcommand. Override this to provide a fallback when no command is given on the commandline.

Raises:



56
57
58
# File 'lib/executable.rb', line 56

def command_missing
  raise NoCommandError
end

#execute_command(argv = ARGV) ⇒ Object

Used to invoke the command.



50
51
52
# File 'lib/executable.rb', line 50

def execute_command(argv=ARGV)
  Executable.run(self, argv)
end

#option_missing(opt, *argv) ⇒ Object

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.

Raises:



65
66
67
# File 'lib/executable.rb', line 65

def option_missing(opt, *argv)
  raise NoOptionError, opt
end