tabulo 2.3.3 → 2.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,12 +1,17 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
  require "rake-version"
4
+ require "yard"
4
5
 
5
6
  RSpec::Core::RakeTask.new(:spec)
6
7
 
7
- task :default => :spec
8
+ task default: :spec
8
9
 
9
10
  RakeVersion::Tasks.new do |v|
10
11
  v.copy "lib/tabulo/version.rb"
11
12
  v.copy "README.md", all: true
12
13
  end
14
+
15
+ YARD::Rake::YardocTask.new do |t|
16
+ t.options = ["--markup-provider=redcarpet", "--markup=markdown"]
17
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.3
1
+ 2.6.1
@@ -4,6 +4,7 @@ require "tabulo/util"
4
4
  require "tabulo/table"
5
5
  require "tabulo/version"
6
6
  require "tabulo/row"
7
+ require "tabulo/cell_data"
7
8
  require "tabulo/cell"
8
9
  require "tabulo/column"
9
10
  require "tabulo/border"
@@ -3,118 +3,77 @@ module Tabulo
3
3
  # @!visibility private
4
4
  class Border
5
5
 
6
+ Style = Struct.new(
7
+ :corner_top_left, :corner_top_right, :corner_bottom_right, :corner_bottom_left,
8
+ :edge_top, :edge_right, :edge_bottom, :edge_left,
9
+ :tee_top, :tee_right, :tee_bottom, :tee_left,
10
+ :divider_vertical, :divider_horizontal, :intersection)
11
+
6
12
  STYLES = {
7
- ascii: {
8
- corner_top_left: "+",
9
- corner_top_right: "+",
10
- corner_bottom_right: "+",
11
- corner_bottom_left: "+",
12
- edge_top: "-",
13
- edge_right: "|",
14
- edge_bottom: "-",
15
- edge_left: "|",
16
- tee_top: "+",
17
- tee_right: "+",
18
- tee_bottom: "+",
19
- tee_left: "+",
20
- divider_vertical: "|",
21
- divider_horizontal: "-",
22
- intersection: "+",
23
- },
24
- classic: {
25
- corner_top_left: "+",
26
- corner_top_right: "+",
27
- edge_top: "-",
28
- edge_right: "|",
29
- edge_left: "|",
30
- tee_top: "+",
31
- tee_right: "+",
32
- tee_left: "+",
33
- divider_vertical: "|",
34
- divider_horizontal: "-",
35
- intersection: "+",
36
- },
37
- reduced_ascii: {
38
- corner_top_left: "",
39
- corner_top_right: "",
40
- corner_bottom_right: "",
41
- corner_bottom_left: "",
42
- edge_top: "-",
43
- edge_right: "",
44
- edge_bottom: "-",
45
- edge_left: "",
46
- tee_top: " ",
47
- tee_right: "",
48
- tee_bottom: " ",
49
- tee_left: "",
50
- divider_vertical: " ",
51
- divider_horizontal: "-",
52
- intersection: " ",
53
- },
54
- reduced_modern: {
55
- corner_top_left: "",
56
- corner_top_right: "",
57
- corner_bottom_right: "",
58
- corner_bottom_left: "",
59
- edge_top: "─",
60
- edge_right: "",
61
- edge_bottom: "─",
62
- edge_left: "",
63
- tee_top: " ",
64
- tee_right: "",
65
- tee_bottom: " ",
66
- tee_left: "",
67
- divider_vertical: " ",
68
- divider_horizontal: "─",
69
- intersection: " ",
70
- },
71
- markdown: {
72
- corner_top_left: "",
73
- corner_top_right: "",
74
- corner_bottom_right: "",
75
- corner_bottom_left: "",
76
- edge_top: "",
77
- edge_right: "|",
78
- edge_bottom: "",
79
- edge_left: "|",
80
- tee_top: "",
81
- tee_right: "|",
82
- tee_bottom: "",
83
- tee_left: "|",
84
- divider_vertical: "|",
85
- divider_horizontal: "-",
86
- intersection: "|",
87
- },
88
- modern: {
89
- corner_top_left: "┌",
90
- corner_top_right: "┐",
91
- corner_bottom_right: "┘",
92
- corner_bottom_left: "└",
93
- edge_top: "─",
94
- edge_right: "│",
95
- edge_bottom: "─",
96
- edge_left: "│",
97
- tee_top: "┬",
98
- tee_right: "┤",
99
- tee_bottom: "┴",
100
- tee_left: "├",
101
- divider_vertical: "│",
102
- divider_horizontal: "─",
103
- intersection: "┼",
104
- },
105
- blank: {
106
- },
13
+ ascii:
14
+ Style.new(
15
+ "+", "+", "+", "+",
16
+ "-", "|", "-", "|",
17
+ "+", "+", "+", "+",
18
+ "|", "-", "+",
19
+ ),
20
+ classic:
21
+ Style.new(
22
+ "+", "+", "", "",
23
+ "-", "|", "", "|",
24
+ "+", "+", "", "+",
25
+ "|", "-", "+",
26
+ ),
27
+ reduced_ascii:
28
+ Style.new(
29
+ "", "", "", "",
30
+ "-", "", "-", "",
31
+ " ", "", " ", "",
32
+ " ", "-", " ",
33
+ ),
34
+ reduced_modern:
35
+ Style.new(
36
+ "", "", "", "",
37
+ "─", "", "─", "",
38
+ " ", "", " ", "",
39
+ " ", "─", " ",
40
+ ),
41
+ markdown:
42
+ Style.new(
43
+ "", "", "", "",
44
+ "", "|", "", "|",
45
+ "", "|", "", "|",
46
+ "|", "-", "|",
47
+ ),
48
+ modern:
49
+ Style.new(
50
+ "┌", "", "┘", "└",
51
+ "─", "", "─", "│",
52
+ "┬", "┤", "┴", "├",
53
+ "│", "", "┼",
54
+ ),
55
+ blank:
56
+ Style.new(
57
+ "", "", "", "",
58
+ "", "", "", "",
59
+ "", "", "", "",
60
+ "", "", "",
61
+ ),
107
62
  }
