tty 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +45 -6
- data/lib/tty.rb +23 -2
- data/lib/tty/color.rb +0 -4
- data/lib/tty/support/conversion.rb +34 -0
- data/lib/tty/support/delegatable.rb +44 -0
- data/lib/tty/support/utils.rb +2 -0
- data/lib/tty/system.rb +31 -0
- data/lib/tty/table.rb +155 -20
- data/lib/tty/table/error.rb +10 -0
- data/lib/tty/table/renderer.rb +16 -6
- data/lib/tty/table/renderer/basic.rb +60 -20
- data/lib/tty/table/renderer/color.rb +1 -1
- data/lib/tty/table/renderer/unicode.rb +1 -1
- data/lib/tty/table/validatable.rb +34 -0
- data/lib/tty/terminal.rb +149 -0
- data/lib/tty/version.rb +1 -1
- data/spec/tty/support/conversion_spec.rb +38 -0
- data/spec/tty/support/delegatable_spec.rb +26 -0
- data/spec/tty/support/fixtures/classes.rb +17 -0
- data/spec/tty/system/platform_spec.rb +21 -0
- data/spec/tty/table/access_spec.rb +59 -0
- data/spec/tty/table/eql_spec.rb +28 -0
- data/spec/tty/table/initialize_spec.rb +53 -0
- data/spec/tty/table/properties_spec.rb +24 -0
- data/spec/tty/table/renderer/basic_spec.rb +20 -0
- data/spec/tty/table/renderer_spec.rb +7 -0
- data/spec/tty/table/validatable_spec.rb +19 -0
- data/spec/tty/terminal/size_spec.rb +94 -0
- metadata +34 -11
- data/lib/tty/support/.utils.rb.swo +0 -0
- data/spec/tty/table/table_spec.rb +0 -75
data/lib/tty/table/renderer.rb
CHANGED
@@ -5,7 +5,7 @@ module TTY
|
|
5
5
|
|
6
6
|
# @api public
|
7
7
|
def self.renderer
|
8
|
-
@renderer ||= if TTY
|
8
|
+
@renderer ||= if TTY.terminal.color?
|
9
9
|
TTY::Table::Renderer::Color
|
10
10
|
else
|
11
11
|
TTY::Table::Renderer::Basic
|
@@ -23,11 +23,14 @@ module TTY
|
|
23
23
|
#
|
24
24
|
# @api public
|
25
25
|
module Renderer
|
26
|
+
extend TTY::Delegatable
|
26
27
|
|
27
28
|
autoload :Basic, 'tty/table/renderer/basic'
|
28
29
|
autoload :Color, 'tty/table/renderer/color'
|
29
30
|
autoload :Unicode, 'tty/table/renderer/unicode'
|
30
31
|
|
32
|
+
RENDERER_DELEGATED_METHODS = [ :render, :extract_column_widths, :total_width]
|
33
|
+
|
31
34
|
RENDERER_MAPPER = {
|
32
35
|
:basic => TTY::Table::Renderer::Basic,
|
33
36
|
:color => TTY::Table::Renderer::Color,
|
@@ -43,13 +46,18 @@ module TTY
|
|
43
46
|
end
|
44
47
|
|
45
48
|
def pick_renderer(renderer)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
if renderer
|
50
|
+
RENDERER_MAPPER[renderer].new
|
51
|
+
else
|
52
|
+
self.renderer
|
53
|
+
end
|
51
54
|
end
|
52
55
|
|
56
|
+
# Return the default renderer
|
57
|
+
#
|
58
|
+
# @return [TTY::Table::Renderer]
|
59
|
+
#
|
60
|
+
# @api public
|
53
61
|
def renderer
|
54
62
|
@renderer ||= TTY::Table.renderer.new
|
55
63
|
end
|
@@ -58,6 +66,8 @@ module TTY
|
|
58
66
|
@renderer = renderer
|
59
67
|
end
|
60
68
|
|
69
|
+
delegatable_method :renderer, *RENDERER_DELEGATED_METHODS
|
70
|
+
|
61
71
|
end # Renderer
|
62
72
|
|
63
73
|
end # Table
|
@@ -9,6 +9,10 @@ module TTY
|
|
9
9
|
|
10
10
|
attr_reader :indent
|
11
11
|
|
12
|
+
attr_reader :column_widths
|
13
|
+
|
14
|
+
attr_reader :rows
|
15
|
+
|
12
16
|
# @param [Hash] options
|
13
17
|
# :indent - Indent the first column by indent value
|
14
18
|
# :padding - Pad out the row cell by padding value
|
@@ -16,9 +20,10 @@ module TTY
|
|
16
20
|
#
|
17
21
|
# @return [Table::Renderer::Basic]
|
18
22
|
def initialize(options={})
|
19
|
-
@padding =
|
23
|
+
@padding = 0
|
20
24
|
@indent = options.fetch :indent, 0
|
21
|
-
@
|
25
|
+
@column_widths = []
|
26
|
+
@column_aligns = options.fetch :column_aligns, []
|
22
27
|
end
|
23
28
|
|
24
29
|
# Sets the output padding,
|
@@ -36,12 +41,6 @@ module TTY
|
|
36
41
|
new(options).render(rows)
|
37
42
|
end
|
38
43
|
|
39
|
-
# @api private
|
40
|
-
def extract_column_widths
|
41
|
-
# TODO Calculate column widths if none provided
|
42
|
-
# throw an error if too many columns as compared to terminal width
|
43
|
-
end
|
44
|
-
|
45
44
|
# Renders table
|
46
45
|
#
|
47
46
|
# @param [Enumerable] rows
|
@@ -50,24 +49,65 @@ module TTY
|
|
50
49
|
# @return [String] string representation of table
|
51
50
|
#
|
52
51
|
# @api public
|
53
|
-
def render(rows)
|
52
|
+
def render(rows, options={})
|
54
53
|
return if rows.empty?
|
55
|
-
|
54
|
+
# TODO: Decide about table orientation
|
55
|
+
@rows = rows
|
56
56
|
body = []
|
57
57
|
unless rows.length.zero?
|
58
|
-
rows
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
extract_column_widths(rows)
|
59
|
+
body += render_rows
|
60
|
+
end
|
61
|
+
body.join("\n")
|
62
|
+
end
|
63
|
+
|
64
|
+
# Calcualte total table width
|
65
|
+
#
|
66
|
+
# @return [Integer]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def total_width
|
70
|
+
column_widths.reduce(:+)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Calcualte maximum column widths
|
74
|
+
#
|
75
|
+
# @return [Array] column widths
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def extract_column_widths(rows)
|
79
|
+
# TODO: throw an error if too many columns as compared to terminal width
|
80
|
+
colcount = rows.max{ |a,b| a.size <=> b.size }.size
|
81
|
+
maximas = []
|
82
|
+
start = 0
|
83
|
+
|
84
|
+
start.upto(colcount - 1) do |index|
|
85
|
+
maximum = rows.map { |row|
|
86
|
+
row[index] ? (row[index].to_s.size) : 0
|
87
|
+
}.max
|
88
|
+
maximas << maximum
|
89
|
+
end
|
90
|
+
@column_widths = maximas
|
91
|
+
end
|
92
|
+
|
93
|
+
# Adjust the rows to maximum widths
|
94
|
+
#
|
95
|
+
# @return [Arrays[String]]
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
def render_rows
|
99
|
+
rows.map do |row|
|
100
|
+
line = ""
|
101
|
+
row.each_with_index do |column, index|
|
102
|
+
column_width = column_widths[index]
|
103
|
+
if index == row.size - 1
|
104
|
+
line << "%-#{column_width}s" % column.to_s
|
105
|
+
else
|
106
|
+
line << "%-#{column_width}s " % column.to_s
|
66
107
|
end
|
67
|
-
body << line
|
68
108
|
end
|
109
|
+
line
|
69
110
|
end
|
70
|
-
body.join("\n")
|
71
111
|
end
|
72
112
|
|
73
113
|
end # Basic
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Table
|
5
|
+
module Validatable
|
6
|
+
|
7
|
+
MIN_CELL_WIDTH = 3.freeze
|
8
|
+
|
9
|
+
# Check if table rows are the equal size
|
10
|
+
#
|
11
|
+
# @raise [DimensionMismatchError]
|
12
|
+
# if the rows are not equal length
|
13
|
+
#
|
14
|
+
# @return [nil]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def assert_row_sizes(rows)
|
18
|
+
size = (rows[0] || []).size
|
19
|
+
rows.each do |row|
|
20
|
+
if not row.size == size
|
21
|
+
raise TTY::Table::DimensionMismatchError, "row size differs (#{row.size} should be #{size})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert_matching_widths(rows)
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_string_values(rows)
|
30
|
+
end
|
31
|
+
|
32
|
+
end # Validatable
|
33
|
+
end # Table
|
34
|
+
end # TTY
|
data/lib/tty/terminal.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Terminal
|
5
|
+
|
6
|
+
@@default_width = 80
|
7
|
+
|
8
|
+
@@default_height = 24
|
9
|
+
|
10
|
+
# Return default width of terminal
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# default_width = TTY::Terminal.default_width
|
14
|
+
#
|
15
|
+
# @return [Integer]
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def default_width
|
19
|
+
@@default_width
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set default width of terminal
|
23
|
+
#
|
24
|
+
# @param [Integer] width
|
25
|
+
#
|
26
|
+
# @return [Integer]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def default_width=(width)
|
30
|
+
@@default_width = width
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return default height of terminal
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# default_height = TTY::Terminal.default_height
|
37
|
+
#
|
38
|
+
# @return [Integer]
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def default_height
|
42
|
+
@@default_height
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set default height of terminal
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# default_height = TTY::Terminal.default_height
|
49
|
+
#
|
50
|
+
# @return [Integer]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def default_height=(height)
|
54
|
+
@@default_height = height
|
55
|
+
end
|
56
|
+
|
57
|
+
# Determine current width
|
58
|
+
#
|
59
|
+
# @return [Integer] width
|
60
|
+
#
|
61
|
+
# @api width
|
62
|
+
def width
|
63
|
+
if ENV['TTY_COLUMNS'] =~ /^\d+$/
|
64
|
+
result = ENV['TTY_COLUMNS'].to_i
|
65
|
+
else
|
66
|
+
result = TTY::System.unix? ? dynamic_width : default_width
|
67
|
+
end
|
68
|
+
rescue
|
69
|
+
default_width
|
70
|
+
end
|
71
|
+
|
72
|
+
# Determine current height
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def height
|
76
|
+
if ENV['TTY_LINES'] =~ /^\d+$/
|
77
|
+
result = ENV['TTY_LINES'].to_i
|
78
|
+
else
|
79
|
+
result = TTY::System.unix? ? dynamic_height : self.default_height
|
80
|
+
end
|
81
|
+
rescue
|
82
|
+
self.default_height
|
83
|
+
end
|
84
|
+
|
85
|
+
# Calculate dynamic width of the terminal
|
86
|
+
#
|
87
|
+
# @return [Integer] width
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def dynamic_width
|
91
|
+
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Calculate dynamic height of the terminal
|
95
|
+
#
|
96
|
+
# @return [Integer] height
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
def dynamic_height
|
100
|
+
@dynamic_height ||= (dynamic_height_stty.nonzero? || dynamic_height_tput)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Detect terminal width with stty utility
|
104
|
+
#
|
105
|
+
# @return [Integer] width
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def dynamic_width_stty
|
109
|
+
%x{stty size 2>/dev/null}.split[1].to_i
|
110
|
+
end
|
111
|
+
|
112
|
+
# Detect terminal height with stty utility
|
113
|
+
#
|
114
|
+
# @return [Integer] height
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
def dynamic_height_stty
|
118
|
+
%x{stty size 2>/dev/null}.split[0].to_i
|
119
|
+
end
|
120
|
+
|
121
|
+
# Detect terminal width with tput utility
|
122
|
+
#
|
123
|
+
# @return [Integer] width
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def dynamic_width_tput
|
127
|
+
%x{tput cols 2>/dev/null}.to_i
|
128
|
+
end
|
129
|
+
|
130
|
+
# Detect terminal height with tput utility
|
131
|
+
#
|
132
|
+
# @return [Integer] height
|
133
|
+
#
|
134
|
+
# @api public
|
135
|
+
def dynamic_height_tput
|
136
|
+
%x{tput lines 2>/dev/null}.to_i
|
137
|
+
end
|
138
|
+
|
139
|
+
# Check if terminal supports color
|
140
|
+
#
|
141
|
+
# @return [Boolean]
|
142
|
+
#
|
143
|
+
# @api public
|
144
|
+
def color?
|
145
|
+
%x{tput colors 2>/dev/null}.to_i > 2
|
146
|
+
end
|
147
|
+
|
148
|
+
end # Terminal
|
149
|
+
end # TTY
|
data/lib/tty/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe TTY::Conversion do
|
6
|
+
let(:described_class) { Class.new { include TTY::Conversion } }
|
7
|
+
let(:object) { described_class.new }
|
8
|
+
let(:enumerable) { [] }
|
9
|
+
|
10
|
+
subject { object.convert_to_array(enumerable) }
|
11
|
+
|
12
|
+
context 'Array type' do
|
13
|
+
it { should == enumerable }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'Hash type' do
|
17
|
+
let(:enumerable) { {:a => 1, :b => 2} }
|
18
|
+
|
19
|
+
it { should include([:a, 1]) }
|
20
|
+
|
21
|
+
it { should include([:b, 2]) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'responds to #to_ary' do
|
25
|
+
let(:converted) { [] }
|
26
|
+
let(:enumerable) { mock('Enumerable', :to_ary => converted) }
|
27
|
+
|
28
|
+
it { should == converted }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'does not respond to #to_ary' do
|
32
|
+
let(:enumerable) { mock('Enumerable') }
|
33
|
+
|
34
|
+
it 'raises error' do
|
35
|
+
expect { subject}.to raise_error(TTY::TypeError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require File.expand_path('../fixtures/classes', __FILE__)
|
5
|
+
|
6
|
+
describe TTY::Delegatable do
|
7
|
+
let(:target) { :test }
|
8
|
+
let(:methods) { [:output] }
|
9
|
+
let(:object) { Class.new(DelegetableSpec::Object)}
|
10
|
+
let(:delegatable) { object.new }
|
11
|
+
|
12
|
+
subject { object.delegatable_method target, *methods }
|
13
|
+
|
14
|
+
it 'creates a method #output' do
|
15
|
+
expect { subject }.to change { delegatable.respond_to?(:output) }.
|
16
|
+
from(false).
|
17
|
+
to(true)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'delegates #output to #test' do
|
21
|
+
subject
|
22
|
+
value = mock('value')
|
23
|
+
delegatable.should_receive(:output).and_return(value)
|
24
|
+
delegatable.output.should == value
|
25
|
+
end
|
26
|
+
end
|