Class: FlexArgs

Inherits:
Object
  • Object
show all
Defined in:
lib/flex_args.rb,
lib/flex_args/parser.rb,
lib/flex_args/version.rb,
lib/flex_args/constraint.rb

Overview

## Abstract

This is FlexArgs, and I am Robert Dober

It will allow for a versatile parsing of command line arguments.

By default the behavior is as follows

### Positional arguments

Are all arguments that do not:

- start with a `-`
- contain any of the following characters: `:,`
- contain the string `".."`

Therefore the following arguments will be parsed as

“‘spec # Only default positional arguments

parse(%w[hello world.glob 13 a-b]).positionals => %w[hello world.glob 13 a-b]

“‘

Now flags start with a ‘-` getting us many single letter flags or a `–` as in Posix

“‘spec # Flags and positionals

args = parse(%w[hello --verbose -world 42])
expect(args.positionals).to eq(%w[hello 42])
expect(args.flags).to eq(Set.new(%i[verbose w o r l d]))

“‘ ### Value arguments

Instead of posix syntax like ‘–key=value` or `–key value` `FlexArgs` uses the `key:value` or `key,alpha,beta` syntax.

“‘spec # Just some values

parse(%w[alpha:42 hello:world]).values => {alpha: 42, hello: "world"}

“‘

N.B. that all digits values are converted to integers, as we can see in the following example signs are also taken into account.

#### Ranges

“‘spec # Ranges and signs

input = %w[range:-2..3 offset:+3 -V a/b.rb]
parsed = parse(input)
expect(parsed.positionals).to eq(["a/b.rb"])
expect(parsed.values).to eq(range: -2..3, offset: 3)
expect(parsed.flags).to eq(Set.new([:V]))

“‘

#### Lists

“‘spec # Just a list

parse(%w[a_list:1,2,a,3..4]).values => {a_list: [1, 2, "a", 3..4]}

“‘

But what if we want ‘,` inside a value? You need to double it

“‘spec # Escaping `,`

parse(%w[escaped:1,a,,b,c]).values => {escaped: [1, "a,b", "c"]}

“‘

#### Multiple Values

This is an alternative way to get a list of values

“‘spec # Multiple Values => Lists

parse(%w[list:1 list:2 hello list:3]).values => {list: [1, 2, 3]}

“‘

### Advanced Features

More advanced features are set with methods on the the instance before invoking ‘parse`.

These features are

- Aliased flags
- Defaults
- Constraints
   - allowed
   - required
   - domain checks
   - format checks
- Custom Transformations
- Semantic Checks (v0.2)

and they are documented in the rdocs of the corresponding methods

Defined Under Namespace

Modules: Version Classes: Constraint, Parser

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#alias_definitionsObject (readonly)

Returns the value of attribute alias_definitions.



101
102
103
# File 'lib/flex_args.rb', line 101

def alias_definitions
  @alias_definitions
end

#default_valuesObject (readonly)

Returns the value of attribute default_values.



101
102
103
# File 'lib/flex_args.rb', line 101

def default_values
  @default_values
end

#resultObject (readonly)

Returns the value of attribute result.



101
102
103
# File 'lib/flex_args.rb', line 101

def result
  @result
end

Instance Method Details

#aliases(alias_definitions) ⇒ Object

## Aliases are defined by passing a hash

The key of the hash is the alias and it will be defined as an alias to the value of the hash for each grapheme of the key, here is how that works

“‘spec # Aliases

parser = FlexArgs
  .new
  .aliases(hu: :help, v: :version)

expect(parser.parse(%w[-uv]).flags).to eq(Set.new(%i[help version]))
expect(parser.parse(%w[-h]).flags).to eq(Set.new(%i[help]))

“‘



131
132
133
134
135
136
137
# File 'lib/flex_args.rb', line 131

def aliases(alias_definitions)
  alias_definitions
    .each do |key, value|
      key.to_s.grapheme_clusters.each { define_alias it, value }
    end
  self
end

#allow(*values) ⇒ Object

## Allowed values and flags

As soon as this method is called the parser will check that all values in args are allowed

Values are either identified by their name (a ‘Symbol`) or by their name and default value (a pair of `Symbol` and any value

“‘spec

parser = FlexArgs
  .new
  .allow(:min, [:max, 3])

expect(parser.parse(%w[min:1]).values)
  .to eq(min: 1, max: 3)

“‘

Therefore the following args are illegal and an ‘ArgumentError` is raised

“‘spec # Argument Error for unallowed value

expect do
  FlexArgs
    .new
    .allow(:n)
    .parse(%w[a:2])
end
  .to raise_error(
    ArgumentError,
    "unallowed value arg a:"
  )

“‘



175
176
177
178
# File 'lib/flex_args.rb', line 175

def allow(*values)
  values.each { allow_value it }
  self
end

#allowed?(value) ⇒ Boolean

Returns:

  • (Boolean)


180
181
182
183
# File 'lib/flex_args.rb', line 180

def allowed?(value)
  return true unless @allowed_values
  @allowed_values.member?(value)
end

#constrain(value, *constraints, &block) ⇒ Object

## Constraints

Constraints are defined with the `constraint` method.

**N.B.** defined Constraints **after** allow unless you
want to explicitly add the value arg with `allow`

### Simple forms

#### Regexp constraint

“‘spec # regexp constraints

 parser = FlexArgs
            .new
            .constrain(:n, %r{\A \d+ \z}x)

expect(parser.parse(%w[n:42]).values).to eq(n: 42)

expect { parser.parse(%w[n:42a]) }
  .to raise_error(ArgumentError, 'regexp constraint n: (?x-mi:\A \d+ \z) violated by value "42a"')

“‘

#### Range constraints

“‘spec # range constraints

  parser = FlexArgs
             .new
             .constrain(:n, 2..3)

expect(parser.parse(%w[n:2]).values).to eq(n: 2)

expect { parser.parse(%w[n:42a]) }
  .to raise_error(ArgumentError, 'range constraint n: 2..3 violated by value "42a"')

expect { parser.parse(%w[n:42]) }
  .to raise_error(ArgumentError, 'range constraint n: 2..3 violated by value "42"')

“‘

Please find the documentation about more constraints [here](Constraint.html)



230
231
232
233
234
# File 'lib/flex_args.rb', line 230

def constrain(value, *constraints, &block)
  @allowed_values << value.to_sym if @allowed_values
  @constraints[value.to_sym] << Constraint.new(value, *constraints, &block)
  self
end

#constraints(key) ⇒ Object



185
# File 'lib/flex_args.rb', line 185

def constraints(key) = @constraints[key]

#parse(args) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/flex_args.rb', line 103

def parse(args)
  result = Parser.new(self).parse(args)
  case result
  in [:ok, result1]
    result1
  in [:error, errors]
    raise ArgumentError, errors.join("\n")
  end
end

#valuesObject

Just returns the parsed values, or their default values if a default was provided and the value was not present in the provided arguments.



241
242
243
# File 'lib/flex_args.rb', line 241

def values
  default_values.merge(values_from_args)
end