Class: Toys::Utils::Terminal

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

Overview

A simple terminal class.

Styles

This class supports ANSI styled output where supported.

Styles may be specified in any of the following forms:

  • A symbol indicating the name of a well-known style, or the name of a defined style.
  • An rgb string in hex "rgb" or "rrggbb" form.
  • An ANSI code string in \e[XXm form.
  • An array of ANSI codes as integers.

Defined Under Namespace

Classes: TerminalError

Constant Summary collapse

CLEAR_CODE =

ANSI style code to clear styles

Returns:

  • (String)
"\e[0m"
BUILTIN_STYLE_NAMES =

Standard ANSI style codes by name.

Returns:

  • (Hash{Symbol => Array<Integer>})
{
  clear: [0],
  reset: [0],
  bold: [1],
  faint: [2],
  italic: [3],
  underline: [4],
  blink: [5],
  reverse: [7],
  black: [30],
  red: [31],
  green: [32],
  yellow: [33],
  blue: [34],
  magenta: [35],
  cyan: [36],
  white: [37],
  on_black: [30],
  on_red: [31],
  on_green: [32],
  on_yellow: [33],
  on_blue: [34],
  on_magenta: [35],
  on_cyan: [36],
  on_white: [37],
  bright_black: [90],
  bright_red: [91],
  bright_green: [92],
  bright_yellow: [93],
  bright_blue: [94],
  bright_magenta: [95],
  bright_cyan: [96],
  bright_white: [97],
  on_bright_black: [100],
  on_bright_red: [101],
  on_bright_green: [102],
  on_bright_yellow: [103],
  on_bright_blue: [104],
  on_bright_magenta: [105],
  on_bright_cyan: [106],
  on_bright_white: [107],
}.freeze
DEFAULT_SPINNER_FRAME_LENGTH =

Default length of a single spinner frame, in seconds.

Returns:

  • (Float)
0.1
DEFAULT_SPINNER_FRAMES =

Default set of spinner frames.

Returns:

  • (Array<String>)
["-", "\\", "|", "/"].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input: $stdin, output: $stdout, styled: nil) ⇒ Terminal

Create a terminal.

Parameters:

  • input (IO, nil) (defaults to: $stdin)

    Input stream.

  • output (IO, Logger, nil) (defaults to: $stdout)

    Output stream or logger.

  • styled (Boolean, nil) (defaults to: nil)

    Whether to output ansi styles. If nil, the setting is inferred from whether the output has a tty.



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/toys/utils/terminal.rb', line 139

def initialize(input: $stdin, output: $stdout, styled: nil)
  @input = input
  @output = output
  @styled =
    if styled.nil?
      output.respond_to?(:tty?) && output.tty?
    else
      styled ? true : false
    end
  @named_styles = BUILTIN_STYLE_NAMES.dup
end

Instance Attribute Details

#inputIO? (readonly)

Input stream

Returns:

  • (IO, nil)


161
162
163
# File 'lib/toys/utils/terminal.rb', line 161

def input
  @input
end

#outputIO, ... (readonly)

Output stream or logger

Returns:

  • (IO, Logger, nil)


155
156
157
# File 'lib/toys/utils/terminal.rb', line 155

def output
  @output
end

#styledBoolean

Whether output is styled

Returns:

  • (Boolean)


167
168
169
# File 'lib/toys/utils/terminal.rb', line 167

def styled
  @styled
end

Class Method Details

.remove_style_escapes(str) ⇒ String

Returns a copy of the given string with all ANSI style codes removed.

Parameters:

  • str (String)

    Input string

Returns:

  • (String)

    String with styles removed



127
128
129
# File 'lib/toys/utils/terminal.rb', line 127

