Class: Toys::Utils::StandardUI

Inherits:
Object
  • Object
show all
Defined in:
lib/toys/utils/standard_ui.rb

Overview

An object that implements standard UI elements, such as error reports and logging, as provided by the toys command line. Specifically, it implements pretty formatting of log entries and stack traces, and renders using ANSI coloring where available via Terminal.

This object can be used to implement toys-style behavior when creating a CLI object. For example:

require "toys/utils/standard_ui"
ui = Toys::Utils::StandardUI.new
cli = Toys::CLI.new(**ui.cli_args)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output: nil) ⇒ StandardUI

Create a Standard UI.

By default, all output is written to $stderr, and will share a single Terminal object, allowing multiple tools and/or threads to interleave messages without interrupting one another.

Parameters:

  • output (IO, Toys::Utils::Terminal) (defaults to: nil)

    Where to write output. You can pass a terminal object, or an IO stream that will be wrapped in a terminal output. Default is $stderr.



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/toys/utils/standard_ui.rb', line 30

def initialize(output: nil)
  require "logger"
  require "toys/utils/terminal"
  @terminal = output || $stderr
  @terminal = Terminal.new(output: @terminal) unless @terminal.is_a?(Terminal)
  @log_header_severity_styles = {
    "FATAL" => [:bright_magenta, :bold, :underline],
    "ERROR" => [:bright_red, :bold],
    "WARN" => [:bright_yellow],
    "INFO" => [:bright_cyan],
    "DEBUG" => [:white],
  }
end

Instance Attribute Details

#log_header_severity_stylesHash{String => Array<Symbol>} (readonly)

A hash that maps severities to styles recognized by Terminal. Used to style the header for each log entry. This hash can be modified in place to adjust the behavior of loggers created by this UI.

Returns:

  • (Hash{String => Array<Symbol>})


59
60
61
# File 'lib/toys/utils/standard_ui.rb', line 59

def log_header_severity_styles
  @log_header_severity_styles
end

#terminalToys::Utils::Terminal (readonly)

The terminal underlying this UI



49
50
51
# File 'lib/toys/utils/standard_ui.rb', line 49

def terminal
  @terminal
end

Instance Method Details

#cli_argsHash

Convenience method that returns a hash of arguments that can be passed to the CLI constructor. Includes the :error_handler and :logger_factory arguments.

Returns:

  • (Hash)


68
69
70
71
72
73
# File 'lib/toys/utils/standard_ui.rb', line 68

def cli_args
  {
    error_handler: error_handler,
    logger_factory: logger_factory,
  }
end

#display_error_notice(error) ⇒ Object

Displays a default output for an error. Displays the error, the backtrace, and contextual information regarding what tool was run and where in its code the error occurred.

This method is used by #error_handler_impl and can be overridden to change its behavior.

Parameters:



197
198
199
200
201
# File 'lib/toys/utils/standard_ui.rb', line 197

def display_error_notice(error)
  @terminal.puts
  @terminal.puts(cause_string(error.cause))
  @terminal.puts(context_string(error), :bold)
end

#display_signal_notice(error) ⇒ Object

Displays a default output for a signal received.

This method is used by #error_handler_impl and can be overridden to change its behavior.

Parameters:

  • error (SignalException)


178
179
180
181
182
183
184
185
# File 'lib/toys/utils/standard_ui.rb', line 178

def display_signal_notice(error)
  @terminal.puts
  if error.is_a?(::Interrupt)
    @terminal.puts("INTERRUPTED", :bold)
  else
    @terminal.puts("SIGNAL RECEIVED: #{error.signm || error.signo}", :bold)
  end
end

#error_handlerProc

Returns an error handler conforming to the :error_handler argument to the CLI constructor. Specifically, it returns the #error_handler_impl method as a proc.

Returns:

  • (Proc)


82
83
84
# File 'lib/toys/utils/standard_ui.rb', line 82

def error_handler
  @error_handler ||= method(:error_handler_impl).to_proc
end

#error_handler_impl(error) ⇒ Integer

Implementation of the error handler. As dictated by the error handler specification in CLI, this must take a ContextualError as an argument, and return an exit code.

The base implementation uses #display_error_notice and #display_signal_notice to print an appropriate message to the UI's terminal, and uses #exit_code_for to determine the correct exit code. Any of those methods can be overridden by a subclass to alter their behavior, or this main implementation method can be overridden to change the overall behavior.

Parameters:

Returns:

  • (Integer)

    The exit code



112
113
114
115
116
117
118
119
120
# File 'lib/toys/utils/standard_ui.rb', line 112

def error_handler_impl(error)
  cause = error.cause
  if cause.is_a?(::SignalException)
    display_signal_notice(cause)
  else
    display_error_notice(error)
  end
  exit_code_for(cause)
end

#exit_code_for(error) ⇒ Integer

Returns an exit code appropriate for the given exception. Currently, the logic interprets signals (returning the convention of 128 + signo), usage errors (returning the conventional value of 2), and tool not runnable errors (returning the conventional value of 126), and defaults to 1 for all other error types.

This method is used by #error_handler_impl and can be overridden to change its behavior.

Parameters:

  • error (Exception)

    The exception raised. This method expects the original exception, rather than a ContextualError.

Returns:

  • (Integer)

    The appropriate exit code



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/toys/utils/standard_ui.rb', line 157

def exit_code_for(error)
  case error
  when ArgParsingError
    2
  when NotRunnableError
    126
  when ::SignalException
    error.signo + 128
  else
    1
  end
end

#logger_factoryProc

Returns a logger factory conforming to the :logger_factory argument to the CLI constructor. Specifically, it returns the #logger_factory_impl method as a proc.

Returns:

  • (Proc)


93
94
95
# File 'lib/toys/utils/standard_ui.rb', line 93

def logger_factory
  @logger_factory ||= method(:logger_factory_impl).to_proc
end

#logger_factory_impl(_tool) ⇒ Logger

Implementation of the logger factory. As dictated by the logger factory specification in CLI, this must take a ToolDefinition as an argument, and return a Logger.

The base implementation returns a logger that writes to the UI's terminal, using #logger_formatter_impl as the formatter. It sets the level to Logger::WARN by default. Either this method or the helper methods can be overridden to change this behavior.

Parameters:

Returns:

  • (Logger)


136
137
138
139
140
141
# File 'lib/toys/utils/standard_ui.rb', line 136

def logger_factory_impl(_tool)
  logger = ::Logger.new(@terminal)
  logger.formatter = method(:logger_formatter_impl).to_proc
  logger.level = ::Logger::WARN
  logger
end

#logger_formatter_impl(severity, time, _progname, msg) ⇒ String

Implementation of the formatter used by loggers created by this UI's logger factory. This interface is defined by the standard Logger class.

This method can be overridden to change the behavior of loggers created by this UI.

Parameters:

  • severity (String)
  • time (Time)
  • _progname (String)
  • msg (Object)

Returns:

  • (String)


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/toys/utils/standard_ui.rb', line 217

def logger_formatter_impl(severity, time, _progname, msg)
  msg_str =
    case msg
    when ::String
      msg
    when ::Exception
      "#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).join("\n")
    else
      msg.inspect
    end
  timestr = time.strftime("%Y-%m-%d %H:%M:%S")
  header = format("[%<time>s %<sev>5s]", time: timestr, sev: severity)
  styles = log_header_severity_styles[severity]
  header = @terminal.apply_styles(header, *styles) if styles
  "#{header}  #{msg_str}\n"
end