table_print 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/table_print.rb +72 -34
- data/table_print.gemspec +65 -0
- data/test/test_column.rb +94 -0
- data/test/test_table_print.rb +0 -14
- metadata +7 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/table_print.rb
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
# handle multi-level includes like 'tp User.all, :include => "blogs.title"' and ActiveRecord associations
|
4
4
|
# allow other output venues besides 'puts'
|
5
5
|
# allow fine-grained formatting
|
6
|
+
# on-the-fly column definitions (pass a proc as an include, eg 'tp User.all, :include => {:column_name => "Zodiac", :display_method => lambda {|u| find_zodiac_sign(u.birthday)}}')
|
7
|
+
# allow user to pass ActiveRelation instead of a data array? That could open up so many options!
|
8
|
+
# a :short_booleans method could save a little space (replace true/false with T/F or 1/0)
|
6
9
|
#
|
7
10
|
# bugs
|
8
11
|
#
|
@@ -10,8 +13,6 @@
|
|
10
13
|
|
11
14
|
class TablePrint
|
12
15
|
|
13
|
-
MAX_FIELD_LENGTH = 30
|
14
|
-
|
15
16
|
# TODO: make options for things like MAX_FIELD_LENGTH
|
16
17
|
# TODO: make options for things like separator
|
17
18
|
# TODO: make options for things like column order
|
@@ -23,6 +24,9 @@ class TablePrint
|
|
23
24
|
def tp(data, options = {})
|
24
25
|
data = wrap(data).compact
|
25
26
|
|
27
|
+
# TODO: need to do a better job of handling options.
|
28
|
+
options[:column_options] ||= {}
|
29
|
+
|
26
30
|
# nothing to see here
|
27
31
|
if data.empty?
|
28
32
|
return "No data."
|
@@ -40,37 +44,26 @@ class TablePrint
|
|
40
44
|
# TODO: stop checking field length once we hit the max
|
41
45
|
# TODO: don't check field length on fixed-width columns
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
# column headers
|
46
|
-
display_methods.each do |m|
|
47
|
-
field_lengths[m] = m.to_s.length
|
48
|
-
end
|
49
|
-
|
50
|
-
data.each do |obj|
|
51
|
-
display_methods.each do |m|
|
52
|
-
field_value = truncate(obj.send(m).to_s)
|
53
|
-
field_lengths[m] = [field_lengths[m], field_value.length].max
|
54
|
-
end
|
55
|
-
end
|
47
|
+
# make columns for all the display methods
|
48
|
+
columns = display_methods.collect { |m| Column.new(data, m, options[:column_options][m]) }
|
56
49
|
|
57
|
-
output = []
|
50
|
+
output = [] # a list of rows. we'll join this with newlines when we're done
|
58
51
|
|
52
|
+
# column headers
|
59
53
|
row = []
|
60
|
-
|
61
|
-
|
62
|
-
field_length = field_lengths[m]
|
63
|
-
row << ("%-#{field_length}s" % field_value.upcase)
|
54
|
+
columns.each do |column|
|
55
|
+
row << column.formatted_header
|
64
56
|
end
|
65
57
|
output << row.join(separator)
|
58
|
+
|
59
|
+
# a row of hyphens to separate the headers from the data
|
66
60
|
output << ("-" * row.join(separator).length)
|
67
61
|
|
68
|
-
|
62
|
+
# the data!
|
63
|
+
data.each do |data_obj|
|
69
64
|
row = []
|
70
|
-
|
71
|
-
|
72
|
-
field_length = field_lengths[m]
|
73
|
-
row << ("%-#{field_length}s" % field_value)
|
65
|
+
columns.each do |column|
|
66
|
+
row << column.formatted_field_value(data_obj)
|
74
67
|
end
|
75
68
|
output << row.join(separator)
|
76
69
|
end
|
@@ -80,14 +73,6 @@ class TablePrint
|
|
80
73
|
|
81
74
|
private
|
82
75
|
|
83
|
-
def truncate(field_value)
|
84
|
-
if field_value.length > MAX_FIELD_LENGTH
|
85
|
-
field_value = field_value[0..MAX_FIELD_LENGTH-1]
|
86
|
-
field_value[-3..-1] = "..."
|
87
|
-
end
|
88
|
-
field_value
|
89
|
-
end
|
90
|
-
|
91
76
|
def get_display_methods(data_obj, options)
|
92
77
|
# determine what methods we're going to use
|
93
78
|
|
@@ -153,6 +138,7 @@ class TablePrint
|
|
153
138
|
end
|
154
139
|
|
155
140
|
def clean_display_methods(data_obj, display_methods)
|
141
|
+
# TODO: this should probably be inside Column
|
156
142
|
clean_methods = []
|
157
143
|
display_methods.each do |m|
|
158
144
|
next if m.nil?
|
@@ -173,6 +159,58 @@ class TablePrint
|
|
173
159
|
[object]
|
174
160
|
end
|
175
161
|
end
|
162
|
+
|
163
|
+
class Column
|
164
|
+
attr_accessor :name, :display_method, :options, :data, :field_length, :max_field_length
|
165
|
+
|
166
|
+
def initialize(data, display_method, options = {})
|
167
|
+
options ||= {} # could have been passed an explicit nil
|
168
|
+
self.data = data # HACK? would rather not keep pointers to the data set all over the place
|
169
|
+
self.display_method = display_method
|
170
|
+
self.name = options[:name] || display_method.gsub("_", " ")
|
171
|
+
self.max_field_length = options[:max_field_length] || 30
|
172
|
+
self.max_field_length = [self.max_field_length, 1].max # numbers less than one are meaningless
|
173
|
+
end
|
174
|
+
|
175
|
+
def formatted_header
|
176
|
+
"%-#{self.field_length}s" % truncate(self.name.upcase)
|
177
|
+
end
|
178
|
+
|
179
|
+
def formatted_field_value(data_obj)
|
180
|
+
"%-#{self.field_length}s" % truncate(data_obj.send(self.display_method).to_s)
|
181
|
+
end
|
182
|
+
|
183
|
+
def field_length
|
184
|
+
return @field_length if defined?(@field_length) # we don't want to loop every time this is called!
|
185
|
+
|
186
|
+
# fixed-width fields don't require the full loop below
|
187
|
+
case data.first.send(self.display_method)
|
188
|
+
when Time
|
189
|
+
return [data.first.send(self.display_method).to_s.length, self.max_field_length].min
|
190
|
+
when TrueClass, FalseClass
|
191
|
+
return 5
|
192
|
+
end
|
193
|
+
|
194
|
+
length = self.name.length
|
195
|
+
self.data.each do |data_obj|
|
196
|
+
length = [length, data_obj.send(self.display_method).to_s.length].max
|
197
|
+
break if length >= self.max_field_length # we're never going to longer than the global max, so why keep going
|
198
|
+
end
|
199
|
+
@field_length = [length, self.max_field_length].min
|
200
|
+
@field_length
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def truncate(field_value)
|
206
|
+
copy = String.new(field_value)
|
207
|
+
if copy.length > self.max_field_length
|
208
|
+
copy = copy[0..self.max_field_length-1]
|
209
|
+
copy[-3..-1] = "..." unless self.max_field_length <= 3 # don't use ellipses when the string is tiny
|
210
|
+
end
|
211
|
+
copy
|
212
|
+
end
|
213
|
+
end
|
176
214
|
end
|
177
215
|
|
178
216
|
module Kernel
|
@@ -180,7 +218,7 @@ module Kernel
|
|
180
218
|
start = Time.now
|
181
219
|
table_print = TablePrint.new
|
182
220
|
puts table_print.tp(data, options)
|
183
|
-
|
221
|
+
Time.now - start
|
184
222
|
end
|
185
223
|
|
186
224
|
module_function :tp
|
data/table_print.gemspec
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{table_print}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Chris Doyle"]
|
12
|
+
s.date = %q{2011-04-13}
|
13
|
+
s.description = %q{TablePrint formats an object or array of objects into columns for easy reading. To do this, it assumes the objects in your array all respond to the same methods (vs pretty_print or awesome_print, who can't create columns because your objects could be entirely different).}
|
14
|
+
s.email = %q{archslide@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/table_print.rb",
|
28
|
+
"table_print.gemspec",
|
29
|
+
"test/helper.rb",
|
30
|
+
"test/test_column.rb",
|
31
|
+
"test/test_table_print.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/arches/table_print}
|
34
|
+
s.licenses = ["MIT"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.5.2}
|
37
|
+
s.summary = %q{Turn objects into nicely formatted columns for easy reading}
|
38
|
+
s.test_files = [
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/test_column.rb",
|
41
|
+
"test/test_table_print.rb"
|
42
|
+
]
|
43
|
+
|
44
|
+
if s.respond_to? :specification_version then
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
51
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
54
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
55
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
56
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
60
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
61
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
62
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/test/test_column.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TablePrint
|
4
|
+
class Column
|
5
|
+
def _truncate(field_value)
|
6
|
+
truncate(field_value)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestTablePrint < Test::Unit::TestCase
|
12
|
+
|
13
|
+
# TODO: active record tests if defined?(ActiveRecord)
|
14
|
+
|
15
|
+
# Vaguely ordered from most to least granular
|
16
|
+
|
17
|
+
context 'Instantiating a Column' do
|
18
|
+
context 'with a display_method' do
|
19
|
+
setup do
|
20
|
+
@column = TablePrint::Column.new([], "display_method_name")
|
21
|
+
end
|
22
|
+
should 'remember the display method' do
|
23
|
+
assert_equal "display_method_name", @column.display_method
|
24
|
+
end
|
25
|
+
should 'set the name' do
|
26
|
+
assert_equal "display method name", @column.name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with a column name in the options' do
|
31
|
+
setup do
|
32
|
+
@column = TablePrint::Column.new([], "display_method_name", {:name => "test_tube"})
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'set the name according to the options' do
|
36
|
+
assert_equal "test_tube", @column.name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'The truncate function' do
|
42
|
+
should 'let short strings pass through' do
|
43
|
+
assert_equal "asdf", TablePrint::Column.new([], "")._truncate("asdf")
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'truncate long strings with ellipses' do
|
47
|
+
assert_equal "123456789012345678901234567...", TablePrint::Column.new([], "")._truncate("1234567890123456789012345678901234567890")
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when given a max length in the options' do
|
51
|
+
should 'truncate long strings with ellipses' do
|
52
|
+
assert_equal "1234567...", TablePrint::Column.new([], "", :max_field_length => 10)._truncate("1234567890123456789012345678901234567890")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when the max length is tiny' do
|
57
|
+
should 'truncate long strings without ellipses' do
|
58
|
+
assert_equal "1", TablePrint::Column.new([], "", :max_field_length => -10)._truncate("1234567890123456789012345678901234567890")
|
59
|
+
assert_equal "1", TablePrint::Column.new([], "", :max_field_length => 0)._truncate("1234567890123456789012345678901234567890")
|
60
|
+
assert_equal "1", TablePrint::Column.new([], "", :max_field_length => 1)._truncate("1234567890123456789012345678901234567890")
|
61
|
+
assert_equal "12", TablePrint::Column.new([], "", :max_field_length => 2)._truncate("1234567890123456789012345678901234567890")
|
62
|
+
assert_equal "123", TablePrint::Column.new([], "", :max_field_length => 3)._truncate("1234567890123456789012345678901234567890")
|
63
|
+
assert_equal "1...", TablePrint::Column.new([], "", :max_field_length => 4)._truncate("1234567890123456789012345678901234567890")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'The field length function' do
|
69
|
+
should 'find the maximum width of the data' do
|
70
|
+
assert_equal 11, TablePrint::Column.new(["hello there"], "to_s").field_length
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the data is longer than the max_field_length' do
|
74
|
+
should 'equal the max field length' do
|
75
|
+
assert_equal 5, TablePrint::Column.new(["hello there"], "to_s", :max_field_length => 5).field_length
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when the column name is longer than the data' do
|
80
|
+
should 'reflect the column name length' do
|
81
|
+
assert_equal 4, TablePrint::Column.new(["he"], "to_s", :max_field_length => 5).field_length
|
82
|
+
assert_equal 12, TablePrint::Column.new(["hello"], "to_s", :name => "foobar THIS!").field_length
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when the column is boolean and the data is the limiting factor' do
|
87
|
+
should 'always be 5' do
|
88
|
+
assert_equal 5, TablePrint::Column.new([[true]], "first", :name => "dur").field_length
|
89
|
+
assert_equal 5, TablePrint::Column.new([[false]], "first", :name => "dur").field_length
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/test/test_table_print.rb
CHANGED
@@ -29,10 +29,6 @@ class MyClass
|
|
29
29
|
end
|
30
30
|
|
31
31
|
class TablePrint
|
32
|
-
def _truncate(field_value)
|
33
|
-
truncate(field_value)
|
34
|
-
end
|
35
|
-
|
36
32
|
def _get_display_methods(data_obj, options)
|
37
33
|
get_display_methods(data_obj, options)
|
38
34
|
end
|
@@ -85,16 +81,6 @@ class TestTablePrint < Test::Unit::TestCase
|
|
85
81
|
end
|
86
82
|
end
|
87
83
|
|
88
|
-
context 'The truncate function' do
|
89
|
-
should 'let short strings pass through' do
|
90
|
-
assert_equal "asdf", @tp._truncate("asdf")
|
91
|
-
end
|
92
|
-
|
93
|
-
should 'truncate long strings with ellipses' do
|
94
|
-
assert_equal "123456789012345678901234567...", @tp._truncate("1234567890123456789012345678901234567890")
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
84
|
context 'The clean_display_methods function' do
|
99
85
|
should 'only give back valid methods' do
|
100
86
|
assert_equal [], @tp._clean_display_methods(ManyMethods.new, [""])
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: table_print
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris Doyle
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-04-
|
18
|
+
date: 2011-04-13 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -96,7 +96,9 @@ files:
|
|
96
96
|
- Rakefile
|
97
97
|
- VERSION
|
98
98
|
- lib/table_print.rb
|
99
|
+
- table_print.gemspec
|
99
100
|
- test/helper.rb
|
101
|
+
- test/test_column.rb
|
100
102
|
- test/test_table_print.rb
|
101
103
|
has_rdoc: true
|
102
104
|
homepage: http://github.com/arches/table_print
|
@@ -134,4 +136,5 @@ specification_version: 3
|
|
134
136
|
summary: Turn objects into nicely formatted columns for easy reading
|
135
137
|
test_files:
|
136
138
|
- test/helper.rb
|
139
|
+
- test/test_column.rb
|
137
140
|
- test/test_table_print.rb
|