Class: ArgParser::Definition

Inherits:
Object
  • Object
show all
Defined in:
lib/arg-parser/definition.rb

Overview

Represents the collection of possible command-line arguments for a script.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ Definition

Create a new Definition, which is a collection of valid Arguments to be used when parsing a command-line.

Yields:

  • (_self)

Yield Parameters:



21
22
23
24
25
26
27
# File 'lib/arg-parser/definition.rb', line 21

def initialize
    @arguments = {}
    @short_keys = {}
    @require_set = Hash.new{ |h,k| h[k] = [] }
    @title = $0.respond_to?(:titleize) ? $0.titleize : $0
    yield self if block_given?
end

Instance Attribute Details

#purposeString

Returns A short description of the purpose of the script, for display when showing the usage help.

Returns:

  • (String)

    A short description of the purpose of the script, for display when showing the usage help.



16
17
18
# File 'lib/arg-parser/definition.rb', line 16

def purpose
  @purpose
end

#titleString

Returns A title for the script, displayed at the top of the usage and help outputs.

Returns:

  • (String)

    A title for the script, displayed at the top of the usage and help outputs.



13
14
15
# File 'lib/arg-parser/definition.rb', line 13

def title
  @title
end

Instance Method Details

#<<(arg) ⇒ Object

Adds the specified argument to the command-line definition.

Parameters:

  • arg (Argument)

    An Argument sub-class to be added to the command- line definition.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/arg-parser/definition.rb', line 51

def <<(arg)
    case arg
    when PositionalArgument, KeywordArgument, FlagArgument, RestArgument
        if @arguments[arg.key]
            raise ArgumentError, "An argument with key '#{arg.key}' has already been defined"
        end
        if arg.short_key && @short_keys[arg.short_key]
            raise ArgumentError, "An argument with short key '#{arg.short_key}' has already been defined"
        end
        if arg.is_a?(RestArgument) && rest_args
            raise ArgumentError, "Only one rest argument can be defined"
        end
        @arguments[arg.key] = arg
        @short_keys[arg.short_key] = arg if arg.short_key
    else
        raise ArgumentError, "arg must be an instance of PositionalArgument, KeywordArgument, " +
            "FlagArgument or RestArgument"
    end
end

#[](key) ⇒ Argument

Returns the argument with the specified key.

Returns:

  • (Argument)

    the argument with the specified key

Raises:

  • (ArgumentError)

    if no argument has been defined with the specified key.



41
42
43
44
# File 'lib/arg-parser/definition.rb', line 41

def [](key)
    arg = has_key?(key)
    arg or raise NoSuchArgumentError, "No argument defined for key '#{Argument.to_key(key)}'"
end

#argsArray

Returns all arguments that have been defined.

Returns:

  • (Array)

    all arguments that have been defined.



228
229
230
# File 'lib/arg-parser/definition.rb', line 228

def args
    @arguments.values
end

#errorsObject

Return an array of parse errors.

See Also:



177
178
179
# File 'lib/arg-parser/definition.rb', line 177

def errors
    parser.errors
end

#flag_arg(key, desc, opts = {}, &block) ⇒ Object

Add a flag argument to the set of arguments in this command-line argument definition.



91
92
93
# File 'lib/arg-parser/definition.rb', line 91

def flag_arg(key, desc, opts = {}, &block)
    self << ArgParser::FlagArgument.new(key, desc, opts, &block)
end

#flag_argsArray

Returns the flag arguments that have been defined.

Returns:

  • (Array)

    the flag arguments that have been defined



271
272
273
# File 'lib/arg-parser/definition.rb', line 271

def flag_args
    @arguments.values.select{ |arg| FlagArgument === arg }
end

#flag_args?Boolean

Returns True if any flag arguments have been defined.

Returns:

  • (Boolean)

    True if any flag arguments have been defined.



277
278
279
# File 'lib/arg-parser/definition.rb', line 277

def flag_args?
    flag_args.size > 0
end

#has_key?(key) ⇒ Argument

Returns the argument for the given key if it exists, or nil if it does not.

Returns:

  • (Argument)

    the argument for the given key if it exists, or nil if it does not.



32
33
34
35
# File 'lib/arg-parser/definition.rb', line 32

def has_key?(key)
    k = Argument.to_key(key)
    @arguments[k] || @short_keys[k]
end

#keyword_arg(key, desc, opts = {}, &block) ⇒ Object

Add a keyword argument to the set of arguments in this command-line argument definition.



83
84
85
# File 'lib/arg-parser/definition.rb', line 83

def keyword_arg(key, desc, opts = {}, &block)
    self << ArgParser::KeywordArgument.new(key, desc, opts, &block)
