tty-table 0.1.0 → 0.2.0

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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +458 -142
  4. data/lib/tty-table.rb +6 -6
  5. data/lib/tty/table.rb +34 -34
  6. data/lib/tty/table/alignment_set.rb +73 -0
  7. data/lib/tty/table/border.rb +54 -36
  8. data/lib/tty/table/border/null.rb +4 -4
  9. data/lib/tty/table/{columns.rb → column_constraint.rb} +30 -32
  10. data/lib/tty/table/column_set.rb +18 -17
  11. data/lib/tty/table/field.rb +50 -25
  12. data/lib/tty/table/header.rb +6 -2
  13. data/lib/tty/table/indentation.rb +7 -12
  14. data/lib/tty/table/operation/alignment.rb +59 -0
  15. data/lib/tty/table/operation/escape.rb +1 -1
  16. data/lib/tty/table/operation/filter.rb +1 -1
  17. data/lib/tty/table/operation/padding.rb +12 -61
  18. data/lib/tty/table/operation/truncation.rb +2 -2
  19. data/lib/tty/table/operation/wrapped.rb +2 -5
  20. data/lib/tty/table/operations.rb +35 -17
  21. data/lib/tty/table/orientation/vertical.rb +4 -4
  22. data/lib/tty/table/renderer.rb +1 -7
  23. data/lib/tty/table/renderer/basic.rb +69 -63
  24. data/lib/tty/table/version.rb +1 -1
  25. data/spec/spec_helper.rb +3 -4
  26. data/spec/unit/access_spec.rb +8 -8
  27. data/spec/unit/{operation/alignment_set → alignment_set}/each_spec.rb +1 -1
  28. data/spec/unit/{operation/alignment_set → alignment_set}/new_spec.rb +4 -4
  29. data/spec/unit/{operation/alignment_set → alignment_set}/to_ary_spec.rb +1 -1
  30. data/spec/unit/alignment_spec.rb +71 -0
  31. data/spec/unit/border/ascii/rendering_spec.rb +12 -12
  32. data/spec/unit/border/new_spec.rb +2 -2
  33. data/spec/unit/border/null/rendering_spec.rb +2 -2
  34. data/spec/unit/border/unicode/rendering_spec.rb +10 -10
  35. data/spec/unit/{columns → column_constraint}/enforce_spec.rb +15 -12
  36. data/spec/unit/{columns → column_constraint}/widths_spec.rb +6 -6
  37. data/spec/unit/column_set/extract_widths_spec.rb +39 -6
  38. data/spec/unit/data_spec.rb +4 -6
  39. data/spec/unit/each_spec.rb +8 -23
  40. data/spec/unit/each_with_index_spec.rb +27 -33
  41. data/spec/unit/field/length_spec.rb +23 -9
  42. data/spec/unit/field/width_spec.rb +1 -1
  43. data/spec/unit/filter_spec.rb +7 -8
  44. data/spec/unit/header/new_spec.rb +6 -15
  45. data/spec/unit/indentation/indent_spec.rb +21 -0
  46. data/spec/unit/new_spec.rb +73 -0
  47. data/spec/unit/operation/{alignment_set → alignment}/call_spec.rb +1 -1
  48. data/spec/unit/operation/escape/call_spec.rb +2 -3
  49. data/spec/unit/operation/filter/call_spec.rb +2 -3
  50. data/spec/unit/operation/truncation/call_spec.rb +6 -8
  51. data/spec/unit/operation/wrapped/call_spec.rb +15 -8
  52. data/spec/unit/operations/new_spec.rb +1 -1
  53. data/spec/unit/orientation_spec.rb +6 -6
  54. data/spec/unit/padding_spec.rb +29 -32
  55. data/spec/unit/properties_spec.rb +4 -4
  56. data/spec/unit/render_repeat_spec.rb +42 -0
  57. data/spec/unit/render_spec.rb +1 -1
  58. data/spec/unit/render_with_spec.rb +3 -3
  59. data/spec/unit/renderer/ascii/coloring_spec.rb +70 -0
  60. data/spec/unit/renderer/ascii/multiline_spec.rb +101 -0
  61. data/spec/unit/renderer/ascii/padding_spec.rb +37 -10
  62. data/spec/unit/renderer/ascii/render_spec.rb +4 -4
  63. data/spec/unit/renderer/ascii/resizing_spec.rb +22 -22
  64. data/spec/unit/renderer/ascii/separator_spec.rb +1 -1
  65. data/spec/unit/renderer/basic/alignment_spec.rb +20 -20
  66. data/spec/unit/renderer/basic/coloring_spec.rb +43 -28
  67. data/spec/unit/renderer/basic/filter_spec.rb +3 -3
  68. data/spec/unit/renderer/basic/multiline_spec.rb +74 -0
  69. data/spec/unit/renderer/basic/options_spec.rb +9 -9
  70. data/spec/unit/renderer/basic/padding_spec.rb +26 -2
  71. data/spec/unit/renderer/basic/render_spec.rb +4 -4
  72. data/spec/unit/renderer/basic/resizing_spec.rb +18 -18
  73. data/spec/unit/renderer/basic/separator_spec.rb +1 -1
  74. data/spec/unit/renderer/basic/truncation_spec.rb +6 -6
  75. data/spec/unit/renderer/basic/wrapping_spec.rb +3 -3
  76. data/spec/unit/renderer/border_spec.rb +4 -4
  77. data/spec/unit/renderer/unicode/coloring_spec.rb +70 -0
  78. data/spec/unit/renderer/unicode/indentation_spec.rb +1 -1
  79. data/spec/unit/renderer/unicode/padding_spec.rb +26 -26
  80. data/spec/unit/renderer/unicode/render_spec.rb +4 -4
  81. data/spec/unit/renderer/unicode/separator_spec.rb +1 -1
  82. data/spec/unit/to_s_spec.rb +4 -11
  83. data/spec/unit/utf_spec.rb +33 -0
  84. data/tty-table.gemspec +2 -1
  85. metadata +52 -32
  86. data/lib/tty/table/operation/alignment_set.rb +0 -103
  87. data/lib/tty/table/padder.rb +0 -180
  88. data/lib/tty/table/renderer/color.rb +0 -12
  89. data/spec/unit/indentation/insert_indent_spec.rb +0 -27
  90. data/spec/unit/initialize_spec.rb +0 -88
  91. data/spec/unit/padder/parse_spec.rb +0 -45
  92. data/spec/unit/padder/to_s_spec.rb +0 -14
  93. data/spec/unit/renderer/basic/multiline_content_spec.rb +0 -135
  94. data/spec/unit/renderer/style_spec.rb +0 -72
