Class: FlexArgs
- Inherits:
-
Object
- Object
- FlexArgs
- Defined in:
- lib/flex_args.rb,
lib/flex_args/parser.rb,
lib/flex_args/version.rb,
lib/flex_args/transform.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
- Transformations
- Semantic Checks - between multiple values (v0.2)
and they are documented in the rdocs of the corresponding methods
Defined Under Namespace
Modules: Version Classes: Constraint, Parser, Transform
Instance Attribute Summary collapse
-
#alias_definitions ⇒ Object
readonly
Returns the value of attribute alias_definitions.
-
#default_values ⇒ Object
readonly
Returns the value of attribute default_values.
-
#result ⇒ Object
readonly
Returns the value of attribute result.
Instance Method Summary collapse
-
#aliases(alias_definitions) ⇒ Object
## Aliases are defined by passing a hash.
-
#allow(*values) ⇒ Object
## Allowed values and flags.
- #allowed?(value) ⇒ Boolean
-
#constrain(value, *constraints, &block) ⇒ Object
## Constraints.
- #constraints(key) ⇒ Object
- #parse(args) ⇒ Object
-
#transform(value, *transforms, &transformer) ⇒ Object
## Transformers.
-
#values ⇒ Object
Just returns the parsed values, or their default values if a default was provided and the value was not present in the provided arguments.
Instance Attribute Details
#alias_definitions ⇒ Object (readonly)
Returns the value of attribute alias_definitions.
102 103 104 |
# File 'lib/flex_args.rb', line 102 def alias_definitions @alias_definitions end |
#default_values ⇒ Object (readonly)
Returns the value of attribute default_values.
102 103 104 |
# File 'lib/flex_args.rb', line 102 def default_values @default_values end |
#result ⇒ Object (readonly)
Returns the value of attribute result.
102 103 104 |
# File 'lib/flex_args.rb', line 102 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]))
“‘
132 133 134 135 136 137 138 |
# File 'lib/flex_args.rb', line 132 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:"
)
“‘
176 177 178 179 |
# File 'lib/flex_args.rb', line 176 def allow(*values) values.each { allow_value it } self end |
#allowed?(value) ⇒ Boolean
181 182 183 184 |
# File 'lib/flex_args.rb', line 181 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"')
“‘
#### Combining Constraints
More than one constraint can be imposed on a value
Firstly one can combine any constraint with a custom constraint.
N.B. That in this case the custom constraint is executed last, therefore
“‘spec # Order of constraints
parser = FlexArgs.new
.constrain(:n, 1..10) { it == 5 ? [:ok, 100] : [:ok, 2*it] }
expect(parser.parse(%w[n:5]).values).to eq(n: 100)
“‘
However if we had defined the custom constraint (which actually is a transformer as it always returns an :ok value, the range constraint would fail
“‘spec # Bad order
parser = FlexArgs.new
.constrain(:n) { it == 5 ? [:ok, 100] : [:ok, 2*it] }
.constrain(:n, 1..10)
expect { parser.parse(%w[n:5]) }
.to raise_error(ArgumentError)
“‘
As mentioned before, custom constraints that always return :ok values are indeed transformers and can be written more with the transform [method below](/FlexArgs.html#method-i-transform-label-Transformers)
Please find the documentation about more constraints [here](/FlexArgs/Constraint.html)
264 265 266 267 268 269 |
# File 'lib/flex_args.rb', line 264 def constrain(value, *constraints, &block) @allowed_values << value.to_sym if @allowed_values @constraints[value.to_sym] << Constraint.new(value, *constraints) unless constraints.empty? @constraints[value.to_sym] << Constraint.new(value, &block) if block self end |
#constraints(key) ⇒ Object
186 |
# File 'lib/flex_args.rb', line 186 def constraints(key) = @constraints[key] |
#parse(args) ⇒ Object
104 105 106 107 108 109 110 111 112 |
# File 'lib/flex_args.rb', line 104 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 |
#transform(value, *transforms, &transformer) ⇒ Object
## Transformers
Transformers and Constraints are executed in the order they are defined
“‘spec # Transformer assures constraint passes
parser = FlexArgs.new
.transform(:word) { it.downcase }
.constrain(:word, "a".."z")
expect(parser.parse(%w[word:A]).values).to eq(word: "a")
“‘
And therefore
“‘spec # Transformer called too late to assure constraint passes
parser = FlexArgs.new
.constrain(:word, "a".."z")
.transform(:word) { it.downcase }
expect { parser.parse(%w[word:A]) }.to raise_error(ArgumentError)
“‘
295 296 297 298 299 300 |
# File 'lib/flex_args.rb', line 295 def transform(value, *transforms, &transformer) @allowed_values << value.to_sym if @allowed_values @constraints[value.to_sym] << Transform.new(*transforms) unless transforms.empty? @constraints[value.to_sym] << Transform.new(&transformer) if transformer self end |
#values ⇒ Object
Just returns the parsed values, or their default values if a default was provided and the value was not present in the provided arguments.
307 308 309 |
# File 'lib/flex_args.rb', line 307 def values default_values.merge(values_from_args) end |