It’s common to want to build a command-line tool where subcommands share common options.
My approach is to define a macro and expand the shared options inside each subcommand. After macro expansion the code essentially becomes boilerplate.
The approach is simple enough that I imagine you’ve probably already thought of it. You can easily handle cases where the number of subcommands grows or where the shared parts start to diverge.
The unfortunate part is that OptionParser in Crystal’s standard library is difficult to modify due to historical constraints. Just last month, a proposal to add short option bundling — a basic feature — was reverted because it breaks backward compatibility.
Building command-line tools is my main use case for Crystal, so I hope there’s a way to work around this.
My solution is to maintain a hash mapping, {} of Symbol => Proc(OptionParser, CLI, Nil), symbol is the sub-command string, add extra opt rule for sub-command in the Proc, if user input this sub-command, then call the Proc with opts object as arg.
The approach is simple enough that I imagine you’ve probably already thought of it
Actually, I didn’t come up with the idea of doing it this way,, I thought if follow your instructions, we still need a separate OptionParser to process it and find the correct sub-command, right?
My approach is a naive extension of the official reference, requiring only a single parse call:
macro on_common
opt.on("-a", "--aa", "Common option A") do
end
opt.on("-b", "--bb", "Common option B") do
end
end
OptionParser.parse do |opt|
opt.on("sub1", "This is subcommand 1") do
opt.on("-x", "--xx", "Extra option for sub1") do
end
on_common
end
opt.on("sub2", "This is subcommand 2") do
opt.on("-y", "--yy", "Extra option for sub2") do
end
on_common
end
end
This is just one example. I prefer to define all command-line options in a single file, so this approach works well for me. Note that this naive approach has its own drawbacks, and I am not claiming it is always the best solution.