From aab2c57d1af05884947f83c7822c1d586a1d18fa Mon Sep 17 00:00:00 2001 From: Petrik Date: Sun, 6 Aug 2023 18:51:59 +0200 Subject: [PATCH] Add support for printing tables with borders 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 --- lib/thor/shell/basic.rb | 1 + lib/thor/shell/table_printer.rb | 65 ++++++++++++++++++++++++++------- spec/shell/basic_spec.rb | 38 +++++++++++++++++++ 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/lib/thor/shell/basic.rb b/lib/thor/shell/basic.rb index 0708ca59..e20f132c 100644 --- a/lib/thor/shell/basic.rb +++ b/lib/thor/shell/basic.rb @@ -175,6 +175,7 @@ def print_in_columns(array) # ==== Options # indent:: Indent the first column by indent value. # colwidth:: Force the first column to colwidth spaces wide. + # borders:: Adds ascii borders. # def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength printer = TablePrinter.new(stdout, options) diff --git a/lib/thor/shell/table_printer.rb b/lib/thor/shell/table_printer.rb index e1ab6fd1..53d349a0 100644 --- a/lib/thor/shell/table_printer.rb +++ b/lib/thor/shell/table_printer.rb @@ -4,12 +4,15 @@ 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) @@ -17,33 +20,33 @@ def print(array) 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 @@ -51,8 +54,11 @@ def prepare(array) 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 @@ -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 diff --git a/spec/shell/basic_spec.rb b/spec/shell/basic_spec.rb index f9f34c85..ee3889bf 100644 --- a/spec/shell/basic_spec.rb +++ b/spec/shell/basic_spec.rb @@ -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