tabulo 2.3.3 → 2.6.1

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/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