table_print 0.2.3 → 1.0.0.rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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