table_print 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -25,7 +25,7 @@ it assumes the objects in your array all respond to the same methods.
25
25
  $ rails c
26
26
  > tp array_of_objects, options = {}
27
27
 
28
- And you should see something like this:
28
+ You should see something like this:
29
29
 
30
30
  NAME | SUMMARY | TITLE
31
31
  -----------------------------------------------------------------------
@@ -44,7 +44,33 @@ on your model.
44
44
  > tp User.limit(30), {:only => [:address, :city, :state, :zip]}
45
45
 
46
46
  If you're not using ActiveRecord, the TablePrint default is to show all the methods on your object. Thus, the <b>:include</b>
47
- option is useless at the moment. You are still able to use <b>:only</b> and <b>:include</b>.
47
+ option is useless at the moment. You are still able to use <b>:only</b> and <b>:except</b>.
48
+
49
+ You can reference nested objects with the method chain required to reach them. Say you had some users who wrote books, and those
50
+ books had photos.
51
+
52
+ > tp array_of_objects, :only => ["name", "books.title", "books.photos.caption"]
53
+
54
+ NAME | BOOKS > TITLE | BOOKS > PHOTOS > CAPTION
55
+ -------------------------------------------------------------------------
56
+ Michael Connelly | |
57
+ | The Fifth Witness |
58
+ | | Susan was running, fast, away...
59
+ | | Along came a spider.
60
+ | Malcolm X |
61
+ | Bossypants |
62
+ | | Yes! Yes! A thousand times ye...
63
+ | | Don't see many like you aroun...
64
+ Carrot Top | |
65
+ Milton Greene | |
66
+ | How I Learned |
67
+ | | Once upon a time, I was a sma...
68
+ | | Lemons are yellow, limes are ...
69
+ | | Never as a woman her age. I l...
70
+ | Desperados |
71
+ | | Avast.
72
+ | | Giraffes lived a peaceful exi...
73
+
48
74
 
49
75
  === Column options
50
76
 
@@ -61,7 +87,7 @@ Columns have other options, including:
61
87
  will ensure that the column is as skinny as possible, but never above the number you provide.
62
88
 
63
89
  <b>field_length:</b> Useful for very large data sets, this option will explicitly set the column width regardless of the data
64
- it contains.
90
+ it contains. The max_field_length option takes precedence over field_length - ie, field_length can't be longer than max_field_length.
65
91
 
66
92
 
67
93
  == Contributing to table_print
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
data/lib/table_print.rb CHANGED
@@ -6,6 +6,7 @@
6
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
7
  # allow user to pass ActiveRelation instead of a data array? That could open up so many options!
8
8
  # a :short_booleans method could save a little space (replace true/false with T/F or 1/0)
9
+ # we could do some really smart stuff with polymorphic relationships, eg reusing photo column for blogs AND books!
9
10
  #
10
11
  # bugs
11
12
  #
@@ -13,6 +14,7 @@
13
14
 
14
15
  class TablePrint
15
16
 
17
+ # We need this set of built-in types when we determine the default display methods for a given object
16
18
  OBJECT_CLASSES = [String, Bignum, Regexp, ThreadError, Numeric, SystemStackError, IndexError,
17
19
  SecurityError, SizedQueue, IO, Range, Object, Exception, NoMethodError, TypeError, Integer, Dir,
18
20
  ZeroDivisionError, Kernel, RegexpError, SystemExit, NotImplementedError, Hash,
@@ -23,56 +25,49 @@ class TablePrint
23
25
  StandardError, EOFError, LoadError, NameError, NilClass, TrueClass, MatchingData,
24
26
  LocalJumpError, Binding, SignalException, SystemCallError, File, ScriptError, Module, Symbol]
25
27
 
26
- # TODO: make options for things like MAX_FIELD_LENGTH
27
- # TODO: make options for things like separator
28
- # TODO: make options for things like column order
28
+ attr_accessor :columns, :display_methods, :separator
29
+
29
30
  def initialize(options = {})
31
+ # TODO: make options for things like column order
30
32
  end
31
33
 
32
- # TODO: show documentation if invoked with no arguments
33
- # TODO: use *args instead of options
34
34
  def tp(data, options = {})
35
- data = wrap(data).compact
35
+ # TODO: show documentation if invoked with no arguments
36
+ # TODO: use *args instead of options
36
37
 
37
- # nothing to see here
38
- if data.empty?
39
- return "No data."
40
- end
38
+ self.separator = options[:separator] || " | "
41
39
 
42
- separator = " | "
40
+ stack = wrap(data).compact
43
41
 
44
- display_methods = get_display_methods(data.first, options)
45
- unless display_methods.length > 0
46
- return data.inspect.to_s
42
+ if stack.empty?
43
+ return "No data."
47
44
  end
48
45
 
49
- # we're going to load all the data into memory so we can calculate field lengths.
50
- # TODO: there has to be a better way than loading everything into memory
51
- # TODO: stop checking field length once we hit the max
52
- # TODO: don't check field length on fixed-width columns
46
+ self.display_methods = get_display_methods(data.first, options) # these are all strings now
47
+ unless self.display_methods.length > 0
48
+ return stack.inspect.to_s
49
+ end
53
50
 
54
51
  # make columns for all the display methods
55
- columns = display_methods.collect { |m| Column.new(data, m, options[m] || options[m.to_sym]) }
52
+ self.columns = {}
53
+ self.display_methods.each do |m|
54
+ self.columns[m] = ColumnHelper.new(data, m, options[m] || options[m.to_sym])
55
+ end
56
56
 
57
57
  output = [] # a list of rows. we'll join this with newlines when we're done
58
58
 
59
59
  # column headers
60
60
  row = []
61
- columns.each do |column|
62
- row << column.formatted_header
61
+ self.display_methods.each do |m|
62
+ row << self.columns[m].formatted_header
63
63
  end
64
- output << row.join(separator)
64
+ output << row.join(self.separator)
65
65
 
66
66
  # a row of hyphens to separate the headers from the data
67
- output << ("-" * row.join(separator).length)
67
+ output << ("-" * output.first.length)
68
68
 
69
- # the data!
70
- data.each do |data_obj|
71
- row = []
72
- columns.each do |column|
73
- row << column.formatted_field_value(data_obj)
74
- end
75
- output << row.join(separator)
69
+ while stack.length > 0
70
+ format_row(stack, output)
76
71
  end
77
72
 
78
73
  output.join("\n")
