Module: SimpleCLI

Defined in:
lib/simplecli.rb

Overview

Stupidly simple way to get a CLI that handles:

myapp somecommand --blah=5 args --more stuff -y

All it does is, if the “command” (or “action”) passed has a method with the same name, the rest of the args are passed to the method.

If you provide a command_help method that returns help info as a String, that’ll be used when you call:

myapp help somecommand

If you provide a ‘Summary:n blah blah blah’ bit in your help_somecommand, it’ll be used as the command’s summary and your command will show up when you:

myapp commands

To use, include in your class

NOTE: if you use the ‘default’ command functionality, we don’e even bother to check to see if we respond_to? what you provide as a default command, incase it uses method missing or something. So it’s YOUR responsibility to provide this method

Conventionally, your ‘default’ method should simple pass along the arguments to another defined and documented command!

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



31
32
33
# File 'lib/simplecli.rb', line 31

def args
  @args
end

#commandObject (readonly)

Returns the value of attribute command.



31
32
33
# File 'lib/simplecli.rb', line 31

def command
  @command
end

#command_argsObject (readonly)

Returns the value of attribute command_args.



31
32
33
# File 'lib/simplecli.rb', line 31

def command_args
  @command_args
end

#optionsObject

Returns the value of attribute options.



30
31
32
# File 'lib/simplecli.rb', line 30

def options
  @options
end

Instance Method Details

#all_commandsObject

returns names of all defined ‘command’ methods

only returns methods with methodname_help sister methods, inotherwords: only returns DOCUMENTED methods … this should get you to document that command!



128
129
130
# File 'lib/simplecli.rb', line 128

def all_commands
  self.methods.sort.grep( /_help/ ).collect{ |help_method| help_method.gsub( /(.*)_help/ , '\1' ) }
end

#command_missing(args) ⇒ Object

before dropping to default command ( if defined via :default option ), the arguments get passed along to command_missing (you get the original args array)

your command_missing can return a command string (name of the command) or just a proc to call!

actually, if what you respond with responds to #call, we use that, else we call #to_s to get the name of the command to run

***NOTE*** if command_missing makes changes to the args passed to it, these are persisted

and passed to the command.  args.dup if you need to mess with args!

if you return something #call-able, the command arguments will be passed to your block

you can call super to drop back to the default command_missing in SimpleCLI or just return nil to say “nope, can’t find a command for this”



105
106
107
# File 'lib/simplecli.rb', line 105

def command_missing args
  nil
end

#commands(*no_args) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/simplecli.rb', line 198

def commands *no_args
  before_spaces = 4
  after_spaces  = 18
  text = all_commands.inject(''){ |all,cmd| all << "\n#{' ' * before_spaces}#{cmd}#{' ' * (after_spaces - cmd.length)}#{summary_for(cmd)}" }
  puts <<doco 
#{ script_name } commands are:

  DEFAULT COMMAND   #{ @default_command || 'not set' }
#{ text }

For help on a particular command, use '#{ script_name } help COMMAND'.
doco

#If you've made a command and it's not showing up here, you
#need to make help method named 'COMMAND_help' that returns 
#your commands help documentation.
#
#[NOT YET IMPLEMENTED:]
#Commands may be abbreviated, so long as they are unumbiguous.
#e.g. 'snip h commands' is short for 'snip help commands'.
#doco
end

#commands_helpObject

COMMANDS



190
191
192
193
194
195
196
197
# File 'lib/simplecli.rb', line 190

def commands_help
  <<doco
Usage: #{ script_name } commands

Summary:
  List all '#{ script_name }' commands
doco
end

#help(*args) ⇒ Object



178
179
180
181
182
183
184
185
186
187
# File 'lib/simplecli.rb', line 178

def help *args
  command = args.shift
  if command.nil?
    puts help_for( :help )
  elsif (doco = help_for command)
    puts doco
  else
    puts "No documentation found for command: #{command}"
  end
end

#help_for(command) ⇒ Object

returns help String for command



133
134
135
136
# File 'lib/simplecli.rb', line 133

def help_for command
  help_method = "#{ command }_help".to_sym
  self.send( help_method ) if self.respond_to? help_method
end

#help_helpObject

HELP



170
171
172
173
174
175
176
177
# File 'lib/simplecli.rb', line 170

def help_help
  <<doco
Usage: #{ script_name } help COMMAND

Summary:
  Provide help documentation for a command
doco
end

#initialize(args = [], options = {}) ⇒ Object



33
34
35
36
37
# File 'lib/simplecli.rb', line 33

def initialize args = [], options = {}
  @args     = args
  @options  = options
  parse!
end

#parse!Object

figure out what command to run, arguments to pass it, etc

call #run afterwards, to run. or call parse! to parse and run

typically, you shouldn’t call this yourself. call parse when you want to RE-parse the arguments passed in, because initialize auto-parses

typical:

Bin.new( ARGV ).run
Bin.new( ARGV, :default => 'some_default_method ).run

use this is you want to …

bin = Bin.new ARGV
bin.options[:default] = 'some_default_method'
bin.instance_eval { 'do some custom stuff that might change the command to run, etc' }
bin.parse!
bin.run


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/simplecli.rb', line 57

def parse!
  args = @args.clone

  @default_command  = @options[:default].to_s if @options.keys.include? :default
  @commands         = all_commands

  # if a command is discovered, eg. $ myscript foo 1 2 3 # where foo is a command
  if not args.empty? and @commands.map {|c| c.downcase }.include? args.first.downcase
    @command = args.shift.downcase

  # if a command is not discovered, and no arguments were sent at all, eg. $ myscript
  elsif args.empty?
    @command = @default_command || 'usage'

  # there were args passed, but we don't know what to call ... try command_missing first
  elsif command_from_command_missing = command_missing(args)
    @command = command_from_command_missing # if it returns something that's not nil, set it to command

  # there were some arguments, pass if to the default command if there is one, else 'command not found'
  elsif @default_command
    @command = @default_command

  # nothing worked out ... show command not found & usage
  else
    puts "command not found: #{ args.first.downcase }"
    @command = 'usage'

  end
  
  @command_args = args
end

#runObject

run command determined by parse



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/simplecli.rb', line 110

def run
  begin
    if @command.respond_to? :call
      @command.call @command_args
    else
      self.send @command.to_s, *@command_args
    end
  rescue ArgumentError => ex
    puts "'#{@command}' called with wrong number of arguments\n\n"
    puts help_for( @command )
  end
end

#script_nameObject

shortcut to pretty file name of script, which can be used in your help doco



165
166
167
# File 'lib/simplecli.rb', line 165

def script_name
  File.basename $0
end

#summary_for(command) ⇒ Object

returns summary String for command (extracted from help_for command)

Looks for

Summary:
   some summary text here, on a new line after 'Summary:'


144
145
146
147
148
149
150
151
152
# File 'lib/simplecli.rb', line 144

def summary_for command
  doco = help_for command
  if doco
    match = /Summary:\n*(.*)/.match doco
    if match and match.length > 1
      match[1].strip
    end
  end
end

#usage(*args) ⇒ Object

default usage message, called if we can’t figure out what command to run

override in your app by re-defining - it should puts the message (or do whatever) itself! this method doesn’t return a string, like blah_help methods (which are called by #help). usage is called, all by itself … so it needs to print itself!



160
161
162
# File 'lib/simplecli.rb', line 160

def usage *args
  puts "default usage message.  please define a 'usage' method returning a new message."
end