Module: Console

Defined in:
lib/color_console/console.rb,
lib/color_console/table.rb,
lib/color_console/progress.rb,
lib/color_console/log4r_logger.rb,
lib/color_console/platform/ansi.rb,
lib/color_console/java_util_logger.rb,
lib/color_console/platform/windows.rb,
lib/color_console/platform/windows_ffi.rb,
lib/color_console/platform/windows_fiddle.rb

Overview

A module for using our color console for log output with the java.util.logging log framework.

Defined Under Namespace

Modules: JavaUtilLogger, Log4rLogger, Windows

Constant Summary collapse

FOREGROUND_COLORS =

Constants for foreground and background colors

{
    black: 0,
    blue: BLUE | INTENSITY,
    dark_blue: BLUE,
    light_blue: BLUE | INTENSITY,
    cyan: BLUE | GREEN | INTENSITY,
    green: GREEN,
    dark_green: GREEN,
    light_green: GREEN | INTENSITY,
    red: RED | INTENSITY,
    dark_red: RED,
    light_red: RED | INTENSITY,
    magenta: RED | BLUE,
    dark_magenta: RED | BLUE,
    light_magenta: RED | BLUE | INTENSITY,
    yellow: GREEN | RED | INTENSITY,
    gray: BLUE | GREEN | RED,
    dark_gray: INTENSITY,
    light_gray: BLUE | GREEN | RED,
    white: BLUE | GREEN | RED | INTENSITY
}
BACKGROUND_COLORS =
{}
BLUE =

Constants for colour components

0x1
GREEN =
0x2
RED =
0x4
INTENSITY =
0x8

Class Method Summary collapse

Class Method Details

._calculate_widths(rows, col_count, avail_width) ⇒ Object

Calculates the widths to use to display a table of data.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/color_console/table.rb', line 73

def _calculate_widths(rows, col_count, avail_width)
    max_widths = Array.new(col_count)
    rows.each do |row|
        row.each_with_index do |col, i|
            max_widths[i] = col.to_s.length if !max_widths[i] || col.to_s.length > max_widths[i]
        end
    end
    max_width = max_widths.reduce(0, &:+)
    while avail_width < max_width
        # Reduce longest width(s) to next longest
        longest = max_widths.max
        num_longest = max_widths.count{ |w| w == longest }
        next_longest = max_widths.select{ |w| w < longest }.max
        if !next_longest || (num_longest * (longest - next_longest) > max_width - avail_width)
            reduction = (max_width - avail_width) / num_longest
            reduction = 1 if reduction <= 0
            if !next_longest
                max_widths.map!{ |w| w - reduction }
            else
                max_widths.map!{ |w| w > next_longest ? w - reduction : w }
            end
        else
            max_widths.map!{ |w| w > next_longest ? next_longest : w }
        end
        max_width = max_widths.reduce(0, &:+)
    end
    max_widths
end

._clear_line(lines = 1) ⇒ Object

Clears the current line

Raises:

  • (ArgumentError)


129
130
131
132
133
134
135
136
# File 'lib/color_console/platform/ansi.rb', line 129

def _clear_line(lines = 1)
    raise ArgumentError, "Number of lines to clear (#{lines}) must be > 0" if lines < 1
    while lines > 0
        STDOUT.write "\r\e[2K"
        lines -= 1
        STDOUT.write "\e[A" if lines > 0
    end
end

._display_row(row, widths, opts = {}) ⇒ Object

Displays a single row of data within columns of widths width. If the contents of a cell exceeds the available width, it is wrapped, and the row is displayed over multiple lines.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/color_console/table.rb', line 107

def _display_row(row, widths, opts = {})
    fg = opts.fetch(:text_color, opts.fetch(:color, :cyan))
    bg = opts[:background_color]
    indent = opts.fetch(:indent, 0)
    col_sep = opts[:col_sep]
    row_sep = opts[:row_sep]

    line_count = 0
    lines = row.each_with_index.map do |col, i|
        cell_lines = wrap_text(col, widths[i])
        line_count = cell_lines.size if cell_lines.size > line_count
        cell_lines
    end
    (0...line_count).each do |i|
        _write(' ' * indent)
        _write("#{col_sep} ", fg, bg) if col_sep
        line = (0...widths.size).map do |col|
            "%#{row[col].is_a?(Numeric) ? '' : '-'}#{widths[col]}s" %
            (lines[col] && lines[col][i])
        end.join(" #{col_sep} ")
        _write(line, fg, bg)
        _write(" #{col_sep}", fg, bg) if col_sep
        _puts
    end
    _output_row_sep(widths, opts) if row_sep