108
63
 
109
64
  # @!visibility private
110
65
  def self.from(initializer, styler = nil)
111
- new(options(initializer).merge(styler: styler))
66
+ new(**options(initializer).merge(styler: styler))
112
67
  end
113
68
 
114
69
  # @!visibility private
115
70
  def horizontal_rule(column_widths, position = :bottom)
116
71
  left, center, right, segment =
117
72
  case position
73
+ when :title_top
74
+ [@corner_top_left, @edge_top, @corner_top_right, @edge_top]
75
+ when :title_bottom
76
+ [@tee_left, @tee_top, @tee_right, @edge_top]
118
77
  when :top
119
78
  [@corner_top_left, @tee_top, @corner_top_right, @edge_top]
120
79
  when :middle
@@ -123,6 +82,11 @@ module Tabulo
123
82
  [@corner_bottom_left, @tee_bottom, @corner_bottom_right, @edge_bottom]
124
83
  end
125
84
  segments = column_widths.map { |width| segment * width }
85
+
86
+ # Prevent weird bottom edge of title if segments empty but right/left not empty, as in
87
+ # Markdown border.
88
+ left = right = "" if segments.all?(&:empty?)
89
+
126
90
  style("#{left}#{segments.join(center)}#{right}")
127
91
  end
128
92
 
@@ -138,7 +102,7 @@ module Tabulo
138
102
 
139
103
  def self.options(kind)
140
104
  opts = STYLES[kind]
141
- return opts if opts
105
+ return opts.to_h if opts
142
106
  raise InvalidBorderError
143
107
  end
144
108
 
@@ -9,14 +9,28 @@ module Tabulo
9
9
  attr_reader :value
10
10
 
11
11
  # @!visibility private
12
- def initialize(value:, formatter:, alignment:, width:, styler:, truncation_indicator:, padding_character:)
13
- @value = value
14
- @formatter = formatter
12
+ def initialize(
13
+ alignment:,
14
+ cell_data:,
15
+ formatter:,
16
+ left_padding:,
17
+ padding_character:,
18
+ right_padding:,
19
+ styler:,
20
+ truncation_indicator:,
21
+ value:,
22
+ width:)
23
+
15
24
  @alignment = alignment
16
- @width = width
25
+ @cell_data = cell_data
26
+ @formatter = formatter
27
+ @left_padding = left_padding
28
+ @padding_character = padding_character
29
+ @right_padding = right_padding
17
30
  @styler = styler
18
31
  @truncation_indicator = truncation_indicator
19
- @padding_character = padding_character
32
+ @value = value
33
+ @width = width
20
34
  end
21
35
 
22
36
  # @!visibility private
@@ -25,34 +39,53 @@ module Tabulo
25
39
  end
26
40
 
27
41
  # @!visibility private
