tty 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -1
- data/README.md +19 -7
- data/lib/tty.rb +10 -0
- data/lib/tty/support/equatable.rb +1 -0
- data/lib/tty/support/unicode.rb +36 -0
- data/lib/tty/table.rb +3 -3
- data/lib/tty/table/border.rb +104 -0
- data/lib/tty/table/border/ascii.rb +41 -0
- data/lib/tty/table/border/null.rb +49 -0
- data/lib/tty/table/border/unicode.rb +41 -0
- data/lib/tty/table/column_set.rb +77 -0
- data/lib/tty/table/operation/alignment.rb +2 -0
- data/lib/tty/table/operation/alignment_set.rb +4 -58
- data/lib/tty/table/operation/truncation.rb +15 -19
- data/lib/tty/table/operation/wrapped.rb +34 -0
- data/lib/tty/table/renderer.rb +8 -11
- data/lib/tty/table/renderer/ascii.rb +15 -0
- data/lib/tty/table/renderer/basic.rb +21 -34
- data/lib/tty/table/renderer/unicode.rb +2 -2
- data/lib/tty/vector.rb +117 -0
- data/lib/tty/version.rb +1 -1
- data/spec/tty/table/border/ascii/rendering_spec.rb +49 -0
- data/spec/tty/table/border/new_spec.rb +21 -0
- data/spec/tty/table/border/null/rendering_spec.rb +49 -0
- data/spec/tty/table/border/unicode/rendering_spec.rb +49 -0
- data/spec/tty/table/column_set/extract_widths_spec.rb +26 -0
- data/spec/tty/table/operation/alignment_set/align_rows_spec.rb +4 -4
- data/spec/tty/table/operation/alignment_set/new_spec.rb +1 -1
- data/spec/tty/table/operation/wrapped/wrap_spec.rb +22 -0
- data/spec/tty/table/properties_spec.rb +3 -0
- data/spec/tty/table/renderer/basic/render_spec.rb +144 -33
- data/spec/tty/table/renderer/pick_renderer_spec.rb +25 -0
- data/spec/tty/table/to_s_spec.rb +51 -0
- data/spec/tty/vector/new_spec.rb +47 -0
- metadata +35 -8
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -10,8 +10,7 @@ Toolbox for developing CLI clients in Ruby.
|
|
10
10
|
|
11
11
|
Jump-start development of your command line app:
|
12
12
|
|
13
|
-
* Fully customizable table rendering with an easy-to-use API.
|
14
|
-
(status: In Progress)
|
13
|
+
* Fully customizable table rendering with an easy-to-use API. (status: In Progress)
|
15
14
|
* Terminal output colorization. (status: TODO)
|
16
15
|
* Terminal & System detection utilities. (status: In Progress)
|
17
16
|
* Text alignment/padding and diffs. (status: TODO)
|
@@ -85,15 +84,24 @@ or pass your rows in a block
|
|
85
84
|
And then to print do
|
86
85
|
|
87
86
|
```ruby
|
88
|
-
table.to_s
|
89
|
-
|
87
|
+
table.to_s
|
88
|
+
|
89
|
+
a1 a2 a3
|
90
|
+
b1 b2 b3
|
90
91
|
```
|
91
92
|
|
92
|
-
To print `
|
93
|
+
To print border around data table you need to specify `renderer` type out of `basic`, `ascii`, `unicode`. For instance to output unicode border:
|
93
94
|
|
94
|
-
```
|
95
|
-
table = TTY::Table.new renderer: 'unicode'
|
95
|
+
```
|
96
|
+
table = TTY::Table.new ['header1', 'header2'], [['a1', 'a2'], ['b1', 'b2'], renderer: 'unicode'
|
96
97
|
table.to_s
|
98
|
+
|
99
|
+
┌───────┬───────┐
|
100
|
+
│header1│header2│
|
101
|
+
├───────┼───────┤
|
102
|
+
│a1 │a2 │
|
103
|
+
│b1 │b2 │
|
104
|
+
└───────┴───────┘
|
97
105
|
```
|
98
106
|
|
99
107
|
### Terminal
|
@@ -105,6 +113,10 @@ To print `unicode` table
|
|
105
113
|
term.color? # => true or false
|
106
114
|
```
|
107
115
|
|
116
|
+
### Shell
|
117
|
+
|
118
|
+
Main responsibility is to interact with the prompt and provide convenience methods.
|
119
|
+
|
108
120
|
### System
|
109
121
|
|
110
122
|
```ruby
|
data/lib/tty.rb
CHANGED
@@ -7,15 +7,25 @@ require 'tty/support/delegatable'
|
|
7
7
|
require 'tty/support/conversion'
|
8
8
|
require 'tty/support/coercion'
|
9
9
|
require 'tty/support/equatable'
|
10
|
+
require 'tty/support/unicode'
|
10
11
|
|
11
12
|
require 'tty/color'
|
12
13
|
require 'tty/terminal'
|
13
14
|
require 'tty/system'
|
14
15
|
require 'tty/table'
|
16
|
+
require 'tty/vector'
|
17
|
+
|
18
|
+
require 'tty/table/border'
|
19
|
+
require 'tty/table/border/unicode'
|
20
|
+
require 'tty/table/border/ascii'
|
21
|
+
require 'tty/table/border/null'
|
22
|
+
|
23
|
+
require 'tty/table/column_set'
|
15
24
|
|
16
25
|
require 'tty/table/operation/alignment_set'
|
17
26
|
require 'tty/table/operation/alignment'
|
18
27
|
require 'tty/table/operation/truncation'
|
28
|
+
require 'tty/table/operation/wrapped'
|
19
29
|
|
20
30
|
module TTY
|
21
31
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
# A mixin to provide unicode support.
|
5
|
+
module Unicode
|
6
|
+
|
7
|
+
def utf8?(string)
|
8
|
+
string.unpack('U*') rescue return false
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def clean_utf8(string)
|
13
|
+
require 'iconv'
|
14
|
+
if defined? ::Iconv
|
15
|
+
converter = Iconv.new('UTF-8//IGNORE', 'UTF-8')
|
16
|
+
converter.iconv(string)
|
17
|
+
end
|
18
|
+
rescue Exception
|
19
|
+
string
|
20
|
+
end
|
21
|
+
|
22
|
+
if "".respond_to?(:encode)
|
23
|
+
def as_unicode
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
else
|
27
|
+
def as_unicode
|
28
|
+
old, $KCODE = $KCODE, "U"
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
$KCODE = old
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end # Unicode
|
36
|
+
end # TTY
|
data/lib/tty/table.rb
CHANGED
@@ -111,8 +111,9 @@ module TTY
|
|
111
111
|
@rows = coerce(options.fetch :rows, [])
|
112
112
|
@renderer = pick_renderer options[:renderer]
|
113
113
|
# TODO: assert that row_size is the same as column widths & aligns
|
114
|
+
# TODO: this is where column extraction should happen!
|
114
115
|
@column_widths = options.fetch :column_widths, []
|
115
|
-
@alignments = Operation::AlignmentSet.new options[:column_aligns]
|
116
|
+
@alignments = Operation::AlignmentSet.new options[:column_aligns] || []
|
116
117
|
|
117
118
|
assert_row_sizes @rows
|
118
119
|
yield_or_eval &block if block_given?
|
@@ -267,8 +268,7 @@ module TTY
|
|
267
268
|
#
|
268
269
|
# @api public
|
269
270
|
def width
|
270
|
-
|
271
|
-
total_width
|
271
|
+
ColumnSet.new(self).extract_widths!.total_width
|
272
272
|
end
|
273
273
|
|
274
274
|
# Return true if this is an empty table, i.e. if the number of
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Table
|
5
|
+
|
6
|
+
# Abstract base class that is responsible for building the table border.
|
7
|
+
class Border
|
8
|
+
include Unicode
|
9
|
+
|
10
|
+
NEWLINE = "\n"
|
11
|
+
|
12
|
+
# The row cell widths
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
attr_reader :widths
|
16
|
+
private :widths
|
17
|
+
|
18
|
+
# The table row
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
attr_reader :row
|
22
|
+
private :row
|
23
|
+
|
24
|
+
# Instantiate a new object
|
25
|
+
#
|
26
|
+
# @return [Object]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def initialize(*)
|
30
|
+
if self.class == Border
|
31
|
+
raise NotImplementedError, "#{self} is an abstract class"
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# A line spanning all columns marking top of a table.
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def top_line
|
43
|
+
render :top
|
44
|
+
end
|
45
|
+
|
46
|
+
# A line spanning all columns delemeting rows in a table.
|
47
|
+
#
|
48
|
+
# @return [String]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
def separator
|
52
|
+
render :mid
|
53
|
+
end
|
54
|
+
|
55
|
+
# A line spanning all columns delemeting cells in a row.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
def row_line
|
61
|
+
self['left'] + row.join(self['right']) + self['right']
|
62
|
+
end
|
63
|
+
|
64
|
+
# A line spannig all columns marking bottom of a table.
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
def bottom_line
|
70
|
+
render :bottom
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
# Generate particular border type
|
76
|
+
#
|
77
|
+
# @param [String] type
|
78
|
+
# border type one of [:top, :bottom, :mid]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
def render(type)
|
82
|
+
type = type.to_s
|
83
|
+
render_line self[type],
|
84
|
+
self["#{type}_left"] || self[type],
|
85
|
+
self["#{type}_right"] || self[type],
|
86
|
+
self["#{type}_mid"]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Generate a border string
|
90
|
+
#
|
91
|
+
# @param [String] line
|
92
|
+
#
|
93
|
+
# @return [String]
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def render_line(line, left, right, intersection)
|
97
|
+
as_unicode do
|
98
|
+
left + widths.map { |width| line * width }.join(intersection) + right
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end # Border
|
103
|
+
end # Table
|
104
|
+
end # TTY
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Table
|
5
|
+
class Border
|
6
|
+
|
7
|
+
# A class that represents an ascii border.
|
8
|
+
class ASCII < Border
|
9
|
+
|
10
|
+
BORDER_TYPE = {
|
11
|
+
'top' => '-',
|
12
|
+
'top_mid' => '+',
|
13
|
+
'top_left' => '+',
|
14
|
+
'top_right' => '+',
|
15
|
+
'bottom' => '-',
|
16
|
+
'bottom_mid' => '+',
|
17
|
+
'bottom_left' => '+',
|
18
|
+
'bottom_right' => '+',
|
19
|
+
'mid' => '-',
|
20
|
+
'mid_mid' => '+',
|
21
|
+
'mid_left' => '+',
|
22
|
+
'mid_right' => '+',
|
23
|
+
'left' => '|',
|
24
|
+
'right' => '|'
|
25
|
+
}
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def [](type)
|
29
|
+
BORDER_TYPE[type]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def initialize(row)
|
34
|
+
@row = row
|
35
|
+
@widths = row.map { |cell| cell.chars.to_a.size }
|
36
|
+
end
|
37
|
+
|
38
|
+
end # ASCII
|
39
|
+
end # Border
|
40
|
+
end # Table
|
41
|
+
end # TTY
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Table
|
5
|
+
class Border
|
6
|
+
|
7
|
+
# A class that represents no border.
|
8
|
+
class Null < Border
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
def initialize(row)
|
12
|
+
@row = row
|
13
|
+
@widths = row.map { |cell| cell.chars.to_a.size }
|
14
|
+
end
|
15
|
+
|
16
|
+
# A stub top line
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
def top_line
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# A stub separator line
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
def separator
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# A line spanning all columns delemited by space character.
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def row_line
|
36
|
+
row.join(' ')
|
37
|
+
end
|
38
|
+
|
39
|
+
# A stub bottom line
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def bottom_line
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
end # Null
|
47
|
+
end # Border
|
48
|
+
end # Table
|
49
|
+
end # TTY
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Table
|
5
|
+
class Border
|
6
|
+
|
7
|
+
# A class that represents a unicode border.
|
8
|
+
class Unicode < Border
|
9
|
+
|
10
|
+
BORDER_TYPE = {
|
11
|
+
'top' => '─',
|
12
|
+
'top_mid' => '┬',
|
13
|
+
'top_left' => '┌',
|
14
|
+
'top_right' => '┐',
|
15
|
+
'bottom' => '─',
|
16
|
+
'bottom_mid' => '┴',
|
17
|
+
'bottom_left' => '└',
|
18
|
+
'bottom_right' => '┘',
|
19
|
+
'mid' => '─',
|
20
|
+
'mid_mid' => '┼',
|
21
|
+
'mid_left' => '├',
|
22
|
+
'mid_right' => '┤',
|
23
|
+
'left' => '│',
|
24
|
+
'right' => '│'
|
25
|
+
}
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def [](type)
|
29
|
+
BORDER_TYPE[type]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def initialize(row)
|
34
|
+
@row = row
|
35
|
+
@widths = row.map { |cell| cell.chars.to_a.size }
|
36
|
+
end
|
37
|
+
|
38
|
+
end # Unicode
|
39
|
+
end # Border
|
40
|
+
end # Table
|
41
|
+
end # TTY
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Table
|
5
|
+
|
6
|
+
# A class that represents table columns properties.
|
7
|
+
class ColumnSet
|
8
|
+
include Equatable
|
9
|
+
extend Delegatable
|
10
|
+
|
11
|
+
attr_reader :table
|
12
|
+
|
13
|
+
delegatable_method :table, :column_widths
|
14
|
+
|
15
|
+
def initialize(table)
|
16
|
+
@table = table
|
17
|
+
end
|
18
|
+
|
19
|
+
# Calculate total table width
|
20
|
+
#
|
21
|
+
# @return [Integer]
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def total_width
|
25
|
+
column_widths.reduce(:+)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Calcualte maximum column widths
|
29
|
+
#
|
30
|
+
# @return [Array] column widths
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def extract_widths!
|
34
|
+
return column_widths unless column_widths.empty?
|
35
|
+
|
36
|
+
rows = table.to_a
|
37
|
+
data = table.header ? rows + [table.header] : rows
|
38
|
+
colcount = data.max { |row_a, row_b| row_a.size <=> row_b.size }.size
|
39
|
+
|
40
|
+
table.column_widths = find_maximas colcount, data
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Find maximum widths for each row and header if present.
|
47
|
+
#
|
48
|
+
# @param [Integer] colcount
|
49
|
+
# number of columns
|
50
|
+
# @param [Array[Array]] data
|
51
|
+
# the table's header and rows
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
def find_maximas(colcount, data)
|
55
|
+
maximas = []
|
56
|
+
start = 0
|
57
|
+
|
58
|
+
start.upto(colcount - 1) do |index|
|
59
|
+
maximas << find_maximum(data, index)
|
60
|
+
end
|
61
|
+
maximas
|
62
|
+
end
|
63
|
+
|
64
|
+
# Find a maximum column width.
|
65
|
+
#
|
66
|
+
# @param [Array] data
|
67
|
+
#
|
68
|
+
# @param [Integer] index
|
69
|
+
#
|
70
|
+
# @api private
|
71
|
+
def find_maximum(data, index)
|
72
|
+
data.map { |row| row[index] ? (row[index].to_s.size) : 0 }.max
|
73
|
+
end
|
74
|
+
|
75
|
+
end # ColumnSet
|
76
|
+
end # Table
|
77
|
+
end # TTY
|