Terminals are so vague. You have commands, sub commands, args, flags and ENV variables. All separated by spaces, dashes and equals signs... when really all you have is a line of text.
CLI frameworks are vague too. You build your beautiful structure then map user input to those components. But now there's a translation gap between the structure you've defined and what the user types.
Trees just gets you to write what the literal user input will be, then breaks it down into a tree of commands, subcommands and options if need be. Think of your commands as routes with params. Think of the structure as a tree 🌲.
Taken from Rain CLI which powers the Raindeer web framework.
line('new :app_name') do |app_name|
execute { Rain::CLI::Template.build(app_name) }
line('--with-db', '-d') do |app_name|
summary { 'Set up your application with a database.' }
execute { Rain::CLI::Template.build(app_name, db: true) }
end
end
line('static build') do
execute { Static.build }
endThe following structures are equivalent and produce the same tree-like structure internally:
Nested:
line('switch build') do
execute { Switch.build }
line('--reset-delay') do
execute { Switch.build(reset_delay: true) }
end
endFlat:
line('switch build') do
execute { Switch.build }
end
line('switch build --reset-delay') do
execute { Switch.build(reset_delay: true) }
endBecause all the lines are stored as a prefix tree (AKA Trie), you get an autocomplete for free!
Trees will execute the deepest match with an execute block so that you can add summary blocks for flags without doubling up on execute blocks:
line('new :app_name') do |app_name, with_db:|
execute { Rain::CLI::Template.build(app_name, db: with_db) }
line('--with-db', '-d') do
summary { 'Set up your application with a database.' }
end
endThe above structure is equivalent to:
line('new :app_name') do |app_name|
execute { Rain::CLI::Template.build(app_name) }
line('--with-db', '-d') do |app_name|
summary { 'Set up your application with a database.' }
execute { Rain::CLI::Template.build(app_name, db: true) }
end
endDifferent placeholders in your string represent different data types.
Variables are defined by prefixing a space separated word with a colon :.
You can do fancy stuff like root level args pretty easily:
$ cli @prod commandWhich would be defined like:
line('@:environment command')...
The literal text with no special characters will be interpreted as a subcommand.
...
Add a summary to a command, sub command or flag:
line('static build') do
summary { 'Build your static site' }
end