tty 0.0.1 → 0.0.2
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/.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
|