Class: Toys::Utils::Terminal
- Inherits:
-
Object
- Object
- Toys::Utils::Terminal
- 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
"\e[0m"
- BUILTIN_STYLE_NAMES =
Standard ANSI style codes by name.
{ 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.
0.1
- DEFAULT_SPINNER_FRAMES =
Default set of spinner frames.
["-", "\\", "|", "/"].freeze
Instance Attribute Summary collapse
-
#input ⇒ IO?
readonly
Input stream.
-
#output ⇒ IO, ...
readonly
Output stream or logger.
-
#styled ⇒ Boolean
readonly
Whether output is styled.
Class Method Summary collapse
-
.remove_style_escapes(str) ⇒ String
Returns a copy of the given string with all ANSI style codes removed.
Instance Method Summary collapse
-
#<<(str) ⇒ self
Write a line, appending a newline if one is not already present.
-
#apply_styles(str, *styles) ⇒ String
Apply the given styles to the given string, returning the styled string.
-
#ask(prompt, *styles, default: nil, trailing_text: :default) ⇒ String
Ask a question and get a response.
-
#close ⇒ Object
This method is defined so that
::Logger
will recognize a terminal as a log device target, but it does not actually close anything. -
#confirm(prompt = "Proceed? ", *styles, default: nil) ⇒ Boolean
Confirm with the user.
-
#define_style(name, *styles) ⇒ self
Define a named style.
-
#height ⇒ Integer
Return the terminal height.
-
#initialize(input: $stdin, output: $stdout, styled: nil) ⇒ Terminal
constructor
Create a terminal.
-
#newline ⇒ self
Write a newline and flush the current line.
-
#puts(str = "", *styles) ⇒ self
(also: #say)
Write a line, appending a newline if one is not already present.
-
#readline ⇒ String?
Read a line, blocking until one is available.
-
#size ⇒ Array(Integer,Integer)
Return the terminal size as an array of width, height.
-
#spinner(leading_text: "", final_text: "", frame_length: nil, frames: nil, style: nil) ⇒ Object
Display a spinner during a task.
-
#width ⇒ Integer
Return the terminal width.
-
#write(str = "", *styles) ⇒ self
Write a partial line without appending a newline.
Constructor Details
#initialize(input: $stdin, output: $stdout, styled: nil) ⇒ Terminal
Create a terminal.
118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/toys/utils/terminal.rb', line 118 def initialize(input: $stdin, output: $stdout, styled: nil) @input = input @output = output @styled = if styled.nil? output.respond_to?(:tty?) && output.tty? && !::ENV["NO_COLOR"] else styled ? true : false end @named_styles = BUILTIN_STYLE_NAMES.dup @output_mutex = ::Monitor.new @input_mutex = ::Monitor.new end |
Instance Attribute Details
#input ⇒ IO? (readonly)
Input stream
142 143 144 |
# File 'lib/toys/utils/terminal.rb', line 142 def input @input end |
#output ⇒ IO, ... (readonly)
Output stream or logger
136 137 138 |
# File 'lib/toys/utils/terminal.rb', line 136 def output @output end |
#styled ⇒ Boolean (readonly)
Whether output is styled
148 149 150 |
# File 'lib/toys/utils/terminal.rb', line 148 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.
106 107 108 |
# File 'lib/toys/utils/terminal.rb', line 106 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.
214 215 216 |
# File 'lib/toys/utils/terminal.rb', line 214 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.
377 378 379 380 381 382 383 384 385 |
# File 'lib/toys/utils/terminal.rb', line 377 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.
238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/toys/utils/terminal.rb', line 238 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 = readline.to_s.chomp resp.empty? ? default.to_s : resp end |
#close ⇒ Object
This method is defined so that ::Logger
will recognize a terminal as
a log device target, but it does not actually close anything.
190 191 192 |
# File 'lib/toys/utils/terminal.rb', line 190 def close nil end |
#confirm(prompt = "Proceed? ", *styles, default: nil) ⇒ Boolean
Confirm with the user.
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/toys/utils/terminal.rb', line 261 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.
362 363 364 365 |
# File 'lib/toys/utils/terminal.rb', line 362 def define_style(name, *styles) @named_styles[name] = resolve_styles(*styles) self end |
#height ⇒ Integer
Return the terminal height
347 348 349 |
# File 'lib/toys/utils/terminal.rb', line 347 def height size[1] end |
#newline ⇒ self
Write a newline and flush the current line.
222 223 224 |
# File 'lib/toys/utils/terminal.rb', line 222 def newline puts end |
#puts(str = "", *styles) ⇒ self Also known as: say
Write a line, appending a newline if one is not already present.
202 203 204 205 |
# File 'lib/toys/utils/terminal.rb', line 202 def puts(str = "", *styles) str = "#{str}\n" unless str.end_with?("\n") write(str, *styles) end |
#readline ⇒ String?
Read a line, blocking until one is available.
176 177 178 179 180 181 182 183 184 |
# File 'lib/toys/utils/terminal.rb', line 176 def readline @input_mutex.synchronize do begin input&.gets rescue ::IOError nil end end end |
#size ⇒ Array(Integer,Integer)
Return the terminal size as an array of width, height.
325 326 327 328 329 330 331 |
# File 'lib/toys/utils/terminal.rb', line 325 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.
305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/toys/utils/terminal.rb', line 305 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 write(leading_text) unless leading_text.empty? spin = SpinDriver.new(self, frames, Array(style), frame_length) begin yield ensure spin.stop write(final_text) unless final_text.empty? end end |
#width ⇒ Integer
Return the terminal width
338 339 340 |
# File 'lib/toys/utils/terminal.rb', line 338 def width size[0] end |
#write(str = "", *styles) ⇒ self
Write a partial line without appending a newline.
158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/toys/utils/terminal.rb', line 158 def write(str = "", *styles) @output_mutex.synchronize do begin output&.write(apply_styles(str, *styles)) output&.flush rescue ::IOError nil end end self end |