28
- def padded_truncated_subcells(target_height, padding_amount_left, padding_amount_right)
29
- total_padding_amount = padding_amount_left + padding_amount_right
42
+ def padded_truncated_subcells(target_height)
43
+ total_padding_amount = @left_padding + @right_padding
30
44
  truncated = (height > target_height)
31
45
  (0...target_height).map do |subcell_index|
32
46
  append_truncator = (truncated && (total_padding_amount != 0) && (subcell_index + 1 == target_height))
33
- padded_subcell(subcell_index, padding_amount_left, padding_amount_right, append_truncator)
47
+ padded_subcell(subcell_index, append_truncator)
34
48
  end
35
49
  end
36
50
 
37
51
  # @return [String] the content of the Cell, after applying the formatter for this Column (but
38
52
  # without applying any wrapping or the styler).
39
53
  def formatted_content
40
- @formatted_content ||= @formatter.call(@value)
54
+ @formatted_content ||= apply_formatter
41
55
  end
42
56
 
43
57
  private
44
58
 
59
+ def apply_formatter
60
+ if @formatter.arity == 2
61
+ @formatter.call(@value, @cell_data)
62
+ else
63
+ @formatter.call(@value)
64
+ end
65
+ end
66
+
67
+ def apply_styler(content, line_index)
68
+ case @styler.arity
69
+ when 4
70
+ @styler.call(@value, content, @cell_data, line_index)
71
+ when 3
72
+ @styler.call(@value, content, @cell_data)
73
+ else
74
+ @styler.call(@value, content)
75
+ end
76
+ end
77
+
45
78
  def subcells
46
79
  @subcells ||= calculate_subcells
47
80
  end
48
81
 
49
- def padded_subcell(subcell_index, padding_amount_left, padding_amount_right, append_truncator)
50
- lpad = @padding_character * padding_amount_left
82
+ def padded_subcell(subcell_index, append_truncator)
83
+ lpad = @padding_character * @left_padding
51
84
  rpad =
52
85
  if append_truncator
53
- styled_truncation_indicator + padding(padding_amount_right - 1)
86
+ styled_truncation_indicator(subcell_index) + padding(@right_padding - 1)
54
87
  else
55
- padding(padding_amount_right)
88
+ padding(@right_padding)
56
89
  end
57
90
  inner = subcell_index < height ? subcells[subcell_index] : padding(@width)
58
91
  "#{lpad}#{inner}#{rpad}"
@@ -62,31 +95,35 @@ module Tabulo
62
95
  @padding_character * amount
63
96
  end
64
97
 
65
- def styled_truncation_indicator
66
- @styler.call(@value, @truncation_indicator)
98
+ def styled_truncation_indicator(line_index)
99
+ apply_styler(@truncation_indicator, line_index)
67
100
  end
68
101
 
69
102
  def calculate_subcells
103
+ line_index = 0
70
104
  formatted_content.split($/, -1).flat_map do |substr|
71
105
  subsubcells, subsubcell, subsubcell_width = [], String.new(""), 0
72
106
 
73
107
  substr.scan(/\X/).each do |grapheme_cluster|
74
108
  grapheme_cluster_width = Unicode::DisplayWidth.of(grapheme_cluster)
75
109
  if subsubcell_width + grapheme_cluster_width > @width
76
- subsubcells << style_and_align_cell_content(subsubcell)
110
+ subsubcells << style_and_align_cell_content(subsubcell, line_index)
77
111
  subsubcell_width = 0
78
112
  subsubcell.clear
113
+ line_index += 1
79
114
  end
80
115
 
81
116
  subsubcell << grapheme_cluster
82
117
  subsubcell_width += grapheme_cluster_width
83
118
  end
84
119
 
85
- subsubcells << style_and_align_cell_content(subsubcell)
120
+ subsubcells << style_and_align_cell_content(subsubcell, line_index)
121
+ line_index += 1
122
+ subsubcells
86
123
  end
87
124
  end
88
125
 
89
- def style_and_align_cell_content(content)
126
+ def style_and_align_cell_content(content, line_index)
90
127
  padding = Util.max(@width - Unicode::DisplayWidth.of(content), 0)
91
128
  left_padding, right_padding =
92
129
  case real_alignment
@@ -99,7 +136,7 @@ module Tabulo
99
136
  [padding, 0]
100
137
  end
101
138
 
102
- "#{' ' * left_padding}#{@styler.call(@value, content)}#{' ' * right_padding}"
139
+ "#{' ' * left_padding}#{apply_styler(content, line_index)}#{' ' * right_padding}"
103
140
  end
104
141
 
105
142
  def real_alignment