end

._output_row_sep(widths, opts) ⇒ Object

Outputs a row separator line



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/color_console/table.rb', line 137

def _output_row_sep(widths, opts)
    fg = opts.fetch(:text_color, opts.fetch(:color, :cyan))
    bg = opts[:background_color]
    indent = opts.fetch(:indent, 0)
    col_sep = opts[:col_sep]
    row_sep = opts[:row_sep]
    corner = opts.fetch(:corner, col_sep ? '+' * col_sep.length : '')

    sep_row = widths.map{ |width| row_sep * (width + 2) }
    _write(' ' * indent)
    _write(corner, fg, bg)
    _write(sep_row.join(corner), fg, bg)
    _write(corner, fg, bg)
    _puts
end

._puts(text = nil, fg = nil, bg = nil) ⇒ Object

Send a line of text to the screen, terminating with a new-line.

Parameters:

  • text (String) (defaults to: nil)

    The optional text to be written to the console.

  • fg (Symbol, Integer) (defaults to: nil)

    An optional foreground colour name or value.

  • bg (Symbol, Integer) (defaults to: nil)

    An optional background color name or value.



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/color_console/platform/ansi.rb', line 111

def _puts(text = nil, fg = nil, bg = nil)
    if @status_displayed
        _clear_line((@status.length / self.width) + 1)
        @status_displayed = false
    end
    _write("#{text}", fg, bg)
    STDOUT.write "\n"
    if @status
        _write @status, @status_fg, @status_bg
        @status_displayed = true
    end
end

._window_sizeArray?

Get the current console window size.

Returns:

  • (Array, nil)

    Returns a two-dimensional array of [cols, rows], or nil if the console has been redirected.



62
63
64
65
66
67
68
69
# File 'lib/color_console/platform/ansi.rb', line 62

def _window_size
    unless @window_size
        rows = `tput lines`
        cols = `tput cols`
        @window_size = [cols.chomp.to_i, rows.chomp.to_i]
    end
    @window_size
end

._write(text, fg = nil, bg = nil) ⇒ Object

Write a line of text to the console, with optional foreground and background colors.

Parameters:

  • text (String)

    The text to be written to the console.

  • fg (Symbol, Integer) (defaults to: nil)

    An optional foreground colour name or value.

  • bg (Symbol, Integer) (defaults to: nil)

    An optional background color name or value.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/color_console/platform/ansi.rb', line 79

def _write(text, fg = nil, bg = nil)
    if @status_displayed
        _clear_line((@status.length / self.width) + 1)
        @status_displayed = false
    end
    if fg || bg
        reset = true
        if fg
            fg_code = FOREGROUND_COLORS[fg] || fg
            STDOUT.write "\e[#{fg_code}m"
        end

        if bg
            bg_code = BACKGROUND_COLORS[bg] || bg
            STDOUT.write "\e[#{bg_code}m"
        end
    end

    STDOUT.write text

    if reset
        STDOUT.write "\e[0m"
    end
end

.clear_progressObject

Clears any currently displayed progress bar or status message.



66
67
68
# File 'lib/color_console/progress.rb', line 66

def clear_progress
    self.status nil
end

.clear_statusObject

Clears any currently displayed progress bar or status message.



69
70
71
# File 'lib/color_console/progress.rb', line 69

def clear_progress
    self.status nil
end

.display_row(row, widths, opts = {}) ⇒ Object

Displays a single row of data within columns of widths width. If the contents of a cell exceeds the available width, it is wrapped, and the row is displayed over multiple lines.

Parameters:

  • row (Array)

    An array of data to display as a single row.

  • widths (Array<Fixnum>)

    An array of column widths to use for each column.

  • opts (Hash) (defaults to: {})

    An options hash containing settings that determine how the table is rendered.

See Also:

  • for details of the supported options.


60
61
62
63
64
65
# File 'lib/color_console/table.rb', line 60

def display_row(row, widths, opts = {})
    return unless row && row.size > 0
    @lock.synchronize do
        _display_row(row, widths, opts)
    end
end

.display_table(rows, opts = {}) ⇒ Object

Displays an array of arrays as a table of data. The content of each row is aligned and wrapped if necessary to fit the column widths.