@@ -7,24 +7,21 @@ module TTY
7
7
  # Used internally by {Renderer::Basic} to enforce correct column widths.
8
8
  #
9
9
  # @api private
10
- class Columns
11
-
12
- attr_reader :table
13
-
14
- attr_reader :renderer
15
-
10
+ class ColumnConstraint
16
11
  MIN_WIDTH = 1
17
12
 
18
13
  BORDER_WIDTH = 1
19
14
 
20
15
  # Initialize a Columns
21
16
  #
17
+ # @param [TTY::Table] table
18
+ #
22
19
  # @param [TTY::Table::Renderer] renderer
23
20
  #
24
21
  # @api public
25
- def initialize(renderer)
22
+ def initialize(table, renderer)
23
+ @table = table
26
24
  @renderer = renderer
27
- @table = renderer.table
28
25
  end
29
26
 
30
27
  # Estimate outside border size
@@ -42,7 +39,17 @@ module TTY
42
39
  #
43
40
  # @api public
44
41
  def border_size
45
- BORDER_WIDTH * (table.column_size - 1) + outside_border_size
42
+ BORDER_WIDTH * (table.columns_size - 1) + outside_border_size
43
+ end
44
+
45
+ # Measure total padding size for a table
46
+ #
47
+ # @return [Integer]
48
+ #
49
+ # @api public
50
+ def padding_size
51
+ padding = renderer.padding
52
+ (padding.left + padding.right) * (table.columns_size - 1)
46
53
  end
47
54
 
48
55
  # Estimate minimum table width to be able to display content
@@ -51,7 +58,7 @@ module TTY
51
58
  #
52
59
  # @api public
53
60
  def minimum_width
54
- table.column_size * MIN_WIDTH + border_size
61
+ table.columns_size * MIN_WIDTH + border_size
55
62
  end
56
63
 
57
64
  # Return column's natural unconstrained widths
@@ -60,7 +67,7 @@ module TTY
60
67
  #
61
68
  # @api public
62
69
  def natural_width