@@ -80,6 +75,42 @@ class TablePrint
80
75
 
81
76
  private
82
77
 
78
+ def format_row(stack, output)
79
+
80
+ # method_chain is a dot-delimited list of methods, eg "user.blogs.url". It represents the path from the top-level
81
+ # objects to the data_obj.
82
+ data_obj, method_chain = stack.shift
83
+
84
+ # top level objects don't have a method_chain, give them one so we don't have to null-check everywhere
85
+ method_chain ||= ""
86
+
87
+ # represent method_chain strings we've seen for this row as a tree of hash keys.
88
+ # eg, if we have columns for "user.blogs.url" and "user.blogs.title", we only want to add one set of user.blogs to the stack
89
+ method_hash = {}
90
+
91
+ # if no columns in this row produce any data, we don't want to append it to the output. eg, if our only columns are
92
+ # ["id", "blogs.title"] we don't want to print a blank row for every blog we iterate over. We want to entirely skip
93
+ # printing a row for that level of the hierarchy.
94
+ found_data = false
95
+
96
+ # dive right in!
97
+ row = []
98
+ self.display_methods.each do |m|
99
+ column = self.columns[m]
100
+
101
+ # If this column happens to begin a recursion, get those objects on the stack. Pass in the stack-tracking info
102
+ # we've saved: method_chain and method_hash.
103
+ column.add_stack_objects(stack, data_obj, method_chain, method_hash)
104
+
105
+ # all rows show all cells. Even if there's no data we still have to generate an empty cell of the proper width
106
+ row << column.formatted_cell_value(data_obj, method_chain)
107
+ found_data = true unless row[-1].strip.empty?
108
+ end
109
+
110
+ output << row.join(self.separator) if found_data
111
+ end
112
+
113
+ # Sort out the user options into a set of display methods we're going to show. This always returns strings.
83
114
  def get_display_methods(data_obj, options)
84
115
  # determine what methods we're going to use
85
116
 
@@ -88,60 +119,30 @@ class TablePrint
88
119
  # :except - use the default set of methods but NOT the ones passed here
89
120
  # :include - use the default set of methods AND the ones passed here
90
121
  # :only - discard the default set of methods in favor of this list
91
- # :cascade -
92
- #
93
- # eg
94
- #
95
- # tp User.limit(30) # default to using AR columns
96
- # tp User.limit(30) :except => :display_name
97
- # tp User.limit(30) :except => [:display_name, :created_at]
98
- # tp User.limit(30) :except => [:display_name, :timestamps] # a rails convention - but this could just be a type-based filter instead of method-based?
99
- # tp User.limit(30) :include => :status # not an AR column
100
- # tp User.limit(30) :except => :display_name
101
- # tp User.limit(30) :except => :display_name
102
- # tp User.limit(30) :except => :display_name
103
- # tp User.limit(30) :except => :display_name
104
- #
105
- # tp User.include(:blogs).limit(30) :cascade => :blog
106
- # tp User.limit(30) :include => "blog.title" # use dot notation to traverse children
107
- # TODO: how to handle multiple children? eg, a user has fifteen blogs
108
- #
109
- # tp [myClassInstance1, ...] # default to using non-base methods
110
- # tp [myClassInstance1, ...] :except => :blah
111
- # tp [myClassInstance1, ...] :except => [:blah, :blow]
112
- # tp [myClassInstance1, ...] :include => :blah
113
- # tp [myClassInstance1, ...] :include => [:blah, :blow]
114
- # tp [myClassInstance1, ...] :include => :blah, :except => :blow
115
- # tp [myClassInstance1, ...] :only => [:one, :two, :three]
116
-
117
- if options.has_key? :only or options.has_key? "only"
118
- display_methods = clean_display_methods(data_obj, wrap(options[:only]))
122
+ # :cascade - show all methods in child objects
123
+
124
+ if options.has_key? :only
125
+ display_methods = wrap(options[:only]).map { |m| m.to_s }
119
126
  return display_methods if display_methods.length > 0
127
+ else
128
+ display_methods = get_default_display_methods(data_obj) # start with what we can deduce
129
+ display_methods.concat(wrap(options[:include])).map! { |m| m.to_s } # add the includes
130
+ display_methods = (display_methods - wrap(options[:except]).map! { |m| m.to_s }) # remove the excepts
120
131
  end
121
132
 
122
- # make all the options into arrays
123
- methods_to_include = clean_display_methods(data_obj, wrap(options[:include]))
124
- methods_to_except = clean_display_methods(data_obj, wrap(options[:except]))
125
-
126
- # add/remove the includes/excludes from the defaults
127
- display_methods = get_default_display_methods(data_obj)
128
- display_methods.concat(methods_to_include).uniq!
129
- display_methods - methods_to_except
133
+ display_methods.uniq.compact
130
134
  end
131
135
 
136
+ # Sniff the data class for non-standard methods to use as a baseline for display
132
137
  def get_default_display_methods(data_obj)
133
138
  # ActiveRecord
134
139
  return data_obj.class.columns.collect { |c| c.name } if defined?(ActiveRecord) and data_obj.is_a? ActiveRecord::Base
135
140
 
136
- # base types
137
- # TODO: fill out this list. any way to get this programatically? do we actually want to filter out all base ruby types? important question for custom classes inheriting from base types
138
- return [] if [Float, Fixnum, String, Numeric, Array, Hash].include? data_obj.class
139
-
140
141
  # custom class
141
142
  methods = data_obj.class.instance_methods
142
143
  OBJECT_CLASSES.each do |oclass|
143
144
  if data_obj.is_a? oclass
144
- methods = methods - oclass.instance_methods # we're only interested in custom methods, not ruby core methods
145
+ methods = methods - oclass.instance_methods # we're only interested in custom methods, not ruby core methods
145
146
  end
146
147
  end
147
148
 
@@ -150,19 +151,9 @@ class TablePrint
150
151
  methods
151
152
  end
152
153
 
153
- def clean_display_methods(data_obj, display_methods)
154
- # TODO: this should probably be inside Column
155
- clean_methods = []
156
- display_methods.each do |m|
157
- next if m.nil?
158
- next if m == ""
159
- next unless data_obj.respond_to? m
160
- clean_methods << m.to_s
161
- end
162
- clean_methods.uniq
163
- end
164
-
165
154
  # borrowed from rails
155
+ # turn objects into an array
156
+ # TODO: this method is duped, put it someplace everyone can see it
166
157
  def wrap(object)
167
158
  if object.nil?
