columnize 0.3.6 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,159 @@
1
+ # Copyright (C) 2007-2011, 2013 Rocky Bernstein
2
+ # <rockyb@rubyforge.net>
3
+ #
4
+ # Part of Columnize to format in either direction
5
+ module Columnize
6
+ class Columnizer
7
+ ARRANGE_ARRAY_OPTS = {:array_prefix => '[', :line_prefix => ' ', :line_suffix => ',', :array_suffix => ']', :colsep => ', ', :arrange_vertical => false}
8
+ OLD_AND_NEW_KEYS = {:lineprefix => :line_prefix, :linesuffix => :line_suffix}
9
+ # TODO: change colfmt to cell_format; change colsep to something else
10
+ ATTRS = [:arrange_vertical, :array_prefix, :array_suffix, :line_prefix, :line_suffix, :colfmt, :colsep, :displaywidth, :ljust]
11
+
12
+ attr_reader :list, :opts
13
+
14
+ def initialize(list=[], opts={})
15
+ self.list = list
16
+ self.opts = DEFAULT_OPTS.merge(opts)
17
+ end
18
+
19
+ def list=(list)
20
+ @list = list
21
+ if @list.is_a? Array
22
+ @short_circuit = @list.empty? ? "<empty>\n" : nil
23
+ else
24
+ @short_circuit = ''
25
+ @list = []
26
+ end
27
+ end
28
+
29
+ # TODO: freeze @opts
30
+ def opts=(opts)
31
+ @opts = opts
32
+ OLD_AND_NEW_KEYS.each {|old, new| @opts[new] = @opts.delete(old) if @opts.keys.include?(old) and !@opts.keys.include?(new) }
33
+ @opts.merge!(ARRANGE_ARRAY_OPTS) if @opts[:arrange_array]
34
+ set_attrs_from_opts
35
+ end
36
+
37
+ def update_opts(opts)
38
+ self.opts = @opts.merge(opts)
39
+ end
40
+
41
+ def columnize
42
+ return @short_circuit if @short_circuit
43
+
44
+ rows, colwidths = min_rows_and_colwidths
45
+ ncols = colwidths.length
46
+ justify = lambda {|t, c|
47
+ @ljust ? t.ljust(colwidths[c]) : t.rjust(colwidths[c])
48
+ }
49
+ textify = lambda do |row|
50
+ row.map!.with_index(&justify) unless ncols == 1 && @ljust
51
+ "#{@line_prefix}#{row.join(@colsep)}#{@line_suffix}"
52
+ end
53
+
54
+ text = rows.map(&textify)
55
+ text.first.sub!(/^#{@line_prefix}/, @array_prefix) unless @array_prefix.empty?
56
+ text.last.sub!(/#{@line_suffix}$/, @array_suffix) unless @array_suffix.empty?
57
+ text.join("\n") # + "\n" # if we want extra separation
58
+ end
59
+
60
+ # TODO: make this a method, rather than a function (?)
61
+ # compute the smallest number of rows and the max widths for each column
62
+ def min_rows_and_colwidths
63
+ list = @list.map &@stringify
64
+ cell_widths = list.map(&@term_adjuster).map(&:size)
65
+
66
+ # Set default arrangement: one atom per row
67
+ cell_width_max = cell_widths.max
68
+ result = [arrange_by_row(list, list.size, 1), [cell_width_max]]
69
+
70
+ # If any atom > @displaywidth, stop and use one atom per row.
71
+ return result if cell_width_max > @displaywidth
72
+
73
+ # For horizontal arrangement, we want to *maximize* the number
74
+ # of columns. Thus the candidate number of rows (+sizes+) starts
75
+ # at the minumum number of rows, 1, and increases.
76
+
77
+ # For vertical arrangement, we want to *minimize* the number of
78
+ # rows. So here the candidate number of columns (+sizes+) starts
79
+ # at the maximum number of columns, list.length, and
80
+ # decreases. Also the roles of columns and rows are reversed
81
+ # from horizontal arrangement.
82
+
83
+ # Loop from most compact arrangement to least compact, stopping
84
+ # at the first successful packing. The below code is tricky,
85
+ # but very cool.
86
+ #
87
+ # FIXME: In the below code could be DRY'd. (The duplication got
88
+ # introduced when I revised the code - rocky)
89
+ if @arrange_vertical
90
+ (1..list.length).each do |size|
91
+ other_size = (list.size + size - 1) / size
92
+ colwidths = arrange_by_row(cell_widths, other_size, size).map(&:max)
93
+ totwidth = colwidths.inject(&:+) + ((colwidths.length-1) * @colsep.length)
94
+ return [arrange_by_column(list, other_size, size), colwidths] if
95
+ totwidth <= @displaywidth
96
+ end
97
+ else
98
+ list.length.downto(1).each do |size|
99
+ other_size = (list.size + size - 1) / size
100
+ colwidths = arrange_by_column(cell_widths, other_size, size).map(&:max)
101
+ totwidth = colwidths.inject(&:+) + ((colwidths.length-1) * @colsep.length)
102
+ return [arrange_by_row(list, other_size, size), colwidths] if
103
+ totwidth <= @displaywidth
104
+ end
105
+ end
106
+ result
107
+ end
108
+
109
+ # Given +list+, +ncols+, +nrows+, arrange the one-dimensional
110
+ # array into a 2-dimensional lists of lists organized by rows.
111
+ #
112
+ # In either horizontal or vertical arrangement, we will need to
113
+ # access this for the list data or for the width
114
+ # information.
115
+ #
116
+ # Here is an example:
117
+ # arrange_by_row((1..5).to_a, 3, 2) =>
118
+ # [[1,2], [3,4], [5]],
119
+ def arrange_by_row(list, nrows, ncols)
120
+ (0...nrows).map {|r| list[r*ncols, ncols] }.compact
121
+ end
122
+
123
+ # Given +list+, +ncols+, +nrows+, arrange the one-dimensional
124
+ # array into a 2-dimensional lists of lists organized by columns.
125
+ #
126
+ # In either horizontal or vertical arrangement, we will need to
127
+ # access this for the list data or for the width
128
+ # information.
129
+ #
130
+ # Here is an example:
131
+ # arrange_by_column((1..5).to_a, 2, 3) =>
132
+ # [[1,3,5], [2,4]]
133
+ def arrange_by_column(list, nrows, ncols)
134
+ (0...ncols).map do |i|
135
+ (0..nrows-1).inject([]) do |row, j|
136
+ k = i + (j * ncols)
137
+ k < list.length ? row << list[k] : row
138
+ end
139
+ end
140
+ end
141
+
142
+ def set_attrs_from_opts
143
+ ATTRS.each {|attr| self.instance_variable_set "@#{attr}", @opts[attr] }
144
+
145
+ @ljust = !@list.all? {|datum| datum.kind_of?(Numeric)} if @ljust == :auto
146
+ @displaywidth -= @line_prefix.length
147
+ @displaywidth = @line_prefix.length + 4 if @displaywidth < 4
148
+ @stringify = @colfmt ? lambda {|li| @colfmt % li } : lambda {|li| li.to_s }
149
+ @term_adjuster = @opts[:term_adjust] ? lambda {|c| c.gsub(/\e\[.*?m/, '') } : lambda {|c| c }
150
+ end
151
+ end
152
+ end
153
+
154
+ # Demo
155
+ if __FILE__ == $0
156
+ Columnize::DEFAULT_OPTS = {:line_prefix => '', :displaywidth => 80}
157
+ puts Columnize::Columnizer.new.arrange_by_row((1..5).to_a, 2, 3).inspect
158
+ puts Columnize::Columnizer.new.arrange_by_column((1..5).to_a, 2, 3).inspect
159
+ end
@@ -0,0 +1,35 @@
1
+ module Columnize
2
+ computed_displaywidth = (ENV['COLUMNS'] || '80').to_i
3
+ computed_displaywidth = 80 unless computed_displaywidth >= 10
4
+
5
+ # When an option is not specified for the below keys, these are the defaults.
6
+ DEFAULT_OPTS = {
7
+ :arrange_array => false,
8
+ :arrange_vertical => true,
9
+ :array_prefix => '',
10
+ :array_suffix => '',
11
+ :colfmt => nil,
12
+ :colsep => ' ',
13
+ :displaywidth => computed_displaywidth,
14
+ :line_prefix => '',
15
+ :line_suffix => '',
16
+ :ljust => :auto,
17
+ :term_adjust => false
18
+ }
19
+
20
+ # Options parsing routine for Columnize::columnize. In the preferred
21
+ # newer style, +args+ is a hash where each key is one of the option names.
22
+ #
23
+ # In the older style positional arguments are used and the positions
24
+ # are in the order: +displaywidth+, +colsep+, +arrange_vertical+,
25
+ # +ljust+, and +line_prefix+.
26
+ def self.parse_columnize_options(args)
27
+ if 1 == args.size && args[0].kind_of?(Hash) # explicitly passed as a hash
28
+ args[0]
29
+ elsif !args.empty? # passed as ugly positional parameters.
30
+ Hash[args.zip([:displaywidth, :colsep, :arrange_vertical, :ljust, :line_prefix]).map(&:reverse)]
31
+ else
32
+ {}
33
+ end
34
+ end
35
+ end
@@ -3,5 +3,5 @@
3
3
  # require'ing 'columnize'.
4
4
  module Columnize
5
5
  # The current version of this package
6
- VERSION = '0.3.6'
6
+ VERSION = '0.8.9'
7
7
  end
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ require 'test/unit'
3
+
4
+ # Test of Columnize module
5
+ class TestColumnizeArray < Test::Unit::TestCase
6
+ # Ruby 1.8 form of require_relative
7
+ TOP_SRC_DIR = File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
8
+ ENV['COLUMNS'] = '80'
9
+ require File.join(TOP_SRC_DIR, 'columnize.rb')
10
+
11
+ # test columnize
12
+ def test_arrange_array
13
+ data = (1..80).to_a
14
+ expect = "[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,\n" +
15
+ " 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,\n" +
16
+ " 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,\n" +
17
+ " 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80]"
18
+ assert_equal(expect,
19
+ data.columnize(:arrange_array => true, :displaywidth => 80),
20
+ "columnize_opts -> arrange_array")
21
+ end
22
+
23
+ def test_displaywidth
24
+ expect = "1 5 9\n" +
25
+ "2 6 10\n" +
26
+ "3 7\n" +
27
+ "4 8"
28
+ data = (1..10).to_a
29
+ assert_equal(expect, data.columnize(:displaywidth => 10), "displaywidth")
30
+ end
31
+
32
+ def test_colfmt
33
+ expect = "[01, 02,\n" +
34
+ " 03, 04,\n" +
35
+ " 05, 06,\n" +
36
+ " 07, 08,\n" +
37
+ " 09, 10]"
38
+ data = (1..10).to_a
39
+ assert_equal(expect, data.columnize(:arrange_array => true, :colfmt => '%02d', :displaywidth => 10), "arrange_array, colfmt, displaywidth")
40
+ end
41
+
42
+ def test_backwards_compatiblity
43
+ foo = []
44
+ data = (1..11).to_a
45
+ expect = " 1 2 3\n 4 5 6\n 7 8 9\n10 11"
46
+ assert_equal expect, foo.columnize(data, :displaywidth => 10, :arrange_vertical => false)
47
+ end
48
+ end
@@ -3,42 +3,29 @@ require 'test/unit'
3
3
 
4
4
  # Test of Columnize module
5
5
  class TestColumnize < Test::Unit::TestCase
6
- @@TOP_SRC_DIR = File.join(File.expand_path(File.dirname(__FILE__)),
7
- '..', 'lib')
8
- require File.join(@@TOP_SRC_DIR, 'columnize.rb')
9
- include Columnize
10
-
11
- def test_cell_size
12
- assert_equal(3, cell_size('abc', false))
13
- assert_equal(3, cell_size('abc', true))
14
- assert_equal(6, cell_size("\e[0;31mObject\e[0;4m", true))
15
- assert_equal(19, cell_size("\e[0;31mObject\e[0;4m", false))
16
- end
6
+ # Ruby 1.8 form of require_relative
7
+ TOP_SRC_DIR = File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
8
+ require File.join(TOP_SRC_DIR, 'columnize.rb')
17
9
 
18
10
  # test columnize
19
11
  def test_basic
20
- # Try at least one test where we give the module name explicitely.
21
- assert_equal("1, 2, 3\n",
22
- Columnize::columnize([1, 2, 3], 10, ', '))
23
- assert_equal("", columnize(5))
24
- assert_equal("1 3\n2 4\n",
25
- columnize(['1', '2', '3', '4'], 4))
26
- assert_equal("1 2\n3 4\n",
27
- columnize(['1', '2', '3', '4'], 4, ' ', false))
28
- assert_equal("<empty>\n", columnize([]))
29
-
30
-
31
- assert_equal("oneitem\n", columnize(["oneitem"]))
32
-
12
+ assert_equal("1, 2, 3", Columnize::columnize([1, 2, 3], 10, ', '))
13
+ assert_equal("", Columnize::columnize(5))
14
+ assert_equal("1 3\n2 4", Columnize::columnize(['1', '2', '3', '4'], 4))
15
+ assert_equal("1 2\n3 4", Columnize::columnize(['1', '2', '3', '4'], 4, ' ', false))
16
+ assert_equal("<empty>\n", Columnize::columnize([]))
17
+
18
+ assert_equal("oneitem", Columnize::columnize(["oneitem"]))
19
+
33
20
  data = (0..54).map{|i| i.to_s}
34
21
  assert_equal(
35
- "0, 6, 12, 18, 24, 30, 36, 42, 48, 54\n" +
22
+ "0, 6, 12, 18, 24, 30, 36, 42, 48, 54\n" +
36
23
  "1, 7, 13, 19, 25, 31, 37, 43, 49\n" +
37
24
  "2, 8, 14, 20, 26, 32, 38, 44, 50\n" +
38
25
  "3, 9, 15, 21, 27, 33, 39, 45, 51\n" +
39
26
  "4, 10, 16, 22, 28, 34, 40, 46, 52\n" +
40
- "5, 11, 17, 23, 29, 35, 41, 47, 53\n",
41
- columnize(data, 39, ', ', true, false))
27
+ "5, 11, 17, 23, 29, 35, 41, 47, 53",
28
+ Columnize::columnize(data, 39, ', ', true, false))
42
29
 
43
30
  assert_equal(
44
31
  " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9\n" +
@@ -46,8 +33,8 @@ class TestColumnize < Test::Unit::TestCase
46
33
  "20, 21, 22, 23, 24, 25, 26, 27, 28, 29\n" +
47
34
  "30, 31, 32, 33, 34, 35, 36, 37, 38, 39\n" +
48
35
  "40, 41, 42, 43, 44, 45, 46, 47, 48, 49\n" +
49
- "50, 51, 52, 53, 54\n",
50
- columnize(data, 39, ', ', false, false))
36
+ "50, 51, 52, 53, 54",
37
+ Columnize::columnize(data, 39, ', ', false, false))
51
38
 
52
39
 
53
40
  assert_equal(
@@ -57,8 +44,8 @@ class TestColumnize < Test::Unit::TestCase
57
44
  " 27, 28, 29, 30, 31, 32, 33, 34, 35\n" +
58
45
  " 36, 37, 38, 39, 40, 41, 42, 43, 44\n" +
59
46
  " 45, 46, 47, 48, 49, 50, 51, 52, 53\n" +
60
- " 54\n",
61
- columnize(data, 39, ', ', false, false, ' '))
47
+ " 54",
48
+ Columnize::columnize(data, 39, ', ', false, false, ' '))
62
49
 