end

#keyword_argsArray

Returns the keyword arguments that have been defined.

Returns:

  • (Array)

    the keyword arguments that have been defined.



259
260
261
# File 'lib/arg-parser/definition.rb', line 259

def keyword_args
    @arguments.values.select{ |arg| KeywordArgument === arg }
end

#keyword_args?Boolean

Returns True if any keyword arguments have been defined.

Returns:

  • (Boolean)

    True if any keyword arguments have been defined.



265
266
267
# File 'lib/arg-parser/definition.rb', line 265

def keyword_args?
    keyword_args.size > 0
end

#non_positional_argsArray

Returns the non-positional (i.e. keyword and flag) arguments that have been defined.

Returns:

  • (Array)

    the non-positional (i.e. keyword and flag) arguments that have been defined.



247
248
249
# File 'lib/arg-parser/definition.rb', line 247

def non_positional_args
    @arguments.values.reject{ |arg| PositionalArgument === arg || RestArgument === arg }
end

#non_positional_args?Boolean

Returns True if any non-positional arguments have been defined.

Returns:

  • (Boolean)

    True if any non-positional arguments have been defined.



253
254
255
# File 'lib/arg-parser/definition.rb', line 253

def non_positional_args?
    non_positional_args.size > 0
end

#parse(args = ARGV) ⇒ OpenStruct, false

Parse the args array of arguments using this command-line definition.

arguments defined as accessors, and the parsed or default values for each argument as values. If unsuccessful, returns false indicating a parse failure.

Parameters:

  • args (Array, String) (defaults to: ARGV)

    an array of arguments, or a String representing the command-line that is to be parsed.

Returns:

  • (OpenStruct, false)

    if successful, an OpenStruct object with all

See Also:

  • Parser#errors, Parser#show_usage, Parser#show_help


170
171
172
# File 'lib/arg-parser/definition.rb', line 170

def parse(args = ARGV)
    parser.parse(args)
end

#parserParser

Returns a Parser instance that can be used to parse this command-line Definition.

Returns:

  • (Parser)

    a Parser instance that can be used to parse this command-line Definition.



156
157
158
# File 'lib/arg-parser/definition.rb', line 156

def parser
    @parser ||= Parser.new(self)
end

#positional_arg(key, desc, opts = {}, &block) ⇒ Object

Add a positional argument to the set of arguments in this command-line argument definition.



75
76
77
# File 'lib/arg-parser/definition.rb', line 75

def positional_arg(key, desc, opts = {}, &block)
    self << ArgParser::PositionalArgument.new(key, desc, opts, &block)
end

#positional_argsArray

Returns all positional arguments that have been defined.

Returns:

  • (Array)

    all positional arguments that have been defined



234
235
236
# File 'lib/arg-parser/definition.rb', line 234

def positional_args
    @arguments.values.select{ |arg| PositionalArgument === arg }
end

#positional_args?Boolean

Returns True if any positional arguments have been defined.

Returns:

  • (Boolean)

    True if any positional arguments have been defined.



240
241
242
# File 'lib/arg-parser/definition.rb', line 240

def positional_args?
    positional_args.size > 0
end

#predefined_arg(lookup_key, opts = {}) ⇒ Object