168
159
  []
@@ -173,69 +164,174 @@ class TablePrint
173
164
  end
174
165
  end
175
166
 
176
- class Column
177
- attr_accessor :name, :display_method, :options, :data, :field_length, :max_field_length
167
+ class ColumnHelper
168
+ attr_accessor :field_length, :max_field_length, :method, :name, :options
169
+
170
+ # method is a string
171
+ def initialize(data, method, options = {})
172
+ self.method = method
173
+ self.options = options || {} # could have been passed an explicit nil
174
+
175
+ self.name = self.options[:name] || method.gsub("_", " ").gsub(".", " > ")
178
176
 
179
- def initialize(data, display_method, options = {})
180
- self.options = options || {} # could have been passed an explicit nil
181
- self.display_method = display_method
182
- self.name = self.options[:name] || display_method.gsub("_", " ")
183
177
  self.max_field_length = self.options[:max_field_length] || 30
184
- self.max_field_length = [self.max_field_length, 1].max # numbers less than one are meaningless
178
+ self.max_field_length = [self.max_field_length, 1].max # numbers less than one are meaningless
185
179
 
186
- # initialization
187
- self.initialize_field_length(data)
180
+ initialize_field_length(data)
188
181
  end
189
182
 
190
183
  def formatted_header
191
184
  "%-#{self.field_length}s" % truncate(self.name.upcase)
192
185
  end
193
186
 
194
- def formatted_field_value(data_obj)
195
- "%-#{self.field_length}s" % truncate(data_obj.send(self.display_method).to_s)
187
+ def formatted_cell_value(data_obj, method_chain)
188
+ cell_value = ""
189
+
190
+ # top-level objects don't have method chain. Need to check explicitly whether our method is top-level, otherwise
191
+ # if the last method in our chain matches a top-level method we could accidentally print its data in our column.
192
+ #
193
+ # The method chain is what we've been building up as we were "recursing" through previous objects. You could think of
194
+ # it as a prefix for this row. Eg, we could be looping through the columns with a method_chain of "locker.assets",
195
+ # indicating that we've recursed down from user to locker and are now interested in printing assets. So
196
+ #
197
+ unless method_chain == "" and self.method.include? "."
198
+ our_method_chain = self.method.split(".")
199
+ our_method = our_method_chain.pop
200
+
201
+ # check whether the method_chain fully qualifies the path to this particular object. If this is the bottom level
202
+ # of object in the tree, and the method_chain matches all the way down, then it's finally time to print this cell.
203
+ if method_chain == our_method_chain.join(".")
204
+ if data_obj.respond_to? our_method
205
+ cell_value = data_obj.send(our_method)
206
+ end
207
+ end
208
+ end
209
+ "%-#{self.field_length}s" % truncate(cell_value.to_s)
196
210
  end
197
211
 
198
- def initialize_field_length(data)
199
- # skip all this nonsense if we've been explicitly told what to do
200
- if self.options[:field_length] and self.options[:field_length] > 0
201
- length = self.options[:field_length]
202
- else
203
- length = self.name.length # it has to at least be long enough for the column header!
204
-
205
- start = Time.now
206
- data.each do |data_obj|
207
- next if data_obj.nil?
208
-
209
- # fixed-width fields don't require the full loop
210
- case data_obj.send(self.display_method)
211
- when Time
212
- length = data_obj.send(self.display_method).to_s.length
213
- break
214
- when TrueClass, FalseClass
215
- length = [5, length].max
216
- break
217
- end
212
+ # Determine if we need to add some stuff to the stack. If so, put it on top and update the tracking objects.
213
+ def add_stack_objects(stack, data_obj, method_chain, method_hash)
218
214
 
219
- length = [length, data_obj.send(self.display_method).to_s.length].max
220
- break if length >= self.max_field_length # we're never going to longer than the global max, so why keep going
221
- break if (Time.now - start) > 2 # assume if we loop for more than 2s that we've made it through a representative sample, and bail
222
- end
215
+ return unless self.add_to_stack?(method_chain, method_hash)
216
+
217
+ # TODO: probably a less awkward string method to do this
218
+ # current_method is the method we're going to call on our data_obj. Say our column is "locker.assets.url" and
219
+ # our chain is "locker", current_method would be "assets"
220
+ current_method = get_current_method(method_chain)
221
+
222
+ new_stack_objects = []
223
+ if current_method != "" and data_obj.respond_to? current_method
224
+ new_stack_objects = data_obj.send(current_method)
225
+ end
226
+
227
+ # Now that we've seen "locker.assets", no need to add it to the stack again for this row! Save it off in method_hash
228
+ # so when we hit "locker.assets.caption" we won't add the same assets again.
229
+ new_method_chain = method_chain == "" ? current_method : "#{method_chain}.#{current_method}"
230
+ method_hash[new_method_chain] = {}
231
+
232
+ # TODO: probably a cool array method to do this
233
+ # finally - update the stack with the object(s) we found
234
+ wrap(new_stack_objects).reverse_each do |stack_obj|
235
+ stack.unshift [stack_obj, new_method_chain]
236
+ end
237
+ end
238
+
239
+ def add_to_stack?(method_chain, method_hash = {})
240
+
241
+ # Check whether we're involved in this row. method_chain lets us know the path we took to find the current set of
242
+ # data objects. If our method doesn't act upon those objects, bail.
243
+ # eg, if these objects are the result of calling "locker.assets" on top-level user objects, but our method is "blogs.title",
244
+ # all we're going to be doing on this row is pushing out empty cells.
245
+ return unless self.method.start_with? method_chain
246
+
247
+ # check whether another column has already added our objects. if we hit "locker.assets.url" already and we're
248
+ # "locker.assets.caption", the assets are already on the stack. Don't want to add them again.
249
+ new_method_chain = method_chain == "" ? get_current_method(method_chain) : "#{method_chain}.#{get_current_method(method_chain)}"
250
+ return if method_hash.has_key? new_method_chain
251
+
252
+ # OK! this column relates to the data object and hasn't been beaten to the punch. But do we have more levels to recurse, or
253
+ # is this object on the bottom rung and just needs formatting?
254
+
255
+ # if this is the top level, all we need to do is check for a dot, indicating a chain of methods
256
+ if method_chain == ""
257
+ return method.include? "."
223
258
  end
224
259
 