63
50
 
64
51
  data = ["one", "two", "three",
@@ -72,17 +59,16 @@ class TestColumnize < Test::Unit::TestCase
72
59
  "twentyfive","twentysix", "twentyseven"]
73
60
 
74
61
  assert_equal(
75
- "one two three for five six \n" +
76
- "seven eight nine ten eleven twelve \n" +
77
- "thirteen fourteen fifteen sixteen seventeen eightteen \n" +
78
- "nineteen twenty twentyone twentytwo twentythree twentyfour \n" +
79
- "twentyfive twentysix twentyseven\n", columnize(data, 80, ' ', false))
62
+ "one two three for five six \n" +
63
+ "seven eight nine ten eleven twelve \n" +
64
+ "thirteen fourteen fifteen sixteen seventeen eightteen \n" +
65
+ "nineteen twenty twentyone twentytwo twentythree twentyfour\n" +
66
+ "twentyfive twentysix twentyseven", Columnize::columnize(data, 80, ' ', false))
80
67
 
81
68
  assert_equal(
82
69
  "one five nine thirteen seventeen twentyone twentyfive \n" +
83
70
  "two six ten fourteen eightteen twentytwo twentysix \n" +
84
71
  "three seven eleven fifteen nineteen twentythree twentyseven\n" +
85
- "for eight twelve sixteen twenty twentyfour \n", columnize(data))
86
-
72
+ "for eight twelve sixteen twenty twentyfour ", Columnize::columnize(data, 80))
87
73
  end
