Skip to content

Commit

Permalink
Add support for printing tables with borders
Browse files Browse the repository at this point in the history
Adding borders to tables can improve their legibility.

This would allow replacing the custom table for code statistics in
Rails, with the generic implementation in Thor.

By adding :separators to a table a horizontal separator will be added.
This functionality was inspired by: https://github.com/piotrmurach/tty-table
  • Loading branch information
p8 committed Aug 22, 2023
1 parent 9c9ab52 commit aab2c57
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 14 deletions.
1 change: 1 addition & 0 deletions lib/thor/shell/basic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def print_in_columns(array)
# ==== Options
# indent<Integer>:: Indent the first column by indent value.
# colwidth<Integer>:: Force the first column to colwidth spaces wide.
# borders<Boolean>:: Adds ascii borders.
#
def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength
printer = TablePrinter.new(stdout, options)
Expand Down
65 changes: 51 additions & 14 deletions lib/thor/shell/table_printer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,61 @@
class Thor
module Shell
class TablePrinter < ColumnPrinter
BORDER_SEPARATOR = :separator

def initialize(stdout, options = {})
super
@formats = []
@maximas = []
@colwidth = options[:colwidth]
@truncate = options[:truncate] == true ? Terminal.terminal_width : options[:truncate]
@padding = 1
end

def print(array)
return if array.empty?

prepare(array)

print_border_separator if options[:borders]

array.each do |row|
if options[:borders] && row == BORDER_SEPARATOR
print_border_separator
next
end

sentence = "".dup

row.each_with_index do |column, index|
maxima = @maximas[index]

f = if column.is_a?(Numeric)
if index == row.size - 1
# Don't output 2 trailing spaces when printing the last column
"%#{maxima}s"
else
"%#{maxima}s "
end
else
@formats[index]
end
sentence << f % column.to_s
sentence << format_cell(column, row.size, index)
end

sentence = truncate(sentence)
sentence << "|" if options[:borders]
stdout.puts sentence

end
print_border_separator if options[:borders]
end

private

def prepare(array)
array = array.reject{|row| row == BORDER_SEPARATOR }

@formats << "%-#{@colwidth + 2}s".dup if @colwidth
start = @colwidth ? 1 : 0

colcount = array.max { |a, b| a.size <=> b.size }.size

start.upto(colcount - 1) do |index|
maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max

@maximas << maxima
@formats << if index == colcount - 1
@formats << if options[:borders]
"%-#{maxima}s".dup
elsif index == colcount - 1
# Don't output 2 trailing spaces when printing the last column
"%-s".dup
else
Expand All @@ -64,6 +70,37 @@ def prepare(array)
@formats << "%s"
end

def format_cell(column, row_size, index)
maxima = @maximas[index]

f = if column.is_a?(Numeric)
if options[:borders]
# With borders we handle padding separately
"%#{maxima}s"
elsif index == row_size - 1
# Don't output 2 trailing spaces when printing the last column
"%#{maxima}s"
else
"%#{maxima}s "
end
else
@formats[index]
end

cell = "".dup
cell << "|" + " " * @padding if options[:borders]
cell << f % column.to_s
cell << " " * @padding if options[:borders]
cell
end

def print_border_separator
top = @maximas.map do |maxima|
" " * @indent + "+" + "-" * (maxima + 2 * @padding)
end
stdout.puts top.join + "+"
end

def truncate(string)
return string unless @truncate
as_unicode do
Expand Down
38 changes: 38 additions & 0 deletions spec/shell/basic_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,44 @@ def #456 Lanç...
Erik 1234567890123 green
TABLE
end

it "prints a table with borders" do
content = capture(:stdout) { shell.print_table(@table, borders: true) }
expect(content).to eq(<<-TABLE)
+-----+------+-------------+
| abc | #123 | first three |
| | #0 | empty |
| xyz | #786 | last three |
+-----+------+-------------+
TABLE
end

it "prints a table with borders and separators" do
@table.insert(1, :separator)
content = capture(:stdout) { shell.print_table(@table, borders: true) }
expect(content).to eq(<<-TABLE)
+-----+------+-------------+
| abc | #123 | first three |
+-----+------+-------------+
| | #0 | empty |
| xyz | #786 | last three |
+-----+------+-------------+
TABLE
end

it "prints a table with borders and small numbers and right-aligns them" do
table = [
["Name", "Number", "Color"], # rubocop: disable Style/WordArray
["Erik", 1, "green"]
]
content = capture(:stdout) { shell.print_table(table, borders: true) }
expect(content).to eq(<<-TABLE)
+------+--------+-------+
| Name | Number | Color |
| Erik | 1 | green |
+------+--------+-------+
TABLE
end
end

describe "#file_collision" do
Expand Down

0 comments on commit aab2c57

Please sign in to comment.