Multiple arguments¶
So far we only used one argument, but it is not uncommon for a command to support multiple arguments.
To do that with Clip we just have to define other attributes:
require "clip"
module Myapplication
VERSION = "0.1.0"
struct Command
include Clip::Mapper
getter repeat : Int32
getter name : String
end
def self.run
begin
command = Command.parse
rescue ex : Clip::Error
puts ex
return
end
if command.is_a?(Clip::Mapper::Help)
puts command.help
else
hello(command.name, command.repeat)
end
end
def self.hello(name, repeat)
repeat.time { puts "Hello #{name}" }
end
end
Myapplication.run
Our command has now two arguments: NAME
and REPEAT
.
$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication --help
Usage: ./bin/myapplication [OPTIONS] NAME REPEAT
Arguments:
NAME [required]
REPEAT [required]
Options:
--help Show this message and exit.
$ ./bin/myapplication 2 Alice
Hello Alice
Hello Alice
Arguments ordering¶
You may have noticed something strange in the previous example. The usage says "NAME REPEAT" but I wrote "2 Alice", so "REPEAT NAME". Unfortunately, this behavior comes from the Crystal compiler itself.
When we include Clip::Mapper
, two macros are run to generate the parser and the help message.
Those macros rely on TypeNode#instance_vars
to get all the type's attributes.
Sadly, this method does not always return the attributes as they were ordered in the type declaration.
Even sadder, multiple calls can return attributes in a different order.
With our example the parser saw repeat
and then name
, but the help saw name
and then repeat
.
That's why Clip provides a way to ensure the arguments ordering.
It is done with the annotation Clip::Argument
, with a named parameter idx
:
require "clip"
module Myapplication
VERSION = "0.1.0"
struct Command
include Clip::Mapper
@[Clip::Argument(idx: 1)]
getter repeat : Int32
@[Clip::Argument(idx: 2)]
getter name : String
end
def self.run
begin
command = Command.parse
rescue ex : Clip::Error
puts ex
return
end
if command.is_a?(Clip::Mapper::Help)
puts command.help
else
hello(command.name, command.repeat)
end
end
def self.hello(name, repeat)
repeat.time { puts "Hello #{name}" }
end
end
Myapplication.run
The arguments are now correctly ordered both in the help message and in the parser:
$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication --help
Usage: ./bin/myapplication [OPTIONS] REPEAT NAME
Arguments:
REPEAT [required]
NAME [required]
Options:
--help Show this message and exit.
$ ./bin/myapplication 1 Alice
Hello Alice
Arguments consuming order¶
A little note about how the argument are consummed from the user input. Let's take the following command definition:
Usage: ./bin/myapplication [OPTIONS] REPEAT NAME
This command has two required arguments.
Clip always consumes arguments from left to right.
So if the user input is ./bin/myapplication 2 Alice
, Clip consumes 2
and uses it as the value for REPEAT
, then it consumes Alice
and uses it as the value for NAME
.
So far so good.
But what if we make REPEAT
an optional argument?
The command definition now looks like this:
Usage: ./bin/myapplication [OPTIONS] [REPEAT] NAME
When we give both arguments, Clip maps them both, and everything is ok.
But if we give only one argument, Clip still maps it with REPEAT
, as it consumes arguments from left to right.
Then it finds no value for NAME
and raises an error:
require "clip"
module Myapplication
VERSION = "0.1.0"
struct Command
include Clip::Mapper
@[Clip::Argument(idx: 1)]
getter repeat = 1
@[Clip::Argument(idx: 2)]
getter name : String
end
def self.run
begin
command = Command.parse
rescue ex : Clip::Error
puts ex
return
end
if command.is_a?(Clip::Mapper::Help)
puts command.help
else
hello(command.name, command.repeat)
end
end
def self.hello(name, repeat)
repeat.times { puts "Hello #{name}" }
end
end
Myapplication.run
$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication --help
Usage: ./bin/myapplication [OPTIONS] [REPEAT] NAME
Arguments:
REPEAT [default: 1]
NAME [required]
Options:
--help Show this message and exit.
$ ./bin/myapplication 2 Alice
Hello Alice
Hello Alice
$ ./bin/myapplication Alice
Error:
argument's value is invalid: REPEAT
argument is required: NAME
Let's make NAME
optional instead:
require "clip"
module Myapplication
VERSION = "0.1.0"
struct Command
include Clip::Mapper
@[Clip::Argument(idx: 1)]
getter repeat : Int32
@[Clip::Argument(idx: 2)]
getter name = "Barbara"
end
def self.run
begin
command = Command.parse
rescue ex : Clip::Error
puts ex
return
end
if command.is_a?(Clip::Mapper::Help)
puts command.help
else
hello(command.name, command.repeat)
end
end
def self.hello(name, repeat)
repeat.times { puts "Hello #{name}" }
end
end
Myapplication.run
If we give only one argument Clip maps it to REPEAT
, and as NAME
is optional no error is raised:
$ shards build
Dependencies are satisfied
Building: myapplication
$ ./bin/myapplication --help
Usage: ./bin/myapplication [OPTIONS] REPEAT [NAME]
Arguments:
REPEAT [required]
NAME [default: Barbara]
Options:
--help Show this message and exit.
$ ./bin/myapplication 2 Alice
Hello Alice
Hello Alice
$ ./bin/myapplication 1
Hello Barbara
To resume:
- you can use multiple arguments
- arguments are consumed from left to right
- hence optional arguments must be the last ones
Note
This is fully dependant on the current implementation of Clip and you may find other libraries without this constraint.