88
74
  end
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ require 'test/unit'
3
+
4
+ # Test of Columnizer class
5
+ class TestColumnizer < Test::Unit::TestCase
6
+ TOP_SRC_DIR = File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
7
+ require File.join(TOP_SRC_DIR, 'columnize.rb')
8
+
9
+ Columnize::Columnizer.class_eval 'attr_reader :stringify, :short_circuit, :term_adjuster'
10
+ Columnize::Columnizer.class_eval 'attr_reader *ATTRS'
11
+
12
+ # SETTING OPTS IN INITIALIZE
13
+ def test_passed_in_opts
14
+ # passed in opts should be merged with DEFAULT_OPTS
15
+ c = Columnize::Columnizer.new([], :displaywidth => 15)
16
+ assert_equal false, c.opts[:term_adjust], 'term_adjust comes from DEFAULT_OPTS'
17
+ assert_equal 15, c.opts[:displaywidth], 'displaywidth should override DEFAULT_OPTS'
18
+ end
19
+
20
+ def test_ljust_attr
21
+ c = Columnize::Columnizer.new([1,2,3], {:ljust => :auto})
22
+ assert_equal false, c.ljust, 'ljust: :auto should transform to false when all values are numeric'
23
+ c = Columnize::Columnizer.new(['1', 2, 3], {:ljust => :auto})
24
+ assert_equal true, c.ljust, 'ljust: :auto should transform to true when not all values are numeric'
25
+ c = Columnize::Columnizer.new([], {:ljust => false})
26
+ assert_equal false, c.ljust, 'ljust: false should stay false'
27
+ c = Columnize::Columnizer.new([], {:ljust => true})
28
+ assert_equal true, c.ljust, 'ljust: true should stay true'
29
+ end
30
+
31
+ def test_stringify_attr
32
+ c = Columnize::Columnizer.new
33
+ assert_equal '1.0', c.stringify[1.0], 'without colfmt, should be to_s'
34
+ c.update_opts :colfmt => '%02d'
35
+ assert_equal '01', c.stringify[1.0], 'without colfmt, should be to_s'
36
+ end
37
+
38
+ def test_short_circuit_attr
39
+ c = Columnize::Columnizer.new
40
+ assert_equal "<empty>\n", c.short_circuit, 'should explicitly state when empty'
41
+ c.list = 1
42
+ assert_equal '', c.short_circuit, 'should be an empty string when not an array'
43
+ c.list = [1]
44
+ assert_equal nil, c.short_circuit, 'should be nil when list is good'
45
+ end
46
+
47
+ def test_term_adjuster_attr
48
+ c = Columnize::Columnizer.new
49
+ assert_equal 'abc', c.term_adjuster['abc']
50
+ assert_equal "\e[0;31mObject\e[0;4m", c.term_adjuster["\e[0;31mObject\e[0;4m"]
51
+ c.update_opts :term_adjust => true
52
+ assert_equal 'abc', c.term_adjuster['abc']
53
+ assert_equal 'Object', c.term_adjuster["\e[0;31mObject\e[0;4m"]
54
+ end
55
+
56
+ def test_displaywidth_attr
57
+ c = Columnize::Columnizer.new [], :displaywidth => 10, :line_prefix => ' '
58
+ assert_equal 12, c.displaywidth, 'displaywidth within 4 of line_prefix.length'
59
+ c.update_opts :line_prefix => ' '
60
+ assert_equal 8, c.displaywidth, 'displaywidth not within 4 of line_prefix.length'
61
+ end
62
+
63
+ # COLUMNIZE
64
+ def test_columnize_with_short_circuit
65
+ msg = 'Johnny 5 is alive!'
66
+ c = Columnize::Columnizer.new
67
+ c.instance_variable_set(:@short_circuit, msg)
68
+ assert_equal msg, c.columnize, 'columnize should return short_circuit message if set'
69
+ end
70
+
71
+ def test_columnize_applies_ljust
72
+ c = Columnize::Columnizer.new [1,2,3,10,20,30], :displaywidth => 10, :ljust => false, :arrange_vertical => false
73
+ assert_equal " 1 2 3\n10 20 30", c.columnize, "ljust: #{c.ljust}"
74
+ c.update_opts :ljust => true
75
+ assert_equal "1 2 3 \n10 20 30", c.columnize, "ljust: #{c.ljust}"
76
+ end
77
+
78
+ def no_test_columnize_applies_colsep_and_prefix_and_suffix
79
+ c = Columnize::Columnizer.new [1,2,3]
80
+ assert_equal "1 2 3", c.columnize
81
+ c.update_opts :line_prefix => '>', :colsep => '-', :line_suffix => '<'
82
+ assert_equal ">1-2-3<", c.columnize
83
+ end
84
+
85
+ def test_columnize_applies_array_prefix_and_suffix
86
+ c = Columnize::Columnizer.new [1,2,3]
87
+ assert_equal "1 2 3", c.columnize
88
+ c.update_opts :array_prefix => '>', :array_suffix => '<'
89
+ assert_equal ">1 2 3<", c.columnize
90
+ end
91
+
92
+ # NOTE: min_rows_and_colwidths tested in test-min_rows_and_colwidths.rb
93
+
94
+ # arrange_rows_and_cols
95
+ def test_arrange_rows_and_cols
96
+ rows = Columnize::Columnizer.new.arrange_by_row((1..9).to_a, 3, 3)
97
+ assert_equal [[1,2,3],[4,5,6],[7,8,9]], rows, 'rows for (1..9), 3, 3'
98
+
99
+ cols = Columnize::Columnizer.new.arrange_by_column((1..9).to_a, 3, 3)
100
+ assert_equal [[1,4,7],[2,5,8],[3,6,9]], cols, 'cols for (1..9), 3, 3'
101
+
102
+ rows = Columnize::Columnizer.new.arrange_by_row((1..5).to_a, 3, 2)
103
+ assert_equal [[1,2],[3,4],[5]], rows, 'rows for (1..5, 2, 3)'
104
+
105
+ cols = Columnize::Columnizer.new.arrange_by_column((1..5).to_a, 3, 2)
106
+ assert_equal [[1,3,5],[2,4]], cols, 'cols for (1..5, 2, 3)'
107
+ end
108
+
109
+ def test_set_attrs_from_opts
110
+ assert(true, 'test set_attrs_from_opts')
111
+ end
112
+ end