summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb
diff options
context:
space:
mode:
authorBenjamin Sanders <ben@Benjamins-MacBook-Pro.local>2025-10-11 10:34:24 -0400
committerBenjamin Sanders <ben@Benjamins-MacBook-Pro.local>2025-10-11 10:34:24 -0400
commit5571dc766e2143e39762ff0b47c41d1c2e154f4c (patch)
treef4f124d314a7e76c04484515aa0fd1f2e271a601 /vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb
parent359f3309c97fc894b63e0a295c76ab298504d635 (diff)
Vendor bundled gems for deployment
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb')
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb372
1 files changed, 372 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb b/vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb
new file mode 100644
index 0000000..f483e31
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/terminal-table-3.0.2/lib/terminal-table/table.rb
@@ -0,0 +1,372 @@
+require 'unicode/display_width'
+
+module Terminal
+ class Table
+
+ attr_reader :title
+ attr_reader :headings
+
+ ##
+ # Generates a ASCII/Unicode table with the given _options_.
+
+ def initialize options = {}, &block
+ @elaborated = false
+ @headings = []
+ @rows = []
+ @column_widths = []
+ self.style = options.fetch :style, {}
+ self.headings = options.fetch :headings, []
+ self.rows = options.fetch :rows, []
+ self.title = options.fetch :title, nil
+ yield_or_eval(&block) if block
+
+ style.on_change(:width) { require_column_widths_recalc }
+ end
+
+ ##
+ # Align column _n_ to the given _alignment_ of :center, :left, or :right.
+
+ def align_column n, alignment
+ # nil forces the column method to return the cell itself
+ column(n, nil).each do |cell|
+ cell.alignment = alignment unless cell.alignment?
+ end
+ end
+
+ ##
+ # Add a row.
+
+ def add_row array
+ row = array == :separator ? Separator.new(self) : Row.new(self, array)
+ @rows << row
+ require_column_widths_recalc unless row.is_a?(Separator)
+ end
+ alias :<< :add_row
+
+ ##
+ # Add a separator.
+
+ def add_separator(border_type: :div)
+ @rows << Separator.new(self, border_type: border_type)
+ end
+
+ def cell_spacing
+ cell_padding + style.border_y_width
+ end
+
+ def cell_padding
+ style.padding_left + style.padding_right
+ end
+
+ ##
+ # Return column _n_.
+
+ def column n, method = :value, array = rows
+ array.map { |row|
+ # for each cells in a row, find the column with index
+ # just greater than the required one, and go back one.
+ index = col = 0
+ row.cells.each do |cell|
+ break if index > n
+ index += cell.colspan
+ col += 1
+ end
+ cell = row[col - 1]
+ cell && method ? cell.__send__(method) : cell
+ }.compact
+ end
+
+ ##
+ # Return _n_ column including headings.
+
+ def column_with_headings n, method = :value
+ column n, method, headings_with_rows
+ end
+
+ ##
+ # Return columns.
+
+ def columns
+ (0...number_of_columns).map { |n| column n }
+ end
+
+ ##
+ # Return length of column _n_.
+
+ def column_width n
+ column_widths[n] || 0
+ end
+ alias length_of_column column_width # for legacy support
+
+ ##
+ # Return total number of columns available.
+
+ def number_of_columns
+ headings_with_rows.map { |r| r.number_of_columns }.max || 0
+ end
+
+ ##
+ # Set the headings
+
+ def headings= arrays
+ arrays = [arrays] unless arrays.first.is_a?(Array)
+ @headings = arrays.map do |array|
+ row = Row.new(self, array)
+ require_column_widths_recalc
+ row
+ end
+ end
+
+ ##
+ # Elaborate rows to form an Array of Rows and Separators with adjacency properties added.
+ #
+ # This is separated from the String rendering so that certain features may be tweaked
+ # before the String is built.
+
+ def elaborate_rows
+
+ buffer = style.border_top ? [Separator.new(self, border_type: :top, implicit: true)] : []
+ unless @title.nil?
+ buffer << Row.new(self, [title_cell_options])
+ buffer << Separator.new(self, implicit: true)
+ end
+ @headings.each do |row|
+ unless row.cells.empty?
+ buffer << row
+ buffer << Separator.new(self, border_type: :double, implicit: true)
+ end
+ end
+ if style.all_separators
+ @rows.each_with_index do |row, idx|
+ # last separator is bottom, others are :div
+ border_type = (idx == @rows.size - 1) ? :bot : :div
+ buffer << row
+ buffer << Separator.new(self, border_type: border_type, implicit: true)
+ end
+ else
+ buffer += @rows
+ buffer << Separator.new(self, border_type: :bot, implicit: true) if style.border_bottom
+ end
+
+ # After all implicit Separators are inserted we need to save off the
+ # adjacent rows so that we can decide what type of intersections to use
+ # based on column spans in the adjacent row(s).
+ buffer.each_with_index do |r, idx|
+ if r.is_a?(Separator)
+ prev_row = idx > 0 ? buffer[idx - 1] : nil
+ next_row = buffer.fetch(idx + 1, nil)
+ r.save_adjacent_rows(prev_row, next_row)
+ end
+ end
+
+ @elaborated = true
+ @rows = buffer
+ end
+
+ ##
+ # Render the table.
+
+ def render
+ elaborate_rows unless @elaborated
+ @rows.map { |r| style.margin_left + r.render.rstrip }.join("\n")
+ end
+ alias :to_s :render
+
+ ##
+ # Return rows without separator rows.
+
+ def rows
+ @rows.reject { |row| row.is_a? Separator }
+ end
+
+ def rows= array
+ @rows = []
+ array.each { |arr| self << arr }
+ end
+
+ def style=(options)
+ style.apply options
+ end
+
+ def style
+ @style ||= Style.new
+ end
+
+ def title=(title)
+ @title = title
+ require_column_widths_recalc
+ end
+
+ ##
+ # Check if _other_ is equal to self. _other_ is considered equal
+ # if it contains the same headings and rows.
+
+ def == other
+ if other.respond_to? :render and other.respond_to? :rows
+ self.headings == other.headings and self.rows == other.rows
+ end
+ end
+
+ private
+
+ def columns_width
+ column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y_width
+ end
+
+ def recalc_column_widths
+ @require_column_widths_recalc = false
+ n_cols = number_of_columns
+ space_width = cell_spacing
+ return if n_cols == 0
+
+ # prepare rows
+ all_rows = headings_with_rows
+ all_rows << Row.new(self, [title_cell_options]) unless @title.nil?
+
+ # DP states, dp[colspan][index][split_offset] => column_width.
+ dp = []
+
+ # prepare initial value for DP.
+ all_rows.each do |row|
+ index = 0
+ row.cells.each do |cell|
+ cell_value = cell.value_for_column_width_recalc
+ cell_width = Unicode::DisplayWidth.of(cell_value.to_s)
+ colspan = cell.colspan
+
+ # find column width from each single cell.
+ dp[colspan] ||= []
+ dp[colspan][index] ||= [0] # add a fake cell with length 0.
+ dp[colspan][index][colspan] ||= 0 # initialize column length to 0.
+
+ # the last index `colspan` means width of the single column (split
+ # at end of each column), not a width made up of multiple columns.
+ single_column_length = [cell_width, dp[colspan][index][colspan]].max
+ dp[colspan][index][colspan] = single_column_length
+
+ index += colspan
+ end
+ end
+
+ # run DP.
+ (1..n_cols).each do |colspan|
+ dp[colspan] ||= []
+ (0..n_cols-colspan).each do |index|
+ dp[colspan][index] ||= [1]
+ (1...colspan).each do |offset|
+ # processed level became reverse map from width => [offset, ...].
+ left_colspan = offset
+ left_index = index
+ left_width = dp[left_colspan][left_index].keys.first
+
+ right_colspan = colspan - left_colspan
+ right_index = index + offset
+ right_width = dp[right_colspan][right_index].keys.first
+
+ dp[colspan][index][offset] = left_width + right_width + space_width
+ end
+
+ # reverse map it for resolution (max width and short offset first).
+ rmap = {}
+ dp[colspan][index].each_with_index do |width, offset|
+ rmap[width] ||= []
+ rmap[width] << offset
+ end
+
+ # sort reversely and store it back.
+ dp[colspan][index] = Hash[rmap.sort.reverse]
+ end
+ end
+
+ resolve = lambda do |colspan, full_width, index = 0|
+ # stop if reaches the bottom level.
+ return @column_widths[index] = full_width if colspan == 1
+
+ # choose best split offset for partition, or second best result
+ # if first one is not dividable.
+ candidate_offsets = dp[colspan][index].collect(&:last).flatten
+ offset = candidate_offsets[0]
+ offset = candidate_offsets[1] if offset == colspan
+
+ # prepare for next round.
+ left_colspan = offset
+ left_index = index
+ left_width = dp[left_colspan][left_index].keys.first
+
+ right_colspan = colspan - left_colspan
+ right_index = index + offset
+ right_width = dp[right_colspan][right_index].keys.first
+
+ # calculate reference column width, give remaining spaces to left.
+ total_non_space_width = full_width - (colspan - 1) * space_width
+ ref_column_width = total_non_space_width / colspan
+ remainder = total_non_space_width % colspan
+ rem_left_width = [remainder, left_colspan].min
+ rem_right_width = remainder - rem_left_width
+ ref_left_width = ref_column_width * left_colspan +
+ (left_colspan - 1) * space_width + rem_left_width
+ ref_right_width = ref_column_width * right_colspan +
+ (right_colspan - 1) * space_width + rem_right_width
+
+ # at most one width can be greater than the reference width.
+ if left_width <= ref_left_width and right_width <= ref_right_width
+ # use refernce width (evenly partition).
+ left_width = ref_left_width
+ right_width = ref_right_width
+ else
+ # the wider one takes its value, shorter one takes the rest.
+ if left_width > ref_left_width
+ right_width = full_width - left_width - space_width
+ else
+ left_width = full_width - right_width - space_width
+ end
+ end
+
+ # run next round.
+ resolve.call(left_colspan, left_width, left_index)
+ resolve.call(right_colspan, right_width, right_index)
+ end
+
+ full_width = dp[n_cols][0].keys.first
+ unless style.width.nil?
+ new_width = style.width - space_width - style.border_y_width
+ if new_width < full_width
+ raise "Table width exceeds wanted width " +
+ "of #{style.width} characters."
+ end
+ full_width = new_width
+ end
+
+ resolve.call(n_cols, full_width)
+ end
+
+ ##
+ # Return headings combined with rows.
+
+ def headings_with_rows
+ @headings + rows
+ end
+
+ def yield_or_eval &block
+ return unless block
+ if block.arity > 0
+ yield self
+ else
+ self.instance_eval(&block)
+ end
+ end
+
+ def title_cell_options
+ {:value => @title, :alignment => :center, :colspan => number_of_columns}
+ end
+
+ def require_column_widths_recalc
+ @require_column_widths_recalc = true
+ end
+
+ def column_widths
+ recalc_column_widths if @require_column_widths_recalc
+ @column_widths
+ end
+ end
+end