color-console 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +76 -0
- data/lib/color-console.rb +2 -0
- data/lib/color_console.rb +2 -0
- data/lib/console/console.rb +135 -0
- data/lib/console/platform/ansi.rb +134 -0
- data/lib/console/platform/windows.rb +253 -0
- data/lib/console/progress.rb +70 -0
- data/lib/console/table.rb +155 -0
- metadata +56 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013, Adam Gardiner
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
14
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
15
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
17
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
18
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
19
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
20
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
21
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
22
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# ColorConsole
|
2
|
+
|
3
|
+
ColorConsole is a small cross-platform library for outputting text to the console.
|
4
|
+
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
ColorConsole is supplied as a gem, and has no dependencies. To use it, simply:
|
9
|
+
```
|
10
|
+
gem install color-console
|
11
|
+
```
|
12
|
+
|
13
|
+
ColorConsole provides methods for outputting lines of text in different colors, using the `Console.write` and `Console.puts` functions.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'color-console'
|
17
|
+
|
18
|
+
Console.puts "Some text" # Outputs text using the current console colours
|
19
|
+
Console.puts "Some other text", :red # Outputs red text with the current background
|
20
|
+
Console.puts "Yet more text", nil, :blue # Outputs text using the current foreground and a blue background
|
21
|
+
|
22
|
+
# The following lines output BlueRedGreen on a single line, each word in the appropriate color
|
23
|
+
Console.write "Blue ", :blue
|
24
|
+
Console.write "Red ", :red
|
25
|
+
Console.write "Green", :green
|
26
|
+
```
|
27
|
+
|
28
|
+
## Features
|
29
|
+
|
30
|
+
In addition to `Console.puts` and `Console.write` for outputting text in color, ColorConsole also supports:
|
31
|
+
* __Setting the console title__: The title bar of the console window can be set using `Console.title = 'My title'`.
|
32
|
+
* __Status messages__: Status messages (i.e. a line of text at the current scroll position) can be output and
|
33
|
+
updated at any time. The status message will remain at the current scroll point even as new text is output
|
34
|
+
using `Console.puts`.
|
35
|
+
* __Progress bars__: A progress bar can be rendered like a status message, but with a pseudo-graphical representation
|
36
|
+
of the current completion percentage:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
(0..100).do |i|
|
40
|
+
Console.show_progress('Processing data', i)
|
41
|
+
end
|
42
|
+
Console.clear_progress
|
43
|
+
```
|
44
|
+
Output:
|
45
|
+
```
|
46
|
+
[============== 35% ] Processing data
|
47
|
+
```
|
48
|
+
* __Tables__: Data can be output in a tabular representation:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
HEADER_ROW = ['Column 1', 'Column 2', 'Column 3', 'Column 4']
|
52
|
+
MIXED_ROW = [17,
|
53
|
+
'A somewhat longer column',
|
54
|
+
'A very very very long column that should wrap multple lines',
|
55
|
+
'Another medium length column']
|
56
|
+
SECOND_ROW = [24,
|
57
|
+
'Lorem ipsum',
|
58
|
+
'Some more text',
|
59
|
+
'Lorem ipsum dolor sit amet']
|
60
|
+
|
61
|
+
Console.display_table([HEADER_ROW, MIXED_ROW, SECOND_ROW], width: 100,
|
62
|
+
col_sep: '|', row_sep: '-')
|
63
|
+
```
|
64
|
+
Output:
|
65
|
+
```
|
66
|
+
+----------+--------------------------+-----------------------------+-----------------------------+
|
67
|
+
| Column 1 | Column 2 | Column 3 | Column 4 |
|
68
|
+
+----------+--------------------------+-----------------------------+-----------------------------+
|
69
|
+
| 17 | A somewhat longer column | A very very very long | Another medium length |
|
70
|
+
| | | column that should wrap | column |
|
71
|
+
| | | multple lines | |
|
72
|
+
+----------+--------------------------+-----------------------------+-----------------------------+
|
73
|
+
| 24 | Lorem ipsum | Some more text | Lorem ipsum dolor sit amet |
|
74
|
+
+----------+--------------------------+-----------------------------+-----------------------------+
|
75
|
+
```
|
76
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# Require the platform specific functionality
|
2
|
+
if Gem.win_platform?
|
3
|
+
require_relative 'platform/windows'
|
4
|
+
else
|
5
|
+
require_relative 'platform/ansi'
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
# Implements cross-platform functionality for working with a console window to
|
10
|
+
# provide color and progress-bar functionality.
|
11
|
+
module Console
|
12
|
+
|
13
|
+
attr_reader :status, :status_enabled
|
14
|
+
|
15
|
+
|
16
|
+
# Mutex used to ensure we don't intermingle output from multiple threads
|
17
|
+
@lock = Mutex.new
|
18
|
+
|
19
|
+
|
20
|
+
# Returns the width of the console window
|
21
|
+
#
|
22
|
+
# @return the width in characters, or nil if no console is available.
|
23
|
+
def width
|
24
|
+
sz = _window_size
|
25
|
+
sz && sz.first
|
26
|
+
end
|
27
|
+
module_function :width
|
28
|
+
|
29
|
+
|
30
|
+
# Returns the height of the console window
|
31
|
+
#
|
32
|
+
# @return the height in characters, or nil if no console is available.
|
33
|
+
def height
|
34
|
+
sz = _window_size
|
35
|
+
sz && sz.last
|
36
|
+
end
|
37
|
+
module_function :height
|
38
|
+
|
39
|
+
|
40
|
+
# Writes a partital line of text to the console, with optional foreground
|
41
|
+
# and background colors. No line-feed is output.
|
42
|
+
#
|
43
|
+
# @see #puts
|
44
|
+
#
|
45
|
+
# @param text [String] The text to be written to the console.
|
46
|
+
# @param fg [Symbol] An optional foreground colour name or value.
|
47
|
+
# @param bg [Symbol] An optional background color name or value.
|
48
|
+
def write(text, fg = nil, bg = nil)
|
49
|
+
@lock.synchronize do
|
50
|
+
_write(text, fg, bg)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
module_function :write
|
54
|
+
|
55
|
+
|
56
|
+
# Send a line of text to the screen, terminating with a new-line.
|
57
|
+
#
|
58
|
+
# @see #write
|
59
|
+
#
|
60
|
+
# @param text [String] The optional text to be written to the console.
|
61
|
+
# @param fg [Symbol] An optional foreground colour name or value.
|
62
|
+
# @param bg [Symbol] An optional background color name or value.
|
63
|
+
def puts(text = nil, fg = nil, bg = nil)
|
64
|
+
@lock.synchronize do
|
65
|
+
_puts(text, fg, bg)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
module_function :puts
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
# Utility method for wrapping lines of +text+ at +width+ characters.
|
73
|
+
#
|
74
|
+
# @param text [Object] a string of text that is to be wrapped to a
|
75
|
+
# maximum width. If +text+ is not a String, #to_s is called to convert it.
|
76
|
+
# @param width [Integer] the maximum length of each line of text.
|
77
|
+
# @return [Array] an Array of lines of text, each no longer than +width+
|
78
|
+
# characters.
|
79
|
+
def wrap_text(text, width)
|
80
|
+
text = text.to_s
|
81
|
+
if width > 0 && (text.length > width || text.index("\n"))
|
82
|
+
lines = []
|
83
|
+
start, nl_pos, ws_pos, wb_pos, end_pos = 0, 0, 0, 0, text.rindex(/[^\s]/)
|
84
|
+
while start < end_pos
|
85
|
+
last_start = start
|
86
|
+
nl_pos = text.index("\n", start)
|
87
|
+
ws_pos = text.rindex(/ +/, start + width)
|
88
|
+
wb_pos = text.rindex(/[\-,.;#)}\]\/\\]/, start + width - 1)
|
89
|
+
### Debug code ###
|
90
|
+
#STDERR.puts self
|
91
|
+
#ind = ' ' * end_pos
|
92
|
+
#ind[start] = '('
|
93
|
+
#ind[start+width < end_pos ? start+width : end_pos] = ']'
|
94
|
+
#ind[nl_pos] = 'n' if nl_pos
|
95
|
+
#ind[wb_pos] = 'b' if wb_pos
|
96
|
+
#ind[ws_pos] = 's' if ws_pos
|
97
|
+
#STDERR.puts ind
|
98
|
+
### End debug code ###
|
99
|
+
if nl_pos && nl_pos <= start + width
|
100
|
+
lines << text[start...nl_pos].strip
|
101
|
+
start = nl_pos + 1
|
102
|
+
elsif end_pos < start + width
|
103
|
+
lines << text[start..end_pos]
|
104
|
+
start = end_pos
|
105
|
+
elsif ws_pos && ws_pos > start && ((wb_pos.nil? || ws_pos > wb_pos) ||
|
106
|
+
(wb_pos && wb_pos > 5 && wb_pos - 5 < ws_pos))
|
107
|
+
lines << text[start...ws_pos]
|
108
|
+
start = text.index(/[^\s]/, ws_pos + 1)
|
109
|
+
elsif wb_pos && wb_pos > start
|
110
|
+
lines << text[start..wb_pos]
|
111
|
+
start = wb_pos + 1
|
112
|
+
else
|
113
|
+
lines << text[start...(start+width)]
|
114
|
+
start += width
|
115
|
+
end
|
116
|
+
if start <= last_start
|
117
|
+
# Detect an infinite loop, and just return the original text
|
118
|
+
STDERR.puts "Inifinite loop detected at #{__FILE__}:#{__LINE__}"
|
119
|
+
STDERR.puts " width: #{width}, start: #{start}, nl_pos: #{nl_pos}, " +
|
120
|
+
"ws_pos: #{ws_pos}, wb_pos: #{wb_pos}"
|
121
|
+
return [text]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
lines
|
125
|
+
else
|
126
|
+
[text]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
module_function :wrap_text
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
require_relative 'progress'
|
134
|
+
require_relative 'table'
|
135
|
+
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Console
|
2
|
+
|
3
|
+
FOREGROUND_COLORS = {
|
4
|
+
black: '30',
|
5
|
+
blue: '34',
|
6
|
+
dark_blue: '2;34',
|
7
|
+
light_blue: '1;34',
|
8
|
+
cyan: '36',
|
9
|
+
green: '32',
|
10
|
+
dark_green: '2;32',
|
11
|
+
light_green: '1;32',
|
12
|
+
red: '31',
|
13
|
+
dark_red: '2;31',
|
14
|
+
light_red: '1;31',
|
15
|
+
magenta: '35',
|
16
|
+
dark_magenta: '2;35',
|
17
|
+
light_magenta: '1;35',
|
18
|
+
yellow: '33',
|
19
|
+
gray: '37',
|
20
|
+
dark_gray: '2;37',
|
21
|
+
light_gray: '37',
|
22
|
+
white: '1;37'
|
23
|
+
}
|
24
|
+
|
25
|
+
BACKGROUND_COLORS = {
|
26
|
+
black: '40',
|
27
|
+
blue: '44',
|
28
|
+
dark_blue: '2;44',
|
29
|
+
light_blue: '1;44',
|
30
|
+
cyan: '46',
|
31
|
+
green: '42',
|
32
|
+
dark_green: '2;42',
|
33
|
+
light_green: '1;42',
|
34
|
+
red: '41',
|
35
|
+
dark_red: '2;41',
|
36
|
+
light_red: '1;41',
|
37
|
+
magenta: '45',
|
38
|
+
dark_magenta: '2;45',
|
39
|
+
light_magenta: '1;45',
|
40
|
+
yellow: '43',
|
41
|
+
gray: '47',
|
42
|
+
dark_gray: '2;47',
|
43
|
+
light_gray: '47',
|
44
|
+
white: '1;47'
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
# Sets the title bar text of the console window.
|
49
|
+
def title=(text)
|
50
|
+
STDOUT.write "\e]0;#{text}\007"
|
51
|
+
end
|
52
|
+
module_function :title=
|
53
|
+
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
|
58
|
+
# Get the current console window size.
|
59
|
+
#
|
60
|
+
# @return [Array, nil] Returns a two-dimensional array of [cols, rows], or
|
61
|
+
# nil if the console has been redirected.
|
62
|
+
def _window_size
|
63
|
+
unless @window_size
|
64
|
+
rows = `tput lines`
|
65
|
+
cols = `tput cols`
|
66
|
+
@window_size = [cols.chomp.to_i, rows.chomp.to_i]
|
67
|
+
end
|
68
|
+
@window_size
|
69
|
+
end
|
70
|
+
module_function :_window_size
|
71
|
+
|
72
|
+
|
73
|
+
# Write a line of text to the console, with optional foreground and
|
74
|
+
# background colors.
|
75
|
+
#
|
76
|
+
# @param text [String] The text to be written to the console.
|
77
|
+
# @param fg [Symbol, String] An optional foreground colour name or ANSI code.
|
78
|
+
# @param bg [Symbol, String] An optional background color name or ANSI code.
|
79
|
+
def _write(text, fg = nil, bg = nil)
|
80
|
+
if fg || bg
|
81
|
+
reset = true
|
82
|
+
if fg
|
83
|
+
fg_code = FOREGROUND_COLORS[fg] || fg
|
84
|
+
STDOUT.write "\e[#{fg_code}m"
|
85
|
+
end
|
86
|
+
|
87
|
+
if bg
|
88
|
+
bg_code = BACKGROUND_COLORS[bg] || bg
|
89
|
+
STDOUT.write "\e[#{bg_code}m"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
STDOUT.write text
|
94
|
+
|
95
|
+
if reset
|
96
|
+
STDOUT.write "\e[0m"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
module_function :_write
|
100
|
+
|
101
|
+
|
102
|
+
# Send a line of text to the screen, terminating with a new-line.
|
103
|
+
#
|
104
|
+
# @param text [String] The optional text to be written to the console.
|
105
|
+
# @param fg [Symbol, String] An optional foreground colour name or ANSI code.
|
106
|
+
# @param bg [Symbol, String] An optional background color name or ANSI code.
|
107
|
+
def _puts(text = nil, fg = nil, bg = nil)
|
108
|
+
if @status
|
109
|
+
_clear_line (@status.length / self.width) + 1
|
110
|
+
end
|
111
|
+
_write("#{text}", fg, bg)
|
112
|
+
STDOUT.write "\n"
|
113
|
+
if @status
|
114
|
+
_write @status, @status_fg, @status_bg
|
115
|
+
end
|
116
|
+
end
|
117
|
+
module_function :_puts
|
118
|
+
|
119
|
+
|
120
|
+
# Clears the current +lines+ line(s)
|
121
|
+
#
|
122
|
+
# @param lines [Fixnum] Number of lines to clear
|
123
|
+
def _clear_line(lines = 1)
|
124
|
+
raise ArgumentError, "Number of lines to clear (#{lines}) must be > 0" if lines < 1
|
125
|
+
while lines > 0
|
126
|
+
STDOUT.write "\r\e[2K"
|
127
|
+
lines -= 1
|
128
|
+
STDOUT.write "\e[A" if lines > 0
|
129
|
+
end
|
130
|
+
end
|
131
|
+
module_function :_clear_line
|
132
|
+
|
133
|
+
end
|
134
|
+
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
|
4
|
+
module Console
|
5
|
+
|
6
|
+
# Implements Windows-specific platform functionality
|
7
|
+
module Windows
|
8
|
+
|
9
|
+
extend FFI::Library
|
10
|
+
|
11
|
+
ffi_lib 'kernel32.dll'
|
12
|
+
ffi_convention :stdcall
|
13
|
+
|
14
|
+
|
15
|
+
# FFI structure used to get/set information about the current console
|
16
|
+
# window buffer
|
17
|
+
class BufferInfo < FFI::Struct
|
18
|
+
layout :width, :short,
|
19
|
+
:height, :short,
|
20
|
+
:cursor_x, :short,
|
21
|
+
:cursor_y, :short,
|
22
|
+
:text_attributes, :ushort,
|
23
|
+
:window_left, :short,
|
24
|
+
:window_top, :short,
|
25
|
+
:window_right, :short,
|
26
|
+
:window_bottom, :short,
|
27
|
+
:max_width, :short,
|
28
|
+
:max_height, :short
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# FFI structure used to get/set buffer co-ordinates
|
33
|
+
class Coord < FFI::Struct
|
34
|
+
layout :x, :short,
|
35
|
+
:y, :short
|
36
|
+
|
37
|
+
def initialize(x, y)
|
38
|
+
self[:x] = x
|
39
|
+
self[:y] = y
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Define Windows console functions we need
|
44
|
+
attach_function :get_std_handle, :GetStdHandle, [:uint], :pointer
|
45
|
+
attach_function :get_console_screen_buffer_info, :GetConsoleScreenBufferInfo, [:pointer, :pointer], :bool
|
46
|
+
attach_function :set_console_cursor_position, :SetConsoleCursorPosition, [:pointer, Coord.by_value], :bool
|
47
|
+
attach_function :set_console_text_attribute, :SetConsoleTextAttribute, [:pointer, :ushort], :bool
|
48
|
+
attach_function :set_console_title, :SetConsoleTitleA, [:pointer], :bool
|
49
|
+
|
50
|
+
|
51
|
+
# Constants representing STDIN, STDOUT, and STDERR
|
52
|
+
STD_OUTPUT_HANDLE = 0xFFFFFFF5
|
53
|
+
STD_INPUT_HANDLE = 0xFFFFFFF6
|
54
|
+
STD_ERROR_HANDLE = 0xFFFFFFF7
|
55
|
+
|
56
|
+
|
57
|
+
# Retrieve a handle to STDOUT
|
58
|
+
def stdout
|
59
|
+
@stdout ||= self.get_std_handle(STD_OUTPUT_HANDLE)
|
60
|
+
end
|
61
|
+
module_function :stdout
|
62
|
+
private :stdout
|
63
|
+
|
64
|
+
|
65
|
+
# Retrieve a BufferInfo object
|
66
|
+
def buffer_info
|
67
|
+
@buffer_info ||= BufferInfo.new
|
68
|
+
end
|
69
|
+
module_function :buffer_info
|
70
|
+
private :buffer_info
|
71
|
+
|
72
|
+
|
73
|
+
# Populate a BufferInfo structure with details about the current
|
74
|
+
# buffer state.
|
75
|
+
#
|
76
|
+
# @return [BufferInfo] A BufferInfo structure containing fields for
|
77
|
+
# various bits of console state.
|
78
|
+
def get_buffer_info
|
79
|
+
if stdout
|
80
|
+
self.get_console_screen_buffer_info(stdout, buffer_info)
|
81
|
+
@buffer_info
|
82
|
+
end
|
83
|
+
end
|
84
|
+
module_function :get_buffer_info
|
85
|
+
|
86
|
+
|
87
|
+
# Sets the console foreground and background colors.
|
88
|
+
def set_color(color)
|
89
|
+
if stdout && color
|
90
|
+
self.set_console_text_attribute(stdout, color)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
module_function :set_color
|
94
|
+
|
95
|
+
|
96
|
+
# Sets the cursor position to the specified +x+ and +y+ locations in the
|
97
|
+
# console output buffer. If +y+ is nil, the cursor is positioned at +x+ on
|
98
|
+
# the current line.
|
99
|
+
def set_cursor_position(x, y)
|
100
|
+
if stdout && x && y
|
101
|
+
coord = Coord.new(x, y)
|
102
|
+
self.set_console_cursor_position(stdout, coord)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
module_function :set_cursor_position
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Constants for colour components
|
111
|
+
BLUE = 0x1
|
112
|
+
GREEN = 0x2
|
113
|
+
RED = 0x4
|
114
|
+
INTENSITY = 0x8
|
115
|
+
|
116
|
+
# Constants for foreground and background colors
|
117
|
+
FOREGROUND_COLORS = {
|
118
|
+
black: 0,
|
119
|
+
blue: BLUE | INTENSITY,
|
120
|
+
dark_blue: BLUE,
|
121
|
+
light_blue: BLUE | INTENSITY,
|
122
|
+
cyan: BLUE | GREEN | INTENSITY,
|
123
|
+
green: GREEN,
|
124
|
+
dark_green: GREEN,
|
125
|
+
light_green: GREEN | INTENSITY,
|
126
|
+
red: RED | INTENSITY,
|
127
|
+
dark_red: RED,
|
128
|
+
light_red: RED | INTENSITY,
|
129
|
+
magenta: RED | BLUE,
|
130
|
+
dark_magenta: RED | BLUE,
|
131
|
+
light_magenta: RED | BLUE | INTENSITY,
|
132
|
+
yellow: GREEN | RED | INTENSITY,
|
133
|
+
gray: BLUE | GREEN | RED,
|
134
|
+
dark_gray: INTENSITY,
|
135
|
+
light_gray: BLUE | GREEN | RED,
|
136
|
+
white: BLUE | GREEN | RED | INTENSITY
|
137
|
+
}
|
138
|
+
BACKGROUND_COLORS = {}
|
139
|
+
FOREGROUND_COLORS.each{ |k, c| BACKGROUND_COLORS[k] = c << 4 }
|
140
|
+
|
141
|
+
|
142
|
+
# Sets the title bar text of the console window.
|
143
|
+
def title=(text)
|
144
|
+
Windows.set_console_title(text)
|
145
|
+
end
|
146
|
+
module_function :title=
|
147
|
+
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
|
152
|
+
# Save the reset text and background colors
|
153
|
+
buffer = Windows.get_buffer_info
|
154
|
+
@reset_colors = buffer && buffer[:text_attributes]
|
155
|
+
|
156
|
+
|
157
|
+
# Get the current console window size.
|
158
|
+
#
|
159
|
+
# @return [Array, nil] Returns a two-dimensional array of [cols, rows], or
|
160
|
+
# nil if the console has been redirected.
|
161
|
+
def _window_size
|
162
|
+
unless @window_size
|
163
|
+
buffer = Windows.get_buffer_info
|
164
|
+
if buffer
|
165
|
+
if buffer[:window_right] > 0 && buffer[:window_bottom] > 0
|
166
|
+
@window_size = [buffer[:window_right] - buffer[:window_left] + 1,
|
167
|
+
buffer[:window_bottom] - buffer[:window_top] + 1]
|
168
|
+
else
|
169
|
+
@window_size = -1
|
170
|
+
end
|
171
|
+
else
|
172
|
+
@window_size = -1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
@window_size == -1 ? nil : @window_size
|
176
|
+
end
|
177
|
+
module_function :_window_size
|
178
|
+
|
179
|
+
|
180
|
+
# Write a line of text to the console, with optional foreground and
|
181
|
+
# background colors.
|
182
|
+
#
|
183
|
+
# @param text [String] The text to be written to the console.
|
184
|
+
# @param fg [Symbol, Integer] An optional foreground colour name or value.
|
185
|
+
# @param bg [Symbol, Integer] An optional background color name or value.
|
186
|
+
def _write(text, fg = nil, bg = nil)
|
187
|
+
if fg || bg
|
188
|
+
reset = @reset_colors
|
189
|
+
if fg
|
190
|
+
fg_code = FOREGROUND_COLORS[fg] || fg
|
191
|
+
unless fg_code >= 0 && fg_code <= 0x0F
|
192
|
+
raise ArgumentError, "Text color must be a recognised symbol or int"
|
193
|
+
end
|
194
|
+
else
|
195
|
+
fg_code = reset & 0x0F
|
196
|
+
end
|
197
|
+
|
198
|
+
if bg
|
199
|
+
bg_code = BACKGROUND_COLORS[bg] || bg
|
200
|
+
unless bg_code >= 0 && bg_code <= 0xF0
|
201
|
+
raise ArgumentError, "Background color must be a recognised symbol or int"
|
202
|
+
end
|
203
|
+
else
|
204
|
+
bg_code = reset & 0xF0
|
205
|
+
end
|
206
|
+
Windows.set_color(fg_code | bg_code)
|
207
|
+
end
|
208
|
+
|
209
|
+
STDOUT.write text
|
210
|
+
|
211
|
+
if reset
|
212
|
+
Windows.set_color(reset)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
module_function :_write
|
216
|
+
|
217
|
+
|
218
|
+
# Send a line of text to the screen, terminating with a new-line.
|
219
|
+
#
|
220
|
+
# @param text [String] The optional text to be written to the console.
|
221
|
+
# @param fg [Symbol, Integer] An optional foreground colour name or value.
|
222
|
+
# @param bg [Symbol, Integer] An optional background color name or value.
|
223
|
+
def _puts(text = nil, fg = nil, bg = nil)
|
224
|
+
if @status
|
225
|
+
_clear_line (@status.length / self.width) + 1
|
226
|
+
end
|
227
|
+
_write("#{text}\r\n", fg, bg)
|
228
|
+
if @status
|
229
|
+
_write(@status, @status_fg, @status_bg)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
module_function :_puts
|
233
|
+
|
234
|
+
|
235
|
+
# Clears the current line
|
236
|
+
def _clear_line(lines = 1)
|
237
|
+
raise ArgumentError, "Number of lines to clear (#{lines}) must be > 0" if lines < 1
|
238
|
+
buffer = Windows.get_buffer_info
|
239
|
+
if buffer
|
240
|
+
y = buffer[:cursor_y]
|
241
|
+
while lines > 0
|
242
|
+
Windows.set_cursor_position(0, y)
|
243
|
+
STDOUT.write ' ' * (buffer[:window_right] - buffer[:window_left])
|
244
|
+
Windows.set_cursor_position(0, y)
|
245
|
+
lines -= 1
|
246
|
+
y -= 1
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
module_function :_clear_line
|
251
|
+
|
252
|
+
end
|
253
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Console
|
2
|
+
|
3
|
+
# Sets text to be displayed temporarily on the current line. Status text can
|
4
|
+
# be updated or cleared.
|
5
|
+
#
|
6
|
+
# @param status [String] The text to be displayed as the current status.
|
7
|
+
# Pass +nil+ to clear the status display.
|
8
|
+
# @params opts [Hash] Options to control the colour of the status display.
|
9
|
+
# @option opts [Symbol] :text_color The text color to use when displaying
|
10
|
+
# the status message.
|
11
|
+
# @option opts [Symbol] :background_color The background color to use when
|
12
|
+
# rendering the status message
|
13
|
+
def status(msg, opts = {})
|
14
|
+
if self.width
|
15
|
+
@lock.synchronize do
|
16
|
+
if @status
|
17
|
+
# Clear existing status
|
18
|
+
_clear_line (@status.length / self.width) + 1
|
19
|
+
end
|
20
|
+
@completed = nil
|
21
|
+
@status = msg
|
22
|
+
if @status
|
23
|
+
@status_fg = opts.fetch(:text_color, opts.fetch(:color, :cyan))
|
24
|
+
@status_bg = opts[:background_color]
|
25
|
+
_write @status, @status_fg, @status_bg
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
module_function :status
|
31
|
+
|
32
|
+
|
33
|
+
# Displays a progress bar as the current status line. The status line is a
|
34
|
+
# partial line of text printed at the current scroll location, and which
|
35
|
+
# can be updated or cleared.
|
36
|
+
#
|
37
|
+
# @param label [String] The label to be displayed after the progress bar.
|
38
|
+
# @param complete [Fixnum] Number of completed steps.
|
39
|
+
# @param opts [Fixnum, Hash] If a Fixnum is passed, this is the total number
|
40
|
+
# of steps. If a Hash is passed, it is an options hash with the following
|
41
|
+
# possible options.
|
42
|
+
# @option opts [Fixnum] :total The total number of steps; default is 100.
|
43
|
+
# @see #status for other supported options
|
44
|
+
def show_progress(label, complete, opts = {})
|
45
|
+
if self.width
|
46
|
+
opts = {total: opts} if opts.is_a?(Fixnum)
|
47
|
+
total = opts.fetch(:total, 100)
|
48
|
+
complete = total if complete > total
|
49
|
+
bar_length = opts.fetch(:bar_length, 40)
|
50
|
+
completion = complete * bar_length / total
|
51
|
+
pct = "#{complete * 100 / total}%"
|
52
|
+
bar = "#{'=' * completion}#{' ' * (bar_length - completion)}"
|
53
|
+
bar[(bar_length - pct.length) / 2, pct.length] = pct
|
54
|
+
if @completed.nil? || pct != @completed
|
55
|
+
self.status("[#{bar}] #{label}", opts)
|
56
|
+
@completed = pct
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
module_function :show_progress
|
61
|
+
|
62
|
+
|
63
|
+
# Clears any currently displayed progress bar or status message.
|
64
|
+
def clear_progress
|
65
|
+
self.status nil
|
66
|
+
end
|
67
|
+
alias_method :clear_status, :clear_progress
|
68
|
+
module_function :clear_progress, :clear_status
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Console
|
2
|
+
|
3
|
+
# Displays an array of arrays as a table of data. The content of each row
|
4
|
+
# is aligned and wrapped if necessary to fit the column widths.
|
5
|
+
#
|
6
|
+
# @param rows [Array] An array of arrays containing the data to be output.
|
7
|
+
# The number of elements in the first array determines the number of
|
8
|
+
# columns that will be output, unless the :column_widths options is passed;
|
9
|
+
# in that case, the number of column width specifications determines the
|
10
|
+
# number of columns output.
|
11
|
+
# @param opts [Hash] An options hash containing settings that determine how
|
12
|
+
# the table is rendered.
|
13
|
+
# @options opts [Array<Fixnum>] :col_widths The width to use for each column.
|
14
|
+
# @option opts [Fixnum] :width The width of the table. If omitted, the width
|
15
|
+
# is determined instead by the :col_widths option.
|
16
|
+
# @options opts [Char] :col_sep The character to use between each column.
|
17
|
+
# If omitted, no column line is drawn. Note though that each column is
|
18
|
+
# rendered with 1 space of padding on the left and right.
|
19
|
+
# @options opts [Char] :row_sep The character to use to render a row
|
20
|
+
# separator. If omitted, no row separators are rendered.
|
21
|
+
# @options opts [Fixnum] :indent The number of characters to indent the
|
22
|
+
# table from the left margin of the console. If omitted, no indent is
|
23
|
+
# used.
|
24
|
+
# @options opts [Symbol] :color The color in which to render the text of the
|
25
|
+
# table.
|
26
|
+
# @options opts [Symbol] :text_color The color in which to render the text
|
27
|
+
# of the table.
|
28
|
+
# @options opts [Symbol] :background_color The color in which to render the
|
29
|
+
# background of the table.
|
30
|
+
def display_table(rows, opts = {})
|
31
|
+
return unless rows && rows.size > 0 && rows.first.size > 0
|
32
|
+
col_widths = opts[:col_widths]
|
33
|
+
unless col_widths
|
34
|
+
col_count = rows.first.size
|
35
|
+
avail_width = (opts[:width] || ((width || 10000) - opts.fetch(:indent, 0))) -
|
36
|
+
(" #{opts[:col_sep]} ".length * col_count)
|
37
|
+
col_widths = _calculate_widths(rows, col_count, avail_width - 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
@lock.synchronize do
|
41
|
+
_output_row_sep(col_widths, opts) if opts[:row_sep]
|
42
|
+
rows.each do |row|
|
43
|
+
_display_row(row, col_widths, opts)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
module_function :display_table
|
48
|
+
|
49
|
+
|
50
|
+
# Displays a single +row+ of data within columns of +widths+ width. If the
|
51
|
+
# contents of a cell exceeds the available width, it is wrapped, and the row
|
52
|
+
# is displayed over multiple lines.
|
53
|
+
#
|
54
|
+
# @param row [Array] An array of data to display as a single row.
|
55
|
+
# @param widths [Array<Fixnum>] An array of column widths to use for each
|
56
|
+
# column.
|
57
|
+
# @param opts [Hash] An options hash containing settings that determine how
|
58
|
+
# the table is rendered.
|
59
|
+
# @see #display_table for details of the supported options.
|
60
|
+
def display_row(row, widths, opts = {})
|
61
|
+
return unless row && row.size > 0
|
62
|
+
@lock.synchronize do
|
63
|
+
_display_row(row, widths, opts)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
module_function :display_row
|
67
|
+
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
|
72
|
+
# Calculates the widths to use to display a table of data.
|
73
|
+
def _calculate_widths(rows, col_count, avail_width)
|
74
|
+
max_widths = Array.new(col_count)
|
75
|
+
rows.each do |row|
|
76
|
+
row.each_with_index do |col, i|
|
77
|
+
max_widths[i] = col.to_s.length if !max_widths[i] || col.to_s.length > max_widths[i]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
max_width = max_widths.reduce(0, &:+)
|
81
|
+
while avail_width < max_width
|
82
|
+
# Reduce longest width(s) to next longest
|
83
|
+
longest = max_widths.max
|
84
|
+
num_longest = max_widths.count{ |w| w == longest }
|
85
|
+
next_longest = max_widths.select{ |w| w < longest }.max
|
86
|
+
if !next_longest || (num_longest * (longest - next_longest) > max_width - avail_width)
|
87
|
+
reduction = (max_width - avail_width) / num_longest
|
88
|
+
reduction = 1 if reduction <= 0
|
89
|
+
if !next_longest
|
90
|
+
max_widths.map!{ |w| w - reduction }
|
91
|
+
else
|
92
|
+
max_widths.map!{ |w| w > next_longest ? w - reduction : w }
|
93
|
+
end
|
94
|
+
else
|
95
|
+
max_widths.map!{ |w| w > next_longest ? next_longest : w }
|
96
|
+
end
|
97
|
+
max_width = max_widths.reduce(0, &:+)
|
98
|
+
end
|
99
|
+
max_widths
|
100
|
+
end
|
101
|
+
module_function :_calculate_widths
|
102
|
+
|
103
|
+
|
104
|
+
# Displays a single +row+ of data within columns of +widths+ width. If the
|
105
|
+
# contents of a cell exceeds the available width, it is wrapped, and the row
|
106
|
+
# is displayed over multiple lines.
|
107
|
+
def _display_row(row, widths, opts = {})
|
108
|
+
fg = opts.fetch(:text_color, opts.fetch(:color, :cyan))
|
109
|
+
bg = opts[:background_color]
|
110
|
+
indent = opts.fetch(:indent, 0)
|
111
|
+
col_sep = opts[:col_sep]
|
112
|
+
row_sep = opts[:row_sep]
|
113
|
+
|
114
|
+
line_count = 0
|
115
|
+
lines = row.each_with_index.map do |col, i|
|
116
|
+
cell_lines = wrap_text(col, widths[i])
|
117
|
+
line_count = cell_lines.size if cell_lines.size > line_count
|
118
|
+
cell_lines
|
119
|
+
end
|
120
|
+
(0...line_count).each do |i|
|
121
|
+
_write(' ' * indent)
|
122
|
+
_write("#{col_sep} ", fg, bg) if col_sep
|
123
|
+
line = (0...widths.size).map do |col|
|
124
|
+
"%#{row[col].is_a?(Numeric) ? '' : '-'}#{widths[col]}s" %
|
125
|
+
(lines[col] && lines[col][i])
|
126
|
+
end.join(" #{col_sep} ")
|
127
|
+
_write(line, fg, bg)
|
128
|
+
_write(" #{col_sep}", fg, bg) if col_sep
|
129
|
+
_puts
|
130
|
+
end
|
131
|
+
_output_row_sep(widths, opts) if row_sep
|
132
|
+
end
|
133
|
+
module_function :_display_row
|
134
|
+
|
135
|
+
|
136
|
+
# Outputs a row separator line
|
137
|
+
def _output_row_sep(widths, opts)
|
138
|
+
fg = opts.fetch(:text_color, opts.fetch(:color, :cyan))
|
139
|
+
bg = opts[:background_color]
|
140
|
+
indent = opts.fetch(:indent, 0)
|
141
|
+
col_sep = opts[:col_sep]
|
142
|
+
row_sep = opts[:row_sep]
|
143
|
+
corner = opts.fetch(:corner, col_sep ? '+' * col_sep.length : '')
|
144
|
+
|
145
|
+
sep_row = widths.map{ |width| row_sep * (width + 2) }
|
146
|
+
_write(' ' * indent)
|
147
|
+
_write(corner, fg, bg)
|
148
|
+
_write(sep_row.join(corner), fg, bg)
|
149
|
+
_write(corner, fg, bg)
|
150
|
+
_puts
|
151
|
+
end
|
152
|
+
module_function :_output_row_sep
|
153
|
+
|
154
|
+
end
|
155
|
+
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: color-console
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Gardiner
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-22 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! " ColorConsole supports cross-platform (ANSI and Windows) colored
|
15
|
+
text output to the console.\n It also provides useful methods for building
|
16
|
+
command-line interfaces that provide status\n messages and progress bars.\n"
|
17
|
+
email: adam.b.gardiner@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- README.md
|
23
|
+
- LICENSE
|
24
|
+
- lib/color-console.rb
|
25
|
+
- lib/color_console.rb
|
26
|
+
- lib/console/console.rb
|
27
|
+
- lib/console/platform/ansi.rb
|
28
|
+
- lib/console/platform/windows.rb
|
29
|
+
- lib/console/progress.rb
|
30
|
+
- lib/console/table.rb
|
31
|
+
homepage: https://github.com/agardiner/color-console
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.21
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: ColorConsole is a cross-platform library for outputting colored text to the
|
55
|
+
console
|
56
|
+
test_files: []
|