63
- renderer.column_widths.inject(0, &:+) + border_size
70
+ renderer.column_widths.inject(0, &:+) + border_size + padding_size
64
71
  end
65
72
 
66
73
  # Return the constrained column widths.
@@ -72,33 +79,22 @@ module TTY
72
79
  def enforce
73
80
  assert_minimum_width
74
81
 
75
- unless renderer.padding.empty?
76
- renderer.column_widths = adjust_padding
77
- end
78
-
79
82
  if natural_width <= renderer.width
80
- renderer.column_widths = expand if renderer.resize
83
+ renderer.resize ? expand : renderer.column_widths
81
84
  else
82
85
  if renderer.resize
83
- renderer.column_widths = shrink
86
+ shrink
84
87
  else
85
88
  rotate
86
- renderer.column_widths = ColumnSet.widths_from(table)
87
89
  end
88
90
  end
89
91
  end
90
92
 
91
- # Adjust column widths to account for padding whitespace
92
- #
93
- # @api private
94
- def adjust_padding
95
- padding = renderer.padding
96
- column_size = table.column_size
93
+ private
97
94
 
98
- (0...column_size).reduce([]) do |lengths, col|
99
- lengths + [padding.left + renderer.column_widths[col] + padding.right]
100
- end
101
- end
95
+ attr_reader :table
96
+
97
+ attr_reader :renderer
102
98
 
103
99
  # Rotate table to vertical orientation and print information to stdout
104
100
  #
@@ -109,13 +105,14 @@ module TTY
109
105
  'orientation.'
110
106
  table.orientation = :vertical
111
107
  table.rotate
108
+ ColumnSet.widths_from(table)
112
109
  end
113
110
 
114
111
  # Expand column widths to match the requested width
115
112
  #
116
113
  # @api private
117
114
  def expand
118
- column_size = table.column_size
115
+ column_size = table.columns_size
119
116
  ratio = ((renderer.width - natural_width) / column_size.to_f).floor
120
117
 
121
118
  widths = (0...column_size).reduce([]) do |lengths, col|
@@ -128,7 +125,7 @@ module TTY
128
125
  #
129
126
  # @api private
130
127
  def shrink
131
- column_size = table.column_size
128
+ column_size = table.columns_size
132
129
  ratio = ((natural_width - renderer.width) / column_size.to_f).ceil
133
130
 
134
131
  widths = (0...column_size).reduce([]) do |lengths, col|
@@ -155,7 +152,8 @@ module TTY
155
152
  #
156
153
  # @api private
157
154
  def distribute_extra_width(widths)
158
- column_size = table.column_size
155
+ column_size = table.columns_size
156
+ # TODO - add padding size to fully check extra width
159
157
  extra_width = renderer.width - (widths.reduce(:+) + border_size)
160
158
  per_field_width = extra_width / column_size
161
159
  remaining_width = extra_width % column_size
@@ -165,6 +163,6 @@ module TTY
165
163
  width + per_field_width + extra[index]
166
164
  end
167
165
  end
168
- end # Columns
166
+ end # ColumnConstraint
169
167
  end # Table
170
168
  end # TTY
@@ -10,13 +10,13 @@ module TTY
10
10
  class ColumnSet
11
11
  include Equatable
12
12
 
13
- attr_reader :table
14
-
15
13
  # Initialize a ColumnSet
16
14
  #
15
+ # @param [Array[Array[Object]]] data
16
+ #
17
17
  # @api public
18
18
  def initialize(table)
19
- @table = table
19
+ @data = table.data
20
20
  end
21
21
 
22
22
  # Calculate total table width
@@ -34,10 +34,8 @@ module TTY
34
34
  #
35
35
  # @api private
36
36
  def extract_widths
37
- data = table.data
38
37
  colcount = data.max { |row_a, row_b| row_a.size <=> row_b.size }.size
39
-
40
- self.class.find_maximas(colcount, data)
38
+ find_maximas(colcount)
41
39
  end
42
40
 
43
41
  # Assert data integrity for column widths
@@ -72,10 +70,10 @@ module TTY
72
70
  def self.widths_from(table, column_widths = nil)
73
71
  case column_widths
74
72
  when Array
75
- assert_widths(column_widths, table.column_size)
73
+ assert_widths(column_widths, table.columns_size)
76
74
  Array(column_widths).map(&:to_i)
77
75
  when Numeric
