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 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.
@@ -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,2 @@
1
+ require 'console/console'
2
+
@@ -0,0 +1,2 @@
1
+ require 'console/console'
2
+
@@ -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: []