Parameters:

  • rows (Array)

    An array of arrays containing the data to be output. The number of elements in the first array determines the number of columns that will be output, unless the :column_widths options is passed; in that case, the number of column width specifications determines the number of columns output.

  • opts (Hash) (defaults to: {})

    An options hash containing settings that determine how the table is rendered.

Options Hash (opts):

  • :width (Fixnum)

    The width of the table. If omitted, the width is determined instead by the :col_widths option.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/color_console/table.rb', line 30

def display_table(rows, opts = {})
    return unless rows && rows.size > 0 && rows.first.size > 0
    col_widths = opts[:col_widths]
    unless col_widths
        col_count = rows.first.size
        avail_width = (opts[:width] || ((width || 10000) - opts.fetch(:indent, 0))) -
            (" #{opts[:col_sep]} ".length * col_count)
        col_widths = _calculate_widths(rows, col_count, avail_width - 1)
    end

    @lock.synchronize do
        _output_row_sep(col_widths, opts) if opts[:row_sep]
        rows.each do |row|
            _display_row(row, col_widths, opts)
        end
    end
end

.heightObject

Returns the height of the console window

Returns:

  • the height in characters, or nil if no console is available.



40
41
42
43
# File 'lib/color_console/console.rb', line 40

def height
    sz = _window_size
    sz && sz.last
end

.puts(text = nil, fg = nil, bg = nil) ⇒ Object

Send a line of text to the screen, terminating with a new-line.

Parameters:

  • text (String) (defaults to: nil)

    The optional text to be written to the console.

  • fg (Symbol) (defaults to: nil)

    An optional foreground colour name or value.

  • bg (Symbol) (defaults to: nil)

    An optional background color name or value.

See Also:

  • #write


70
71
72
73
74
# File 'lib/color_console/console.rb', line 70

def puts(text = nil, fg = nil, bg = nil)
    @lock.synchronize do
        _puts(text, fg, bg)
    end
end

.replace_console_logger(options = {}) ⇒ Object

Removes any existing console handler, and adds a ColorConsoleHandler.

Parameters:

  • options (Hash) (defaults to: {})

    An options hash.

Options Hash (options):

  • logger (String)

    The name of the logger to replace the handler on. Defaults to an empty string, which returns the root logger.

  • level (Level, Symbol)

    The level to set the logger to.

  • format (String)

    A format string for the layout of the log record.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/color_console/log4r_logger.rb', line 88

def replace_console_logger(options = {})
    logger = options[:logger]
    name = options.fetch(:outputter, 'color-console')
    level = case options.delete(:level)
    when String, Symbol then Log4r::LNAMES.index(options[:level].to_s.upcase)
    end
    STDOUT.puts level

    # Remove any existing console handler
    log = logger ? Log4r::Logger[logger] : Log4r::Logger.root
    log = Log4r::Logger.new(logger) unless log

    log.outputters.each do |o|
        log.remove(o.name) if h.is_a?(Log4r::StdoutOutputter)
    end

    # Add a ColorConsoleHandler
    out = Log4rLogger::ColorConsoleOutputter.new(name, options)
    log.add out

    # Set the log level
    log.level = level if level
end

.show_progress(label, complete, opts = {}) ⇒ Object

Displays a progress bar as the current status line. The status line is a partial line of text printed at the current scroll location, and which can be updated or cleared.

Parameters:

  • label (String)

    The label to be displayed after the progress bar.

  • complete (Fixnum)

    Number of completed steps.

  • opts (Fixnum, Hash) (defaults to: {})

    If a Fixnum is passed, this is the total number of steps. If a Hash is passed, it is an options hash with the following possible options.

Options Hash (opts):

  • :total (Fixnum)

    The total number of steps; default is 100.

See Also:

  • for other supported options


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/color_console/progress.rb', line 46

def show_progress(label, complete, opts = {})
    if self.width
        opts = {total: opts} if opts.is_a?(Fixnum)
        total = opts.fetch(:total, 100)
        complete = total if complete > total
        bar_length = opts.fetch(:bar_length, 40)
        completion = complete * bar_length / total
        pct = "#{complete * 100 / total}%"
        bar = "#{'=' * completion}#{' ' * (bar_length - completion)}"
        bar[(bar_length - pct.length) / 2, pct.length] = pct
        if @completed.nil? || pct != @completed
            self.status("[#{bar}]  #{label}", opts)
            @completed = pct
        end
    end