225
- self.field_length = [length, self.max_field_length].min # never bigger than the max
260
+ # if this isn't the top level, subtract out the part of the chain we've already called before we check for further chaining
261
+ test_method = String.new(method[method_chain.length, method.length])
262
+ test_method = test_method[1, test_method.length] if test_method.start_with? "."
263
+
264
+ test_method.include? "."
226
265
  end
227
266
 
228
267
  private
229
-
268
+
269
+ # borrowed from rails
270
+ # turn objects into an array
271
+ def wrap(object)
272
+ if object.nil?
273
+ []
274
+ elsif object.respond_to?(:to_ary)
275
+ object.to_ary
276
+ else
277
+ [object]
278
+ end
279
+ end
280
+
281
+ # cut off field_value based on our previously determined width
230
282
  def truncate(field_value)
231
283
  copy = String.new(field_value)
232
- if copy.length > self.max_field_length
233
- copy = copy[0..self.max_field_length-1]
234
- copy[-3..-1] = "..." unless self.max_field_length <= 3 # don't use ellipses when the string is tiny
284
+ if copy.length > self.field_length
285
+ copy = copy[0..self.field_length-1]
286
+ copy[-3..-1] = "..." unless self.field_length <= 3 # don't use ellipses when the string is tiny
235
287
  end
236
288
  copy
237
289
  end
290
+
291
+ # determine how wide this column is going to be
292
+ def initialize_field_length(data)
293
+ # skip all this nonsense if we've been explicitly told what to do
294
+ if self.options[:field_length] and self.options[:field_length] > 0
295
+ self.field_length = self.options[:field_length]
296
+ else
297
+ self.field_length = self.name.length # it has to at least be long enough for the column header!
298
+
299
+ find_data_length(data, self.method, Time.now)
300
+ end
301
+
302
+ self.field_length = [self.field_length, self.max_field_length].min # never bigger than the max
303
+ end
304
+
305
+ # recurse through the data set using the method chain to find the longest field (or until time's up)
306
+ def find_data_length(data, method, start)
307
+ return if (Time.now - start) > 2
308
+ return if method.nil?
309
+ return if data.nil?
310
+ return if self.field_length >= self.max_field_length
311
+
312
+ wrap(data).each do |data_obj|
313
+ next_method = method.split(".").first
314
+
315
+ return unless data_obj.respond_to? next_method
316
+
317
+ if next_method == method # done!
318
+ self.field_length = [self.field_length, data_obj.send(next_method).to_s.length].max
319
+ else # keep going
320
+ find_data_length(data_obj.send(next_method), method[(next_method.length + 1)..-1], start)
321
+ end
322
+ end
323
+ end
324
+
325
+ # add the next level to the method_chain
326
+ def get_current_method(method_chain)
327
+ if self.method.start_with? method_chain
328
+ current_method = String.new(self.method)
329
+ current_method = current_method[method_chain.length, current_method.length]
330
+ current_method.split(".").detect { |m| m != "" }
331
+ end
332
+ end
238
333
  end
334
+
239
335
  end
240
336
 
241
337
  module Kernel
@@ -243,10 +339,101 @@ module Kernel
243
339
  start = Time.now
244
340
  table_print = TablePrint.new
245
341
  puts table_print.tp(data, options)
246
- Time.now - start
342
+ Time.now - start # we have to return *something*, might as well be execution time.
247
343
  end
248
344
 
249
345
  module_function :tp
250
346
  end
251
347
 
348
+ ## Some nested classes to make development easier! Make sure you don't commit these uncommented.
349
+ #
350
+ #class TestClass
351
+ # attr_accessor :title, :name, :blogs, :locker
352
+ #
353
+ # def initialize(title, name, blogs, locker)
354
+ # self.title = title
355
+ # self.name = name
356
+ # self.blogs = blogs
357
+ # self.locker = locker
358
+ # end
359
+ #end
360
+ #
361
+ #class Blog
362
+ # attr_accessor :title, :summary
363
+ #
364
+ # def initialize(title, summary)
365
+ # self.title = title
366
+ # self.summary = summary
367
+ # end
368
+ #end
369
+ #
370
+ #class Locker
371
+ # attr_accessor :assets
372
+ #
373
+ # def initialize(assets)
374
+ # self.assets = assets
375
+ # end
376
+ #end
377
+ #
378
+ #class Asset
379
+ # attr_accessor :url, :caption
380
+ #
381
+ # def initialize(url, caption)
382
+ # self.url = url
383
+ # self.caption = caption
384
+ # end
385
+ #end
386
+ #
387
+ #stack = [
388
+ #
389
+ # TestClass.new("one title", "one name", [
390
+ # Blog.new("one blog title1", "one blog sum1"),
391
+ # Blog.new("one blog title2", "one blog sum2"),
392
+ # Blog.new("one blog title3", "one blog sum3"),
393
+ # ],
394
+ # Locker.new([
395
+ # Asset.new("one asset url1", "one asset cap1"),
396
+ # Asset.new("one asset url2", "one asset cap2"),
397
+ # Asset.new("one asset url3", "one asset cap3"),
398
+ # ])
399
+ # ),
400
+ # TestClass.new("two title", "two name", [
401
+ # Blog.new("two blog title1", "two blog sum1"),
402
+ # Blog.new("two blog title2", "two blog sum2"),
403
+ # Blog.new("two blog title3", "two blog sum3"),
404
+ # ],
405
+ # Locker.new([
406
+ # Asset.new("two asset url1", "two asset cap1"),
407
+ # Asset.new("two asset url2", "two asset cap2"),
408
+ # Asset.new("two asset url3", "two asset cap3"),
409
+ # ])
410
+ # ),
411
+ # TestClass.new("three title", "three name", [
412
+ # Blog.new("three blog title1", "three blog sum1"),
413
+ # Blog.new("three blog title2", "three blog sum2"),
414
+ # Blog.new("three blog title3", "three blog sum3"),
415
+ # ],
416
+ # Locker.new([
417
+ # Asset.new("three asset url1", "three asset cap1"),
418
+ # Asset.new("three asset url2", "three asset cap2"),
419
+ # Asset.new("three asset url3", "three asset cap3"),
420
+ # ])
421
+ # ),
422
+ # TestClass.new("four title", "four name", [
423
+ # Blog.new("four blog title1", "four blog sum1"),
424
+ # Blog.new("four blog title2", "four blog sum2"),
425
+ # Blog.new("four blog title3", "four blog sum3"),
426
+ # ],
427
+ # Locker.new([
428
+ # Asset.new("four asset url1", "four asset cap1"),
429
+ # Asset.new("four asset url2", "four asset cap2"),
430
+ # Asset.new("four asset url3", "four asset cap3"),
431
+ # ])
432
+ # ),
433
+ #]
434
+
435
+ #tp stack, :include => ["blogs.title", "blogs.summary", "locker.assets.url", "locker.assets.caption"]
436
+
437
+
438
+
252
439
 
