Module: Tins::GO

Defined in:
lib/tins/go.rb

Overview

A command-line option parsing library that provides a flexible way to parse single-character options with optional arguments. It supports multiple values for the same flag and provides a clean API for handling command-line interfaces.

Examples:

Basic usage

# Parse options with pattern 'xy:z'
options = Tins::GO.go('xy:z', ARGV, defaults: { x: true, y: 'default' })
# Handles: -x -y value -z

Multiple values for same option

# Handle: -f foo -f bar -f baz
options = Tins::GO.go('f:', ARGV)
# options['f'] will contain an ArrayExtension collection with all
values, see `option['f'].to_a`

Defined Under Namespace

Modules: ArrayExtension

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.go(s, args = ARGV, defaults: {}) ⇒ Hash{String => Object}

Parses the argument array args, according to the pattern s, to retrieve the single character command line options from it.

Pattern syntax:

  • 'x': Boolean flag.
  • 'x:': Option requiring an argument.
  • '~x': Explicitly disables the -x option.

Note: If a character is defined as both a boolean and a value flag (e.g., 'xx:'), it is considered ambiguous and will be disabled.

Behavior:

  • If a value flag is missing its argument, or the next argument starts with '-', a warning is issued to STDERR and the flag is not set.
  • This method modifies the args array in-place, removing all parsed options and leaving only the remaining positional arguments.

The defaults argument specifies default values for the options.

An option hash is returned with all found options set to a truthy value representing the number of times they were encountered, or false if not present. When a default value is specified and the flag is not present, the default value is used instead.

Examples:

Basic usage

# Parse options with pattern 'xy:z'
options = Tins::GO.go('xy:z', ARGV, defaults: { x: true, y: 'default' })
# Handles: -x -y value -z

Multiple values for same option

# Handle: -f foo -f bar -f baz
options = Tins::GO.go('f:', ARGV)
# options['f'] will contain an ArrayExtension collection with
# all values, see options['f'].to_a

Boolean flag counting

# Handle: -x -x -x
options = Tins::GO.go('x', ARGV)
# options['x'] will be 3 (truthy numeric value)

Boolean flag not present

# Handle: no -x flag
options = Tins::GO.go('x', ARGV)
# options['x'] will be false

Disabling options with default values

# Handle: ~x (disables -x option) when x has a default value
options = Tins::GO.go('x', ARGV, defaults: { x: true })
# options['x'] will be false if no ~x flag is present

Parameters:

  • s (String)

    Option pattern string where each character represents an option, and ':' indicates the option requires an argument

  • args (Array<String>) (defaults to: ARGV)

    Array of arguments to parse (defaults to ARGV)

  • defaults (Hash{String => Object}) (defaults to: {})

    Default values for options

Returns:



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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/tins/go.rb', line 99

def go(s, args = ARGV, defaults: {})
  d = defaults || {}
  b, v = s.scan(/(.)(:?)/).inject([ {}, {} ]) { |t, (o, a)|
    a = a == ?:
    t[a ? 1 : 0][o] = a ? nil : false
    t
  }
  (b.keys & v.keys).each do |o|
    warn "Warning: Option #{o} passed as boolean and value flag! => Ignoring #{o.inspect}."
    b.delete(o)
    v.delete(o)
  end
  b.each_key do |k|
    d.key?(k) or next
    if [ 0, false, nil ].include?(d[k])
      b[k] = false
    elsif d[k].respond_to?(:to_int)
      b[k] = d[k].to_int
    else
      b[k] = 1
    end
  end
  v.each_key do |k|
    d.key?(k) or next
    if [ 0, false, nil ].include?(d[k])
      v[k] = nil
    else
      v[k] = d[k].to_s
    end
  end
  r = []
  while a = args.shift
    /\A-(?<p>.+)/ =~ a or (r << a; next)
    until p == ''
      o = p.slice!(0, 1)
      if v.key?(o)
        if p.empty? && args.empty?
          warn "Warning: Option -#{o} requires an argument, but none was provided."
          r << a
          break 1
        elsif p == ''
          an = args.first
          if an =~ /\A-/
            warn "Warning: Option -#{o} requires an argument, but #{an.inspect} is not a valid value."
            r << a
            break
          end
          a = args.shift
        else
          a = p
        end
        if v[o].nil? || !(ArrayExtension === v[o])
          a = a.dup
          a.extend ArrayExtension
          a.push a
          v[o] = a
        else
          v[o].push a
        end
        break
      elsif b.key?(o)
        if b[o]
          b[o] += 1
        else
          b[o] = 1
        end
      else
        r << a
      end
    end && break
  end
  r.reject! { |a| (b[p] = false) || true if /\A~(?<p>.)/ =~ a  }
  v.transform_values! do |w|
    if w.is_a?(String) && !w.is_a?(ArrayExtension)
      w = w.dup
      w.extend ArrayExtension
      w.push w
    else
      w
    end
  end
  args.replace r
  b.merge(v)
