Class: Optplus::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/optplus.rb

Overview

Optplus Parser

A wrapper class that adds a little value to writing scipts with optparse. Like Thor but without trying to do too much.

Direct Known Subclasses

NestedParser

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeParser

create an Optplus instance, define the options and parse the command line

This method will call the following if they have been defined:

  • before_all - any setting up needed right at the start

  • options - to add options

  • before_actions - after options have been parsed but before actions are implemented

Parameters:

  • klass (Class)

    for internal use in the instance itself



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/optplus.rb', line 200

def initialize
  
  @klass = self.class
  @klass._help ||= Hash.new
  @_help = false
  @_man = false
  @options = Hash.new
  
  self.before_all if self.respond_to?(:before_all)
  
  begin
    @_optparse = OptionParser.new do |opts|
      @program_name = opts.program_name
      opts.banner = "Usage: #{@program_name} #{@klass._banner}"
      opts.separator ""
      
      @klass._description.each do |dline|
        opts.separator "  " + dline
      end
      
      opts.separator ""
      opts.separator "Actions:"
      opts.separator ""
      flags = 0
      @klass._descriptions.each do |key, value|
        flag = @klass._help.has_key?(key.to_sym) ? '(-h)' : ''
        flags += 1 unless flag == ''
        opts.separator "  #{key} - #{value} #{flag}"
      end
      
      if flags > 0 then
        opts.separator ""
        opts.separator "  (-h indicates actions with additional help)"
        opts.separator ""
      end
        
      opts.separator ""
      opts.separator "Options:"
      opts.separator ""
      
      if @klass._help.length > 0 then
        help_string = 'use with an action for further help'
      else
        help_string = 'you are looking at it'
      end
      options(opts) if self.respond_to?(:options)
      opts.on_tail('-h', '--help', help_string) do
        @_help = true
      end
      
      opts.on_tail('--man', 'output man-like help') do
        @_help = true
        @_man = true
      end
      
    end
  
    @_args = @_optparse.permute(ARGV)
  
    # trap a deliberate exit and force exit before
    # executing before_actions
  rescue ExitOnError => err
    puts err.message.red.bold unless err.message == ''
    exit 1
  end
  
  
end

Instance Attribute Details

#program_nameObject (readonly)

provides convenient access to the name of the program



270
271
272
# File 'lib/optplus.rb', line 270

def program_name
  @program_name
end

Class Method Details

.describe(action, description) ⇒ Object

Add a brief description for a specific action

Add a little Thor-like description before each method. Unlike Thor, you will not get told off if there is no corresponding method but its probably a good idea if you add one.

Parameters:

  • action (Symbol)

    to be described

  • description (String)

    of the action



56
57
58
59
60
61
# File 'lib/optplus.rb', line 56

def describe(action, description)
  @_actions ||= Array.new
  @_actions << action.to_s
  @_descriptions ||= Hash.new
  @_descriptions[action] = description
end

.description(*lines) ⇒ Object

Adds a description to the help/usage

This takes any number of string arguments and displays them as separate lines.

Parameters:

  • lines (Array)

    of description text as variable arguments



43
44
45
# File 'lib/optplus.rb', line 43

def description(*lines)
  @_description = lines
end

.help(action, *lines) ⇒ Object

add a block of helpful text for an action

Adds all of the arguments as lines to display when you use the help switch with the given argument, instead of the general help. Note that optplus does not allow options specific to actions so this is just text.

Parameters:

  • action (String)

    to describe with helpful text

  • lines (Array)

    of helpful text to display as arguments



73
74
75
76
# File 'lib/optplus.rb', line 73

def help(action, *lines)
  @_help ||= Hash.new
  @_help[action] = lines
end

.nest_parser(name, klass, description) ⇒ Object

nest a parser for subcommands This will add the given name to the actions list and then parse the next argument as a subcommand The klass must inherit NestedParser

Parameters:

  • name (Symbol)

    of action to nest

  • klass (Class)

    of Nested Parser

  • description (String)

    of action



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

instance_eval do
  def nest_parser(name, klass, description)
    self.describe(name, description)
    self._help[name] = klass
    class_eval %Q{
      def #{name}
        #{klass}.run!(self)
      end
    }
  end
end

.run!Object

Do the option parsing and actioning stuff

If you write an optplus class, run the script and nothing happens it is because you forgot to add MyClass.run! Simple and easily done.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/optplus.rb', line 97

def run!
  
  @_parent ||= nil
  @_help ||= Hash.new
  
  begin
    me = self.new
    
    if me._needs_help? then
      me._help_me
    elsif me._args.length > 0 then
      action = me.next_argument
      alup = @_actions.abbrev(action)
      if alup.has_key?(action) then
  
        me.before_actions if me.respond_to?(:before_actions)
        
        begin
          me.send(alup[action].to_sym)
          
          # trap a deliberate exit and tidy up
          # if required
        rescue Optplus::ExitOnError => err
          puts err.message.red.bold unless err.message == ''
          me.after_actions if me.respond_to?(:after_actions)
          raise Optplus::ExitOnError, '' # with no message
        end
        
        me.after_actions if me.respond_to?(:after_actions)
        
      else
        puts "Sorry, What?"
        puts ""
        me._get_help
      end
    else
      me._get_help
    end
    
    return true
    
  rescue OptionParser::InvalidOption => opterr
    puts "Error: Invalid Option".red.bold
    puts "I do not understand the option: #{opterr.args.join}"
  rescue OptionParser::InvalidArgument => opterr
    puts "Error: You have entered an invalid argument to an option".red.bold
    puts "The option in question is: #{opterr.args.join(' ')}"
  rescue OptionParser::AmbiguousOption => opterr
    puts "Error: You need to be clearer than that".red.bold
    puts "I am not be sure what option you mean: #{opterr.args.join}"
  rescue OptionParser::AmbiguousArgument => opterr
    puts "Error: You need to be clearer than that".red.bold
    puts "I am not sure what argument you mean: #{opterr.args.join(' ')}"
  rescue OptionParser::MissingArgument => opterr
    puts "Error: You need to provide an argument with that option".red.bold
    puts "This is the option in question: #{opterr.args.join}"
  rescue OptionParser::ParseError => opterr
    puts "Error: the command line is not as expected".red.bold
    puts opterr.to_s
  rescue Optplus::ParseError => err
    puts "Error: #{err.message}".red.bold
  rescue Optplus::ExitOnError => err
    puts err.message.red.bold unless err.message == ''
    raise Optplus::ExitOnError, '' unless @_parent.nil?
  end
  
  # only rescued exceptions will reach here
  exit 1 if @_parent.nil?
  
