Do One Thing - ess/belafonte GitHub Wiki

There is a long tradition (well, going back to the 1970s, at least) that dictates that a command-line application should do one thing, and it should do that one thing well. This applies well to the idea of a Belafonte::App instance.

Keep your app small. A single app class shouldn’t do more than one thing, if you can help it. If you find yourself using options and switches to control the flow of your application's handle method, chances are very good that you'd be better served by breaking that App into several smaller apps, then mounting them under a common command name.

Example

This is a pretty simple example, but it is actually a very minimal alteration of a real-world scenario that has been seen recently.

BadApp

This is the "ssh" command, which supports both connecting to servers and running one-off commands on servers.


class BadApp < Belafonte::App
  title "bad-app"
  summary "connect to a remote server"

  option :command, short: 'c', long: 'command', argument: 'command',
    description: 'command to run'

  arg :server

  def handle
    if option(:command)
      SSH.run(arg(:server).first, option(:command))
    else
      SSH.connect(server)
    end
  end
end

This is how one uses this app:

# Connecting to a server

bad-app some.server.com

# Running a command on a server

bad-app -c 'hostname' some.server.com

That seems too simple to worry about, but what happens when a third, fourth, Nth path is added? Additionally, how hard or easy is it to describe the behavior of this application?

BetterApp

Here, we have created a namespace to contain the entire application, broken both actions out into their own apps, and then wrapped them with a "main" app. This provides arguably better UX than the single-app example, and it is also generally easier to describe the behavior of each part.


module BetterApp

  # An application that allows one to open a shell on a remote server
  class Connect < Belafonte::App
    title 'connect'
    summary 'connect to a remote server via SSH'

    arg :server

    def handle
      SSH.connect(arg(:server).first)
    end
  end

  # An application that allows one to run a command on a remote server
  class Run < Belafonte::App
    title 'run'
    summary 'run a command on a remote server'

    arg :server
    arg :remote_command, times: :unlimited

    def handle
      SSH.run(arg(:server).first, remote_command)
    end

    private
    def remote_command
      arg(:remote_command).join(' ')
    end
  end

  # The main entry point for the "better-app" application
  class Main < Belafonte::App
    title 'better-app'
    summary 'operations for remote servers'

    mount Connect
    mount Run
  end
end

This is how the app is used:

# connect to a server

better-app connect some.server.com

# run a command on a server

better-app run some.server.com hostname