78
- Array.new(table.column_size, column_widths)
76
+ Array.new(table.columns_size, column_widths)
79
77
  when NilClass
80
78
  new(table).extract_widths
81
79
  else
@@ -85,20 +83,22 @@ module TTY
85
83
 
86
84
  private
87
85
 
86
+ attr_reader :data
87
+
88
88
  # Find maximum widths for each row and header if present.
89
89
  #
90
90
  # @param [Integer] colcount
91
91
  # number of columns
92
- # @param [Array[Array]] data
93
- # the table's header and rows
92
+ #
93
+ # @return [Array[Integer]]
94
94
  #
95
95
  # @api private
96
- def self.find_maximas(colcount, data)
96
+ def find_maximas(colcount)
97
97
  maximas = []
98
98
  start = 0
99
99
 
100
100
  start.upto(colcount - 1) do |col_index|
101
- maximas << find_maximum(data, col_index)
101
+ maximas << find_maximum(col_index)
102
102
  end
103
103
  maximas
104
104
  end
@@ -106,15 +106,16 @@ module TTY
106
106
  # Find a maximum column width. The calculation takes into account
107
107
  # wether the content is escaped or not.
108
108
  #
109
- # @param [Array] data
110
- # the table data
111
- #
112
109
  # @param [Integer] index
113
110
  # the column index
114
111
  #
112
+ # @return [Integer]
113
+ #
115
114
  # @api private
116
- def self.find_maximum(data, index)
117
- data.map { |row| (value = row.call(index)) ? value.length : 0 }.max
115
+ def find_maximum(index)
116
+ data.map do |row|
117
+ (field = row.call(index)) ? field.length : 0
118
+ end.max
118
119
  end
119
120
  end # ColumnSet
120
121
  end # Table
@@ -4,7 +4,8 @@ module TTY
4
4
  class Table
5
5
  # A class that represents a unique element in a table.
6
6
  #
7
- # Used internally by {Table::Row} to define internal structure.
7
+ # Used internally by {Table::Header} and {Table::Row} to
8
+ # define internal structure.
8
9
  #
9
10
  # @api private
10
11
  class Field
@@ -15,28 +16,40 @@ module TTY
15
16
  # @api public
16
17
  attr_reader :value
17
18
 
19
+ # The formatted value inside the field used for display
20
+ #
21
+ # @api public
22
+ attr_reader :content
23
+
18
24
  # The name for the value
19
25
  #
20
26
  # @api public
21
27
  attr_reader :name
22
28
 
23
- # TODO: Change to :content to separate value from formatted string
29
+ # The actual value
30
+ #
31
+ # @api public
24
32
  attr_writer :value
25
33
 
26
- # The field value width
34
+ # The formatted string
27
35
  #
28
36
  # @api public
29
- attr_reader :width
37
+ attr_writer :content
30
38
 
31
39
  # Number of columns this field spans. Defaults to 1.
32
40
  #
41
+ # @api public
33
42
  attr_reader :colspan
34
43
 
35
44
  # Number of rows this field spans. Defaults to 1.
36
45
  #
46
+ # @api public
37
47
  attr_reader :rowspan
38
48
 
39
- attr_reader :align
49
+ # The field alignment
50
+ #
51
+ # @api public
52
+ attr_reader :alignment
40
53
 
41
54
  # Initialize a Field
42
55
  #
@@ -49,15 +62,16 @@ module TTY
49
62
  # field.value # => a1
50
63
  #
51
64
  # @example
52
- # field = TTY::Table::Field.new value: 'a1', align: :center
53
- # field.value # => a1
54
- # field.align # => :center
65
+ # field = TTY::Table::Field.new value: 'a1', alignment: :center
66
+ # field.value # => a1
67
+ # field.alignment # => :center
55
68
  #
56
69
  # @api private
57
70
  def initialize(value)
58
71
  options = extract_options(value)
59
- @width = options.fetch(:width) { @value.to_s.size }
60
- @align = options.fetch(:align) { nil }
72
+ @content = @value.to_s
73
+ @width = options[:width]
74
+ @alignment = options.fetch(:alignment) { nil }
61
75
  @colspan = options.fetch(:colspan) { 1 }
62
76
  @rowspan = options.fetch(:rowspan) { 1 }
63
77
  end
@@ -76,15 +90,18 @@ module TTY
76
90
  options