end

Instance Method Details

#go(s, args = ARGV, defaults: {}) ⇒ Hash{String => Object} (private)

Parses the argument array args, according to the pattern s, to retrieve the single character command line options from it.

Pattern syntax:

  • 'x': Boolean flag.
  • 'x:': Option requiring an argument.
  • '~x': Explicitly disables the -x option.

Note: If a character is defined as both a boolean and a value flag (e.g., 'xx:'), it is considered ambiguous and will be disabled.

Behavior:

  • If a value flag is missing its argument, or the next argument starts with '-', a warning is issued to STDERR and the flag is not set.
  • This method modifies the args array in-place, removing all parsed options and leaving only the remaining positional arguments.

The defaults argument specifies default values for the options.

An option hash is returned with all found options set to a truthy value representing the number of times they were encountered, or false if not present. When a default value is specified and the flag is not present, the default value is used instead.

Examples:

Basic usage

# Parse options with pattern 'xy:z'
options = Tins::GO.go('xy:z', ARGV, defaults: { x: true, y: 'default' })
# Handles: -x -y value -z

Multiple values for same option

# Handle: -f foo -f bar -f baz
options = Tins::GO.go('f:', ARGV)
# options['f'] will contain an ArrayExtension collection with
# all values, see options['f'].to_a

Boolean flag counting

# Handle: -x -x -x
options = Tins::GO.go('x', ARGV)
# options['x'] will be 3 (truthy numeric value)

Boolean flag not present

# Handle: no -x flag
options = Tins::GO.go('x', ARGV)
# options['x'] will be false

Disabling options with default values

# Handle: ~x (disables -x option) when x has a default value
options = Tins::GO.go('x', ARGV, defaults: { x: true })
# options['x'] will be false if no ~x flag is present

Parameters:

  • s (String)

    Option pattern string where each character represents an option, and ':' indicates the option requires an argument

  • args (Array<String>) (defaults to: ARGV)

    Array of arguments to parse (defaults to ARGV)

  • defaults (Hash{String => Object}) (defaults to: {})

    Default values for options

Returns:



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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/tins/go.rb', line 99

def go(s, args = ARGV, defaults: {})
  d = defaults || {}
  b, v = s.scan(/(.)(:?)/).inject([ {}, {} ]) { |t, (o, a)|
    a = a == ?:
    t[a ? 1 : 0][o] = a ? nil : false
    t
  }
  (b.keys & v.keys).each do |o|
    warn "Warning: Option #{o} passed as boolean and value flag! => Ignoring #{o.inspect}."
    b.delete(o)
    v.delete(o)
  end
  b.each_key do |k|
    d.key?(k) or next
    if [ 0, false, nil ].include?(d[k])
      b[k] = false
    elsif d[k].respond_to?(:to_int)
      b[k] = d[k].to_int
    else
      b[k] = 1
    end
  end
  v.each_key do |k|
    d.key?(k) or next
    if [ 0, false, nil ].include?(d[k])
      v[k] = nil
    else
      v[k] = d[k].to_s
    end
  end
  r = []
  while a = args.shift
    /\A-(?<p>.+)/ =~ a or (r << a; next)
    until p == ''
      o = p.slice!(0, 1)
      if v.key?(o)
        if p.empty? && args.empty?
          warn "Warning: Option -#{o} requires an argument, but none was provided."
          r << a
          break 1
        elsif p == ''
          an = args.first
          if an =~ /\A-/
            warn "Warning: Option -#{o} requires an argument, but #{an.inspect} is not a valid value."
            r << a
            break
          end
          a = args.shift
        else
          a = p
        end
        if v[o].nil? || !(ArrayExtension === v[o])
          a = a.dup
          a.extend ArrayExtension
          a.push a
          v[o] = a
        else
          v[o].push a
        end
        break
      elsif b.key?(o)
        if b[o]
          b[o] += 1
        else
          b[o] = 1
        end
      else
        r << a
      end
    end && break
  end
  r.reject! { |a| (b[p] = false) || true if /\A~(?<p>.)/ =~ a  }
  v.transform_values! do |w|
    if w.is_a?(String) && !w.is_a?(ArrayExtension)
      w = w.dup
      w.extend ArrayExtension
      w.push w
    else
      w
    end
  end
  args.replace r
  b.merge(v)
end