Lookup a pre-defined argument (created earlier via Argument#register), and add it to this arguments definition.

Parameters:

  • lookup_key (String, Symbol)

    The key under which the pre-defined argument was registered.

  • desc (String)

    An optional override for the argument description for this use of the pre-defined argument.

  • opts (Hash) (defaults to: {})

    An options hash for those select properties that can be overridden on a pre-defined argument.

Options Hash (opts):

  • :description (String)

    The argument description for this use of the pre-defined argument.

  • :usage_break (String)

    The usage break for this use of the pre-defined argument.

  • :required (Boolean)

    Whether this argument is a required (i.e. mandatory) argument.

  • :default (String)

    The default value for the argument, returned in the command-line parse results if no other value is specified.

See Also:



124
125
126
127
128
129
130
131
# File 'lib/arg-parser/definition.rb', line 124

def predefined_arg(lookup_key, opts = {})
    arg = Argument.lookup(lookup_key)
    arg.description = opts[:description] if opts[:description]
    arg.useage_break = opts[:usage_break] if opts.has_key?(:usage_break)
    arg.required = opts[:required] if opts.has_key?(:required)
    arg.default = opts[:default] if opts.has_key?(:default)
    self << arg
end

#require_any_of(*keys) ⇒ Object

Individual arguments are optional, but at least one of keys arguments is required.



143
144
145
# File 'lib/arg-parser/definition.rb', line 143

def require_any_of(*keys)
    @require_set[:any] << keys.map{ |k| self[k] }
end

#require_one_of(*keys) ⇒ Object

Individual arguments are optional, but exactly one of keys arguments is required.



136
137
138
# File 'lib/arg-parser/definition.rb', line 136

def require_one_of(*keys)
    @require_set[:one] << keys.map{ |k| self[k] }
end

#requires_some?Boolean

True if at least one argument is required out of multiple optional args.

Returns:

  • (Boolean)


149
150
151
# File 'lib/arg-parser/definition.rb', line 149

def requires_some?
    @require_set.size > 0
end

#rest_arg(key, desc, opts = {}, &block) ⇒ Object

Add a rest argument to the set of arguments in this command-line argument definition.



99
100
101
# File 'lib/arg-parser/definition.rb', line 99

def rest_arg(key, desc, opts = {}, &block)
    self << ArgParser::RestArgument.new(key, desc, opts, &block)
end

#rest_argsRestArgument

Returns the RestArgument defined for this command-line, or nil if no RestArgument is defined.

Returns:

  • (RestArgument)

    the RestArgument defined for this command-line, or nil if no RestArgument is defined.



284
285
286
# File 'lib/arg-parser/definition.rb', line 284

def rest_args
    @arguments.values.find{ |arg| RestArgument === arg }
end

#rest_args?Boolean

Returns True if a RestArgument has been defined.

Returns:

  • (Boolean)

    True if a RestArgument has been defined.



290
291
292
# File 'lib/arg-parser/definition.rb', line 290

def rest_args?
    !!rest_args
end

#show_help(out = STDOUT, width = 80) ⇒ Array

Generates a more detailed help screen.

Parameters:

  • out (IO) (defaults to: STDOUT)

    an IO object on which the help information will be output. Pass nil if no output to any device is desired.

  • width (Integer) (defaults to: 80)

    the width at which to wrap text.

Returns:

  • (Array)

    An array of lines of text, containing the help text.



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/arg-parser/definition.rb', line 330

def show_help(out = STDOUT, width = 80)
    lines = ['', '']
    lines << title
    lines << title.gsub(/./, '=')
    lines << ''
    if purpose
        lines.concat(wrap_text(purpose, width))
        lines << ''
        lines << ''
    end

    lines << 'USAGE'
    lines << '-----'
    pos_args = positional_args
    opt_args = size - pos_args.size
    usage_args = pos_args.map(&:to_use)
    usage_args << (requires_some? ? 'OPTIONS' : '[OPTIONS]') if opt_args > 0
    usage_args << rest_args.to_use if rest_args?
    lines.concat(wrap_text("  #{RUBY_ENGINE} #{$0} #{usage_args.join(' ')}", width))
    lines << ''

    if positional_args?
        max = positional_args.map{ |a| a.to_s.length }.max
        pos_args = positional_args
        pos_args << rest_args if rest_args?
        pos_args.each do |arg|
            if arg.usage_break
                lines << ''
                lines << arg.usage_break
            end
            desc = arg.description
            desc << "\n[Default: #{arg.default}]" unless arg.default.nil?
            wrap_text(desc, width - max - 6).each_with_index do |line, i|
                lines << "  %-#{max}s    %s" % [[arg.to_s][i], line]
            end
        end
        lines << ''
    end
    if non_positional_args?
        lines << ''
        lines << 'OPTIONS'
        lines << '-------'
        max = non_positional_args.map{ |a| a.to_use.length }.max
        non_positional_args.each do |arg|
            if arg.usage_break
                lines << ''
                lines << arg.usage_break
            end
            desc = arg.description
            desc << "\n[Default: #{arg.default}]" unless arg.default.nil?
            wrap_text(desc, width - max - 6).each_with_index do |line, i|
                lines << "  %-#{max}s    %s" % [[arg.to_use][i], line]
            end
        end
    end
    lines << ''

    lines.each{ |line| line.length < width ? out.puts(line) : out.print(line) } if out
    lines
end

#show_help?Boolean

Whether user indicated they would like help on supported arguments.

Returns:

  • (Boolean)

See Also:

  • Parser#show_help


191
192
193
# File 'lib/arg-parser/definition.rb', line 191

def show_help?
    parser.show_help?
end

#show_usage(out = STDERR, width = 80) ⇒ Object

Generates a usage display string



309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/arg-parser/definition.rb', line 309

def show_usage(out = STDERR, width = 80)
    lines = ['']
    pos_args = positional_args
    opt_args = size - pos_args.size
    usage_args = pos_args.map(&:to_use)
    usage_args << (requires_some? ? 'OPTIONS' : '[OPTIONS]') if opt_args > 0
    usage_args << rest_args.to_use if rest_args?
    lines.concat(wrap_text("USAGE: #{RUBY_ENGINE} #{$0} #{usage_args.join(' ')}", width))
    lines << ''
    lines << 'Specify the /? or --help option for more detailed help'
    lines << ''
    lines.each{ |line| out.puts line } if out
    lines
end

#show_usage?Boolean

Whether user indicated they would like help on usage.

Returns:

  • (Boolean)

See Also:

  • Parser#show_usage


184
185
186
# File 'lib/arg-parser/definition.rb', line 184

def show_usage?
    parser.show_usage?
end

#sizeInteger

Returns the number of arguments that have been defined.

Returns:

  • (Integer)

    the number of arguments that have been defined.



303
304
305
# File 'lib/arg-parser/definition.rb', line 303

def size
    @arguments.size
end

#validate_requirements(args) ⇒ Array

Validates the supplied args Hash object, verifying that any argument set requirements have been satisfied. Returns an array of error messages for each set requirement that is not satisfied.

Parameters:

  • args (Hash)

    a Hash containing the keys and values identified by the parser.

Returns:

  • (Array)

    a list of errors for any argument requirements that have not been satisfied.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/arg-parser/definition.rb', line 204

def validate_requirements(args)
    errors = []
    @require_set.each do |req, sets|
        sets.each do |set|
            count = set.count{ |arg| args.has_key?(arg.key) && args[arg.key] }
            case req
            when :one
                if count == 0
                    errors << "No argument has been specified for one of: #{set.join(', ')}"
                elsif count > 1
                    errors << "Only one argument can been specified from: #{set.join(', ')}"
                end
            when :any
                if count == 0
                    errors << "At least one of the arguments must be specified from: #{set.join(', ')}"
                end
            end
        end
    end
    errors
end

#value_argsArray

Returns all the positional, keyword, and rest arguments that have been defined.

Returns:

  • (Array)

    all the positional, keyword, and rest arguments that have been defined.



297
298
299
# File 'lib/arg-parser/definition.rb', line 297

def value_args
    @arguments.values.select{ |arg| ValueArgument === arg }
end

#wrap_text(text, width) ⇒ Array

Utility method for wrapping lines of text at width characters.

Parameters:

  • text (String)

    a string of text that is to be wrapped to a maximum width.

  • width (Integer)

    the maximum length of each line of text.

Returns:

  • (Array)

    an Array of lines of text, each no longer than width characters.



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/arg-parser/definition.rb', line 399

def wrap_text(text, width)
    if width > 0 && (text.length > width || text.index("\n"))
        lines = []
        start, nl_pos, ws_pos, wb_pos, end_pos = 0, 0, 0, 0, text.rindex(/[^\s]/)
        while start < end_pos
            last_start = start
            nl_pos = text.index("\n", start)
            ws_pos = text.rindex(/ +/, start + width)
            wb_pos = text.rindex(/[\-,.;#)}\]\/\\]/, start + width - 1)
            ### Debug code ###
            #STDERR.puts self
            #ind = ' ' * end_pos
            #ind[start] = '('
            #ind[start+width < end_pos ? start+width : end_pos] = ']'
            #ind[nl_pos] = 'n' if nl_pos
            #ind[wb_pos] = 'b' if wb_pos
            #ind[ws_pos] = 's' if ws_pos
            #STDERR.puts ind
            ### End debug code ###
            if nl_pos && nl_pos <= start + width
                lines << text[start...nl_pos].strip
                start = nl_pos + 1
            elsif end_pos < start + width
                lines << text[start..end_pos]
                start = end_pos
            elsif ws_pos && ws_pos > start && ((wb_pos.nil? || ws_pos > wb_pos) ||
                  (wb_pos && wb_pos > 5 && wb_pos - 5 < ws_pos))
                lines << text[start...ws_pos]
                start = text.index(/[^\s]/, ws_pos + 1)
            elsif wb_pos && wb_pos > start
                lines << text[start..wb_pos]
                start = wb_pos + 1
            else
                lines << text[start...(start+width)]
                start += width
            end
            if start <= last_start
                # Detect an infinite loop, and just return the original text
                STDERR.puts "Inifinite loop detected at #{__FILE__}:#{__LINE__}"
                STDERR.puts "  width: #{width}, start: #{start}, nl_pos: #{nl_pos}, " +
                            "ws_pos: #{ws_pos}, wb_pos: #{wb_pos}"
                return [text]
            end
        end
        lines
    else
        [text]
    end
end