Project

trees

0.0
The project is in a healthy, maintained state
A CLI framework where you write the literal terminal commands and Trees then breaks them apart into a tree of commands and options.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

Gem version GitHub repo Codeberg repo

Trees 🌲🌲🌲

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.

With Trees you write what the literal user input will be, then Trees 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 🌲.

✨ Features:

  • Flat and nested commands use the exact same API
  • Args and flags
  • Zero-dependencies

Example

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 }
end

Nested VS Flat

The 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
end

Flat:

line('switch build') do
  execute { Switch.build }
end

line('switch build --reset-delay') do
  execute { Switch.build(reset_delay: true) }
end

Autocomplete [UNRELEASED]

Because all the lines are stored as a prefix tree (AKA Trie), you get an autocomplete for free!

Execution Behaviour [UNRELEASED]

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
end

The 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
end

API

Different placeholders in your string represent different data types.

:arg

Args are defined by prefixing a space separated word with a colon :.

You can do fancy stuff like root level args pretty easily:

$ cli @prod command

Which would be defined like:

line('@:environment command')

Flag [UNRELEASED]

Syntax: --flag, -f

Option

Syntax: --flag=:value or -o :value

line('build --env=:environment') do |environment|
  # The environment arg is now available.
end

subcommand

Literal text will be interpreted as a subcommand.

line('command subcommand') do
  # ...
end

Summary block

Add a summary to a command, sub command or flag:

line('static build') do
  summary { 'Build your static site' }
end