end

.status(msg, opts = {}) ⇒ Object

Sets text to be displayed temporarily on the current line. Status text can be updated or cleared.

Parameters:

  • status (String)

    The text to be displayed as the current status. Pass nil to clear the status display.

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :text_color (Symbol)

    The text color to use when displaying the status message.

  • :background_color (Symbol)

    The background color to use when rendering the status message



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/color_console/progress.rb', line 13

def status(msg, opts = {})
    if self.width
        @lock.synchronize do
            if @status_displayed
                # Clear existing status
                _clear_line((@status.length / self.width) + 1)
                @status_displayed = false
            end
            @completed = nil
            @status = msg
            if @status
                @status_fg = opts.fetch(:text_color, opts.fetch(:color, :cyan))
                @status_bg = opts[:background_color]
                _write @status, @status_fg, @status_bg
                @status_displayed = true
            end
        end
    end
end

.title=(text) ⇒ Object

Sets the title bar text of the console window.



49
50
51
# File 'lib/color_console/platform/ansi.rb', line 49

def title=(text)
    STDOUT.write "\e]0;#{text}\007"
end

.widthObject

Returns the width of the console window

Returns:

  • the width in characters, or nil if no console is available.



30
31
32
33
# File 'lib/color_console/console.rb', line 30

def width
    sz = _window_size
    sz && sz.first
end

.wrap_text(text, width) ⇒ Array

Utility method for wrapping lines of text at width characters.

Parameters:

  • text (Object)

    a string of text that is to be wrapped to a maximum width. If text is not a String, #to_s is called to convert it.

  • width (Integer)

    the maximum length of each line of text.

Returns:

  • (Array)

    an Array of lines of text, each no longer than width characters.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/color_console/console.rb', line 86

def wrap_text(text, width)
    text = text.to_s
    if width > 0 && (text.length > width || text.index("\n"))
        lines = []
        start, nl_pos, ws_pos, wb_pos, end_pos = 0, 0, 0, 0, text.rindex(/[^\s]/)
        while start < end_pos
            last_start = start
            nl_pos = text.index("\n", start)
            ws_pos = text.rindex(/ +/, start + width)
            wb_pos = text.rindex(/[\-,.;#)}\]\/\\]/, start + width - 1)
            ### Debug code ###
            #STDERR.puts self
            #ind = ' ' * end_pos
            #ind[start] = '('
            #ind[start+width < end_pos ? start+width : end_pos] = ']'
            #ind[nl_pos] = 'n' if nl_pos
            #ind[wb_pos] = 'b' if wb_pos
            #ind[ws_pos] = 's' if ws_pos
            #STDERR.puts ind
            ### End debug code ###
            if nl_pos && nl_pos <= start + width
                lines << text[start...nl_pos].strip
                start = nl_pos + 1
            elsif end_pos < start + width
                lines << text[start..end_pos]
                start = end_pos
            elsif ws_pos && ws_pos > start && ((wb_pos.nil? || ws_pos > wb_pos) ||
                  (wb_pos && wb_pos > 5 && wb_pos - 5 < ws_pos))
                lines << text[start...ws_pos]
                start = text.index(/[^\s]/, ws_pos + 1)
            elsif wb_pos && wb_pos > start
                lines << text[start..wb_pos]
                start = wb_pos + 1
            else
                lines << text[start...(start+width)]
                start += width
            end
            if start <= last_start
                # Detect an infinite loop, and just return the original text
                STDERR.puts "Inifinite loop detected at #{__FILE__}:#{__LINE__}"
                STDERR.puts "  width: #{width}, start: #{start}, nl_pos: #{nl_pos}, " +
                            "ws_pos: #{ws_pos}, wb_pos: #{wb_pos}"
                return [text]
            end
        end
        lines
    else
        [text]
    end
end

.write(text, fg = nil, bg = nil) ⇒ Object

Writes a partital line of text to the console, with optional foreground and background colors. No line-feed is output.

Parameters:

  • text (String)

    The text to be written to the console.

  • fg (Symbol) (defaults to: nil)

    An optional foreground colour name or value.

  • bg (Symbol) (defaults to: nil)

    An optional background color name or value.

See Also:

  • #puts


55
56
57
58
59
# File 'lib/color_console/console.rb', line 55

def write(text, fg = nil, bg = nil)
    @lock.synchronize do
        _write(text, fg, bg)
    end
end