data/table_print.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{table_print}
8
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Chris Doyle"]
12
- s.date = %q{2011-04-17}
12
+ s.date = %q{2011-04-26}
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
14
  s.email = %q{archslide@gmail.com}
15
15
  s.extra_rdoc_files = [
data/test/helper.rb CHANGED
@@ -16,3 +16,41 @@ require 'table_print'
16
16
 
17
17
  class Test::Unit::TestCase
18
18
  end
19
+
20
+ class MyNestedClass
21
+ attr_accessor :title, :name, :summary, :captions
22
+
23
+ def initialize(title, name, summary, captions)
24
+ self.title = title
25
+ self.name = name
26
+ self.summary = summary
27
+ self.captions = captions.collect{|c| Caption.new(c[:text], c[:photo_url])}
28
+ end
29
+
30
+ class Caption
31
+ attr_accessor :text, :photo_url
32
+
33
+ def initialize(text, photo_url)
34
+ self.text = text
35
+ self.photo_url = photo_url
36
+ end
37
+ end
38
+
39
+ def self.setup
40
+ [["how to", "bert", "bertto", [{:text => "no, really, how to", :photo_url => "http://www.123.com/456.jpg"}]],
41
+ ["enemies", "ernie", "ernieenemies", [{:text => "no, really, enemies", :photo_url => "http://www.234.com/567.jpg"}]],
42
+ ["a walk to forget", "big bird", "bigbirdforget", [{:text => "no, really, a walk to forget", :photo_url => "http://www.345.com/678.jpg"}]],
43
+ ["cookies", "cookie monster", "cookiemonstercookies", [{:text => "no, really, cookies", :photo_url => "http://www.456.com/789.jpg"}]],
44
+ ["your mom", "the count", "thecountmom", [{:text => "no, really, your mom", :photo_url => "http://www.789.com/1011.jpg"}]],
45
+ ["fire!", "elmo", "elmofire!", [{:text => "no, really, fire!", :photo_url => "http://www.8910.com/1112.jpg"}]],
46
+ ["eat your veggies", "michelle obama", "michelleobamaveggies", [{:text => "no, really, eat your veggies", :photo_url => "http://www.91011.com/1213.jpg"}]],
47
+ ["wakka wakka", "ellen degeneres", "ellendegenereswakka", [{:text => "no, really, wakka wakka", :photo_url => "http://www.101112.com/1314.jpg"}]],
48
+ ["peas and carrots", "the hulk", "thehulkcarrots", [{:text => "no, really, peas and carrots", :photo_url => "http://www.abc.com/def.jpg"}]],
49
+ ["juan valdez", "camaro", "camaravaldez", [{:text => "no, really, juan valdez", :photo_url => "http://www.bcd.com/efg.jpg"}]],
50
+ ["fish fish fish", "alaska", "alaskafish", [{:text => "no, really, fish fish fish", :photo_url => "http://www.cde.com/fgh.jpg"}]],
51
+ ["tracks", "sir toppem hat", "sirtoppemhattracks", [{:text => "no, really, tracks", :photo_url => "http://www.def.com/ghi.jpg"}]],
52
+ ["smoking stacked", "thomas", "thomasstacked", [{:text => "no, really, smoking stacked", :photo_url => "http://www.efg.com/hij.jpg"}]],
53
+ ["cannes", "alpo", "alpocannes", [{:text => "no, really, cannes", :photo_url => "http://www.fgh.com/ijk.jpg"}]],
54
+ ].collect { |a| MyNestedClass.new(a[0], a[1], a[2], a[3]) }
55
+ end
56
+ end
data/test/test_column.rb CHANGED
@@ -1,10 +1,30 @@
1
1
  require 'helper'
2
2
 
3
3
  class TablePrint
4
- class Column
4
+ class ColumnHelper
5
5
  def _truncate(field_value)
6
6
  truncate(field_value)
7
7
  end
8
+
9
+ def _get_current_method(method_chain)
10
+ get_current_method(method_chain)
11
+ end
12
+
13
+ def _add_to_stack?(method_chain, method_hash)
14
+ add_to_stack?(method_chain, method_hash)
15
+ end
16
+
17
+ def _formatted_cell_value(data_obj, method_chain)
18
+ formatted_cell_value(data_obj, method_chain)
19
+ end
20
+
21
+ def _find_data_length(data, method_chain, start)
22
+ find_data_length(data, method_chain, start)
23
+ end
24
+
25
+ def _add_stack_objects(stack, data_obj, method_chain, start)
26
+ add_stack_objects(stack, data_obj, method_chain, start)
27
+ end
8
28
  end
9
29
  end
10
30
 
@@ -12,15 +32,35 @@ class TestTablePrint < Test::Unit::TestCase
12
32
 
13
33
  # TODO: active record tests if defined?(ActiveRecord)
14
34
 
15
- # Vaguely ordered from most to least granular
35
+ # Ordered to match method order in the Column class
36
+
37
+ # attr_accessor :field_length, :max_field_length, :method, :name, :options
38
+ context 'A column object' do
39
+ setup do
40
+ @column = TablePrint::ColumnHelper.new([], "to_s")
41
+ @column.field_length = 0
42
+ @column.max_field_length = 0
43
+ @column.method = 0
44
+ @column.name = 0
45
+ @column.options = 0
46
+ end
47
+ should 'allow writes to and reads from its attributes' do
48
+ assert_equal 0, @column.field_length
49
+ assert_equal 0, @column.max_field_length
50
+ assert_equal 0, @column.method
51
+ assert_equal 0, @column.name
52
+ assert_equal 0, @column.options
53
+ end
54
+ end
16
55
 
56
+ # def initialize(data, method, options = {})
17
57
  context 'Instantiating a Column' do
18
58
  context 'with a display_method' do
19
59
  setup do
20
- @column = TablePrint::Column.new([], "to_s")
60
+ @column = TablePrint::ColumnHelper.new([], "to_s")
21
61
  end
22
62
  should 'remember the display method' do
23
- assert_equal "to_s", @column.display_method
63
+ assert_equal "to_s", @column.method
24
64
  end
25
65
  should 'set the name' do
26
66
  assert_equal "to s", @column.name
@@ -30,18 +70,27 @@ class TestTablePrint < Test::Unit::TestCase
30
70
  context 'with options including' do
31
71
  context 'name' do
32
72
  setup do
33
- @column = TablePrint::Column.new([], "first", {:name => "test_tube"})
73
+ @column = TablePrint::ColumnHelper.new([], "first", {:name => "test_tube"})
34
74
  end
35
75
 
36
76
  should 'set the name according to the options' do
37
77
  assert_equal "test_tube", @column.name
38
78
  end
79
+
80
+ context 'when the method name contains dots' do
81
+ setup do
82
+ @column = TablePrint::ColumnHelper.new(["short"], "method1.method2", {:name => "test_tube"})
83
+ end
84
+ should 'use the option' do
85
+ assert_equal "TEST_TUBE", @column.formatted_header
86
+ end
87
+ end
39
88
  end
40
89
 
41
90
  context 'a field_length' do
42
91
  context 'that is valid' do
43
92
  setup do
44
- @column = TablePrint::Column.new(["short"], "first", {:field_length => 20})
93
+ @column = TablePrint::ColumnHelper.new(["short"], "first", {:field_length => 20})
45
94
  end
46
95
 
47
96
  should 'set the field length according to the options' do
@@ -51,7 +100,7 @@ class TestTablePrint < Test::Unit::TestCase
51
100
 
52
101
  context 'that is less than 1' do
53
102
  setup do
54
- @column = TablePrint::Column.new(["short"], "first", {:field_length => 0})
103
+ @column = TablePrint::ColumnHelper.new(["short"], "first", {:field_length => 0})
55
104
  end
56
105
 
57
106
  should 'ignore the field_length' do
@@ -61,7 +110,7 @@ class TestTablePrint < Test::Unit::TestCase
61
110
 
62
111
  context 'that is bigger than the max' do
63
112
  setup do
64
- @column = TablePrint::Column.new(["short"], "first", {:field_length => 20, :max_field_length => 10})
113
+ @column = TablePrint::ColumnHelper.new(["short"], "first", {:field_length => 20, :max_field_length => 10})
65
114
  end
66
115
 
67
116
  should 'respect the max_field_length' do
@@ -72,64 +121,259 @@ class TestTablePrint < Test::Unit::TestCase
72
121
  end
73
122
  end
74
123
 
124
+ # def formatted_header
125
+ context 'The formatted header function' do
126
+ context 'when the method name contains no special characters and is shorter than the max field length' do
127
+ setup do
128
+ @column = TablePrint::ColumnHelper.new(["short"], "first", {:field_length => 10})
129
+ end
130
+ should 'uppercase the method name and pad with spaces' do
131
+ assert_equal "FIRST ", @column.formatted_header
132
+ end
133
+ end
134
+
135
+ context 'when the method name contains no special characters and is longer than the max field length' do
136
+ setup do
137
+ @column = TablePrint::ColumnHelper.new(["short"], "longMethodName", {:field_length => 10})
138
+ end
139
+ should 'uppercase and truncate the method name' do
140
+ assert_equal "LONGMET...", @column.formatted_header
141
+ end
142
+ end
143
+
144
+ context 'when the method name contains underscores' do
145
+ setup do
146
+ @column = TablePrint::ColumnHelper.new(["short"], "method_name")
147
+ end
148
+ should 'replace underscores with spaces' do
149
+ assert_equal "METHOD NAME", @column.formatted_header
150
+ end
151
+ end
152
+
153
+ context 'when the method name contains dots' do
154
+ setup do
155
+ @column = TablePrint::ColumnHelper.new(["short"], "method1.method2")
156
+ end
157
+ should 'replace dots with greater-thans' do
158
+ assert_equal "METHOD1 > METHOD2", @column.formatted_header
159
+ end
160
+ end
161
+
162
+ context 'when the user passes a name in the column options' do
163
+ setup do
164
+ @column = TablePrint::ColumnHelper.new(["short"], "method1.method2", {:name => "whoop"})
165
+ end
166
+ should 'use that name instead of the method name' do
167
+ assert_equal "WHOOP", @column.formatted_header
168
+ end
169
+ end
170
+ end
171
+
172
+ # def formatted_cell_value(data_obj, method_chain)
173
+ context 'The formatted_cell_value method' do
174
+ should 'return whitespace if the method chain does not exactly match the column definition method' do
175
+ assert_equal " ", TablePrint::ColumnHelper.new([], "captions.text")._formatted_cell_value("test", "id")
176
+ assert_equal " ", TablePrint::ColumnHelper.new([], "captions.text")._formatted_cell_value("test", "captions")
177
+ end
178
+
179
+ should 'return whitespace if the method chain begins the ' do
180
+ assert_equal "no, really, ...", TablePrint::ColumnHelper.new([], "captions.text")._formatted_cell_value(MyNestedClass.setup.first.captions.first, "captions")
181
+ end
182
+ end
183
+
184
+ # def add_stack_objects(stack, data_obj, method_chain, method_hash)
185
+ context 'The add_stack_objects method' do
186
+ context 'when objects need to be added to the stack' do
187
+ setup do
188
+ @tp = TablePrint::ColumnHelper.new(MyNestedClass.setup, "captions.text")
189
+ @stack = [1, 2, 3]
190
+ @tp._add_stack_objects(@stack, MyNestedClass.setup.first, "", {})
191
+ end
192
+ should 'increase stack size' do
193
+ assert_equal 4, @stack.size
194
+ end
195
+ should 'push the new objects on the front of the stack' do
196
+ assert_equal MyNestedClass::Caption, @stack.first.first.class
197
+ end
198
+ should 'include the updated method_chain in the stack' do
199
+ assert_equal "captions", @stack.first.last
200
+ end
201
+ end
202
+ end
203
+
204
+
205
+ # def add_to_stack?(method_chain, method_hash = {})
206
+ context 'The add_to_stack? method' do
207
+ should 'appropriately respond to its arguments' do
208
+
209
+ # our first method produces an array, so yes, stack 'em up
210
+ assert TablePrint::ColumnHelper.new(MyNestedClass.setup, "captions.text")._add_to_stack?("", {})
211
+
212
+ # captions has already been called, so it gets popped off our method chain. text is the final method, so no, don't stack
213
+ assert !TablePrint::ColumnHelper.new(MyNestedClass.setup, "captions.text")._add_to_stack?("captions", {})
214
+
215
+ # the method isn't one of ours, so there's nothing for us to do
216
+ assert !TablePrint::ColumnHelper.new(MyNestedClass.setup, "captions.text")._add_to_stack?("id", {})
217
+
218
+ # another column has already added the captions to the stack, so there's no need for us to do it
219
+ assert !TablePrint::ColumnHelper.new(MyNestedClass.setup, "captions.text")._add_to_stack?("", {"captions" => {}})
220
+ end
221
+ end
222
+
223
+ # def wrap(object)
224
+
225
+ # def truncate(field_value)
75
226
  context 'The truncate function' do
76
227
  should 'let short strings pass through' do
77
- assert_equal "asdf", TablePrint::Column.new([], "")._truncate("asdf")
228
+ assert_equal "asdf", TablePrint::ColumnHelper.new(["a long long long string"], "first")._truncate("asdf")
78
229
  end
79
230
 
80
231
  should 'truncate long strings with ellipses' do
81
- assert_equal "123456789012345678901234567...", TablePrint::Column.new([], "")._truncate("1234567890123456789012345678901234567890")
232
+ # have to put long data in the data set to field_length is pushed out to the default max_field_length
233
+ assert_equal "123456789012345678901234567...", TablePrint::ColumnHelper.new(["1234567890123456789012345678901234567890"], "first")._truncate("1234567890123456789012345678901234567890")
82
234
  end
83
235
 
84
- context 'when given a max length in the options' do
236
+ context 'with a non-default field length' do
85
237
  should 'truncate long strings with ellipses' do
86
- assert_equal "1234567...", TablePrint::Column.new([], "", :max_field_length => 10)._truncate("1234567890123456789012345678901234567890")
238
+ tp = TablePrint::ColumnHelper.new([], "")
239
+ tp.field_length = 10
240
+ assert_equal "1234567...", tp._truncate("1234567890123456789012345678901234567890")
87
241
  end
88
242
  end
89
243
 
90
244
  context 'when the max length is tiny' do
91
245
  should 'truncate long strings without ellipses' do
92
- assert_equal "1", TablePrint::Column.new([], "", :max_field_length => -10)._truncate("1234567890123456789012345678901234567890")
93
- assert_equal "1", TablePrint::Column.new([], "", :max_field_length => 0)._truncate("1234567890123456789012345678901234567890")
94
- assert_equal "1", TablePrint::Column.new([], "", :max_field_length => 1)._truncate("1234567890123456789012345678901234567890")
95
- assert_equal "12", TablePrint::Column.new([], "", :max_field_length => 2)._truncate("1234567890123456789012345678901234567890")
96
- assert_equal "123", TablePrint::Column.new([], "", :max_field_length => 3)._truncate("1234567890123456789012345678901234567890")
97
- assert_equal "1...", TablePrint::Column.new([], "", :max_field_length => 4)._truncate("1234567890123456789012345678901234567890")
246
+ assert_equal "123456789012345678901234567...", TablePrint::ColumnHelper.new(["1234567890123456789012345678901234567890"], "first", :field_length => -10)._truncate("1234567890123456789012345678901234567890")
247
+ assert_equal "123456789012345678901234567...", TablePrint::ColumnHelper.new(["1234567890123456789012345678901234567890"], "first", :field_length => 0)._truncate("1234567890123456789012345678901234567890")
248
+ assert_equal "1", TablePrint::ColumnHelper.new([], "", :field_length => 1)._truncate("1234567890123456789012345678901234567890")
249
+ assert_equal "12", TablePrint::ColumnHelper.new([], "", :field_length => 2)._truncate("1234567890123456789012345678901234567890")
250
+ assert_equal "123", TablePrint::ColumnHelper.new([], "", :field_length => 3)._truncate("1234567890123456789012345678901234567890")
251
+ assert_equal "1...", TablePrint::ColumnHelper.new([], "", :field_length => 4)._truncate("1234567890123456789012345678901234567890")
98
252
  end
99
253
  end
100
254
  end
101
255
 
256
+ # def initialize_field_length(data)
102
257
  context 'The field length function' do
258
+ should 'honor the field_length options' do
259
+ assert_equal 5, TablePrint::ColumnHelper.new(["hello there madam"], "to_s", :field_length => 5).field_length
260
+ assert_equal 30, TablePrint::ColumnHelper.new(["hello there madam"], "to_s", :field_length => 5).max_field_length
261
+ end
262
+
263
+ should 'honor the max_length option' do
264
+ assert_equal 5, TablePrint::ColumnHelper.new(["hello there madam"], "to_s", :max_field_length => 5).field_length
265
+ assert_equal 5, TablePrint::ColumnHelper.new(["hello there madam"], "to_s", :max_field_length => 5).max_field_length
266
+ end
267
+
268
+ should 'honor the max_length option over the field_length option' do
269
+ assert_equal 5, TablePrint::ColumnHelper.new(["hello there madam"], "to_s", :max_field_length => 5, :field_length => 10).field_length
270
+ assert_equal 5, TablePrint::ColumnHelper.new(["hello there madam"], "to_s", :max_field_length => 5, :field_length => 10).max_field_length
271
+ end
272
+
103
273
  should 'find the maximum width of the data' do
104
- assert_equal 11, TablePrint::Column.new(["hello there"], "to_s").field_length
274
+ assert_equal 11, TablePrint::ColumnHelper.new(["hello there"], "to_s").field_length
105
275
  end
106
276
 
107
277
  context 'when the data is longer than the max_field_length' do
108
278
  should 'equal the max field length' do
109
- assert_equal 5, TablePrint::Column.new(["hello there"], "to_s", :max_field_length => 5).field_length
279
+ assert_equal 5, TablePrint::ColumnHelper.new(["hello there"], "to_s", :max_field_length => 5).field_length
110
280
  end
111
281
  end
112
282
 
113
283
  context 'when the column name is longer than the data' do
114
284
  should 'reflect the column name length' do
115
- assert_equal 4, TablePrint::Column.new(["he"], "to_s", :max_field_length => 5).field_length
116
- assert_equal 12, TablePrint::Column.new(["hello"], "to_s", :name => "foobar THIS!").field_length
285
+ assert_equal 4, TablePrint::ColumnHelper.new(["he"], "to_s", :max_field_length => 5).field_length
286
+ assert_equal 12, TablePrint::ColumnHelper.new(["hello"], "to_s", :name => "foobar THIS!").field_length
117
287
  end
118
288
  end
119
289
 
120
- context 'when the column is boolean and the data is the limiting factor' do
121
- should 'always be 5' do
122
- assert_equal 5, TablePrint::Column.new([[true]], "first", :name => "dur").field_length
123
- assert_equal 5, TablePrint::Column.new([[false]], "first", :name => "dur").field_length
290
+ context 'when the method is recursive' do
291
+ should 'find the maximum width of the data' do
292
+ assert_equal 30, TablePrint::ColumnHelper.new(MyNestedClass.setup, "captions.photo_url", :max_field_length => 50).field_length
124
293
  end
125
294
  end
295
+ end
126
296
 
127
- context 'when the column is boolean and the data is not the limiting factor' do
128
- should 'be the column name length' do
129
- assert_equal 7, TablePrint::Column.new([[true]], "unshift").field_length
130
- assert_equal 8, TablePrint::Column.new([[false]], "unshift", :name => "durables").field_length
297
+ # def find_data_length(data, method, start)
298
+ context 'The find_data_length method' do
299
+ context 'when method_chain is a top level method' do
300
+ setup do
301
+ @tp = TablePrint::ColumnHelper.new([], "")
302
+ @tp._find_data_length(MyNestedClass.setup, "title", Time.now)
303
+ end
304
+ should 'set field_length to the longest value in the data set' do
305
+ assert_equal 16, @tp.field_length
306
+ end
307
+ end
308
+
309
+ context 'when method_chain is not a top level method' do
310
+ setup do
311
+ @tp = TablePrint::ColumnHelper.new([], "")
312
+ @tp._find_data_length(MyNestedClass.setup, "captions.text", Time.now)
313
+ end
314
+ should 'set field_length to the longest value in the data set' do
315
+ assert_equal 28, @tp.field_length
316
+ end
317
+ end
318
+
319
+ context 'when the data value is longer than max_field_length' do
320
+ setup do
321
+ @tp = TablePrint::ColumnHelper.new([], "", :max_field_length => 10)
322
+ @tp._find_data_length(MyNestedClass.setup, "captions.photo_url", Time.now)
323
+ end
324
+ should 'ignore max_field_length (initialize_field_length handles that - this method is just about the data)' do
325
+ assert_equal 26, @tp.field_length
131
326
  end
132
327
  end
133
328
  end
134
329
 
330
+ # def get_current_method(method_chain)
331
+ context 'get_current_method' do
332
+ context 'with a simple method signature' do
333
+ context 'and no method chain' do
334
+ should 'return the method itself' do
335
+ assert_equal "m1", TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1")._get_current_method("")
336
+ end
337
+ end
338
+ context 'and a method chain that does not match' do
339
+ should 'return nil' do
340
+ assert_equal nil, TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1")._get_current_method("m2")
341
+ end
342
+ end
343
+ context 'and an overly long method chain' do
344
+ should 'return nil' do
345
+ assert_equal nil, TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1")._get_current_method("m1.m2")
346
+ end
347
+ end
348
+ end
349
+
350
+ context 'with a compound method signature' do
351
+ context 'and no method chain' do
352
+ should 'return the first method in the chain' do
353
+ assert_equal "m1", TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1.m2.m3")._get_current_method("")
354
+ end
355
+ end
356
+ context 'and a method chain that does not match' do
357
+ should 'return nil' do
358
+ assert_equal nil, TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1.m2.m3")._get_current_method("m2")
359
+ end
360
+ end
361
+ context 'and a valid method chain' do
362
+ should 'return the next method in the chain' do
363
+ assert_equal "m2", TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1.m2.m3")._get_current_method("m1")
364
+ assert_equal "m3", TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1.m2.m3")._get_current_method("m1.m2")
365
+ end
366
+ end
367
+ context 'and an overly long method chain' do
368
+ should 'return nil' do
369
+ assert_equal nil, TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1.m2.m3")._get_current_method("m1.m2.m3.m4")
370
+ end
371
+ end
372
+ context 'and a method chain matching our method signature' do
373
+ should 'return nil' do
374
+ assert_equal nil, TablePrint::ColumnHelper.new(MyNestedClass.setup, "m1.m2.m3")._get_current_method("m1.m2.m3")
375
+ end
376
+ end
377
+ end
378
+ end
135
379
  end
@@ -28,6 +28,7 @@ class MyClass
28
28
  end
29
29
  end
30
30
 
31
+
31
32
  class TablePrint
32
33
  def _get_display_methods(data_obj, options)
33
34
  get_display_methods(data_obj, options)
@@ -37,8 +38,8 @@ class TablePrint
37
38
  get_default_display_methods(data_obj)
38
39
  end
39
40
 
40
- def _clean_display_methods(data_obj, display_methods)
41
- clean_display_methods(data_obj, display_methods)
41
+ def _sort_display_methods(display_methods)
42
+ sort_display_methods(display_methods)
42
43
  end
43
44
  end
44
45
 
@@ -93,19 +94,6 @@ class TestTablePrint < Test::Unit::TestCase
93
94
  end
94
95
  end
95
96
 
96
- context 'The clean_display_methods function' do
97
- should 'only give back valid methods' do
98
- assert_equal [], @tp._clean_display_methods(ManyMethods.new, [""])
99
- assert_equal [], @tp._clean_display_methods(ManyMethods.new, [nil])
100
- assert_equal ["title"], @tp._clean_display_methods(ManyMethods.new, ["title"])
101
- assert_equal ["title"], @tp._clean_display_methods(ManyMethods.new, [:title])
102
- assert_equal ["title"], @tp._clean_display_methods(ManyMethods.new, ["title", "title"])
103
- assert_equal ["title"], @tp._clean_display_methods(ManyMethods.new, ["title", :title])
104
- assert_equal ["title"], @tp._clean_display_methods(ManyMethods.new, [:title, :title])
105
- assert_equal ["title"], @tp._clean_display_methods(ManyMethods.new, ["title", "january1985"])
106
- end
107
- end
108
-
109
97
  context 'The default display methods for a custom class with one method' do
110
98
  should 'be that method' do
111
99
  assert_equal ["title"], @tp._get_default_display_methods(OneMethod.new)
@@ -171,3 +159,4 @@ class TestTablePrint < Test::Unit::TestCase
171
159
  end
172
160
  end
173
161
  end
162
+
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: 31
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
8
  - 2
10
- version: 0.1.2
9
+ - 0
10
+ version: 0.2.0
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-17 00:00:00 -07:00
18
+ date: 2011-04-26 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency