Create custom commands
Marten lets you create custom management commands as part of your applications. This allows you to contribute new features and behaviors to the Marten CLI.
Basic management command definition
Custom management commands are defined as subclasses of the Marten::CLI::Command
abstract class. Such subclasses should be defined in a cli/
folder at the root of the application, and it should be ensured that they are required by your cli.cr
file (see Creating applications for more details regarding the structure of an application).
Management command classes must at least define a #run
method, which will be called when the subcommand is executed:
class MyCommand < Marten::CLI::Command
help "Command that does something"
def run
# Do something
end
end
As you can see in the previous example, the #help
class method allows setting a "help text" that will be displayed when the help information of the command is requested.
If the above command was part of an installed application, it could be executed by using the Marten CLI as follows:
marten my_command
Accepting options and arguments
Marten management commands can accept options and arguments. These differ and may be used for different use cases:
- options usually use the
-h
/--help
style and can receive arguments if needed. They can be specified in any order - arguments are positional and only their values must be specified
By default options and arguments are always optional. That being said, they can be made mandatory in the command execution logic if needed.
Both options and arguments must be specified in the optional #setup
method: this method will be called to prepare the definition of the command, including its arguments and options.
For example:
class MyCommand < Marten::CLI::Command
help "Command that does something"
@arg1 : String?
@example : Bool = false
def setup
on_argument(:arg1, "The first argument") { |v| @arg1 = v }
on_option("example", "An example option") { @example = true }
end
def run
# Do something
end
end
In the above example, the #on_argument
instance method is used to define an arg1
argument. This method requires an argument name, an associated help text, and a proc where the value of the argument will be forwarded at execution time (which allows you to assign it to an instance variable or process it if you wish to). Similarly, the #on_option
instance method is used to define an example
option. In this case, the name of the option and its associated help text must be specified, and a proc can be defined to identify that the option was specified at execution time (which can be used to set a related boolean instance variable for example).
The above command would produce the following help information:
Usage: marten my_command [options] [arg1]
Command that does something
Arguments:
arg1 The first argument
Options:
--example An example option
--error-trace Show full error trace (if a compilation is involved)
--no-color Disable colored output
-h, --help Show this help
Configuring options
As mentioned previously, it is possible to make use of the #on_option
instance method to configure a specific command option (eg. --option
). It expects a flag name and a description, and it yields a block to let the command properly assign the option value to the command object at execution time:
on_option("example", "An example option") { @example = true }
Note that the --
must not be included in the option name.
Alternatively, it is possible to specify options that accept both a short flag (eg. -h
) and a long flag (eg. --help
):
on_option("e", "example", "An example option") { @example = true }
Configuring options that accept arguments
It is possible to make use of the #on_option_with_arg
instance method to configure a specific command option with an associated argument. This method will configure a command option (eg. --option
) and an associated argument. It expects a flag name, an argument name, and a description. It yields a block to let the command properly assign the option to the command object at execution time:
on_option_with_arg(:option, :arg, "The name of the option") { @arg = arg }
Alternatively, it is possible to specify options that accept both a short flag (eg. -h
) and a long flag (eg. --help
):
on_option_with_arg("o", "option", "arg", "The name of the option") { |arg| @arg = arg }
Configuring arguments
As mentioned previously, it is possible to make use of the #on_argument
instance method in order to configure a specific command argument. This method expects an argument name and a description, and it yields a block to let the command properly assign the argument value to the command object at execution time:
on_argument(:arg, "The name of the argument") { |value| @arg_var = value }
It should be noted that the order in which arguments are defined is important: this order corresponds to the order in which arguments will need to be specified when invoking the subcommand.
Outputting text contents
When writing management commands, you will likely need to write text contents to the output file descriptor. To do so, you can make use of the #print
instance method:
class HelloWorldCommand < Marten::CLI::Command
help "Command that prints Hello World!"
def run
print("Hello World!")
end
end
It should be noted that you can also choose to "style" the content you specify to #print
by wrapping your string with a call to the #style
method. For example:
class HelloWorldCommand < Marten::CLI::Command
help "Command that prints Hello World!"
def run
print(style("Hello World!", fore: :light_blue, mode: :bold))
end
end
As you can see, the #style
method can be used to apply fore
and mode
styles to a specific text value. The values you can use for the fore
and mode
arguments are the same as the ones that you can use with the Colorize
module (which comes with the standard library).
Handling error cases
You will likely want to handle error situations when writing management commands. For example, to return error messages if a specified argument is not provided or if it is invalid. To do so you can make use of the #print_error
helper method, which will print the passed string to the error file descriptor:
class HelloWorldCommand < Marten::CLI::Command
help "Command that prints Hello World!"
@name : String?
def setup
on_argument(:name, "A name") { |v| @name = v }
end
def run
if @name.nil?
print_error("A name must be provided!")
else
print("Hello World, #{@name}!")
end
end
end
Alternatively, you can make use of the #print_error_and_exit
method to print a message to the error file descriptor and to exit the execution of the command.
Customizing the subcommand name
By default, management command names are inferred by using their associated class names (eg. a MyCommand
command class would translate to a my_command
subcommand). That being said, it should be noted that you can define a custom subcommand name by leveraging the #command_name
class method:
class MyCommand < Marten::CLI::Command
command_name :dummycommand
help "Command that does something"
def run
# Do something
end
end
It is also worth mentioning that command aliases can be configured easily by using the #command_aliases
helper method. For example:
class MyCommand < Marten::CLI::Command
command_name :test
command_aliases :t
help "Command that does something"
def run
# Do something
end
end