end

.usage(txt) ⇒ Object

define the usage banner, less “Usage: <prog_name>”!

For example: usage “[options] [actions] [filename]” becomes: “Usage: progname [options] [actions] [filename]”

Parameters:

  • txt (String)

    that is the banner



34
35
36
# File 'lib/optplus.rb', line 34

def usage(txt)
  @_banner = txt
end

Instance Method Details

#all_argumentsArray

return all of the remaining args, or an empty array

This clears all remaining arguments so that subsequent calls e.g. to #next_argument return nil

Returns:

  • (Array)

    of arguments



330
331
332
333
334
# File 'lib/optplus.rb', line 330

def all_arguments
  args = @_args.dup
  @_args = Array.new
  return args
end

#all_optionsObject



371
372
373
# File 'lib/optplus.rb', line 371

def all_options
  @options.dup
end

#debug_option(opts, switch = '-D') ⇒ Object

add optparse option for debug mode

Parameters:

  • opts (Optparse)

    being the optparse instance

  • switch (String) (defaults to: '-D')

    being the short-form option on the command line



276
277
278
279
280
# File 'lib/optplus.rb', line 276

def debug_option(opts, switch='-D')
  opts.on_tail(switch, '--debug', 'show debug information') do |d|
    @options[:debug] = d
  end
end

#exit_on_error(msg = '') ⇒ Object

call this to exit the script in case of an error and ensure any tidying up has been done



385
386
387
# File 'lib/optplus.rb', line 385

def exit_on_error(msg='')
  raise Optplus::ExitOnError, msg
end

#get_option(key) ⇒ Object

get the value of the option

Returns nil if there is no option with the given key

Parameters:

  • key (Symbol)

    to the option to get

Returns:

  • (Object)

    or nil if no option set



367
368
369
# File 'lib/optplus.rb', line 367

def get_option(key)
  @options[key]
end

#manObject

output all the help in one go!



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/optplus.rb', line 433

def man
  puts "Help Manual for #{@program_name}"
  puts ""
  _get_help
  @klass._help.each_pair do |action, help|
    puts "Action: #{action}"
    puts ""
    if help.kind_of?(Array) then
      help.each do |hline|
        puts "  " + hline
      end
    else

      np = help.new(self)
      np._get_help(2)
      puts ""
      
      help._help.each_pair do |subaction, subhelp|
        puts "  Subaction: #{subaction}"
        puts ""
        subhelp.each do |hline|
          puts "    " + hline
        end
        puts ""
      end
    end
    puts " "
  end
  puts ""
end

#next_argumentString

return the next argument, if there is one or nil otherwise

Returns:

  • (String)

    being the next argument



300
301
302
# File 'lib/optplus.rb', line 300

def next_argument
  @_args.shift
end

#next_argument_or(default) ⇒ String

return the next argument or the given default

Parameters:

  • default (Object)

    to return if no argument

Returns:

  • (String)

    being the next argument or the default



308
309
310
# File 'lib/optplus.rb', line 308

def next_argument_or(default)
  next_argument || default
end

#next_argument_or_error(msg) ⇒ String

return the next argument or raise exception with the given message

The exception does not need to be handled because run! will rescue it and display an error message.

Parameters:

  • msg (String)

    to attach to exception

Returns:

  • (String)

    being the next argument

Raises:



320
321
322
# File 'lib/optplus.rb', line 320

def next_argument_or_error(msg)
  next_argument || raise(Optplus::ParseError, msg)
end

#option?(key) ⇒ Boolean

check if the option has been set

Parameters:

  • key (Symbol)

    for the option to test

Returns:

  • (Boolean)

    true if option has been set



379
380
381
# File 'lib/optplus.rb', line 379

def option?(key)
  @options.has_key?(key)
end

#set_option(key, value = true) ⇒ Object

set the value of the given option, which defaults to true

If a value is omitted then the option is set to be true

Parameters:

  • key (Symbol)

    to use in getting the option

  • value (Object) (defaults to: true)

    to set the option to



357
358
359
# File 'lib/optplus.rb', line 357

def set_option(key, value=true)
  @options[key] = value
end

#verbose_option(opts, switch = '-V') ⇒ Object

add optparse option for verbose mode

Parameters:

  • opts (Optparse)

    being the optparse instance

  • switch (String) (defaults to: '-V')

    being the short-form option on the command line



286
287
288
289
290
# File 'lib/optplus.rb', line 286

def verbose_option(opts, switch='-V')
  opts.on_tail(switch, '--verbose', 'show verbose information') do |v|
    @options[:verbose] = v
  end
end