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.



198
199
200
# File 'lib/arg-parser/definition.rb', line 198

def args
    @arguments.values
end

#errorsObject

Return an array of parse errors.

See Also:



147
148
149
# File 'lib/arg-parser/definition.rb', line 147

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



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

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.



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

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.



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

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.



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

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.



217
218
219
# File 'lib/arg-parser/definition.rb', line 217

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.



223
224
225
# File 'lib/arg-parser/definition.rb', line 223

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


140
141
142
# File 'lib/arg-parser/definition.rb', line 140

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.



126
127
128
# File 'lib/arg-parser/definition.rb', line 126

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



204
205
206
# File 'lib/arg-parser/definition.rb', line 204

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.



210
211
212
# File 'lib/arg-parser/definition.rb', line 210

def positional_args?
    positional_args.size > 0
end

#require_any_of(*keys) ⇒ Object

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



113
114
115
# File 'lib/arg-parser/definition.rb', line 113

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.



106
107
108
# File 'lib/arg-parser/definition.rb', line 106

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)


119
120
121
# File 'lib/arg-parser/definition.rb', line 119

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.



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

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.



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

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.



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
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
# File 'lib/arg-parser/definition.rb', line 300

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


161
162
163
# File 'lib/arg-parser/definition.rb', line 161

def show_help?
    parser.show_help?
end

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

Generates a usage display string



279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/arg-parser/definition.rb', line 279

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


154
155
156
# File 'lib/arg-parser/definition.rb', line 154

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.



273
274
275
# File 'lib/arg-parser/definition.rb', line 273

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.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/arg-parser/definition.rb', line 174

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.



267
268
269
# File 'lib/arg-parser/definition.rb', line 267

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.



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/arg-parser/definition.rb', line 369

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