Class: Brut::CLI::App

Inherits:
Object
  • Object
show all
Includes:
ExecutionResults, I18n::ForCLI
Defined in:
lib/brut/cli/app.rb

Overview

Base class for all Brut-powered CLI Apps. Your subclass will call or override methods to declare the UI of your CLI app, including the commands it provides and options it recognizes. These mostly help to provide command-line documentation for your app, but also provide basic help with accessing the command line arguments and options. Internally, this uses Ruby's OptionParser.

The types of CLIs this framework supports are command suites where a single CLI app has several subcommands, similar to git. As such, the CLI has several parts that you can configure:

cli_app [global options] «subcommand» [command options] [arguments]
  • Global options appear between the CLI app's executable and the name of the subcommand. These affect any command. A common example is --log-level. These are configured with on or App.option_parser.
  • Subcommand is a single string representing the command to execute. The available commands are returned by App.commands although it's more conventional to declare inner classes of your app that extend Command.
  • Command options appear after the subcommand and apply only to that sub command. They are declared with Command.on or Command.opts.
  • Arguments are any additional values present on the command line. They are defined per-command and can be documented via Command.args.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from I18n::ForCLI

#capture, #html_escape, #safe

Methods included from I18n::BaseMethods

#l, #t, #t_direct, #this_field_value

Methods included from ExecutionResults

#abort_execution, #as_execution_result, #cli_usage_error, #continue_execution, #show_cli_usage, #stop_execution

Constructor Details

#initialize(global_options:, out:, err:, executor:) ⇒ App

Create the App. This is called by Brut::CLI::AppRunner.

Parameters:



145
146
147
148
149
150
151
152
153
# File 'lib/brut/cli/app.rb', line 145

def initialize(global_options:,out:,err:,executor:)
  @global_options = global_options
  @out            = out
  @err            = err
  @executor       = executor
  if self.class.default_env
    @global_options.set_default(:env,self.class.default_env)
  end
end

Class Method Details

.commandsObject

Returns a list of Command classes that each represent the subcommands your CLI app accepts. By default, this will look for all internal classes that extend Command and use them as your subcommands. This means that you don't need to override this method and can instead define classes inside your app subclass.



26
27
28
29
30
31
32
# File 'lib/brut/cli/app.rb', line 26

def self.commands
  self.constants.map { |name|
    self.const_get(name)
  }.select { |constant|
    constant.kind_of?(Class) && constant.ancestors.include?(Brut::CLI::Command) && constant.instance_methods.include?(:execute)
  }
end

.configure_only!Object

Call this if your app must operate before the Brut framework starts up.



134
135
136
# File 'lib/brut/cli/app.rb', line 134

def self.configure_only!
  @configure_only = true
end

.configure_only?Boolean

Returns:

  • (Boolean)


137
# File 'lib/brut/cli/app.rb', line 137

def self.configure_only? = !!@configure_only

.default_command(new_command_name = nil) ⇒ Object

Specify the default command to use when no subcommand is given.

Parameters:

  • new_command_name (String) (defaults to: nil)

    if present, sets the name of the command to run when none is given on the command line. When omitted, returns the currently configured name. The default is help.



66
67
68
69
70
71
72
# File 'lib/brut/cli/app.rb', line 66

def self.default_command(new_command_name=nil)
  if new_command_name.nil?
    return @default_command || "help"
  else
    @default_command = new_command_name.to_s
  end
end

.default_envObject

Returns the default project env, based on the logic described in requires_project_env



129
130
# File 'lib/brut/cli/app.rb', line 129

def self.default_env           = @default_env
# Returns true if this app requires a project env

.description(new_description = nil) ⇒ String

Call this to set the one-line description of your command line app.

Parameters:

  • new_description (String) (defaults to: nil)

    When present, sets the description of this app. When omitted, returns the current description.

Returns:

  • (String)

    the current description (if called with no parameters)



38
39
40
41
42
43
44
# File 'lib/brut/cli/app.rb', line 38

def self.description(new_description=nil)
  if new_description.nil?
    return @description.to_s
  else
    @description = new_description
  end
end

.env_var(var_name, purpose:) ⇒ Object

Call this for each environment variable your app responds to. These would be variables that affect any of the subcommands. For command-specific environment variables, see Command.env_var.

Parameters:

  • var_name (String)

    Declares that this app recognizes this environment variable.

  • purpose (String)

    An explanation for how this environment variable affects the app. Used in documentation.