@@ -0,0 +1,14 @@
1
+ module Tabulo
2
+
3
+ # Contains information about a particular {Cell} in the {Table}.
4
+ #
5
+ # @attr source [Object] The member of this {Cell}'s {Table}'s underlying enumerable from which
6
+ # this {Cell}'s {Row} was derived.
7
+ # @attr row_index [Integer] The positional index of the {Cell}'s {Row}. The topmost {Row} of the
8
+ # {Table} has index 0, the next has index 1, etc.. The header row(s) are not counted for the purpose
9
+ # of this numbering.
10
+ # @attr column_index [Integer] The positional index of the {Cell}'s {Column}. The leftmost {Column}
11
+ # of the {Table} has index 0, the next has index 1, etc..
12
+ CellData = Struct.new(:source, :row_index, :column_index)
13
+
14
+ end
@@ -5,6 +5,9 @@ module Tabulo
5
5
 
6
6
  attr_accessor :width
7
7
  attr_reader :header
8
+ attr_reader :index
9
+ attr_reader :left_padding
10
+ attr_reader :right_padding
8
11
 
9
12
  def initialize(
10
13
  align_body:,
@@ -13,7 +16,10 @@ module Tabulo
13
16
  formatter:,
14
17
  header:,
15
18
  header_styler:,
19
+ index:,
20
+ left_padding:,
16
21
  padding_character:,
22
+ right_padding:,
17
23
  styler:,
18
24
  truncation_indicator:,
19
25
  width:)
@@ -23,12 +29,22 @@ module Tabulo
23
29
  @extractor = extractor
24
30
  @formatter = formatter
25
31
  @header = header
32
+ @index = index
33
+ @left_padding = left_padding
34
+ @right_padding = right_padding
26
35
 
27
36
  @header_styler =
28
37
  if header_styler
29
- -> (_, s) { header_styler.call(s) }
38
+ case header_styler.arity
39
+ when 3
40
+ -> (_, str, cell_data, line_index) { header_styler.call(str, cell_data.column_index, line_index) }
41
+ when 2
42
+ -> (_, str, cell_data) { header_styler.call(str, cell_data.column_index) }
43
+ else
44
+ -> (_, str) { header_styler.call(str) }
45
+ end
30
46
  else
31
- -> (_, s) { s }
47
+ -> (_, str) { str }
32
48
  end
33
49
 
34
50
  @padding_character = padding_character
@@ -38,10 +54,16 @@ module Tabulo
38
54
  end
39
55
 
40
56
  def header_cell
57
+ if @header_styler.arity >= 3
58
+ cell_data = CellData.new(nil, nil, @index)
59
+ end
41
60
  Cell.new(
42
61
  alignment: @align_header,
62
+ cell_data: cell_data,
43
63
  formatter: -> (s) { s },
64
+ left_padding: @left_padding,
44
65
  padding_character: @padding_character,
66
+ right_padding: @right_padding,
45
67
  styler: @header_styler,
46
68
  truncation_indicator: @truncation_indicator,
47
69
  value: @header,
@@ -49,20 +71,44 @@ module Tabulo
49
71
  )
50
72
  end
51
73
 
52
- def body_cell(source)
74
+ def body_cell(source, row_index:, column_index:)
75
+ if body_cell_data_required?
76
+ cell_data = CellData.new(source, row_index, @index)
77
+ end
53
78
  Cell.new(
54
79
  alignment: @align_body,
80
+ cell_data: cell_data,
55
81
  formatter: @formatter,
82
+ left_padding: @left_padding,
56
83
  padding_character: @padding_character,
84
+ right_padding: @right_padding,
57
85
  styler: @styler,
58
86
  truncation_indicator: @truncation_indicator,
59
- value: body_cell_value(source),
87
+ value: body_cell_value(source, row_index: row_index, column_index: column_index),
60
88
  width: @width,
61
89
  )
62
90
  end
63
91
 
64
- def body_cell_value(source)
65
- @extractor.call(source)
92
+ def body_cell_value(source, row_index:, column_index:)
93
+ if @extractor.arity == 2
94
+ @extractor.call(source, row_index)
95
+ else
96
+ @extractor.call(source)
97
+ end
98
+ end
99
+
100
+ def padded_width
101
+ width + total_padding
102
+ end
103
+
104
+ def total_padding
105
+ @left_padding + @right_padding
106
+ end
107
+
108
+ private
109
+
110
+ def body_cell_data_required?
111
+ @cell_data_required ||= (@styler.arity == 3 || @formatter.arity == 2)
66
112
  end
67
113
  end
68
114
  end