From eac2737a164d0dc94a93056e596f92302d8c3481 Mon Sep 17 00:00:00 2001 From: Ariel Rzezak Date: Wed, 11 Mar 2026 20:54:37 -0300 Subject: [PATCH 1/2] Add auto-vertical layout when table exceeds terminal width Introduce List class for psql \x-style expanded output and auto-detect terminal width to choose between Table (horizontal) and List (vertical) formatting. --- README.md | 4 +++ lib/taql.rb | 17 ++++++++++- lib/taql/list.rb | 49 ++++++++++++++++++++++++++++++ lib/taql/table.rb | 4 +++ test/test_list.rb | 74 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_table.rb | 8 +++++ 6 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 lib/taql/list.rb create mode 100644 test/test_list.rb diff --git a/README.md b/README.md index 399ae69..0f31bc8 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,10 @@ The return value is a native PG::Result object, which supports mapping or extrac => # ``` +## TODO + +- [ ] `--vertical` / `--horizontal` CLI flags to override auto-detection + ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. diff --git a/lib/taql.rb b/lib/taql.rb index 610bfff..2ebedc0 100644 --- a/lib/taql.rb +++ b/lib/taql.rb @@ -1,4 +1,6 @@ +require "io/console" require_relative "taql/cli" +require_relative "taql/list" require_relative "taql/table" require_relative "taql/version" require_relative "taql/railtie" if defined?(Rails) @@ -10,13 +12,26 @@ class << self def execute(query, options = {}, connection: nil) (connection || default_connection).execute(query).tap do |result| if (results = result.entries).any? - $stdout.puts Table.new(results, markdown: options[:markdown]) + $stdout.puts formatter(results, markdown: options[:markdown]) end end end private + def formatter(results, markdown: false) + table = Table.new(results, markdown: markdown) + if !markdown && table.table_width > terminal_width + List.new(results, terminal_width: terminal_width) + else + table + end + end + + def terminal_width + IO.console&.winsize&.last || Float::INFINITY + end + def default_connection @default_connection&.call || raise("Taql not properly initialized with Rails") end diff --git a/lib/taql/list.rb b/lib/taql/list.rb new file mode 100644 index 0000000..c6a4425 --- /dev/null +++ b/lib/taql/list.rb @@ -0,0 +1,49 @@ +module Taql + class List + DASH = "-".freeze + PIPE = " | ".freeze + + def initialize(entries, terminal_width: nil) + @entries = entries.map { |entry| entry.transform_values(&:to_s) } + @terminal_width = terminal_width + end + + def print + output if entries.any? + end + + private + + attr_reader :entries + + def headers + @headers ||= entries.flat_map(&:keys).uniq + end + + def max_header_width + @max_header_width ||= headers.map(&:length).max + end + + def divider_width + @terminal_width&.finite? ? @terminal_width : 32 + end + + def output + records = entries.map { |entry| [separator, *rows(entry)].join("\n") } + + [*records, separator].join("\n") + end + + def separator + DASH * divider_width + end + + def rows(entry) + headers.map do |header| + "#{header.upcase.rjust(max_header_width)}#{PIPE}#{entry[header]}" + end + end + + alias_method :to_s, :print + end +end diff --git a/lib/taql/table.rb b/lib/taql/table.rb index d2d9783..1552bb7 100644 --- a/lib/taql/table.rb +++ b/lib/taql/table.rb @@ -12,6 +12,10 @@ def initialize(entries, markdown: false) @markdown = markdown end + def table_width + column_widths.sum + (3 * columns.count) + 1 + end + def body entries.map(&:values) end diff --git a/test/test_list.rb b/test/test_list.rb new file mode 100644 index 0000000..b152233 --- /dev/null +++ b/test/test_list.rb @@ -0,0 +1,74 @@ +require "test_helper" + +class TestList < Minitest::Test + def test_vertical_output + list = Taql::List.new([ + {"name" => "Alice", "age" => 30}, + {"name" => "Bob", "age" => 25} + ], terminal_width: 40) + + result = list.print + + assert_includes result, "NAME | Alice" + assert_includes result, " AGE | 25" + end + + def test_single_record + list = Taql::List.new([ + {"name" => "Alice", "age" => 30} + ], terminal_width: 40) + + result = list.print + + assert_includes result, "NAME | Alice" + assert_includes result, " AGE | 30" + end + + def test_divider_fills_terminal_width + list = Taql::List.new([ + {"name" => "Alice"} + ], terminal_width: 40) + + lines = list.print.lines.map(&:chomp) + + assert_equal 40, lines.first.length + assert_equal 40, lines.last.length + end + + def test_trailing_separator + list = Taql::List.new([ + {"name" => "Alice"} + ], terminal_width: 40) + + last_line = list.print.lines.last.chomp + + assert_match(/\A-+\z/, last_line) + end + + def test_renders_nothing_when_empty + list = Taql::List.new([]) + + assert_nil list.print + end + + def test_default_divider_without_terminal_width + list = Taql::List.new([ + {"name" => "Alice"} + ]) + + result = list.print + + assert_match(/\A-+\n/, result) + end + + def test_headers_are_right_padded + list = Taql::List.new([ + {"name" => "Alice", "email" => "alice@example.com"} + ], terminal_width: 40) + + result = list.print + + assert_includes result, " NAME | Alice" + assert_includes result, "EMAIL | alice@example.com" + end +end diff --git a/test/test_table.rb b/test/test_table.rb index 127eaf1..afcb154 100644 --- a/test/test_table.rb +++ b/test/test_table.rb @@ -77,4 +77,12 @@ def test_headers_with_heterogeneous_entries assert_equal ["name", "age", "email"], table.headers end + + def test_table_width_calculation + table = Taql::Table.new([ + {"name" => "Alice", "age" => 30} + ]) + + assert_equal 15, table.table_width + end end From b0055b7e5c1f628e510eca414623a4924eedf069 Mon Sep 17 00:00:00 2001 From: Ariel Rzezak Date: Wed, 11 Mar 2026 21:18:57 -0300 Subject: [PATCH 2/2] Update TODO flags to match class names --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f31bc8..67ce36d 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ The return value is a native PG::Result object, which supports mapping or extrac ## TODO -- [ ] `--vertical` / `--horizontal` CLI flags to override auto-detection +- [ ] `--list` / `--table` CLI flags to override auto-detection ## Development