77
91
  end
78
92
 
79
- # Return the width this field would normally have bar other contraints
93
+ # Reset to original value
80
94
  #
81
95
  # @api public
82
- def value_width
83
- @width
96
+ def reset!
97
+ @content = @value.to_s
84
98
  end
85
99
 
86
- def value_height
87
- @height
100
+ # The content width
101
+ #
102
+ # @api public
103
+ def width
104
+ @width || UnicodeUtils.display_width(@content)
88
105
  end
89
106
 
90
107
  # Return number of lines this value spans.
@@ -95,8 +112,8 @@ module TTY
95
112
  #
96
113
  # @api public
97
114
  def lines
98
- escaped = value.to_s.scan(/(\\n|\\t|\\r)/)
99
- escaped.empty? ? value.to_s.split(/\n/, -1) : [value.to_s]
115
+ escaped = content.scan(/(\\n|\\t|\\r)/)
116
+ escaped.empty? ? content.split(/\n/, -1) : [content]
100
117
  end
101
118
 
102
119
  # If the string contains unescaped new lines then the longest token
@@ -106,7 +123,9 @@ module TTY
106
123
  #
107
124
  # @api public
108
125
  def length
109
- (lines.max_by(&:length) || '').size
126
+ (lines.map do |line|
127
+ display_width(self.class.color.strip(line))
128
+ end << 0).max
110
129
  end
111
130
 
112
131
  # Extract the number of lines this value spans
@@ -119,20 +138,26 @@ module TTY
119
138
  end
120
139
 
121
140
  def chars
122
- value.chars
141
+ content.chars
123
142
  end
124
143
 
125
- # Render value inside this field box
144
+ # Return field content
145
+ #
146
+ # @return [String]
126
147
  #
127
148
  # @api public
128
- def render
149
+ def to_s
150
+ content
129
151
  end
130
152
 
131
- # Return field value
132
- #
133
153
  # @api public
134
- def to_s
135
- value
154
+ def self.color
155
+ @color ||= Pastel.new(enabled: true)
156
+ end
157
+
158
+ # @api public
159
+ def display_width(string)
160
+ UnicodeUtils.display_width(string)
136
161
  end
137
162
  end # Field
138
163
  end # Table
@@ -39,8 +39,8 @@ module TTY
39
39
  # Iterate over each element in the vector
40
40
  #
41
41
  # @example
42
- # vec = Vector[1,2,3]
43
- # vec.each { |element| ... }
42
+ # header = TTY::Table::Header.new [1,2,3]
43
+ # header.each { |element| ... }
44
44
  #
45
45
  # @return [self]
46
46
  #
@@ -158,6 +158,10 @@ module TTY
158
158
  def to_hash
159
159
  to_a.hash
160
160
  end
161
+
162
+ def inspect
163
+ "#<#{self.class.name} fields=#{to_a}>"
164
+ end
161
165
  end # Header
162
166
  end # Table
163
167
  end # TTY
@@ -4,21 +4,16 @@ module TTY
4
4
  class Table
5
5
  # A class responsible for indenting table representation
6
6
  class Indentation
7
-
8
- attr_reader :renderer
9
-
10
- # Initialize an Indentation
7
+ # The amount of indentation
11
8
  #
12
9
  # @api public
13
- def initialize(renderer)
14
- @renderer = renderer
15
- end
10
+ attr_accessor :indentation
16
11
 
17
- # Create indentation
12
+ # Initialize an Indentation
18
13
  #
19
14
  # @api public
20
- def indentation
21
- ' ' * renderer.indent
15
+ def initialize(indentation)
16
+ @indentation = indentation
22
17
  end
23
18
 
24
19
  # Return a table part with indentation inserted
@@ -27,7 +22,7 @@ module TTY
27
22
  # the rendered table part
28
23
  #
29
24
  # @api public
30
- def insert_indent(part)
25
+ def indent(part)
31
26
  if part.respond_to?(:to_a)
32
27
  part.map { |line| insert_indentation(line) }
33
28
  else
@@ -45,7 +40,7 @@ module TTY
45
40
  # @api public
46
41
  def insert_indentation(line)
47
42
  line = line.is_a?(Array) ? line[0] : line
48
- line.insert(0, indentation) if line
43
+ line.insert(0, ' ' * indentation) if line
49
44
  end
50
45
  end # Indentation
51
46
  end # Table