Nicely formatting output to console, specifying number of tabs
Asked Answered
R

7

60

I am generating a script that is outputting information to the console. The information is some kind of statistic with a value. So much like a hash.

So one value's name may be 8 characters long and another is 3. when I am looping through outputting the information with two \t some of the columns aren't aligned correctly.

So for example the output might be as such:

long value name          14
short              12
little             13
tiny               123421
long name again          912421

I want all the values lined up correctly. Right now I am doing this:

puts "#{value_name} - \t\t #{value}"

How could I say for long names, to only use one tab? Or is there another solution?

Rand answered 6/7, 2009 at 15:35 Comment(0)
L
24

There is usually a %10s kind of printf scheme that formats nicely.
However, I have not used ruby at all, so you need to check that.


Yes, there is printf with formatting.
The above example should right align in a space of 10 chars.
You can format based on your widest field in the column.

printf ([port, ]format, arg...)

Prints arguments formatted according to the format like sprintf. If the first argument is the instance of the IO or its subclass, print redirected to that object. the default is the value of $stdout.

Lupine answered 6/7, 2009 at 15:38 Comment(3)
There is a printf and sprintf that use the C arguments to format a string. They are methods on Kernal (effectively built-in). See ruby-doc.org/core/classes/Kernel.html#M005962.Ator
remember printf(*args) has got an implementation on string#:%: "%s %10s" % [value_name, value] looks great. Anyway, don't deface your code with large terms of this!Rubbery
predhme: after all these years, and I didn't know that you could left align with negative values. Thanks for that! +1Implicate
P
58

Provided you know the maximum length to be no more than 20 characters:

printf "%-20s %s\n", value_name, value

If you want to make it more dynamic, something like this should work nicely:

longest_key = data_hash.keys.max_by(&:length)
data_hash.each do |key, value|
  printf "%-#{longest_key.length}s %s\n", key, value
end
Pollypollyanna answered 6/7, 2009 at 15:54 Comment(1)
I used your code and substituted 'ENV' for 'data_hash', immediately useful - Thanks!!Binnacle
L
24

There is usually a %10s kind of printf scheme that formats nicely.
However, I have not used ruby at all, so you need to check that.


Yes, there is printf with formatting.
The above example should right align in a space of 10 chars.
You can format based on your widest field in the column.

printf ([port, ]format, arg...)

Prints arguments formatted according to the format like sprintf. If the first argument is the instance of the IO or its subclass, print redirected to that object. the default is the value of $stdout.

Lupine answered 6/7, 2009 at 15:38 Comment(3)
There is a printf and sprintf that use the C arguments to format a string. They are methods on Kernal (effectively built-in). See ruby-doc.org/core/classes/Kernel.html#M005962.Ator
remember printf(*args) has got an implementation on string#:%: "%s %10s" % [value_name, value] looks great. Anyway, don't deface your code with large terms of this!Rubbery
predhme: after all these years, and I didn't know that you could left align with negative values. Thanks for that! +1Implicate
G
23

String has a built-in ljust for exactly this:

x = {"foo"=>37, "something long"=>42, "between"=>99}
x.each { |k, v| puts "#{k.ljust(20)} #{v}" }
# Outputs:
#  foo                  37
#  something long       42
#  between              99

Or, if you want tabs, you can do a little math (assuming tab display width of 8) and write a short display function:

def tab_pad(label, tab_stop = 4)
  label_tabs = label.length / 8
  label.ljust(label.length + tab_stop - label_tabs, "\t")
end

x.each { |k, v| puts "#{tab_pad(k)}#{v}" }
# Outputs: 
#  foo                  37
#  something long       42
#  between              99
Gehlenite answered 15/1, 2016 at 5:31 Comment(1)
The ljust method works perfectly for what the OP wanted.Parable
B
9

There was few bugs in it before, but now you can use most of printf syntax with % operator:

1.9.3-p194 :025 > " %-20s %05d" % ['hello', 12]
 => " hello                00012" 

Of course you can use precalculated width too:

1.9.3-p194 :030 > "%-#{width}s %05x" % ['hello', 12]
  => "hello          0000c" 
Bitumen answered 7/10, 2012 at 22:21 Comment(0)
S
4

I wrote a thing

  • Automatically detects column widths
  • Spaces with spaces
  • Array of arrays [[],[],...] or array of hashes [{},{},...]
  • Does not detect columns too wide for console window

    lists = [ [ 123, "SDLKFJSLDKFJSLDKFJLSDKJF" ], [ 123456, "ffff" ], ]

array_maxes

def array_maxes(lists)
  lists.reduce([]) do |maxes, list|
    list.each_with_index do |value, index|
      maxes[index] = [(maxes[index] || 0), value.to_s.length].max
    end
    maxes
  end
end

array_maxes(lists)
# => [6, 24]

puts_arrays_columns

def puts_arrays_columns(lists)
  maxes = array_maxes(hashes)
  lists.each do |list|
    list.each_with_index do |value, index|
      print " #{value.to_s.rjust(maxes[index])},"
    end
    puts
  end
end

puts_arrays_columns(lists)

# Output:
#     123, SDLKFJSLDKFJSLDKFJLSDKJF,
#  123456,                     ffff,

and another thing

hashes = [
  { "id" => 123,    "name" => "SDLKFJSLDKFJSLDKFJLSDKJF" },
  { "id" => 123456, "name" => "ffff" },
]

hash_maxes

def hash_maxes(hashes)
  hashes.reduce({}) do |maxes, hash|
    hash.keys.each do |key|
      maxes[key] = [(maxes[key] || 0), key.to_s.length].max
      maxes[key] = [(maxes[key] || 0), hash[key].to_s.length].max
    end
    maxes
  end
end

hash_maxes(hashes)
# => {"id"=>6, "name"=>24}

puts_hashes_columns

def puts_hashes_columns(hashes)
  maxes = hash_maxes(hashes)

  return if hashes.empty?

  # Headers
  hashes.first.each do |key, value|
    print " #{key.to_s.rjust(maxes[key])},"
  end
  puts

  hashes.each do |hash|
    hash.each do |key, value|
      print " #{value.to_s.rjust(maxes[key])},"
    end
    puts
  end

end

puts_hashes_columns(hashes)

# Output:
#      id,                     name,
#     123, SDLKFJSLDKFJSLDKFJLSDKJF,
#  123456,                     ffff,

Edit: Fixes hash keys considered in the length.

hashes = [
  { id: 123,    name: "DLKFJSDLKFJSLDKFJSDF", asdfasdf: :a  },
  { id: 123456, name: "ffff",                 asdfasdf: :ab },
]

hash_maxes(hashes)
# => {:id=>6, :name=>20, :asdfasdf=>8}

Want to whitelist columns columns?

hashes.map{ |h| h.slice(:id, :name) }
# => [
#  { id: 123,    name: "DLKFJSDLKFJSLDKFJSDF" },
#  { id: 123456, name: "ffff"                 },
#]
Sukkoth answered 4/8, 2014 at 20:56 Comment(0)
A
2

For future reference and people who look at this or find it... Use a gem. I suggest https://github.com/wbailey/command_line_reporter

Amylene answered 17/3, 2015 at 19:5 Comment(0)
D
0

You typically don't want to use tabs, you want to use spaces and essentially setup your "columns" your self or else you run into these types of problems.

Dynamics answered 6/7, 2009 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.