51
52
53
# File 'lib/brut/cli/app.rb', line 51

def self.env_var(var_name,purpose:)
  env_vars[var_name] = purpose
end

.option_parserOptionParser

Returns the configured OptionParser used to parse global portion of the command line. If you don't want to call opts, you can create and return a fully-formed OptionParser by overriding this method. By default, it will create one with a conventional banner.

Returns:

  • (OptionParser)


97
98
99
100
101
# File 'lib/brut/cli/app.rb', line 97

def self.option_parser
  @option_parser ||= OptionParser.new do |opts|
    opts.banner = "%{app} %{global_options} commands [command options] [args]"
  end
end

.optsOptionParser

Provides access to an OptionParser you can use to declare flags and switches that should be accepted globally. The way to use this is to call .on and provide a description for an option as you would to OptionParser. The only difference is that you should not pass a block to this. When the command line is parsed, the results will be placed into a Options instance made available to your command.

Examples:


class MyApp < Brut::CLI::App

  opts.on("--dry-run","Don't change anything, just pretend")

end

Returns:

  • (OptionParser)


88
89
90
# File 'lib/brut/cli/app.rb', line 88

def self.opts
  self.option_parser
end

.requires_project_env(default: "development") ⇒ Object

Call this if your CLI requires a project environment as context for what it does. For example, a command to analyze the database needs to know if it should operate on development, test, or production. When called, this will do a few things:

  • Your app will recognize --env=ENVIRONMENT as a global option
  • Your app will recognize the RACK_ENV environment variable.

When your app executes, the project environment will be determined as follows:

  1. If --env was on the command line, that is the environment used
  2. If RACK_ENV is set in the environment, that is used
  3. Otherwise the value given to the default: parameter of this method is used.

Parameters:

  • default (String) (defaults to: "development")

    name of the environment to use if none was specified. nil should be used to require the environment to be specified explicitly.



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/brut/cli/app.rb', line 116

def self.requires_project_env(default: "development")
  default_message = if default.nil?
                      ""
                    else
                      " (default '#{default}')"
                    end
  opts.on("--env=ENVIRONMENT","Project environment#{default_message}")
  @default_env = ENV["RACK_ENV"] || default
  @requires_project_env = true
  self.env_var("RACK_ENV",purpose: "default project environment when --env is omitted")
end

.requires_project_env?Boolean

Returns true if this app requires a project env

Returns:

  • (Boolean)


131
# File 'lib/brut/cli/app.rb', line 131

def self.requires_project_env? = @requires_project_env

Instance Method Details

#after_bootstrap(app:) ⇒ Object

Called after all setup has been executed. Brut will have been started/loaded. This will not be called if anything caused execution to be aborted.

Parameters:



184
185
# File 'lib/brut/cli/app.rb', line 184

def after_bootstrap(app:)
end

#before_executeObject

Called before anything else happens. You can override this to perform any setup or other checking before Brut is started up.



177
178
# File 'lib/brut/cli/app.rb', line 177

def before_execute
end

#execute!(command, project_root:) ⇒ Object

Executes the command. Called by Brut::CLI::AppRunner.

Parameters:

  • command (Brut::CLI::Command)

    the command to run, based on what was on the command line

  • project_root (Pathname)

    root of the Brut app's project files.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/brut/cli/app.rb', line 191

def execute!(command,project_root:)
  before_execute
  set_env_if_needed
  command.set_env_if_needed
  load_env(project_root:)
  command.before_execute
  app = nil
  bootstrap_result = begin
                       require "#{project_root}/app/bootstrap"
                       bootstrap = Bootstrap.new
                       if self.class.configure_only?
                         bootstrap.configure_only!
                       else
                         bootstrap.bootstrap!
                       end
                       app = bootstrap.app
                       continue_execution
                     rescue => ex
                       as_execution_result(command.handle_bootstrap_exception(ex))
                     end
  if bootstrap_result.stop?
    return bootstrap_result
  end
  after_bootstrap(app:)
  command.after_bootstrap(app:)
  if self.class.configure_only?
    as_execution_result(command.execute)
  else
    result = Brut.container.instrumentation.span("CLI #{$0}", prefix: "brut.cli", class: self.class.name) do |span|
      as_execution_result(command.execute)
    end
    result
  end
rescue Brut::CLI::Error => ex
  abort_execution(ex.message)
end