diff --git a/README.md b/README.md index 399ae69..67ce36d 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 + +- [ ] `--list` / `--table` 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