table_print 0.2.3 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ Before do
2
+ Sandbox.cleanup!
3
+ end
@@ -0,0 +1,77 @@
1
+ require 'cat'
2
+ require 'ostruct'
3
+ require 'table_print'
4
+
5
+ Given /^a class named (.*)$/ do |klass|
6
+ Sandbox.add_class(klass)
7
+ end
8
+
9
+ Given /^(.*) has attributes (.*)$/ do |klass, attributes|
10
+ attrs = attributes.split(",").map { |attr| attr.strip }
11
+
12
+ Sandbox.add_attributes(klass, *attrs)
13
+ end
14
+
15
+ Given /^(.*) has a class method named (.*) with (.*)$/ do |klass, method_name, blk|
16
+ Sandbox.add_class_method(klass, method_name, &eval(blk))
17
+ end
18
+
19
+ Given /^(.*) has a method named (\w*) with (.*)$/ do |klass, method_name, blk|
20
+ Sandbox.add_method(klass, method_name, &eval(blk))
21
+ end
22
+
23
+ When /^I instantiate a (.*) with (\{.*\})$/ do |klass, args|
24
+ @objs ||= OpenStruct.new
25
+ @objs.send("#{klass.downcase}=", Sandbox.const_get_from_string(klass).new(eval(args)))
26
+ end
27
+
28
+ When /^I instantiate a (.*) with (\{.*\}) and (add it|assign it) to (.*)$/ do |klass, args, assignment_method, target|
29
+ # the thing we're instantiating
30
+ child = Sandbox.const_get_from_string(klass).new(eval(args))
31
+
32
+ # the place we're going to add it
33
+ method_chain = target.split(".")
34
+ target_method = method_chain.pop
35
+ target_object = method_chain.inject(@objs) { |obj, method_name| obj.send(method_name) }
36
+
37
+ # how we're going to add it
38
+ if assignment_method == "assign it"
39
+ target_object.send("#{target_method}=", child)
40
+ else
41
+ target_object.send("#{target_method}") << child
42
+ end
43
+ end
44
+
45
+ When /^configure (.*) with (.*)$/ do |klass, config|
46
+ klass = Sandbox.const_get_from_string(klass)
47
+ TablePrint::Config.set(klass, eval(config))
48
+ end
49
+
50
+ When /table_print ([\w:]*), (.*)$/ do |klass, options|
51
+ tp(@objs.send(klass.downcase), eval(options))
52
+ end
53
+
54
+ When /table_print ([\w\.:]*)$/ do |klass|
55
+ obj = @objs.send(klass.split(".").first.downcase)
56
+ obj = obj.send(klass.split(".").last) if klass.include? "." # hack - we're assuming only two levels. use inject to find the target.
57
+
58
+ tp(obj)
59
+ end
60
+
61
+ Then /^the output should contain$/ do |string|
62
+ output = []
63
+ while line = @r.gets
64
+ output << line
65
+ end
66
+ @r.close
67
+
68
+ output.zip(string.split("\n")).each do |actual, expected|
69
+ actual.gsub(/\s/m, "").split(//).sort.join.should == expected.gsub(" ", "").split(//).sort.join
70
+ end
71
+ end
72
+
73
+ def tp(data, options={})
74
+ @r, w = IO.pipe
75
+ w.puts TablePrint::Printer.table_print(data, options)
76
+ w.close
77
+ end
data/lib/cattr.rb ADDED
@@ -0,0 +1,46 @@
1
+ class Class
2
+ def cattr_reader(*syms)
3
+ syms.flatten.each do |sym|
4
+ next if sym.is_a?(Hash)
5
+ class_eval(<<-EOS, __FILE__, __LINE__)
6
+ unless defined? @@#{sym}
7
+ @@#{sym} = nil
8
+ end
9
+
10
+ def self.#{sym}
11
+ @@#{sym}
12
+ end
13
+
14
+ def #{sym}
15
+ @@#{sym}
16
+ end
17
+ EOS
18
+ end
19
+ end
20
+
21
+ def cattr_writer(*syms)
22
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
23
+ syms.flatten.each do |sym|
24
+ class_eval(<<-EOS, __FILE__, __LINE__)
25
+ unless defined? @@#{sym}
26
+ @@#{sym} = nil
27
+ end
28
+
29
+ def self.#{sym}=(obj)
30
+ @@#{sym} = obj
31
+ end
32
+
33
+ #{"
34
+ def #{sym}=(obj)
35
+ @@#{sym} = obj
36
+ end
37
+ " unless options[:instance_writer] == false }
38
+ EOS
39
+ end
40
+ end
41
+
42
+ def cattr_accessor(*syms)
43
+ cattr_reader(*syms)
44
+ cattr_writer(*syms)
45
+ end
46
+ end
data/lib/column.rb ADDED
@@ -0,0 +1,45 @@
1
+ module TablePrint
2
+ class Column
3
+ attr_reader :formatters
4
+ attr_writer :width
5
+ attr_accessor :name, :data, :time_format
6
+
7
+ def initialize(attr_hash={})
8
+ @formatters = []
9
+ attr_hash.each do |k, v|
10
+ self.send("#{k}=", v)
11
+ end
12
+ end
13
+
14
+ def name=(n)
15
+ @name = n.to_s
16
+ end
17
+
18
+ def formatters=(formatter_list)
19
+ formatter_list.each do |f|
20
+ add_formatter(f)
21
+ end
22
+ end
23
+
24
+ def display_method=(method)
25
+ method = method.to_s unless method.is_a? Proc
26
+ @display_method = method
27
+ end
28
+
29
+ def display_method
30
+ @display_method ||= name
31
+ end
32
+
33
+ def add_formatter(formatter)
34
+ @formatters << formatter
35
+ end
36
+
37
+ def data_width
38
+ [name.length].concat(data.compact.collect(&:to_s).collect(&:length)).max
39
+ end
40
+
41
+ def width
42
+ @width ||= data_width
43
+ end
44
+ end
45
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'cattr'
2
+
3
+ module TablePrint
4
+ class Config
5
+ cattr_accessor :max_width, :time_format
6
+
7
+ DEFAULT_MAX_WIDTH = 30
8
+ DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
9
+
10
+ @@max_width = DEFAULT_MAX_WIDTH
11
+ @@time_format = DEFAULT_TIME_FORMAT
12
+
13
+ @@klasses = {}
14
+
15
+ def self.set(klass, val)
16
+ if klass.is_a? Class
17
+ @@klasses[klass] = val # val is a hash of column options
18
+ else
19
+ TablePrint::Config.send("#{klass}=", val.first)
20
+ end
21
+ end
22
+
23
+ def self.for(klass)
24
+ @@klasses.fetch(klass) {}
25
+ end
26
+
27
+ def self.clear(klass)
28
+ if klass.is_a? Class
29
+ @@klasses.delete(klass)
30
+ else
31
+ original_value = TablePrint::Config.const_get("DEFAULT_#{klass.to_s.upcase}")
32
+ TablePrint::Config.send("#{klass}=", original_value)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,91 @@
1
+ require 'column'
2
+ require 'config'
3
+
4
+ module TablePrint
5
+ class ConfigResolver
6
+ def initialize(klass, default_column_names, *options)
7
+ @column_hash = {}
8
+
9
+ @default_columns = default_column_names.collect { |name| option_to_column(name) }
10
+
11
+ @included_columns = []
12
+ @excepted_columns = []
13
+ @only_columns = []
14
+
15
+ process_option_set(TablePrint::Config.for(klass))
16
+ process_option_set(options)
17
+ end
18
+
19
+ def process_option_set(options)
20
+
21
+ options = [options].flatten
22
+ options.delete_if { |o| o == {} }
23
+
24
+ # process special symbols
25
+
26
+ @included_columns.concat [get_and_remove(options, :include)].flatten
27
+ @included_columns.map! do |option|
28
+ if option.is_a? Column
29
+ option if option.is_a? Column
30
+ else
31
+ option_to_column(option)
32
+ end
33
+ end
34
+
35
+ @included_columns.each do |c|
36
+ @column_hash[c.name] = c
37
+ end
38
+
39
+ # excepted columns don't need column objects since we're just going to throw them out anyway
40
+ @excepted_columns.concat [get_and_remove(options, :except)].flatten
41
+
42
+ # anything that isn't recognized as a special option is assumed to be a column name
43
+ options.compact!
44
+ @only_columns = options.collect { |name| option_to_column(name) } unless options.empty?
45
+ end
46
+
47
+ def get_and_remove(options_array, key)
48
+ except = options_array.select do |option|
49
+ option.is_a? Hash and option.keys.include? key
50
+ end
51
+
52
+ return [] if except.empty?
53
+ except = except.first
54
+
55
+ option_of_interest = except.fetch(key)
56
+ except.delete(key)
57
+
58
+ options_array.delete(except) if except.keys.empty? # if we've taken all the info from this option, get rid of it
59
+
60
+ option_of_interest
61
+ end
62
+
63
+ def option_to_column(option)
64
+ if option.is_a? Hash
65
+ name = option.keys.first
66
+ if option[name].is_a? Proc
67
+ option = {:name => name, :display_method => option[name]}
68
+ else
69
+ option = option[name].merge(:name => name)
70
+ end
71
+ else
72
+ option = {:name => option}
73
+ end
74
+ c = Column.new(option)
75
+ @column_hash[c.name] = c
76
+ c
77
+ end
78
+
79
+ def usable_column_names
80
+ base = @default_columns
81
+ base = @only_columns unless @only_columns.empty?
82
+ Array(base).collect(&:name) + Array(@included_columns).collect(&:name) - Array(@excepted_columns).collect(&:to_s)
83
+ end
84
+
85
+ def columns
86
+ usable_column_names.collect do |name|
87
+ @column_hash[name]
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,85 @@
1
+ require 'row_group'
2
+ require 'hash_extensions'
3
+
4
+ module TablePrint
5
+ class Fingerprinter
6
+ def lift(columns, object)
7
+ @column_names_by_display_method = {}
8
+ columns.each { |c| @column_names_by_display_method[c.display_method] = c.name }
9
+
10
+ column_hash = display_methods_to_nested_hash(columns.collect(&:display_method))
11
+
12
+ hash_to_rows("", column_hash, object)
13
+ end
14
+
15
+ def hash_to_rows(prefix, hash, objects)
16
+ rows = []
17
+
18
+ # convert each object into its own row
19
+ Array(objects).each do |target|
20
+ row = populate_row(prefix, hash, target)
21
+ rows << row
22
+
23
+ # make a group and recurse for the columns we don't handle
24
+ groups = create_child_group(prefix, hash, target)
25
+ row.add_children(groups) unless groups.all? {|g| g.children.empty?}
26
+ end
27
+
28
+ rows
29
+ end
30
+
31
+ def populate_row(prefix, hash, target)
32
+ row = TablePrint::Row.new()
33
+
34
+ # populate a row with the columns we handle
35
+ cells = {}
36
+ handleable_columns(hash).each do |method|
37
+ display_method = (prefix == "" ? method : "#{prefix}.#{method}")
38
+ cell_value = method.call(target) if method.is_a? Proc
39
+ cell_value ||= target.send(method)
40
+ cells[@column_names_by_display_method[display_method]] = cell_value
41
+ end
42
+
43
+ row.set_cell_values(cells)
44
+ end
45
+
46
+ def create_child_group(prefix, hash, target)
47
+ passable_columns(hash).collect do |name|
48
+ recursing_prefix = "#{prefix}#{'.' unless prefix == ''}#{name}"
49
+ group = RowGroup.new
50
+ group.add_children hash_to_rows(recursing_prefix, hash[name], target.send(name))
51
+ group
52
+ end
53
+ end
54
+
55
+ def handleable_columns(hash)
56
+ # get the keys where the value is an empty hash
57
+ hash.select { |k, v| v == {} }.collect { |k, v| k }
58
+ end
59
+
60
+ def passable_columns(hash)
61
+ # get the keys where the value is not an empty hash
62
+ hash.select { |k, v| v != {} }.collect { |k, v| k }
63
+ end
64
+
65
+ def display_methods_to_nested_hash(display_methods)
66
+ extended_hash = {}.extend TablePrint::HashExtensions::ConstructiveMerge
67
+
68
+ # turn each column chain into a nested hash and add it to the output
69
+ display_methods.inject(extended_hash) do |hash, display_method|
70
+ hash.constructive_merge!(display_method_to_nested_hash(display_method))
71
+ end
72
+ end
73
+
74
+ def display_method_to_nested_hash(display_method)
75
+ hash = {}
76
+
77
+ return {display_method => {}} if display_method.is_a? Proc
78
+
79
+ display_method.split(".").inject(hash) do |hash_level, method|
80
+ hash_level[method] ||= {}
81
+ end
82
+ hash
83
+ end
84
+ end
85
+ end
data/lib/formatter.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'config'
2
+
3
+ module TablePrint
4
+ class TimeFormatter
5
+ def initialize(time_format=nil)
6
+ @format = time_format
7
+ @format ||= TablePrint::Config.time_format
8
+ end
9
+
10
+ def format(value)
11
+ return value unless value.is_a? Time
12
+ value.strftime @format
13
+ end
14
+ end
15
+
16
+ class NoNewlineFormatter
17
+ def format(value)
18
+ value.to_s.gsub(/\r\n/, "\n").gsub(/\n/, " ")
19
+ end
20
+ end
21
+
22
+ class FixedWidthFormatter
23
+ def initialize(width)
24
+ @width = width
25
+ end
26
+
27
+ def format(value)
28
+ "%-#{width}s" % truncate(value)
29
+ end
30
+
31
+ def width
32
+ [@width, TablePrint::Config.max_width].min
33
+ end
34
+
35
+ private
36
+ def truncate(value)
37
+ return "" unless value
38
+
39
+ value = value.to_s
40
+ return value unless value.length > width
41
+
42
+ "#{value[0..width-4]}..."
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ module TablePrint
2
+ module HashExtensions
3
+ module ConstructiveMerge
4
+ def constructive_merge(hash)
5
+ target = dup
6
+
7
+ hash.keys.each do |key|
8
+ if hash[key].is_a? Hash and self[key].is_a? Hash
9
+ target[key].extend ConstructiveMerge
10
+ target[key] = target[key].constructive_merge(hash[key])
11
+ next
12
+ end
13
+
14
+ target[key] = hash[key]
15
+ end
16
+
17
+ target
18
+ end
19
+
20
+ def constructive_merge!(hash)
21
+ target = self
22
+
23
+ hash.keys.each do |key|
24
+ if hash[key].is_a? Hash and self[key].is_a? Hash
25
+ target[key].extend ConstructiveMerge
26
+ target[key] = target[key].constructive_merge(hash[key])
27
+ next
28
+ end
29
+
30
+ target[key] = hash[key]
31
+ end
32
+
33
+ target
34
+ end
35
+ end
36
+ end
37
+ end