columnize 0.3.6 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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