color-console 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|