├── .travis.yml ├── console_table_screenshot.png ├── Gemfile ├── Rakefile ├── .gitignore ├── LICENSE ├── console_table.gemspec ├── examples ├── purchases.rb └── stocks.rb ├── lib └── console_table.rb ├── README.md └── test └── test_console_table.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | -------------------------------------------------------------------------------- /console_table_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rodhilton/console_table/HEAD/console_table_screenshot.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in console_table.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | require 'rake/testtask' 4 | 5 | Rake::TestTask.new do |t| 6 | t.libs << 'test' 7 | end 8 | 9 | desc "Run tests" 10 | task :default => :test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .config 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | 20 | # for a library or gem, you might want to ignore these files since the code is 21 | # intended to run in multiple environments; otherwise, check them in: 22 | Gemfile.lock 23 | .ruby-version 24 | .ruby-gemset 25 | 26 | .rvmrc 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Rod Hilton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /console_table.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "console_table" 7 | spec.version = "0.3.1" 8 | spec.authors = ["Rod Hilton"] 9 | spec.email = ["consoletable@rodhilton.com"] 10 | spec.summary = %q{Simplifies printing tables of information to commandline consoles} 11 | spec.description = %q{Allows developers to define tables with specifically-sized columns, which can then have entries printed to them that are automatically formatted, truncated, and padded to fit in the console window.} 12 | spec.homepage = "https://github.com/rodhilton/console_table" 13 | spec.license = "MIT" 14 | 15 | spec.files = `git ls-files -z`.split("\x0") 16 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 17 | spec.require_paths = ["lib"] 18 | 19 | spec.add_development_dependency 'simplecov', '~> 0.9' 20 | spec.add_development_dependency "bundler", "~> 2.3.7" 21 | spec.add_development_dependency 'rake', '~> 0' 22 | spec.add_development_dependency 'minitest', '~> 5.5' 23 | spec.add_development_dependency 'colorize', '~> 0.7' 24 | spec.add_development_dependency 'pry' 25 | 26 | end 27 | -------------------------------------------------------------------------------- /examples/purchases.rb: -------------------------------------------------------------------------------- 1 | require 'console_table' 2 | require 'faker' 3 | require 'colorize' 4 | 5 | # Just a goofy example that simulates a big table of purchase data. 6 | 7 | layout = [ 8 | {size: 6, title: "Pur.#"}, 9 | {size: 20, title: "Customer"}, 10 | {size: 13, title: "Product Code"}, 11 | {size: "*", title: "Product Name", ellipsize: true}, 12 | {size: 5, title: "Price", justify: :right}, 13 | {size: 3, title: "Qty"}, 14 | {size: 8, title: "Date"}, 15 | {size: 11, title: "Profit/Loss", justify: :right} 16 | ] 17 | 18 | sum = 0 19 | 20 | ConsoleTable.define(layout, :title=>"Product Purchases".upcase.bold.green.underline) do |table| 21 | (0..25).each do |i| 22 | name = Faker::Name.name 23 | profit = (rand(1*100000)-30000)/100.00 24 | table << [ 25 | sprintf("%010d", rand(1..999999999)).blue, 26 | name, 27 | Faker::Code.ean.red, 28 | Faker::Commerce.product_name, 29 | sprintf("%#.2f", Faker::Commerce.price).bold.green, 30 | rand(1..20).to_s.magenta, 31 | Faker::Date.backward(14).strftime("%m/%d/%y").yellow, 32 | sprintf("%#.2f", profit).colorize(profit < 0 ? :red : :green) 33 | ] 34 | 35 | sum = sum + profit 36 | 37 | sleep(rand(1..10)/20.0) 38 | end 39 | 40 | table.footer << "Total Profit: #{sprintf("%#.2f", sum).to_s.colorize(sum < 0 ? :red : :green)}" 41 | end -------------------------------------------------------------------------------- /examples/stocks.rb: -------------------------------------------------------------------------------- 1 | require 'console_table' 2 | 3 | require 'json' 4 | require 'net/http' 5 | require 'open-uri' 6 | require 'colorize' 7 | 8 | symbols = ["YHOO", "AAPL", "GOOG", "MSFT", "C", "MMM", "KO", "WMT", "GM", "IBM", "MCD", "VZ", "HD", "DIS", "INTC"] 9 | 10 | params = symbols.collect{|s| "\"#{s}\"" }.join(",") 11 | url = "http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.quotes where symbol in (#{params})&env=http://datatables.org/alltables.env&format=json" 12 | uri = URI.parse(URI::encode(url)) 13 | response = Net::HTTP.get_response(uri) 14 | json = JSON.parse(response.body) 15 | 16 | table_config = [ 17 | {:key=>:symbol, :title=>"Symbol", :size=>6}, 18 | {:key=>:name, :title=>"Name", :size=>17}, 19 | {:key=>:price, :title=>"Price", :size=>5, :justify=>:right}, 20 | {:key=>:change, :title=>"Change", :size=>7, :justify=>:right}, 21 | {:key=>:recommendation, :title=>"Recommendation", :size=>15, :justify=>:right} 22 | ] 23 | 24 | ConsoleTable.define(table_config, :title=>"Stock Prices") do |table| 25 | 26 | json["query"]["results"]["quote"].each do |j| 27 | change = j["ChangeRealtime"] 28 | if change.start_with?("+") 29 | change = change.green 30 | else 31 | change = change.red 32 | end 33 | 34 | recommendation = (rand() <= 0.5) ? "BUY!".white.on_green.bold.underline : "Sell".yellow 35 | 36 | table << [ 37 | j["Symbol"].magenta, 38 | j["Name"], 39 | j["LastTradePriceOnly"], 40 | change, 41 | recommendation 42 | ] 43 | 44 | end 45 | 46 | table.footer << "Recommendations randomly generated" 47 | 48 | end -------------------------------------------------------------------------------- /lib/console_table.rb: -------------------------------------------------------------------------------- 1 | # ConsoleTable allows you to define a table with columns set to specific sizes, 2 | # and then give that table rows of data which it will format into a view that fits 3 | # inside a terminal/console. 4 | # 5 | # Author:: Rod Hilton 6 | # License:: MIT 7 | module ConsoleTable 8 | 9 | # Define a console table. Requires a table layout which specifies column information 10 | # like sizes, titles, and key names. 11 | def self.define(layout, options={}, &block) 12 | table = ConsoleTableClass.new(layout, options) 13 | table.send(:print_header) 14 | block.call(table) 15 | table.send(:print_footer) 16 | end 17 | 18 | class ConsoleTableClass 19 | require 'io/console' 20 | 21 | # Add strings to the footer array to have them formatted when the table closes 22 | attr_reader :footer 23 | 24 | # Add rows to the table. Prints immediately, consult documentation for examples 25 | def <<(options) 26 | print(options) 27 | end 28 | 29 | protected 30 | 31 | def initialize(column_layout, options={}) 32 | @original_column_layout = [] 33 | 34 | if column_layout.is_a? Integer 35 | column_layout = (1..column_layout).collect{|i| "Column #{i}"} 36 | end 37 | 38 | if column_layout.is_a? Array 39 | column_layout.each_with_index do |layout, i| 40 | if layout.is_a? String 41 | @original_column_layout << {:key => "col#{i+1}".to_sym, :title=>layout, :size=>"*"} 42 | elsif layout.is_a? Integer 43 | @original_column_layout << {:key => "col#{i+1}".to_sym, :title=>"Column #{i+1}", :size=>layout} 44 | elsif layout.is_a? Float 45 | @original_column_layout << {:key => "col#{i+1}".to_sym, :title=>"Column #{i+1}", :size=>layout} 46 | elsif layout[:key].nil? and layout[:title].nil? 47 | @original_column_layout << layout.merge({:key => "col#{i+1}".to_sym, :title=>"Column #{i+1}"}) 48 | elsif layout[:title].nil? 49 | @original_column_layout << layout.merge({:title => layout[:key].to_s.capitalize}) 50 | elsif layout[:key].nil? 51 | @original_column_layout << layout.merge({:key => "col#{i+1}".to_sym}) 52 | else 53 | @original_column_layout << layout 54 | end 55 | end 56 | else 57 | raise("Column layout invalid, must be a num of columns or an array of column definitions") 58 | end 59 | 60 | #Mostly used for mocking/testing 61 | @out = options[:output] || $stdout 62 | @set_width = options[:width] 63 | 64 | @title = options[:title] 65 | @borders = options[:borders] || false #Lines between every cell, implies outline 66 | @left_margin = options[:left_margin] || 0 67 | @right_margin = options[:right_margin] || 0 68 | @headings = options[:headings].nil? ? true : options[:headings] 69 | @ellipse = options[:ellipse] || "..." 70 | 71 | #Set outline, just the upper and lower lines 72 | if @borders 73 | @outline = true 74 | elsif not options[:outline].nil? 75 | @outline = options[:outline] 76 | else 77 | @outline = true 78 | end 79 | 80 | @footer = [] 81 | @headings_printed = false 82 | @count = 0 83 | 84 | calc_column_widths 85 | Signal.trap('SIGWINCH', proc { calc_column_widths }) 86 | end 87 | 88 | def print_header() 89 | #Kind of weird but basically if there's a title, there will be a space between the top border and the actual data, meaning we don't 90 | #want special characters printed for the "joining" of the lines. If there's no title, however, we do. 91 | if @title.nil? 92 | print_line("=", "*", false) 93 | else 94 | print_line("=", "*", true) 95 | end if @outline 96 | 97 | unless @title.nil? 98 | @out.print " "*@left_margin 99 | @out.print "|" if @borders 100 | @out.print format(@working_width, @title, false, :center) 101 | @out.print "|" if @borders 102 | @out.print "\n" 103 | print_line if @borders 104 | end 105 | end 106 | 107 | def print_footer() 108 | if should_print_footer 109 | print_line 110 | end 111 | 112 | footer_lines.each do |line| 113 | @out.print " " * @left_margin 114 | @out.print "|" if @borders 115 | @out.print format(@working_width, line, false, :right) 116 | @out.print "|" if @borders 117 | @out.print "\n" 118 | end 119 | 120 | if should_print_footer 121 | print_line("=", "*", true) 122 | else 123 | print_line("=", "*", false) 124 | end if @outline 125 | end 126 | 127 | private 128 | 129 | def print_headings() 130 | @headings_printed = true 131 | 132 | @out.print " "*@left_margin 133 | if @borders 134 | @out.print "|" 135 | end 136 | 137 | @column_widths.each_with_index do |column, i| 138 | justify = column[:justify] || :left 139 | ellipsize = column[:ellipsize] || false 140 | title = column[:title].strip 141 | @out.print format(column[:size], title, ellipsize, justify) 142 | 143 | if @borders 144 | @out.print "|" 145 | else 146 | @out.print " " if i < @column_widths.size-1 147 | end 148 | end 149 | @out.print "\n" 150 | 151 | print_line unless @borders #this line will be printed when the NEXT LINE of actual data prints out if borders are on, because that call PRE-prints the separator line 152 | end 153 | 154 | def print_line(char="-", join_char="+", edge_join_only=false) 155 | if @borders #use +'s to join columns 156 | @out.print " " * @left_margin 157 | @out.print join_char 158 | @column_widths.each_with_index do |column, i| 159 | @out.print char*column[:size] 160 | if edge_join_only and i < @column_widths.length - 1 161 | @out.print char 162 | else 163 | @out.print join_char 164 | end 165 | end 166 | @out.print "\n" 167 | else #just print long lines 168 | @out.print " " * @left_margin 169 | @out.print "#{char}" * (@working_width + (@borders ? 2 : 0)) 170 | @out.print "\n" 171 | end 172 | end 173 | 174 | def should_print_footer 175 | footer_lines.length > 0 176 | end 177 | 178 | def footer_lines 179 | footer_lines = [] 180 | @footer.each do |line| 181 | lines = line.split("\n") 182 | lines.each do |l| 183 | footer_lines << l.strip unless l.nil? or uncolorize(l).strip == "" 184 | end 185 | end 186 | footer_lines 187 | end 188 | 189 | def print(options) 190 | 191 | if options.is_a? String 192 | print_plain(options) 193 | return 194 | end 195 | 196 | print_headings unless @headings_printed or not @headings 197 | 198 | if options.is_a? Array #If an array or something is supplied, infer the order from the heading order 199 | munged_options = {} 200 | options.each_with_index do |element, i| 201 | munged_options[@original_column_layout[i][:key]] = element 202 | end 203 | 204 | options = munged_options 205 | end 206 | 207 | 208 | print_line if @borders unless not @headings and @count == 0 209 | 210 | @out.print " "*@left_margin 211 | if @borders 212 | @out.print "|" 213 | end 214 | #column order is set, so go through each column and look up values in the incoming options 215 | @column_widths.each_with_index do |column, i| 216 | to_print = options[column[:key]] || "" 217 | justify = column[:justify] || :left 218 | ellipsize = column[:ellipsize] || false 219 | 220 | if to_print.is_a? String 221 | justify = infer_justify_from_string(to_print, justify) 222 | 223 | @out.print format(column[:size], normalize(to_print), ellipsize, justify) 224 | elsif to_print.is_a? Hash 225 | justify = infer_justify_from_string(to_print[:text], justify) 226 | 227 | text = normalize(to_print[:text]) || "" 228 | 229 | ellipsize = to_print[:ellipsize] unless to_print[:ellipsize].nil? 230 | justify = to_print[:justify] unless to_print[:justify].nil? 231 | 232 | formatted=format(column[:size], text, ellipsize, justify) 233 | 234 | @out.print formatted 235 | else 236 | text = to_print.to_s 237 | justify = infer_justify_from_string(text, justify) 238 | @out.print format(column[:size], normalize(text), ellipsize, justify) 239 | end 240 | 241 | if @borders 242 | @out.print "|" 243 | else 244 | @out.print " " if i < @column_widths.size-1 245 | end 246 | end 247 | @out.print "\n" 248 | 249 | @count = @count + 1 250 | end 251 | 252 | def infer_justify_from_string(to_print, justify) 253 | uncolorized = uncolorize(to_print) 254 | if uncolorized.start_with?("\t") and uncolorized.end_with?("\t") 255 | justify = :center 256 | elsif uncolorized.start_with?("\t") 257 | justify = :right 258 | elsif uncolorized.end_with?("\t") 259 | justify = :left 260 | end 261 | justify 262 | end 263 | 264 | def normalize(string) 265 | if string.nil? 266 | nil 267 | else 268 | normalized = string.to_s 269 | normalized = normalized.sub(/^(\e\[\d[^m]*?m)(\s+)/, '\2\1') #Any leading spaces preceeded by a color code should be swapped with the color code itself, so the spaces themselves aren't colored 270 | normalized = normalized.sub(/(\s+)(\e\[\d[^m]*?m)$/, '\2\1') 271 | normalized = normalized.gsub(/\s+/, " ").strip #Primarily to remove any tabs or newlines 272 | normalized 273 | end 274 | end 275 | 276 | def calc_column_widths() 277 | @column_widths = [] 278 | 279 | total_width = @set_width 280 | begin 281 | total_width = IO.console.winsize[1] 282 | rescue => ex 283 | total_width = ENV["COLUMNS"].to_i unless ENV["COLUMNS"].nil? 284 | total_width = 79 if total_width.nil? 285 | end if total_width.nil? 286 | 287 | keys = @original_column_layout.collect { |d| d[:key] }.uniq 288 | if keys.length < @original_column_layout.length 289 | raise("ConsoleTable configuration invalid, same key defined more than once") 290 | end 291 | 292 | num_spacers = @original_column_layout.length - 1 293 | num_spacers = num_spacers + 2 if @borders 294 | set_sizes = @original_column_layout.collect { |x| x[:size] }.find_all { |x| x.is_a? Integer } 295 | used_up = set_sizes.inject(:+) || 0 296 | available = total_width - used_up - @left_margin - @right_margin - num_spacers 297 | 298 | if available < 0 299 | raise("ConsoleTable configuration invalid, current window is too small to display required sizes") 300 | end 301 | 302 | percentages = @original_column_layout.collect { |x| x[:size] }.find_all { |x| x.is_a? Float } 303 | percent_used = percentages.inject(:+) || 0 304 | 305 | if percent_used > 1.0 306 | raise("ConsoleTable configuration invalid, percentages total value greater than 100%") 307 | end 308 | 309 | percent_available = 1 - percent_used 310 | stars = @original_column_layout.collect { |x| x[:size] or x[:size].nil? }.find_all { |x| x.is_a? String } 311 | num_stars = [stars.length, 1].max 312 | percent_for_stars = percent_available.to_f / num_stars 313 | 314 | @original_column_layout.each do |column_config| 315 | if column_config[:size].is_a? Integer 316 | @column_widths << column_config #As-is when integer 317 | elsif column_config[:size].is_a? Float 318 | @column_widths << column_config.merge({:size => (column_config[:size]*available).floor}) 319 | elsif column_config[:size].nil? or column_config[:size].is_a?(String) && column_config[:size] == "*" 320 | @column_widths << column_config.merge({:size => (percent_for_stars*available).floor}) 321 | else 322 | raise("ConsoleTable configuration invalid, '#{column_config[:size]}' is not a valid size") 323 | end 324 | end 325 | 326 | @working_width = (@column_widths.inject(0) { |res, c| res+c[:size] }) + @column_widths.length - 1 327 | end 328 | 329 | 330 | def uncolorize(string) 331 | string.gsub(/\e\[\d[^m]*?m/m, "") 332 | end 333 | 334 | def format(length, text, ellipsize=false, justify=:left) 335 | uncolorized = uncolorize(text) 336 | if uncolorized.length > length 337 | 338 | ellipsize = false if uncolorize(@ellipse).length >= length 339 | 340 | if ellipsize 341 | goal_length = length-uncolorize(@ellipse).length 342 | else 343 | goal_length = length 344 | end 345 | 346 | parts = text.scan(/(\e\[\d.*?m)|(.)/) #The idea here is to break the string up into control codes and single characters 347 | #We're going to now count up until we hit goal length, but we're not going to ever count control codes against the count 348 | #We're also going to keep track of if a non-resetting control code is 'active', so we know to reset at the end if need-be 349 | 350 | #I can't think of a better way to do this, it's probably dumb 351 | current_length = 0 352 | current_index = 0 353 | final_string_parts = [] 354 | color_active = false 355 | 356 | while current_length < goal_length 357 | color_code, regular_text = parts[current_index] 358 | if not regular_text.nil? 359 | current_length = current_length + 1 360 | final_string_parts << regular_text 361 | elsif not color_code.nil? 362 | if color_code == "\e[0m" 363 | color_active = false if color_active 364 | else 365 | color_active = true 366 | end 367 | final_string_parts << color_code 368 | end 369 | current_index = current_index + 1 370 | end 371 | 372 | final_string_parts << @ellipse if ellipsize 373 | final_string_parts << "\e[0m" if color_active 374 | 375 | final_string_parts.join("") 376 | else 377 | space = length-uncolorized.length 378 | if justify == :right 379 | (" "*space) + text 380 | elsif justify == :center 381 | left_side = space/2 382 | right_side = space - left_side 383 | (" " * left_side) + text + (" "*right_side) 384 | else #assume left 385 | text + (" "*space) 386 | end 387 | end 388 | end 389 | 390 | def print_plain(to_print) 391 | print_line if @borders 392 | 393 | @out.print " "*@left_margin 394 | @out.print "|" if @borders 395 | 396 | if to_print.is_a? String 397 | justify = infer_justify_from_string(to_print, :left) 398 | @out.print format(@working_width, normalize(to_print), false, justify) 399 | end 400 | 401 | @out.print "|" if @borders 402 | @out.print "\n" 403 | end 404 | end 405 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ConsoleTable [![Gem Version](https://badge.fury.io/rb/console_table.svg)](https://rubygems.org/gems/console_table) [![Build Status](https://travis-ci.org/rodhilton/console_table.svg?branch=master)](https://travis-ci.org/rodhilton/console_table) 2 | ========= 3 | 4 | ConsoleTable is a helper class that allows you to print data to a console in a clean, table-like fashion. It's intended for use 5 | in commandline applications with information-dense output. It checks your terminal window size (or COLUMNS environment variable) to ensure 6 | your data will fit, allows you to define table column sizes, and then just supply the ConsoleTable instance with data which will be 7 | truncated or padded as needed to fit your specifications. 8 | 9 | It can be used to generate output similar to this screenshot: 10 | 11 | ![image](console_table_screenshot.png) 12 | 13 | You're able to specify left/right/center text justification, headers, footers, and most importantly different sizes including exact character widths, screen percentages, and `*` for whatever is left. If the window resizes the class will notice and output all new lines with recalculated locations (previous lines are not re-printed). 14 | 15 | **Note**: This project is _not_ like `hirb` or [`console-text-formatter`](https://github.com/cjfinc/console-text-formatter)and it makes no attempt to take any sort of ActiveRecord objects or an array of hashes and automatically fit the data to a nice table. It gives much, much more control over to the developer in how the output is formatted, but is much more difficult to work with as a trade-off. ConsoleTable is meant to save on a lot of math and calculation, but will do no analyzing of your data itself in order to format it, and it will never print wider than your console window. Data is printed as rows are added, ConsoleTable doesn't "wait" until it has all the data to figure out optimal sizes for columns. Please read the Usage section carefully for examples. 16 | 17 | 18 | ## Installation 19 | 20 | Add this line to your application's Gemfile: 21 | 22 | gem 'console_table' 23 | 24 | And then execute: 25 | 26 | $ bundle 27 | 28 | Or install it yourself as: 29 | 30 | $ gem install console_table 31 | 32 | ## Quick Start 33 | 34 | Here's a short-and-sweet example of using ConsoleTable 35 | 36 | ```ruby 37 | require 'console_table' 38 | 39 | ConsoleTable.define(["Name", "DOB", "Title"]) do |table| 40 | table << ["Rod", "06-15-80", "Software Engineer"] 41 | table << ["Julia", "04-25-81", "Piano Teacher"] 42 | end 43 | ``` 44 | 45 | This program will output, on a console 80 characters wide: 46 | 47 | ``` 48 | ================================================================================ 49 | Name DOB Title 50 | -------------------------------------------------------------------------------- 51 | Rod 06-15-80 Software Engineer 52 | Julia 04-25-81 Piano Teacher 53 | ================================================================================ 54 | ``` 55 | 56 | This usage takes advantage of the fact that ConsoleTable infers a lot of defaults. For more details about how to exercise the real power of ConsoleTable, refer to the next section. 57 | 58 | ## Usage 59 | 60 | ConsoleTable needs a lot of information to get going, and it can be somewhat awkward to work with. 61 | 62 | First, we need to define the layout of our table. This consists of the exact order of the columns we want, their identifiers, titles, and sizes. Because we want to specify order, the layout config is an array, and each element of the array is a hash of config values for the column that element of the array defines. For example: 63 | 64 | ```ruby 65 | table_config = [ 66 | {:key=>:name, :size=>15, :title=>"Name"}, 67 | {:key=>:birthday, :size=>8, :title=>"DOB"}, 68 | {:key=>:nickname, :size=>0.3, :title=>"Nickname(s)"}, 69 | {:key=>:motto, :size=>"*", :title=>"Motto"}, 70 | ] 71 | 72 | ConsoleTable.define(table_config) do |table| 73 | #Print data to the table here 74 | end 75 | ``` 76 | 77 | Here we've defined a table with four columns, a Name, DOB, Nickname, and Motto. As we defined the details of the columns, by using an array we also defined their order. 78 | 79 | * The Name column is exactly 15 characters wide, meaning that any data printed into that column will be right-padded with spaces to take up 15 characters, or if the name is longer than 15 characters it will be truncated. 80 | * Next is the DOB, this is another field that's exactly 8 characters. 81 | * After that is the Nickname, which has a size of 0.3, or 30%. This column will take 30% of the available space in the window, but will never encroach on space allocated to exact-character sizes like DOB or Name. 82 | * Last is Motto, whose size is simply `"*"`. The `"*"` is the default size as well, so the entire `:size` could have been left out of this column definition. `"*"` means to consume whatever is left of the window. You can have multiple `"*"` entries, and the available space will be divided among them equally. 83 | 84 | Percentages and `*` sizes have no minimums, meaning they may not appear at all if the window is too small. In this case, we have a table whose first column is 15 characters and whose second column is 8 characters. The remaining columns will only appear if there is room, and there will be spaces between all four columns, so three spaces separating. Thus, your window must be 15+8+3=26 characters wide or an exception will be thrown. 85 | 86 | Once we have our table, we must print to it. Printing a line of data means supplying the table with a `Hash` whose keys match the values for `:key` in the column config. So for example, we might do: 87 | 88 | ```ruby 89 | ConsoleTable.define(table_config) do |table| 90 | table << { 91 | :name=>"Rod", 92 | :birthday=>"04-14-80", 93 | :nickname=>"Chainsaw", 94 | :motto=>"It works on my machine" 95 | } 96 | end 97 | ``` 98 | 99 | If we run this in a console exactly 80 characters wide, the result is: 100 | 101 | ``` 102 | =============================================================================== 103 | Name DOB Nickname(s) Motto 104 | ------------------------------------------------------------------------------- 105 | Rod 04-14-80 Chainsaw It works on my machine 106 | =============================================================================== 107 | ``` 108 | 109 | We can specify justification options for columns as well, and even overwrite them when we supply row data, simply by using `Hash`es instead of `String`s for the values. For example: 110 | 111 | ```ruby 112 | table_config = [ 113 | {:key=>:name, :size=>15, :title=>"Name"}, 114 | {:key=>:birthday, :size=>8, :title=>"DOB"}, 115 | {:key=>:nickname, :size=>0.3, :title=>"Nickname(s)", :justify=>:center}, 116 | {:key=>:motto, :size=>"*", :title=>"Motto", :justify=>:right}, 117 | ] 118 | 119 | ConsoleTable.define(table_config) do |table| 120 | table << { 121 | :name=>"Rod", 122 | :birthday=>"04-14-80", 123 | :nickname=>{:text=>"Chainsaw", :justify=>:left}, 124 | :motto=>"It works on my machine" 125 | } 126 | end 127 | ``` 128 | 129 | This will output: 130 | 131 | ``` 132 | =============================================================================== 133 | Name DOB Nickname(s) Motto 134 | ------------------------------------------------------------------------------- 135 | Rod 04-14-80 Chainsaw It works on my machine 136 | =============================================================================== 137 | ``` 138 | 139 | Notice how the Nickname column specifies that the justification will be centered, but when we actually output the data we say `{:text=>"Chainsaw", :justify=>:left}` thus overriding the justification to be left. As a result, the header name is centered, but the data is left-aligned. In the case of motto, we set the justification to the right-aligned, but never override it, so both the column header and the data itself are right-aligned. 140 | 141 | There are lots of different supported options for outputting the row data elements, here's a pretty comprehensive example and it's output 142 | 143 | ```ruby 144 | table_config = [ 145 | {:key=>:name, :size=>15, :title=>"Name"}, 146 | {:key=>:birthday, :size=>8, :title=>"DOB"}, 147 | {:key=>:nickname, :size=>0.3, :title=>"Nickname(s)", :justify=>:center}, 148 | {:key=>:motto, :size=>"*", :title=>"Motto", :justify=>:right} 149 | ] 150 | 151 | ConsoleTable.define(table_config) do |table| 152 | table << { 153 | :name=>"Rod".colorize(:red) #Uses 'colorize' gem 154 | :birthday=>"04-14-80".blue, 155 | :nickname=>{:text=>"Chainsaw", :justify=>:left}, 156 | :motto=>{:text=>"This is a very long motto, I don't mind if it gets cut off but I'd like it to indicate as such with ellipses", :ellipsize=>true} 157 | } 158 | end 159 | ``` 160 | 161 | will output 162 | 163 | ``` 164 | =============================================================================== 165 | Name DOB Nickname(s) Motto 166 | ------------------------------------------------------------------------------- 167 | Rod 04-14-80 Chainsaw This is a very long motto, I don't... 168 | =============================================================================== 169 | ``` 170 | 171 | Due to limitations of this readme format, you'll have to take my word for it that the colors placed by the 'colorize' gem are preserved, and that the table correctly handles resetting ANSI colors when truncating messages that have formatting. 172 | 173 | Another little trick is that you can specify justification options by convention without using hashes for the values, simply by appending or prepending a tab character to the string. For example: 174 | 175 | ```ruby 176 | table_config = [ 177 | {:key=>:col1, :size=>26, :title=>"Column 1", :justify=>:right}, 178 | {:key=>:col2, :size=>26, :title=>"Column 2", :justify=>:left}, 179 | {:key=>:col3, :size=>26, :title=>"Column 3", :justify=>:center} 180 | ] 181 | 182 | ConsoleTable.define(table_config) do |table| 183 | 184 | table << { 185 | :col1=>"Right", 186 | :col2=>"Left", 187 | :col3=>"Center" 188 | } 189 | 190 | table << { 191 | :col1=>"Left\t", 192 | :col2=>"\tCenter\t", 193 | :col3=>"\tRight" 194 | } 195 | end 196 | ``` 197 | 198 | This will output: 199 | 200 | ``` 201 | ================================================================================ 202 | Column 1 Column 2 Column 3 203 | -------------------------------------------------------------------------------- 204 | Right Left Center 205 | Left Center Right 206 | ================================================================================ 207 | ``` 208 | 209 | Notice here how the columns all have justifications set, but the values in the second row override all of them, simply by appending a tab character for left justified, prepending a tab character for right justified, and doing both for center. The tab characters themselves are removed. Please note this convention is not supported for the column definitions using the "title" attribute, there you must use hash properties to set a column-level justification. 210 | 211 | You can also add a title and a footer to the table, or indent the entire table within the window using different options. Again, here's another example that should more-or-less speak for itself. 212 | 213 | 214 | ```ruby 215 | require 'console_table' 216 | require 'colorize' 217 | 218 | table_config = [ 219 | {:key=>:title, :size=>15, :title=>"Movie Title"}, 220 | {:key=>:name, :size=>15, :title=>"Name"}, 221 | {:key=>:release_date, :size=>8, :title=>"Release Date Too Long"}, 222 | {:key=>:tagline, :size=>"*", :title=>"Motto", :justify=>:right}, 223 | ] 224 | 225 | ConsoleTable.define(table_config, :left_margin=>5, :right_margin=>10, :title=>"Movie Killers") do |table| 226 | table << { 227 | :title=>"Friday the 13th", 228 | :name=>{:text=>"Jason's Mom", :justify=>:left}, 229 | :release_date=>"05-09-80".blue, 230 | :tagline=>{:text=>"They were warned...They are doomed...And on Friday the 13th, nothing will save them.", :ellipsize=>true} 231 | } 232 | 233 | table << { 234 | :title=>"Halloween".white.on_red, 235 | :name=>{:text=>"Michael Meyers", :justify=>:left}, 236 | :release_date=>"10-25-80".blue, 237 | :tagline=>{:text=>"Everyone is entitled to one good scare", :ellipsize=>true} 238 | } 239 | 240 | table << { 241 | :title=>{:text=>"Nightmare on Elm St."}, 242 | :name=>{:text=>"Freddy Krueger", :justify=>:left}, 243 | :release_date=>{text: "11-16-84".blue}, 244 | :tagline=>{:text=>"A scream that wakes you up, might be your own", :ellipsize=>true} 245 | } 246 | 247 | table << ["Hellraiser", "Pinhead", "9-18-87", "Demon to some. Angel to others."] 248 | 249 | table.footer << "This is just a line of footer text" 250 | table.footer << "This is a second footer with \nlots of \nlinebreaks in it." 251 | end 252 | ``` 253 | 254 | which yields: 255 | 256 | ``` 257 | ================================================================= 258 | Movie Killers 259 | Movie Title Name Release Motto 260 | ----------------------------------------------------------------- 261 | Friday the 13th Jason's Mom 05-09-80 They were warned...Th... 262 | Halloween Michael Meyers 10-25-80 Everyone is entitled ... 263 | Nightmare on El Freddy Krueger 11-16-84 A scream that wakes y... 264 | Hellraiser Pinhead 9-18-87 Demon to some. Angel to 265 | ----------------------------------------------------------------- 266 | This is just a line of footer text 267 | This is a second footer with 268 | lots of 269 | linebreaks in it. 270 | ================================================================= 271 | ``` 272 | 273 | Note the alternative method of calling `<<` where you can supply an Array instead of a hash, and ConsoleTable will infer from the array order which value goes in what column 274 | 275 | Here's a somewhat more practical example: 276 | 277 | ```ruby 278 | require 'console_table' 279 | 280 | require 'json' 281 | require 'net/http' 282 | require 'open-uri' 283 | require 'colorize' 284 | 285 | symbols = ["YHOO", "AAPL", "GOOG", "MSFT", "C", "MMM", "KO", "WMT", "GM", "IBM", "MCD", "VZ", "HD", "DIS", "INTC"] 286 | 287 | params = symbols.collect{|s| "\"#{s}\"" }.join(",") 288 | url = "http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.quotes where symbol in (#{params})&env=http://datatables.org/alltables.env&format=json" 289 | uri = URI.parse(URI::encode(url)) 290 | response = Net::HTTP.get_response(uri) 291 | json = JSON.parse(response.body) 292 | 293 | table_config = [ 294 | {:key=>:symbol, :title=>"Symbol", :size=>6}, 295 | {:key=>:name, :title=>"Name", :size=>17}, 296 | {:key=>:price, :title=>"Price", :size=>5, :justify=>:right}, 297 | {:key=>:change, :title=>"Change", :size=>7, :justify=>:right}, 298 | {:key=>:recommendation, :title=>"Recommendation", :size=>15, :justify=>:right} 299 | ] 300 | 301 | ConsoleTable.define(table_config, :title=>"Stock Prices") do |table| 302 | 303 | json["query"]["results"]["quote"].each do |j| 304 | change = j["ChangeRealtime"] 305 | if change.start_with?("+") 306 | change = change.green 307 | else 308 | change = change.red 309 | end 310 | 311 | recommendation = (rand() <= 0.5) ? "BUY!".white.on_green.bold.underline : "Sell".yellow 312 | 313 | table << [ 314 | j["Symbol"].magenta, 315 | j["Name"], 316 | j["LastTradePriceOnly"], 317 | change, 318 | recommendation 319 | ] 320 | 321 | end 322 | 323 | table.footer << "Recommendations randomly generated" 324 | 325 | end 326 | ``` 327 | 328 | And the output: 329 | 330 | ``` 331 | ====================================================== 332 | Stock Prices 333 | Symbol Name Price Change Recommendation 334 | ------------------------------------------------------ 335 | YHOO Yahoo! Inc. 44.42 +0.495 Sell 336 | AAPL Apple Inc. 127.0 +0.62 BUY! 337 | GOOG Google Inc. 549.0 +6.08 BUY! 338 | MSFT Microsoft Corpora 43.87 +0.78 Sell 339 | C Citigroup, Inc. C 51.20 +0.31 BUY! 340 | MMM 3M Company Common 165.9 +0.03 Sell 341 | KO Coca-Cola Company 41.99 -0.18 BUY! 342 | WMT Wal-Mart Stores, 85.81 -0.08 Sell 343 | GM General Motors Co 37.62 -0.40 BUY! 344 | IBM International Bus 160.4 +1.88 BUY! 345 | MCD MCDONALD'S CORPOR 95.65 +0.56 BUY! 346 | VZ Verizon Communica 49.31 -0.21 BUY! 347 | HD Home Depot, Inc. 111.8 -0.27 BUY! 348 | DIS Walt Disney Compa 104.1 +0.59 BUY! 349 | INTC Intel Corporation 34.36 +0.235 Sell 350 | ------------------------------------------------------ 351 | Recommendations randomly generated 352 | ====================================================== 353 | ``` 354 | 355 | And yes, you can make the table super-ugly and lame if you want by adding `:borders=>true` to the define call, like so: 356 | 357 | `ConsoleTable.define(table_config, :title=>"Stock Prices", :borders=>true) do |table|` 358 | 359 | Which will yield this, if you're into that sort of thing: 360 | 361 | ``` 362 | *======================================================* 363 | | Stock Prices | 364 | +------+-----------------+-----+-------+---------------+ 365 | |Symbol|Name |Price| Change| Recommendation| 366 | +------+-----------------+-----+-------+---------------+ 367 | |YHOO |Yahoo! Inc. |44.42| +0.495| BUY!| 368 | +------+-----------------+-----+-------+---------------+ 369 | |AAPL |Apple Inc. |127.0| +0.62| BUY!| 370 | +------+-----------------+-----+-------+---------------+ 371 | |GOOG |Google Inc. |549.0| +6.08| Sell| 372 | +------+-----------------+-----+-------+---------------+ 373 | |MSFT |Microsoft Corpora|43.87| +0.78| Sell| 374 | +------+-----------------+-----+-------+---------------+ 375 | |C |Citigroup, Inc. C|51.20| +0.31| Sell| 376 | +------+-----------------+-----+-------+---------------+ 377 | |MMM |3M Company Common|165.9| +0.03| BUY!| 378 | +------+-----------------+-----+-------+---------------+ 379 | |KO |Coca-Cola Company|41.99| -0.18| Sell| 380 | +------+-----------------+-----+-------+---------------+ 381 | |WMT |Wal-Mart Stores, |85.81| -0.08| BUY!| 382 | +------+-----------------+-----+-------+---------------+ 383 | |GM |General Motors Co|37.62| -0.40| BUY!| 384 | +------+-----------------+-----+-------+---------------+ 385 | |IBM |International Bus|160.4| +1.88| BUY!| 386 | +------+-----------------+-----+-------+---------------+ 387 | |MCD |MCDONALD'S CORPOR|95.65| +0.56| Sell| 388 | +------+-----------------+-----+-------+---------------+ 389 | |VZ |Verizon Communica|49.31| -0.21| BUY!| 390 | +------+-----------------+-----+-------+---------------+ 391 | |HD |Home Depot, Inc. |111.8| -0.27| BUY!| 392 | +------+-----------------+-----+-------+---------------+ 393 | |DIS |Walt Disney Compa|104.1| +0.59| BUY!| 394 | +------+-----------------+-----+-------+---------------+ 395 | |INTC |Intel Corporation|34.36| +0.235| Sell| 396 | +------+-----------------+-----+-------+---------------+ 397 | | Recommendations randomly generated| 398 | *======================================================* 399 | ``` 400 | 401 | A lot of times, supplying less information means that ConsoleTable will just guess at various defaults. For example, if you do not provide a key for the table config, ConsoleTable will simply number your keys with names like 'col1', 'col2', etc. If you do not provide a title, it will `capitalize` whatever key you are using for the column, or use something like "Column 1" if you do not supply one. If you do not supply a size, ConsoleTable will just assume you want "\*" as the size (remember, you can have many "*"s and the space is divided equally). 402 | 403 | Additionally, you can give ONLY the titles for columns by simply suppling an array of strings as the console config. With this ability, it's possible to define and use ConsoleTable in a remarkably abbreviated manner, where most of the control of the formatting is unexercised. 404 | 405 | ```ruby 406 | require 'console_table' 407 | 408 | ConsoleTable.define(["Name", "DOB", "Title"]) do |table| 409 | table << ["Rod", "06-15-80", "Software Engineer"] 410 | table << ["Julia", "04-25-81", "Piano Teacher"] 411 | end 412 | 413 | ``` 414 | 415 | Which yields: 416 | 417 | ``` 418 | ================================================================================ 419 | Name DOB Title 420 | -------------------------------------------------------------------------------- 421 | Rod 06-15-80 Software Engineer 422 | Julia 04-25-81 Piano Teacher 423 | ================================================================================ 424 | ``` 425 | 426 | Which is the original Quick Start example up above. 427 | 428 | For more examples of different ways of using ConsoleTable, refer to the test file in this repository, which has many many different examples of usage. 429 | 430 | ## Contributing 431 | 432 | 1. Fork it ( http://github.com/rodhilton/console_table/fork ) 433 | 2. Create your feature branch (`git checkout -b my-new-feature`) 434 | 3. Commit your changes (`git commit -am 'Add some feature'`) 435 | 4. Push to the branch (`git push origin my-new-feature`) 436 | 5. Create new Pull Request 437 | -------------------------------------------------------------------------------- /test/test_console_table.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'simplecov' 3 | SimpleCov.start 4 | 5 | require 'minitest/autorun' 6 | 7 | #-- 8 | # TODO: if you're doing center or right-justification, should it trim from the sides or 9 | # from the left, respectively? 10 | # TODO: "compact" option, which when true makes tables output like they do here, but when 11 | # compact is FALSE and borders is true, there's an extra space between the | and the text 12 | # but only in the middle - on the sides the | goes up against the margin 13 | #++ 14 | class ConsoleTableTest < Minitest::Test 15 | 16 | def setup 17 | require 'console_table' 18 | require 'colorize' 19 | @mock_out = StringIO.new 20 | end 21 | 22 | def test_basic 23 | table_config = [ 24 | {:key => :col1, :size => 20, :title => "Column 1"}, 25 | {:key => :col2, :size => 20, :title => "Column 2"}, 26 | ] 27 | 28 | ConsoleTable.define(table_config, :width => 100, :output=>@mock_out) do |table| 29 | table << { 30 | :col1 => "Row 1, Column 1", 31 | :col2 => "Row 1, Column 2" 32 | } 33 | 34 | table << { 35 | :col1 => "Row 2, Column 1", 36 | :col2 => "Row 2, Column 1" 37 | } 38 | end 39 | 40 | #2345678901234567890##2345678901234567890 41 | expected=<<-END 42 | ========================================= 43 | Column 1 Column 2 44 | ----------------------------------------- 45 | Row 1, Column 1 Row 1, Column 2 46 | Row 2, Column 1 Row 2, Column 1 47 | ========================================= 48 | END 49 | 50 | assert_output_equal expected, @mock_out.string 51 | end 52 | 53 | def test_unicode 54 | table_config = [ 55 | {:key => :col1, :size => 20, :title => "Column 1"}, 56 | {:key => :col2, :size => 20, :title => "Column 2"}, 57 | {:key => :col3, :size => 20, :title => "Column 3"}, 58 | ] 59 | 60 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 61 | table << { 62 | :col1 => "I ♥ Unicode", 63 | :col2 => "I ☂ Unicode", 64 | :col3 => "I ♞ Unicode" 65 | } 66 | 67 | table << { 68 | :col1 => "I ⚂ Unicode Even More", 69 | :col2 => "I \u00A5 Unicode Even More", 70 | :col3 => "I µ Unicode Even More" 71 | } 72 | 73 | table << { 74 | :col1 => {:text => "I ⁂ Unicode", :justify => :right}, 75 | :col2 => {:text => "I \u2190 Unicode", :justify => :center}, 76 | :col3 => {:text => "I ✓ Unicode", :justify => :left} 77 | } 78 | end 79 | 80 | expected=<<-END 81 | ============================================================== 82 | Column 1 Column 2 Column 3 83 | -------------------------------------------------------------- 84 | I ♥ Unicode I ☂ Unicode I ♞ Unicode 85 | I ⚂ Unicode Even Mor I ¥ Unicode Even Mor I µ Unicode Even Mor 86 | I ⁂ Unicode I ← Unicode I ✓ Unicode 87 | ============================================================== 88 | END 89 | 90 | assert_output_equal expected, @mock_out.string 91 | end 92 | 93 | def test_spacing_convention_sets_justification 94 | table_config = [ 95 | {:key => :col1, :size => 20, :title => "Column 1"}, 96 | {:key => :col2, :size => 20, :title => "Column 2"}, 97 | {:key => :col3, :size => 20, :title => "Column 3"}, 98 | ] 99 | 100 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 101 | table << [ 102 | "\tRight", 103 | "\tCenter\t", 104 | "Left\t" 105 | ] 106 | end 107 | 108 | expected=<<-END 109 | ============================================================== 110 | Column 1 Column 2 Column 3 111 | -------------------------------------------------------------- 112 | Right Center Left 113 | ============================================================== 114 | END 115 | 116 | assert_output_equal expected, @mock_out.string 117 | end 118 | 119 | def test_newlines_converted_to_spaces_in_middle_stripped_at_ends 120 | table_config = [ 121 | {:key => :col1, :size => 20, :title => "Column 1"}, 122 | {:key => :col2, :size => 20, :title => "Column 2"}, 123 | {:key => :col3, :size => 20, :title => "Column 3"}, 124 | ] 125 | 126 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 127 | table << [ 128 | {:text => "Bl\nah", :justify => :left}, 129 | {:text => "\nStuff", :justify => :left}, 130 | {:text => "Junk\n", :justify => :right} 131 | ] 132 | end 133 | 134 | expected=<<-END 135 | ============================================================== 136 | Column 1 Column 2 Column 3 137 | -------------------------------------------------------------- 138 | Bl ah Stuff Junk 139 | ============================================================== 140 | END 141 | 142 | assert_output_equal expected, @mock_out.string 143 | end 144 | 145 | def test_linebreak_inside_colorcode_still_resets 146 | table_config = [ 147 | {:key => :col1, :size => 20, :title => "Column 1"}, 148 | {:key => :col2, :size => 5, :title => "Column 2"}, 149 | ] 150 | 151 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 152 | table << [ 153 | "Bl\nah".blue, 154 | "1234\nStuff".red 155 | ] 156 | end 157 | 158 | expected=<<-END 159 | ========================== 160 | Column 1 Colum 161 | -------------------------- 162 | Bl ah 1234 163 | ========================== 164 | END 165 | 166 | assert_includes @mock_out.string, "\e[0;34;49mBl ah\e[0m" #ensure the color got reset 167 | assert_includes @mock_out.string, "\e[0;31;49m1234 \e[0m" #ensure the color got reset 168 | 169 | assert_output_equal expected, @mock_out.string 170 | end 171 | 172 | def test_spacing_after_colors_is_preserved 173 | table_config = [ 174 | {:key => :col1, :size => 60, :title => "Column 1"}, 175 | ] 176 | 177 | ConsoleTable.define(table_config, :width => 60, :output => @mock_out) do |table| 178 | table << [ 179 | "hello - \e[0;34;49mTest\e[0m - this is a test" 180 | ] 181 | 182 | table << [ 183 | "\e[0;34;49mTest\e[0m - this is a test" 184 | ] 185 | 186 | table << [ 187 | "blah and stuff - \e[0;34;49mTest\e[0m" 188 | ] 189 | end 190 | 191 | expected=<<-END 192 | ============================================================ 193 | Column 1 194 | ------------------------------------------------------------ 195 | hello - Test - this is a test 196 | Test - this is a test 197 | blah and stuff - Test 198 | ============================================================ 199 | END 200 | 201 | assert_output_equal expected, @mock_out.string 202 | end 203 | 204 | def test_can_ellipsize_at_column_level 205 | table_config = [ 206 | {:key => :col1, :size => 20, :title => "Column 1", :ellipsize => true}, 207 | {:key => :col2, :size => 20, :title => "Column 2"}, 208 | ] 209 | 210 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 211 | table << [ 212 | "This is way too long to fit here", 213 | "This is way too long to fit here", 214 | ] 215 | 216 | table << [ 217 | {text: "This is way too long to fit here", :ellipsize => false}, 218 | {text: "This is way too long to fit here", :ellipsize => true}, 219 | ] 220 | end 221 | 222 | expected=<<-END 223 | ========================================= 224 | Column 1 Column 2 225 | ----------------------------------------- 226 | This is way too l... This is way too long 227 | This is way too long This is way too l... 228 | ========================================= 229 | END 230 | 231 | assert_output_equal expected, @mock_out.string 232 | end 233 | 234 | def test_justify_convention_followed_in_hash_text_but_overrideable 235 | table_config = [ 236 | {:key => :col1, :size => 20, :title => "Column 1", :justify => :center}, 237 | ] 238 | 239 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 240 | table << ["Short"] 241 | table << ["\tShort"] 242 | table << ["Short\t"] 243 | table << [{:text => "Short", :justify => :right}] 244 | table << [{:text => "Short", :justify => :left}] 245 | table << [{:text => "\tShort"}] 246 | table << [{:text => "Short\t"}] 247 | table << [{:text => "\tShort", :justify => :left}] #Override 248 | table << [{:text => "Short\t", :justify => :right}] #Override 249 | end 250 | 251 | expected=<<-END 252 | ==================== 253 | Column 1 254 | -------------------- 255 | Short 256 | Short 257 | Short 258 | Short 259 | Short 260 | Short 261 | Short 262 | Short 263 | Short 264 | ==================== 265 | END 266 | 267 | assert_output_equal expected, @mock_out.string 268 | end 269 | 270 | def test_should_not_color_tabs_or_ignore_tab_justify_convention_if_inside_color 271 | table_config = [ 272 | {:key => :col1, :size => 20, :title => "Column 1", :justify => :center}, 273 | {:key => :col2, :size => 20, :title => "Column 2", :justify => :right}, 274 | {:key => :col3, :size => 20, :title => "Column 3", :justify => :left}, 275 | ] 276 | 277 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 278 | table << [ 279 | "\tRight".blue, 280 | "\tCenter\t".red, 281 | "Left\t".magenta, 282 | ] 283 | end 284 | 285 | expected=<<-END 286 | ============================================================== 287 | Column 1 Column 2 Column 3 288 | -------------------------------------------------------------- 289 | Right Center Left 290 | ============================================================== 291 | END 292 | 293 | assert_includes @mock_out.string, " \e[0;34;49mRight\e[0m" #space is on outside of coor 294 | assert_includes @mock_out.string, " \e[0;35;49mLeft\e[0m " #space is on outside of color 295 | assert_includes @mock_out.string, " \e[0;31;49mCenter\e[0m" #this assert fails due to what I'm pretty sure is a bug in gsub(), but it's not the end of the world so I'm not doing a workaround 296 | 297 | assert_output_equal expected, @mock_out.string 298 | end 299 | 300 | def test_spaces_preserved_in_middle_but_stripped_at_ends 301 | table_config = [ 302 | {:key => :col1, :size => 20, :title => "Column 1"}, 303 | {:key => :col2, :size => 20, :title => "Column 2"}, 304 | {:key => :col3, :size => 20, :title => "Column 3"}, 305 | ] 306 | 307 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 308 | table << [ 309 | {:text => "Bl ah", :justify => :left}, 310 | {:text => " Stuff", :justify => :left}, 311 | {:text => "Junk ", :justify => :right} 312 | ] 313 | end 314 | 315 | expected=<<-END 316 | ============================================================== 317 | Column 1 Column 2 Column 3 318 | -------------------------------------------------------------- 319 | Bl ah Stuff Junk 320 | ============================================================== 321 | END 322 | 323 | assert_output_equal expected, @mock_out.string 324 | end 325 | 326 | def test_ignores_tabbing_convention_if_setting_justification_explicitly 327 | table_config = [ 328 | {:key => :col1, :size => 20, :title => "Column 1"}, 329 | {:key => :col2, :size => 20, :title => "Column 2"}, 330 | {:key => :col3, :size => 20, :title => "Column 3"}, 331 | ] 332 | 333 | ConsoleTable.define(table_config, :width => 62, :output => @mock_out) do |table| 334 | table << [ 335 | {:text => "\tBlah", :justify => :left}, 336 | {:text => "\tStuff\t", :justify => :right}, 337 | {:text => "\tJunk", :justify => :center} 338 | ] 339 | end 340 | 341 | expected=<<-END 342 | ============================================================== 343 | Column 1 Column 2 Column 3 344 | -------------------------------------------------------------- 345 | Blah Stuff Junk 346 | ============================================================== 347 | END 348 | 349 | assert_output_equal expected, @mock_out.string 350 | end 351 | 352 | def test_can_use_convenient_operator 353 | table_config = [ 354 | {:key => :col1, :size => 20, :title => "Column 1"}, 355 | {:key => :col2, :size => 20, :title => "Column 2"}, 356 | ] 357 | 358 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 359 | table << { 360 | :col1 => "Row 1, Column 1", 361 | :col2 => "Row 1, Column 2" 362 | } 363 | 364 | table << { 365 | :col1 => "Row 2, Column 1", 366 | :col2 => "Row 2, Column 1" 367 | } 368 | end 369 | 370 | expected=<<-END 371 | ========================================= 372 | Column 1 Column 2 373 | ----------------------------------------- 374 | Row 1, Column 1 Row 1, Column 2 375 | Row 2, Column 1 Row 2, Column 1 376 | ========================================= 377 | END 378 | 379 | assert_output_equal expected, @mock_out.string 380 | end 381 | 382 | def test_can_supply_array_and_order_is_inferred 383 | table_config = [ 384 | {:key => :col1, :size => 20, :title => "Column 1"}, 385 | {:key => :col2, :size => 20, :title => "Column 2"}, 386 | ] 387 | 388 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 389 | table << [ 390 | "Row 1, Column 1", 391 | "Row 1, Column 2" 392 | ] 393 | 394 | table << [ 395 | {:text => "Row 2, Column 1", :justify => :center}, 396 | {:text => "Row 2, Column 2", :justify => :right} 397 | ] 398 | end 399 | 400 | expected=<<-END 401 | ========================================= 402 | Column 1 Column 2 403 | ----------------------------------------- 404 | Row 1, Column 1 Row 1, Column 2 405 | Row 2, Column 1 Row 2, Column 2 406 | ========================================= 407 | END 408 | 409 | assert_output_equal expected, @mock_out.string 410 | end 411 | 412 | def test_percents 413 | table_config = [ 414 | {:key => :col1, :size => 0.3, :title => "Column 1"}, 415 | {:key => :col2, :size => 0.7, :title => "Column 2"}, 416 | ] 417 | 418 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 419 | table << { 420 | :col1 => "Row 1, Column 1", 421 | :col2 => "Row 1, Column 2" 422 | } 423 | 424 | table << ["Row 2, Column 1", "Row 2, Column 1"] 425 | end 426 | 427 | expected=<<-END 428 | =================================================================================================== 429 | Column 1 Column 2 430 | --------------------------------------------------------------------------------------------------- 431 | Row 1, Column 1 Row 1, Column 2 432 | Row 2, Column 1 Row 2, Column 1 433 | =================================================================================================== 434 | END 435 | 436 | assert_output_equal expected, @mock_out.string 437 | end 438 | 439 | def test_star_fills_all_extra_space 440 | table_config = [ 441 | {:key => :col1, :size => 20, :title => "Column 1"}, 442 | {:key => :col2, :size => "*", :title => "Column 2"}, 443 | ] 444 | 445 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 446 | table << { 447 | :col1 => "Row 1, Column 1", 448 | :col2 => "Row 1, Column 2" 449 | } 450 | 451 | table << { 452 | :col1 => "Row 2, Column 1", 453 | :col2 => "Row 2, Column 1" 454 | } 455 | end 456 | 457 | expected=<<-END 458 | ==================================================================================================== 459 | Column 1 Column 2 460 | ---------------------------------------------------------------------------------------------------- 461 | Row 1, Column 1 Row 1, Column 2 462 | Row 2, Column 1 Row 2, Column 1 463 | ==================================================================================================== 464 | END 465 | 466 | assert_output_equal expected, @mock_out.string 467 | end 468 | 469 | def test_multiple_stars_split_evenly 470 | table_config = [ 471 | {:key => :col1, :size => 20, :title => "Column 1"}, 472 | {:key => :col2, :size => "*", :title => "Column 2"}, 473 | {:key => :col3, :size => "*", :title => "Column 3"}, 474 | ] 475 | 476 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 477 | table << { 478 | :col1 => "Row 1, Column 1", 479 | :col2 => "Row 1, Column 2", 480 | :col3 => "Row 1, Column 3" 481 | } 482 | 483 | table << { 484 | :col1 => "Row 2, Column 1", 485 | :col2 => "Row 2, Column 1", 486 | :col3 => "Row 2, Column 3" 487 | } 488 | end 489 | 490 | expected=<<-END 491 | ==================================================================================================== 492 | Column 1 Column 2 Column 3 493 | ---------------------------------------------------------------------------------------------------- 494 | Row 1, Column 1 Row 1, Column 2 Row 1, Column 3 495 | Row 2, Column 1 Row 2, Column 1 Row 2, Column 3 496 | ==================================================================================================== 497 | END 498 | 499 | assert_output_equal expected, @mock_out.string 500 | end 501 | 502 | def test_no_size_assumed_to_be_star 503 | table_config = [ 504 | {:key => :col1, :title => "Column 1"}, 505 | {:key => :col2, :title => "Column 2"}, 506 | ] 507 | 508 | ConsoleTable.define(table_config, :width => 40, :output => @mock_out) do |table| 509 | table << { 510 | :col1 => "Row 1, Column 1", 511 | :col2 => "Row 1, Column 2", 512 | } 513 | 514 | end 515 | 516 | expected=<<-END 517 | =============================================================================== 518 | Column 1 Column 2 519 | ------------------------------------------------------------------------------- 520 | Row 1, Column 1 Row 1, Column 2 521 | =============================================================================== 522 | END 523 | 524 | assert_output_equal expected, @mock_out.string 525 | end 526 | 527 | def test_no_name_defaulted_to_capitalize_of_key_name 528 | table_config = [ 529 | {:key => :col1}, 530 | {:key => :col2}, 531 | ] 532 | 533 | ConsoleTable.define(table_config, :width => 40, :output => @mock_out) do |table| 534 | table << { 535 | :col1 => "Row 1, Column 1", 536 | :col2 => "Row 1, Column 2", 537 | } 538 | 539 | end 540 | 541 | expected=<<-END 542 | =============================================================================== 543 | Col1 Col2 544 | ------------------------------------------------------------------------------- 545 | Row 1, Column 1 Row 1, Column 2 546 | =============================================================================== 547 | END 548 | 549 | assert_output_equal expected, @mock_out.string 550 | end 551 | 552 | def test_can_combine_percentages_fixed_and_stars 553 | table_config = [ 554 | {:key => :col1, :size => 20, :title => "Column 1"}, 555 | {:key => :col2, :size => 0.3, :title => "Column 2"}, 556 | {:key => :col3, :size => 4, :title => "Column 3"}, 557 | {:key => :col4, :size => "*", :title => "Column 4"}, 558 | {:key => :col5, :size => 0.2, :title => "Column 5"}, 559 | {:key => :col6, :size => "*", :title => "Column 6"}, 560 | {:key => :col7, :size => 10, :title => "Column 7"} 561 | ] 562 | 563 | ConsoleTable.define(table_config, :width => 160, :output => @mock_out) do |table| 564 | table << { 565 | :col1 => "Row 1, Column 1", 566 | :col2 => "Row 1, Column 2", 567 | :col3 => "Row 1, Column 3", 568 | :col4 => "Row 1, Column 4", 569 | :col5 => "Row 1, Column 5", 570 | :col6 => "Row 1, Column 6", 571 | :col7 => "Row 1, Column 7", 572 | } 573 | 574 | table << { 575 | :col1 => "Row 2, Column 1", 576 | :col2 => "Row 2, Column 2", 577 | :col3 => "Row 2, Column 3", 578 | :col4 => "Row 2, Column 4", 579 | :col5 => "Row 2, Column 5", 580 | :col6 => "Row 2, Column 6", 581 | :col7 => "Row 2, Column 7", 582 | } 583 | end 584 | 585 | expected=<<-END 586 | ================================================================================================================================================================ 587 | Column 1 Column 2 Colu Column 4 Column 5 Column 6 Column 7 588 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- 589 | Row 1, Column 1 Row 1, Column 2 Row Row 1, Column 4 Row 1, Column 5 Row 1, Column 6 Row 1, Col 590 | Row 2, Column 1 Row 2, Column 2 Row Row 2, Column 4 Row 2, Column 5 Row 2, Column 6 Row 2, Col 591 | ================================================================================================================================================================ 592 | END 593 | 594 | assert_output_equal expected, @mock_out.string 595 | end 596 | 597 | def test_wont_create_layout_too_large_for_screen 598 | table_config = [ 599 | {:key => :col1, :size => 20, :title => "Column 1"}, 600 | {:key => :col2, :size => 20, :title => "Column 2"}, 601 | ] 602 | 603 | assert_raises(RuntimeError) { ConsoleTable.define(table_config, :width => 30) } 604 | end 605 | 606 | def test_wont_create_layout_with_more_than_100_percent 607 | table_config = [ 608 | {:key => :col1, :size => 0.8, :title => "Column 1"}, 609 | {:key => :col2, :size => 0.3, :title => "Column 2"}, 610 | ] 611 | 612 | assert_raises(RuntimeError) { ConsoleTable.define(table_config) } 613 | end 614 | 615 | def test_wont_create_layout_with_invalid_size 616 | table_config = [ 617 | {:key => :col1, :size => 0.8, :title => "Column 1"}, 618 | {:key => :col2, :size => "hello!", :title => "Column 2"}, 619 | ] 620 | 621 | assert_raises(RuntimeError) { ConsoleTable.define(table_config) } 622 | end 623 | 624 | def test_wont_allow_repeats_of_key_names 625 | table_config = [ 626 | {:key => :col1, :size => 20, :title => "Column 1"}, 627 | {:key => :col1, :size => 20, :title => "Column 2"}, 628 | ] 629 | 630 | assert_raises(RuntimeError) { ConsoleTable.define(table_config) } 631 | end 632 | 633 | def test_wont_create_layout_when_column_layout_isnt_num_or_array 634 | assert_raises(RuntimeError) { ConsoleTable.define("4", :width => 30) } 635 | assert_raises(RuntimeError) { ConsoleTable.define({}, :width => 30) } 636 | end 637 | 638 | def test_can_truncate_output 639 | table_config = [ 640 | {:key => :col1, :size => 20, :title => "Column 1"} 641 | ] 642 | 643 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 644 | table << ["This is short"] 645 | 646 | table << {:col1 => "This is way too long and it needs to get cut off"} 647 | 648 | table << [ 649 | {:text => "This is way too long and it needs to get cut off", :ellipsize => true} 650 | ] 651 | 652 | end 653 | 654 | expected=<<-END 655 | ==================== 656 | Column 1 657 | -------------------- 658 | This is short 659 | This is way too long 660 | This is way too l... 661 | ==================== 662 | END 663 | 664 | assert_output_equal expected, @mock_out.string 665 | end 666 | 667 | def test_can_truncate_titles 668 | table_config = [ 669 | {:key => :col1, :size => 20, :title => "This column title is too long"} 670 | ] 671 | 672 | ConsoleTable.define(table_config, :width => 100, :title=>"Hello world this is too long", :output => @mock_out) do |table| 673 | table << ["This is short"] 674 | 675 | end 676 | 677 | expected=<<-END 678 | ==================== 679 | Hello world this is 680 | This column title is 681 | -------------------- 682 | This is short 683 | ==================== 684 | END 685 | 686 | assert_output_equal expected, @mock_out.string 687 | end 688 | 689 | def test_can_justify_columns_and_override_in_rows 690 | table_config = [ 691 | {:key => :col1, :size => 20, :title => "Column 1"}, 692 | {:key => :col2, :size => 20, :title => "Column 2", :justify => :center}, 693 | {:key => :col3, :size => 20, :title => "Column 3", :justify => :right} 694 | ] 695 | 696 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 697 | table << { 698 | :col1 => "Short1", 699 | :col2 => "Short2", 700 | :col3 => "Short3" 701 | } 702 | 703 | table << { 704 | :col1 => {text: "Short1"}, 705 | :col2 => {text: "Short2"}, 706 | :col3 => {text: "Short3"} 707 | } 708 | 709 | table << { 710 | :col1 => {text: "Short1", :justify => :center}, 711 | :col2 => {text: "Short2", :justify => :right}, 712 | :col3 => {text: "Short3", :justify => :left} 713 | } 714 | end 715 | 716 | expected=<<-END 717 | ============================================================== 718 | Column 1 Column 2 Column 3 719 | -------------------------------------------------------------- 720 | Short1 Short2 Short3 721 | Short1 Short2 Short3 722 | Short1 Short2 Short3 723 | ============================================================== 724 | END 725 | 726 | assert_output_equal expected, @mock_out.string 727 | end 728 | 729 | def test_huge_example 730 | 731 | table_config = [ 732 | {:key => :title, :size => 15, :title => "Movie Title"}, 733 | {:key => :name, :size => 15, :title => "Name"}, 734 | {:key => :release_date, :size => 8, :title => "Release Date Too Long"}, 735 | {:key => :tagline, :size => "*", :title => "Motto", :justify => :right}, 736 | ] 737 | 738 | ConsoleTable.define(table_config, :left_margin => 5, :right_margin => 10, :width => 80, :title => "Movie Killers", :output => @mock_out) do |table| 739 | table << { 740 | :title => {:text => "Friday the 13th"}, 741 | :name => {:text => "Jason's Mom", :justify => :left}, 742 | :release_date => {text: "05-09-80"}, 743 | :tagline => {:text => "They were warned...They are doomed...And on Friday the 13th, nothing will save them.", :ellipsize => true} 744 | } 745 | 746 | table << { 747 | :title => {:text => "Halloween"}, 748 | :name => {:text => "Michael Meyers", :justify => :left}, 749 | :release_date => {text: "10-25-80"}, 750 | :tagline => {:text => "Everyone is entitled to one good scare", :ellipsize => true} 751 | } 752 | 753 | table << { 754 | :title => {:text => "Nightmare on Elm St."}, 755 | :name => {:text => "Freddy Krueger", :justify => :left}, 756 | :release_date => {text: "11-16-84"}, 757 | :tagline => {:text => "A scream that wakes you up, might be your own", :ellipsize => true} 758 | } 759 | 760 | table << ["Hellraiser", "Pinhead", "9-18-87", "Demon to some. Angel to others."] 761 | 762 | table.footer << "This is just a line of footer text" 763 | table.footer << "This is a second footer with \nlots of \nlinebreaks in it." 764 | end 765 | 766 | expected=<<-END 767 | ================================================================= 768 | Movie Killers 769 | Movie Title Name Release Motto 770 | ----------------------------------------------------------------- 771 | Friday the 13th Jason's Mom 05-09-80 They were warned...Th... 772 | Halloween Michael Meyers 10-25-80 Everyone is entitled ... 773 | Nightmare on El Freddy Krueger 11-16-84 A scream that wakes y... 774 | Hellraiser Pinhead 9-18-87 Demon to some. Angel to 775 | ----------------------------------------------------------------- 776 | This is just a line of footer text 777 | This is a second footer with 778 | lots of 779 | linebreaks in it. 780 | ================================================================= 781 | END 782 | 783 | assert_output_equal expected, @mock_out.string 784 | end 785 | 786 | def test_printing_a_single_string_does_full_line 787 | table_config = [ 788 | {:key => :col1, :size => 20, :title => "Column 1"}, 789 | {:key => :col2, :size => 20, :title => "Column 2"}, 790 | ] 791 | 792 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 793 | table << "This is just a string, it should ignore columns" 794 | end 795 | 796 | expected=<<-END 797 | ========================================= 798 | This is just a string, it should ignore c 799 | ========================================= 800 | END 801 | 802 | assert_output_equal expected, @mock_out.string 803 | end 804 | 805 | #TODO: Really don't love how this works.. truncate from right? Auto word-wrap? something.. 806 | def test_printing_a_single_string_truncates_footer 807 | table_config = [ 808 | {:key => :col1, :size => 20, :title => "Column 1"}, 809 | ] 810 | 811 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 812 | table << "Blah" 813 | 814 | table.footer << "This is a really long footer that should automatically be truncated" 815 | table.footer << "This is a really long footer \n that should automatically be truncated" 816 | end 817 | 818 | expected=<<-END 819 | ==================== 820 | Blah 821 | -------------------- 822 | This is a really lon 823 | This is a really lon 824 | that should automati 825 | ==================== 826 | END 827 | 828 | assert_output_equal expected, @mock_out.string 829 | end 830 | 831 | def test_printing_a_single_after_data_makes_headings_show_up 832 | table_config = [ 833 | {:key => :col1, :size => 20, :title => "Column 1"}, 834 | {:key => :col2, :size => 20, :title => "Column 2"}, 835 | ] 836 | 837 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 838 | table << ["One", "Two"] 839 | table << "This is just a string, it should ignore columns" 840 | table << ["One", "Two"] 841 | end 842 | 843 | expected=<<-END 844 | ========================================= 845 | Column 1 Column 2 846 | ----------------------------------------- 847 | One Two 848 | This is just a string, it should ignore c 849 | One Two 850 | ========================================= 851 | END 852 | 853 | assert_output_equal expected, @mock_out.string 854 | end 855 | 856 | def test_can_have_a_bordered_table 857 | table_config = [ 858 | {:key => :col1, :size => 20, :title => "Column 1"}, 859 | {:key => :col2, :size => 0.3, :title => "Column 2"}, 860 | {:key => :col3, :size => 10, :title => "Column 3", :justify => :center}, 861 | {:key => :col4, :size => "*", :title => "Column 4", :justify => :center} 862 | ] 863 | 864 | ConsoleTable.define(table_config, :left_margin => 10, :right_margin => 7, :width => 100, :title => "Test Title", :borders => true, :output => @mock_out) do |table| 865 | (1..5).each do |row| 866 | table << (1..4).collect { |i| "Row #{row}, Column #{i}".red } 867 | end 868 | 869 | table << "Plain line needs borders" 870 | table.footer << "Footer needs borders" 871 | table.footer << "Footer still \n needs borders" 872 | 873 | end 874 | 875 | expected=<<-END 876 | *================================================================================* 877 | | Test Title | 878 | +--------------------+--------------+----------+---------------------------------+ 879 | |Column 1 |Column 2 | Column 3 | Column 4 | 880 | +--------------------+--------------+----------+---------------------------------+ 881 | |Row 1, Column 1 |Row 1, Column |Row 1, Col| Row 1, Column 4 | 882 | +--------------------+--------------+----------+---------------------------------+ 883 | |Row 2, Column 1 |Row 2, Column |Row 2, Col| Row 2, Column 4 | 884 | +--------------------+--------------+----------+---------------------------------+ 885 | |Row 3, Column 1 |Row 3, Column |Row 3, Col| Row 3, Column 4 | 886 | +--------------------+--------------+----------+---------------------------------+ 887 | |Row 4, Column 1 |Row 4, Column |Row 4, Col| Row 4, Column 4 | 888 | +--------------------+--------------+----------+---------------------------------+ 889 | |Row 5, Column 1 |Row 5, Column |Row 5, Col| Row 5, Column 4 | 890 | +--------------------+--------------+----------+---------------------------------+ 891 | |Plain line needs borders | 892 | +--------------------+--------------+----------+---------------------------------+ 893 | | Footer needs borders| 894 | | Footer still| 895 | | needs borders| 896 | *================================================================================* 897 | END 898 | 899 | assert_output_equal expected, @mock_out.string 900 | end 901 | 902 | def test_outline_joins_only_when_no_footer_or_header 903 | table_config = [ 904 | {:key => :col1, :size => 20, :title => "Column 1"}, 905 | {:key => :col2, :size => 0.3, :title => "Column 2"}, 906 | {:key => :col3, :size => 10, :title => "Column 3", :justify => :center}, 907 | {:key => :col4, :size => "*", :title => "Column 4", :justify => :center} 908 | ] 909 | 910 | #borders are true, so outline false should be ignored 911 | ConsoleTable.define(table_config, :left_margin => 10, :right_margin => 7, :width => 100, :borders => true, :outline => false, :output => @mock_out) do |table| 912 | (1..5).each do |row| 913 | table << (1..4).collect { |i| "Row #{row}, Column #{i}" } 914 | end 915 | 916 | table << "Plain line needs borders" 917 | 918 | end 919 | 920 | expected=<<-END 921 | *====================*==============*==========*=================================* 922 | |Column 1 |Column 2 | Column 3 | Column 4 | 923 | +--------------------+--------------+----------+---------------------------------+ 924 | |Row 1, Column 1 |Row 1, Column |Row 1, Col| Row 1, Column 4 | 925 | +--------------------+--------------+----------+---------------------------------+ 926 | |Row 2, Column 1 |Row 2, Column |Row 2, Col| Row 2, Column 4 | 927 | +--------------------+--------------+----------+---------------------------------+ 928 | |Row 3, Column 1 |Row 3, Column |Row 3, Col| Row 3, Column 4 | 929 | +--------------------+--------------+----------+---------------------------------+ 930 | |Row 4, Column 1 |Row 4, Column |Row 4, Col| Row 4, Column 4 | 931 | +--------------------+--------------+----------+---------------------------------+ 932 | |Row 5, Column 1 |Row 5, Column |Row 5, Col| Row 5, Column 4 | 933 | +--------------------+--------------+----------+---------------------------------+ 934 | |Plain line needs borders | 935 | *====================*==============*==========*=================================* 936 | END 937 | 938 | assert_output_equal expected, @mock_out.string 939 | end 940 | 941 | def test_can_have_no_outline_if_requested 942 | table_config = [ 943 | {:key => :col1, :size => 20, :title => "Column 1"}, 944 | {:key => :col2, :size => 0.3, :title => "Column 2"}, 945 | ] 946 | 947 | ConsoleTable.define(table_config, :width => 60, :outline => false, :title => "Still has a title", :output => @mock_out) do |table| 948 | (1..5).each do |row| 949 | table << (1..2).collect { |i| "Row #{row}, Column #{i}" } 950 | end 951 | 952 | end 953 | 954 | expected=<<-END 955 | Still has a title 956 | Column 1 Column 2 957 | -------------------------------- 958 | Row 1, Column 1 Row 1, Colu 959 | Row 2, Column 1 Row 2, Colu 960 | Row 3, Column 1 Row 3, Colu 961 | Row 4, Column 1 Row 4, Colu 962 | Row 5, Column 1 Row 5, Colu 963 | END 964 | 965 | assert_output_equal expected, @mock_out.string 966 | end 967 | 968 | def test_can_use_colors_without_affecting_layout 969 | table_config = [ 970 | {:key => :col1, :size => 10, :title => "Column 1", :justify => :left}, 971 | {:key => :col2, :size => 10, :title => "Column 2", :justify => :center}, 972 | {:key => :col3, :size => 10, :title => "Column 3", :justify => :right}, 973 | ] 974 | 975 | ConsoleTable.define(table_config, :width => 120, :output => @mock_out) do |table| 976 | table << ["Short".blue, "Short".bold, "Short".red.on_blue] 977 | 978 | table << ["Much much longer".blue, "Much much longer".bold, "Much much longer".red.on_blue] 979 | 980 | table << [ 981 | {:text => "Much much longer".blue, :ellipsize => true}, 982 | {:text => "Much much longer".underline, :ellipsize => true}, 983 | {:text => "Much much longer".on_magenta, :ellipsize => true} 984 | ] 985 | 986 | table << [ 987 | {:text => "Much much longer".yellow, :ellipsize => true}, 988 | {:text => "Normal, should reset", :ellipsize => true}, 989 | {:text => "Much much longer".bold, :ellipsize => true} 990 | ] 991 | end 992 | 993 | expected=<<-END 994 | ================================ 995 | Column 1 Column 2 Column 3 996 | -------------------------------- 997 | Short Short Short 998 | Much much Much much Much much 999 | Much mu... Much mu... Much mu... 1000 | Much mu... Normal,... Much mu... 1001 | ================================ 1002 | END 1003 | 1004 | assert_includes @mock_out.string, "\e[1;39;49mShort\e[0m" #Should have normal color codes 1005 | assert_includes @mock_out.string, "\e[0;33;49mMuch mu...\e[0m" #the cut-off one should keep the color code for ellipses, then reset 1006 | 1007 | assert_output_equal expected, @mock_out.string 1008 | end 1009 | 1010 | def test_will_generate_default_column_keys_and_titles_and_sizes_if_not_provided 1011 | table_config = [ 1012 | {:size => 20, :title => "Column 1"}, 1013 | {:key => :col2, :size => 20, :title => "Column 2"}, 1014 | {:size => 20, :title => "Column 3"}, 1015 | {:key => :col4, :size => 20}, 1016 | {:size => 20}, 1017 | {} 1018 | ] 1019 | 1020 | ConsoleTable.define(table_config, :width => 125, :output => @mock_out) do |table| 1021 | table << ["A", "B", "C", "D", "E", "F"] 1022 | 1023 | end 1024 | 1025 | expected=<<-END 1026 | ============================================================================================================================= 1027 | Column 1 Column 2 Column 3 Col4 Column 5 Column 6 1028 | ----------------------------------------------------------------------------------------------------------------------------- 1029 | A B C D E F 1030 | ============================================================================================================================= 1031 | END 1032 | 1033 | assert_output_equal expected, @mock_out.string 1034 | end 1035 | 1036 | def test_can_use_a_string_instead_of_hash_to_default_everything 1037 | table_config = [ 1038 | {:size => 20, :title => "Column 1"}, 1039 | "Simple Column", 1040 | {:size => 20, :title => "Column 3"}, 1041 | ] 1042 | 1043 | ConsoleTable.define(table_config, :width => 70, :output => @mock_out) do |table| 1044 | table << ["A", "B", "C"] 1045 | end 1046 | 1047 | expected=<<-END 1048 | ====================================================================== 1049 | Column 1 Simple Column Column 3 1050 | ---------------------------------------------------------------------- 1051 | A B C 1052 | ====================================================================== 1053 | END 1054 | 1055 | assert_output_equal expected, @mock_out.string 1056 | end 1057 | 1058 | def test_can_use_a_config_of_nothing_but_titles_if_you_really_want 1059 | table_config = [ 1060 | "A Column", "B Column", "C Column" 1061 | ] 1062 | 1063 | ConsoleTable.define(table_config, :width => 30, :output => @mock_out) do |table| 1064 | table << ["A", "B", "C"] 1065 | end 1066 | 1067 | expected=<<-END 1068 | ============================= 1069 | A Column B Column C Column 1070 | ----------------------------- 1071 | A B C 1072 | ============================= 1073 | END 1074 | 1075 | assert_output_equal expected, @mock_out.string 1076 | end 1077 | 1078 | def test_can_disable_headings 1079 | table_config = [ 1080 | {:key => :col1, :size => 20, :title => "Column 1"}, 1081 | {:key => :col2, :size => 20, :title => "Column 2"}, 1082 | ] 1083 | 1084 | ConsoleTable.define(table_config, :width => 100, :headings => false, :output => @mock_out) do |table| 1085 | table << { 1086 | :col1 => "Row 1, Column 1", 1087 | :col2 => "Row 1, Column 2" 1088 | } 1089 | 1090 | table << { 1091 | :col1 => "Row 2, Column 1", 1092 | :col2 => "Row 2, Column 1" 1093 | } 1094 | end 1095 | 1096 | expected=<<-END 1097 | ========================================= 1098 | Row 1, Column 1 Row 1, Column 2 1099 | Row 2, Column 1 Row 2, Column 1 1100 | ========================================= 1101 | END 1102 | 1103 | assert_output_equal expected, @mock_out.string 1104 | 1105 | end 1106 | 1107 | def test_can_disable_headings_and_outline 1108 | table_config = [ 1109 | {:key => :col1, :size => 20, :title => "Column 1"}, 1110 | {:key => :col2, :size => 20, :title => "Column 2"}, 1111 | ] 1112 | 1113 | ConsoleTable.define(table_config, :width => 100, :headings => false, :outline=>false, :output => @mock_out) do |table| 1114 | table << { 1115 | :col1 => "Row 1, Column 1", 1116 | :col2 => "Row 1, Column 2" 1117 | } 1118 | 1119 | table << { 1120 | :col1 => "Row 2, Column 1", 1121 | :col2 => "Row 2, Column 1" 1122 | } 1123 | end 1124 | 1125 | expected=<<-END 1126 | Row 1, Column 1 Row 1, Column 2 1127 | Row 2, Column 1 Row 2, Column 1 1128 | END 1129 | 1130 | assert_output_equal expected, @mock_out.string 1131 | 1132 | end 1133 | 1134 | def test_can_disable_headings_with_borders 1135 | table_config = [ 1136 | {:key => :col1, :size => 20, :title => "Column 1"}, 1137 | {:key => :col2, :size => 20, :title => "Column 2"}, 1138 | ] 1139 | 1140 | ConsoleTable.define(table_config, :width => 100, :headings => false, :borders => true, :output => @mock_out) do |table| 1141 | table << { 1142 | :col1 => "Row 1, Column 1", 1143 | :col2 => "Row 1, Column 2" 1144 | } 1145 | 1146 | table << { 1147 | :col1 => "Row 2, Column 1", 1148 | :col2 => "Row 2, Column 1" 1149 | } 1150 | end 1151 | 1152 | expected=<<-END 1153 | *====================*====================* 1154 | |Row 1, Column 1 |Row 1, Column 2 | 1155 | +--------------------+--------------------+ 1156 | |Row 2, Column 1 |Row 2, Column 1 | 1157 | *====================*====================* 1158 | END 1159 | 1160 | assert_output_equal expected, @mock_out.string 1161 | end 1162 | 1163 | def test_can_define_a_table_with_just_numbers_for_size 1164 | table_config = [ 1165 | {:key => :col1, :size => 20, :title => "Column 1"}, 1166 | 15, 1167 | ] 1168 | 1169 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 1170 | table << { 1171 | :col1 => "Row 1, Column 1", 1172 | :col2 => "Row 1, Column 2" 1173 | } 1174 | 1175 | table << { 1176 | :col1 => "Row 2, Column 1", 1177 | :col2 => "Row 2, Column 1" 1178 | } 1179 | end 1180 | 1181 | expected=<<-END 1182 | ==================================== 1183 | Column 1 Column 2 1184 | ------------------------------------ 1185 | Row 1, Column 1 Row 1, Column 2 1186 | Row 2, Column 1 Row 2, Column 1 1187 | ==================================== 1188 | END 1189 | 1190 | assert_output_equal expected, @mock_out.string 1191 | 1192 | end 1193 | 1194 | def test_can_define_a_mix_and_match_of_floats_and_hashes_and_strings 1195 | table_config = [ 1196 | 0.4, 1197 | {:key => :a_column, :size => 20, :title => "A Column"}, 1198 | 15, 1199 | "Title" 1200 | ] 1201 | 1202 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 1203 | table << [ 1204 | "Row 1, Column 1", 1205 | "Row 1, Column 2", 1206 | "Row 1, Column 3", 1207 | "Row 1, Column 4" 1208 | ] 1209 | 1210 | table << [ 1211 | "Row 2, Column 1", 1212 | "Row 2, Column 2", 1213 | "Row 2, Column 3", 1214 | "Row 2, Column 4" 1215 | ] 1216 | end 1217 | 1218 | expected=<<-END 1219 | =================================================================================================== 1220 | Column 1 A Column Column 3 Title 1221 | --------------------------------------------------------------------------------------------------- 1222 | Row 1, Column 1 Row 1, Column 2 Row 1, Column 3 Row 1, Column 4 1223 | Row 2, Column 1 Row 2, Column 2 Row 2, Column 3 Row 2, Column 4 1224 | =================================================================================================== 1225 | END 1226 | 1227 | assert_output_equal expected, @mock_out.string 1228 | 1229 | end 1230 | 1231 | def test_can_define_a_group_of_equal_size_columns_with_just_the_number_of_columns 1232 | ConsoleTable.define(4, :width => 100, :output => @mock_out) do |table| 1233 | table << [ 1234 | "Row 1, Column 1", 1235 | "Row 1, Column 2", 1236 | "Row 1, Column 3", 1237 | "Row 1, Column 4" 1238 | ] 1239 | 1240 | table << [ 1241 | "Row 2, Column 1", 1242 | "Row 2, Column 2", 1243 | "Row 2, Column 3", 1244 | "Row 2, Column 4" 1245 | ] 1246 | end 1247 | 1248 | expected=<<-END 1249 | =================================================================================================== 1250 | Column 1 Column 2 Column 3 Column 4 1251 | --------------------------------------------------------------------------------------------------- 1252 | Row 1, Column 1 Row 1, Column 2 Row 1, Column 3 Row 1, Column 4 1253 | Row 2, Column 1 Row 2, Column 2 Row 2, Column 3 Row 2, Column 4 1254 | =================================================================================================== 1255 | END 1256 | 1257 | assert_output_equal expected, @mock_out.string 1258 | 1259 | end 1260 | 1261 | def test_can_define_a_table_as_just_array_of_numbers 1262 | table_config = [ 1263 | 20, 1264 | 15, 1265 | ] 1266 | 1267 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 1268 | table << { 1269 | :col1 => "Row 1, Column 1", 1270 | :col2 => "Row 1, Column 2" 1271 | } 1272 | 1273 | table << { 1274 | :col1 => "Row 2, Column 1", 1275 | :col2 => "Row 2, Column 1" 1276 | } 1277 | end 1278 | 1279 | expected=<<-END 1280 | ==================================== 1281 | Column 1 Column 2 1282 | ------------------------------------ 1283 | Row 1, Column 1 Row 1, Column 2 1284 | Row 2, Column 1 Row 2, Column 1 1285 | ==================================== 1286 | END 1287 | 1288 | assert_output_equal expected, @mock_out.string 1289 | 1290 | end 1291 | 1292 | def test_can_define_a_table_with_just_numbers_including_floats 1293 | table_config = [ 1294 | 0.5, 1295 | 15, 1296 | ] 1297 | 1298 | ConsoleTable.define(table_config, :width => 100, :output => @mock_out) do |table| 1299 | table << { 1300 | :col1 => "Row 1, Column 1", 1301 | :col2 => "Row 1, Column 2" 1302 | } 1303 | 1304 | table << { 1305 | :col1 => "Row 2, Column 1", 1306 | :col2 => "Row 2, Column 1" 1307 | } 1308 | end 1309 | 1310 | expected=<<-END 1311 | ========================================================== 1312 | Column 1 Column 2 1313 | ---------------------------------------------------------- 1314 | Row 1, Column 1 Row 1, Column 2 1315 | Row 2, Column 1 Row 2, Column 1 1316 | ========================================================== 1317 | END 1318 | 1319 | assert_output_equal expected, @mock_out.string 1320 | 1321 | end 1322 | 1323 | def test_can_use_console_table_as_glorified_tab_replacement 1324 | ConsoleTable.define(2, :width => 40, :headings=>false, :outline=>false, :output => @mock_out) do |table| 1325 | table << [ 1326 | "Row 1, Column 1", 1327 | "\tRow 1, Column 2" 1328 | ] 1329 | 1330 | table << [ 1331 | "Row 2, Column 1", 1332 | "\tRow 2, Column 2" 1333 | ] 1334 | end 1335 | 1336 | expected=<<-END 1337 | Row 1, Column 1 Row 1, Column 2 1338 | Row 2, Column 1 Row 2, Column 2 1339 | END 1340 | 1341 | assert_output_equal expected, @mock_out.string 1342 | 1343 | end 1344 | 1345 | def test_can_print_basically_anything 1346 | table_config = [ 1347 | {:key => :col1, :size => 20, :title => "Column 1"} 1348 | ] 1349 | 1350 | ConsoleTable.define(table_config, :width => 100, :output=>@mock_out) do |table| 1351 | table << ["String"] 1352 | table << [42] 1353 | table << [0.3] 1354 | table << [:why_not] 1355 | table << [nil] 1356 | 1357 | end 1358 | 1359 | expected=<<-END 1360 | ==================== 1361 | Column 1 1362 | -------------------- 1363 | String 1364 | 42 1365 | 0.3 1366 | why_not 1367 | 1368 | ==================== 1369 | END 1370 | 1371 | assert_output_equal expected, @mock_out.string 1372 | end 1373 | 1374 | def test_plain_line_can_be_justified 1375 | table_config = [ 1376 | {:key => :col1, :size => 20, :title => "Column 1"} 1377 | ] 1378 | 1379 | ConsoleTable.define(table_config, :width => 30, :output => @mock_out) do |table| 1380 | table << ["One"] 1381 | 1382 | table << "Plain line" 1383 | table << "Plain line\t" 1384 | table << "\tPlain line\t" 1385 | table << "\tPlain line" 1386 | 1387 | table << ["Two"] 1388 | 1389 | end 1390 | 1391 | expected=<<-END 1392 | ==================== 1393 | Column 1 1394 | -------------------- 1395 | One 1396 | Plain line 1397 | Plain line 1398 | Plain line 1399 | Plain line 1400 | Two 1401 | ==================== 1402 | END 1403 | 1404 | assert_output_equal expected, @mock_out.string 1405 | end 1406 | 1407 | def test_trimming_without_cutting_off_color_doesnt_result_in_extra_color_reset 1408 | ConsoleTable.define([30], :width => 30, :output => @mock_out) do |table| 1409 | table << ["This is way too long and #{"will".red} be truncated"] 1410 | end 1411 | 1412 | expected=<<-END 1413 | ============================== 1414 | Column 1 1415 | ------------------------------ 1416 | This is way too long and will 1417 | ============================== 1418 | END 1419 | 1420 | assert_includes @mock_out.string, "and \e[0;31;49mwill\e[0m \n" 1421 | 1422 | assert_output_equal expected, @mock_out.string 1423 | end 1424 | 1425 | class Fake 1426 | def initialize(id) 1427 | @fake_id = id 1428 | end 1429 | 1430 | def to_s 1431 | "Fake: #{@fake_id}" 1432 | end 1433 | end 1434 | 1435 | def test_justify_and_ellipsize_non_strings_or_hashes 1436 | 1437 | layout = [ 1438 | {:size=>8, :title=>"Column 1", :ellipsize=>true}, 1439 | {:size=>8, :title=>"Column 2", :justify=>:right} 1440 | ] 1441 | 1442 | ConsoleTable.define(layout, :width => 20, :output => @mock_out) do |table| 1443 | table << [ 1444 | :this_is_a_super_long_symbol_that_should_get_cut_off, 1445 | :right 1446 | ] 1447 | 1448 | table << [ 1449 | 912376409123648791, 1450 | 23423 1451 | ] 1452 | 1453 | table << [ 1454 | 0.39712390487191234, 1455 | 0.12 1456 | ] 1457 | 1458 | table << [ 1459 | Fake.new(341928374134), 1460 | Fake.new(1) 1461 | ] 1462 | end 1463 | 1464 | expected=<<-END 1465 | ================= 1466 | Column 1 Column 2 1467 | ----------------- 1468 | this_... right 1469 | 91237... 23423 1470 | 0.397... 0.12 1471 | Fake:... Fake: 1 1472 | ================= 1473 | END 1474 | assert_output_equal expected, @mock_out.string 1475 | end 1476 | 1477 | def test_can_custom_ellipse 1478 | table_config = [ 1479 | {:key => :col1, :size => 10, :title => "Column 1", :ellipsize=>true}, 1480 | {:key => :col2, :size => 10, :title => "Column 2", :ellipsize=>true, :justify=>:right}, 1481 | ] 1482 | 1483 | ConsoleTable.define(table_config, :width => 100, :ellipse=>"…", :output=>@mock_out) do |table| 1484 | table << { 1485 | :col1 => "Row 1, Column 1", 1486 | :col2 => "Row 1, Column 2" 1487 | } 1488 | 1489 | table << { 1490 | :col1 => {text: "Row 2, Column 1", :ellipsize=>false}, 1491 | :col2 => {text: "Row 2, Column 1", :ellipsize=>false} 1492 | } 1493 | end 1494 | 1495 | expected=<<-END 1496 | ===================== 1497 | Column 1 Column 2 1498 | --------------------- 1499 | Row 1, Co… Row 1, Co… 1500 | Row 2, Col Row 2, Col 1501 | ===================== 1502 | END 1503 | 1504 | assert_output_equal expected, @mock_out.string 1505 | end 1506 | 1507 | def test_can_use_colorized_custom_ellipse_for_some_reason 1508 | table_config = [ 1509 | {:key => :col1, :size => 10, :title => "Column 1", :ellipsize=>true}, 1510 | {:key => :col2, :size => 10, :title => "Column 2", :ellipsize=>true, :justify=>:right}, 1511 | ] 1512 | 1513 | ConsoleTable.define(table_config, :width => 100, :ellipse=>"…".red, :output=>@mock_out) do |table| 1514 | table << { 1515 | :col1 => "Row 1, Column 1", 1516 | :col2 => "Row 1, Column 2" 1517 | } 1518 | 1519 | table << { 1520 | :col1 => {text: "Row 2, Column 1", :ellipsize=>false}, 1521 | :col2 => {text: "Row 2, Column 1", :ellipsize=>false} 1522 | } 1523 | end 1524 | 1525 | expected=<<-END 1526 | ===================== 1527 | Column 1 Column 2 1528 | --------------------- 1529 | Row 1, Co… Row 1, Co… 1530 | Row 2, Col Row 2, Col 1531 | ===================== 1532 | END 1533 | 1534 | assert_includes @mock_out.string, "\e[0;31;49m…\e[0m" 1535 | 1536 | assert_output_equal expected, @mock_out.string 1537 | end 1538 | 1539 | def test_can_use_extra_long_ellipse 1540 | table_config = [ 1541 | {:key => :col1, :size => 10, :title => "Column 1", :ellipsize=>true}, 1542 | {:key => :col2, :size => 10, :title => "Column 2", :ellipsize=>true, :justify=>:right}, 1543 | ] 1544 | 1545 | ConsoleTable.define(table_config, :width => 100, :ellipse=>" (cont)", :output=>@mock_out) do |table| 1546 | table << { 1547 | :col1 => "Row 1, Column 1", 1548 | :col2 => "Row 1, Column 2" 1549 | } 1550 | 1551 | table << { 1552 | :col1 => {text: "Row 2, Column 1", :ellipsize=>false}, 1553 | :col2 => {text: "Row 2, Column 1", :ellipsize=>false} 1554 | } 1555 | end 1556 | 1557 | expected=<<-END 1558 | ===================== 1559 | Column 1 Column 2 1560 | --------------------- 1561 | Row (cont) Row (cont) 1562 | Row 2, Col Row 2, Col 1563 | ===================== 1564 | END 1565 | 1566 | 1567 | assert_output_equal expected, @mock_out.string 1568 | end 1569 | 1570 | def test_ellipse_char_too_long_for_area_is_ignored_entirely 1571 | table_config = [ 1572 | {:key => :col1, :size => 10, :title => "Column 1", :ellipsize=>true}, 1573 | {:key => :col2, :size => 10, :title => "Column 2", :ellipsize=>true, :justify=>:right}, 1574 | ] 1575 | 1576 | ConsoleTable.define(table_config, :width => 100, :ellipse=>" (this is a long ellipse)", :output=>@mock_out) do |table| 1577 | table << { 1578 | :col1 => "Row 1, Column 1", 1579 | :col2 => "Row 1, Column 2" 1580 | } 1581 | 1582 | table << { 1583 | :col1 => {text: "Row 2, Column 1", :ellipsize=>false}, 1584 | :col2 => {text: "Row 2, Column 1", :ellipsize=>false} 1585 | } 1586 | end 1587 | 1588 | expected=<<-END 1589 | ===================== 1590 | Column 1 Column 2 1591 | --------------------- 1592 | Row 1, Col Row 1, Col 1593 | Row 2, Col Row 2, Col 1594 | ===================== 1595 | END 1596 | 1597 | assert_output_equal expected, @mock_out.string 1598 | end 1599 | 1600 | private 1601 | def assert_output_equal(expected, actual) 1602 | expected_lines = expected.split("\n") 1603 | actual_lines = actual.split("\n") 1604 | assert_equal expected_lines.length, actual_lines.length 1605 | expected_lines.each_with_index do |expected_line, i| 1606 | actual_line = actual_lines[i] 1607 | assert_equal expected_line.uncolorize.rstrip, actual_line.uncolorize.rstrip 1608 | end 1609 | 1610 | end 1611 | end --------------------------------------------------------------------------------