def self.remove_style_escapes(str)
  str.gsub(/\e\[\d+(;\d+)*m/, "")
end

Instance Method Details

#<<(str) ⇒ self

Write a line, appending a newline if one is not already present.

Parameters:

  • str (String)

    The line to write

Returns:

  • (self)


203
204
205
# File 'lib/toys/utils/terminal.rb', line 203

def <<(str)
  puts(str)
end

#apply_styles(str, *styles) ⇒ String

Apply the given styles to the given string, returning the styled string. Honors the styled setting; if styling is disabled, does not add any ANSI style codes and in fact removes any existing codes. If styles were added, ensures that the string ends with a clear code.

Parameters:

  • str (String)

    String to style

  • styles (Symbol, String, Array<Integer>...)

    Styles to apply

Returns:

  • (String)

    The styled string



366
367
368
369
370
371
372
373
374
# File 'lib/toys/utils/terminal.rb', line 366

def apply_styles(str, *styles)
  if styled
    prefix = escape_styles(*styles)
    suffix = prefix.empty? || str.end_with?(CLEAR_CODE) ? "" : CLEAR_CODE
    "#{prefix}#{str}#{suffix}"
  else
    Terminal.remove_style_escapes(str)
  end
end

#ask(prompt, *styles, default: nil, trailing_text: :default) ⇒ String

Ask a question and get a response.

Parameters:

  • prompt (String)

    Required prompt string.

  • styles (Symbol, String, Array<Integer>...)

    Styles to apply to the prompt.

  • default (String, nil) (defaults to: nil)

    Default value, or nil for no default. Uses nil if not specified.

  • trailing_text (:default, String, nil) (defaults to: :default)

    Trailing text appended to the prompt, nil for none, or :default to show the default.

Returns:

  • (String)


227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/toys/utils/terminal.rb', line 227

def ask(prompt, *styles, default: nil, trailing_text: :default)
  if trailing_text == :default
    trailing_text = default.nil? ? nil : "[#{default}]"
  end
  if trailing_text
    ptext, pspaces, = prompt.partition(/\s+$/)
    prompt = "#{ptext} #{trailing_text}#{pspaces}"
  end
  write(prompt, *styles)
  resp = input&.gets.to_s.chomp
  resp.empty? ? default.to_s : resp
end

#confirm(prompt = "Proceed? ", *styles, default: nil) ⇒ Boolean

Confirm with the user.

Parameters:

  • prompt (String) (defaults to: "Proceed? ")

    Prompt string. Defaults to "Proceed?".

  • styles (Symbol, String, Array<Integer>...)

    Styles to apply to the prompt.

  • default (Boolean, nil) (defaults to: nil)

    Default value, or nil for no default. Uses nil if not specified.

Returns:

  • (Boolean)


250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/toys/utils/terminal.rb', line 250

def confirm(prompt = "Proceed? ", *styles, default: nil)
  default_val, trailing_text =
    case default
    when true
      ["y", "(Y/n)"]
    when false
      ["n", "(y/N)"]
    else
      [nil, "(y/n)"]
    end
  resp = ask(prompt, *styles, default: default_val, trailing_text: trailing_text)
  return true if resp =~ /^y/i
  return false if resp =~ /^n/i
  if resp.nil? && default.nil?
    raise TerminalError, "Cannot confirm because the input stream is at eof."
  end
  if !resp.strip.empty? || default.nil?
    if input.nil?
      raise TerminalError, "Cannot confirm because there is no input stream."
    end
    confirm('Please answer "y" or "n"', default: default)
  else
    default
  end
end

#define_style(name, *styles) ⇒ self

Define a named style.

Style names must be symbols. The definition of a style may include any valid style specification, including the symbol names of existing defined styles.

Parameters:

  • name (Symbol)

    The name for the style

  • styles (Symbol, String, Array<Integer>...)

Returns:

  • (self)


351
352
353
354
# File 'lib/toys/utils/terminal.rb', line 351

def define_style(name, *styles)
  @named_styles[name] = resolve_styles(*styles)
  self
end

#heightInteger

Return the terminal height

Returns:

  • (Integer)


336
337
338
# File 'lib/toys/utils/terminal.rb', line 336

def height
  size[1]
end

#newlineself

Write a newline and flush the current line.

Returns:

  • (self)


211
212
213
# File 'lib/toys/utils/terminal.rb', line 211

def newline
  puts
end

#puts(str = "", *styles) ⇒ self Also known as: say

Write a line, appending a newline if one is not already present.

Parameters:

  • str (String) (defaults to: "")

    The line to write

  • styles (Symbol, String, Array<Integer>...)

    Styles to apply to the entire line.

Returns:

  • (self)


191
192
193
194
# File 'lib/toys/utils/terminal.rb', line 191

def puts(str = "", *styles)
  str = "#{str}\n" unless str.end_with?("\n")
  write(str, *styles)
end

#sizeArray(Integer,Integer)

Return the terminal size as an array of width, height.

Returns:

  • (Array(Integer,Integer))


314
315
316
317
318
319
320
# File 'lib/toys/utils/terminal.rb', line 314

def size
  if @output.respond_to?(:tty?) && @output.tty? && @output.respond_to?(:winsize)
    @output.winsize.reverse
  else
    [80, 25]
  end
end

#spinner(leading_text: "", final_text: "", frame_length: nil, frames: nil, style: nil) ⇒ Object

Display a spinner during a task. You should provide a block that performs the long-running task. While the block is executing, a spinner will be displayed.

Parameters:

  • leading_text (String) (defaults to: "")

    Optional leading string to display to the left of the spinner. Default is the empty string.

  • frame_length (Float) (defaults to: nil)

    Length of a single frame, in seconds. Defaults to DEFAULT_SPINNER_FRAME_LENGTH.

  • frames (Array<String>) (defaults to: nil)

    An array of frames. Defaults to DEFAULT_SPINNER_FRAMES.

  • style (Symbol, Array<Symbol>) (defaults to: nil)

    A terminal style or array of styles to apply to all frames in the spinner. Defaults to empty,

  • final_text (String) (defaults to: "")

    Optional final string to display when the spinner is complete. Default is the empty string. A common practice is to set this to newline.

Returns:

  • (Object)

    The return value of the block.



294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/toys/utils/terminal.rb', line 294

def spinner(leading_text: "", final_text: "",
            frame_length: nil, frames: nil, style: nil)
  return nil unless block_given?
  frame_length ||= DEFAULT_SPINNER_FRAME_LENGTH
  frames ||= DEFAULT_SPINNER_FRAMES
  output.write(leading_text) unless leading_text.empty?
  spin = SpinDriver.new(self, frames, Array(style), frame_length)
  begin
    yield
  ensure
    spin.stop
    output.write(final_text) unless final_text.empty?
  end
end

#widthInteger

Return the terminal width

Returns:

  • (Integer)


327
328
329
# File 'lib/toys/utils/terminal.rb', line 327

def width
  size[0]
end

#write(str = "", *styles) ⇒ self

Write a partial line without appending a newline.

Parameters:

  • str (String) (defaults to: "")

    The line to write

  • styles (Symbol, String, Array<Integer>...)

    Styles to apply to the partial line.

Returns:

  • (self)


177
178
179
180
181
# File 'lib/toys/utils/terminal.rb', line 177

def write(str = "", *styles)
  output&.write(apply_styles(str, *styles))
  output&.flush
  self
end