From 5839dad3708dc20cfa999e0ff300e2c9a89f5981 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 73883f31..ca8531e6 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. + # colwidth:: 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 3e6482d8..a9474227 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 < Printer + 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_separator if options[:borders] + array.each do |row| + if options[:borders] && row == BORDER_SEPARATOR + print_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_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_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