tabular 0.0.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +34 -0
- data/Rakefile +1 -3
- data/VERSION +1 -1
- data/lib/tabular.rb +8 -4
- data/lib/tabular/column.rb +35 -8
- data/lib/tabular/columns.rb +49 -19
- data/lib/tabular/keys.rb +14 -0
- data/lib/tabular/renderer.rb +11 -0
- data/lib/tabular/row.rb +53 -20
- data/lib/tabular/support/zero.rb +29 -0
- data/lib/tabular/table.rb +94 -24
- data/tabular.gemspec +46 -41
- data/test/column_test.rb +59 -18
- data/test/columns_test.rb +39 -14
- data/test/row_test.rb +72 -13
- data/test/table_test.rb +140 -6
- data/test/zero_test.rb +21 -0
- metadata +82 -47
- data/.gitignore +0 -2
data/lib/tabular/table.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
module Tabular
|
2
2
|
# Simple Enumerable list of Hashes. Use Table.read(file_path) to read file.
|
3
3
|
class Table
|
4
|
-
|
5
|
-
|
4
|
+
include Tabular::Keys
|
5
|
+
|
6
|
+
attr_reader :options, :rows
|
7
|
+
attr_accessor :row_mapper
|
8
|
+
|
6
9
|
# +file+ : file path as String or File
|
7
10
|
# Assumes .txt = tab-delimited, .csv = CSV, .xls = Excel. Assumes first row is the header.
|
8
11
|
# Normalizes column names to lower-case with underscores.
|
@@ -13,16 +16,16 @@ module Tabular
|
|
13
16
|
else
|
14
17
|
file
|
15
18
|
end
|
16
|
-
|
19
|
+
|
17
20
|
raise "Could not find '#{file_path}'" unless File.exists?(file_path)
|
18
21
|
options = extract_options(options)
|
19
|
-
|
22
|
+
|
20
23
|
format = self.format_from(options.delete(:as), file_path)
|
21
24
|
data = read_file(file_path, format)
|
22
|
-
|
25
|
+
|
23
26
|
Table.new data, options
|
24
27
|
end
|
25
|
-
|
28
|
+
|
26
29
|
# +format+ : :csv, :txt, or :xls
|
27
30
|
# Returns Array of Arrays
|
28
31
|
def self.read_file(file_path, format)
|
@@ -54,49 +57,116 @@ module Tabular
|
|
54
57
|
raise "Cannot read '#{format}' format. Expected :xls, :xlsx, :txt, or :csv"
|
55
58
|
end
|
56
59
|
end
|
57
|
-
|
58
|
-
# Pass data in as +rows+. Expects rows to be an Enumerable of Enumerables.
|
60
|
+
|
61
|
+
# Pass data in as +rows+. Expects rows to be an Enumerable of Enumerables.
|
59
62
|
# Maps rows to Hash-like Tabular::Rows.
|
60
63
|
#
|
61
64
|
# Options:
|
62
65
|
# :columns => { :original_name => :preferred_name, :column_name => { :column_type => :boolean } }
|
63
66
|
def initialize(rows = [], *options)
|
64
|
-
options = Table.extract_options(options)
|
65
|
-
|
67
|
+
@options = Table.extract_options(options)
|
68
|
+
self.rows = rows
|
69
|
+
end
|
70
|
+
|
71
|
+
def rows
|
72
|
+
@rows ||= []
|
73
|
+
end
|
74
|
+
|
75
|
+
def rows=(source_rows = [])
|
76
|
+
return [] unless source_rows
|
66
77
|
|
67
|
-
|
68
|
-
|
69
|
-
self << row
|
70
|
-
else
|
71
|
-
@columns = Tabular::Columns.new(row, options[:columns])
|
72
|
-
end
|
78
|
+
source_rows.each do |row|
|
79
|
+
self.<< row
|
73
80
|
end
|
81
|
+
|
82
|
+
rows
|
74
83
|
end
|
75
|
-
|
84
|
+
|
76
85
|
# Return Row at zero-based index, or nil if Row is out of bounds
|
77
86
|
def [](index)
|
78
87
|
rows[index]
|
79
88
|
end
|
80
|
-
|
89
|
+
|
81
90
|
def <<(row)
|
82
|
-
|
91
|
+
if row_mapper
|
92
|
+
cells = row_mapper.map(row)
|
93
|
+
else
|
94
|
+
cells = row
|
95
|
+
end
|
96
|
+
|
97
|
+
if @columns.nil? && !cells.respond_to?(:keys)
|
98
|
+
@columns = Tabular::Columns.new(self, cells, options[:columns])
|
99
|
+
return columns
|
100
|
+
end
|
101
|
+
|
102
|
+
_row = Tabular::Row.new(self, cells, row)
|
103
|
+
_row.keys.each do |key|
|
104
|
+
columns << key
|
105
|
+
end
|
106
|
+
rows << _row
|
107
|
+
_row
|
83
108
|
end
|
84
|
-
|
109
|
+
|
85
110
|
def inspect
|
86
111
|
rows.map { |row| row.join(",") }.join("\n")
|
87
112
|
end
|
88
|
-
|
113
|
+
|
89
114
|
def columns
|
90
|
-
@columns
|
115
|
+
@columns ||= Tabular::Columns.new(self, [])
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete_blank_columns!
|
119
|
+
columns.map(&:key).each do |key|
|
120
|
+
if rows.all? { |row| row[key].blank? || row[key].zero? }
|
121
|
+
delete_column key
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def delete_homogenous_columns!
|
127
|
+
return if rows.size < 2
|
128
|
+
|
129
|
+
columns.map(&:key).each do |key|
|
130
|
+
value = rows.first[key]
|
131
|
+
if rows.all? { |row| row[key] == value }
|
132
|
+
delete_column key
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def strip!
|
138
|
+
rows.each do |row|
|
139
|
+
columns.each do |column|
|
140
|
+
value = row[column.key]
|
141
|
+
if value.respond_to?(:strip)
|
142
|
+
row[column.key] = value.strip
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
91
146
|
end
|
92
147
|
|
148
|
+
def delete_column(key)
|
149
|
+
rows.each do |row|
|
150
|
+
row.delete key
|
151
|
+
end
|
152
|
+
columns.delete key
|
153
|
+
end
|
154
|
+
|
155
|
+
def renderer=(value)
|
156
|
+
columns.renderer = value
|
157
|
+
end
|
158
|
+
|
159
|
+
def renderers
|
160
|
+
columns.renderers
|
161
|
+
end
|
162
|
+
|
93
163
|
def to_s
|
94
164
|
"#<#{self.class} #{rows.size}>"
|
95
165
|
end
|
96
166
|
|
97
167
|
|
98
168
|
private
|
99
|
-
|
169
|
+
|
100
170
|
def self.extract_options(options)
|
101
171
|
if options
|
102
172
|
options.flatten.first || {}
|
@@ -104,7 +174,7 @@ module Tabular
|
|
104
174
|
{}
|
105
175
|
end
|
106
176
|
end
|
107
|
-
|
177
|
+
|
108
178
|
def self.format_from(as_option, file_path)
|
109
179
|
if as_option.present?
|
110
180
|
as_option
|
data/tabular.gemspec
CHANGED
@@ -1,66 +1,71 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.0
|
7
|
+
s.name = "tabular"
|
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 = ["Scott Willson"]
|
12
|
-
s.date =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
12
|
+
s.date = "2013-05-05"
|
13
|
+
s.description = "Tabular is a Ruby library for reading, writing, and manipulating CSV, tab-delimited and Excel data."
|
14
|
+
s.email = "scott.willson@gmail.cpm"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
17
|
-
|
17
|
+
"README"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
|
-
"
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
"test/fixtures/quoted.txt",
|
37
|
-
"test/fixtures/sample.csv",
|
38
|
-
"test/fixtures/sample.lif",
|
39
|
-
"test/helper.rb",
|
40
|
-
"test/row_test.rb",
|
41
|
-
"test/table_test.rb"
|
42
|
-
]
|
43
|
-
s.homepage = %q{http://github.com/scottwillson/tabular}
|
44
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
45
|
-
s.require_paths = ["lib"]
|
46
|
-
s.rubygems_version = %q{1.3.7}
|
47
|
-
s.summary = %q{Read, write, and manipulate CSV, tab-delimited and Excel data}
|
48
|
-
s.test_files = [
|
20
|
+
"Gemfile",
|
21
|
+
"Gemfile.lock",
|
22
|
+
"LICENSE",
|
23
|
+
"README",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/tabular.rb",
|
27
|
+
"lib/tabular/column.rb",
|
28
|
+
"lib/tabular/columns.rb",
|
29
|
+
"lib/tabular/keys.rb",
|
30
|
+
"lib/tabular/renderer.rb",
|
31
|
+
"lib/tabular/row.rb",
|
32
|
+
"lib/tabular/support/object.rb",
|
33
|
+
"lib/tabular/support/zero.rb",
|
34
|
+
"lib/tabular/table.rb",
|
35
|
+
"tabular.gemspec",
|
49
36
|
"test/column_test.rb",
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
37
|
+
"test/columns_test.rb",
|
38
|
+
"test/fixtures/blank.txt",
|
39
|
+
"test/fixtures/excel.xls",
|
40
|
+
"test/fixtures/quoted.txt",
|
41
|
+
"test/fixtures/sample.csv",
|
42
|
+
"test/fixtures/sample.lif",
|
43
|
+
"test/helper.rb",
|
44
|
+
"test/row_test.rb",
|
45
|
+
"test/table_test.rb",
|
46
|
+
"test/zero_test.rb"
|
54
47
|
]
|
48
|
+
s.homepage = "http://github.com/scottwillson/tabular"
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = "1.8.25"
|
51
|
+
s.summary = "Read, write, and manipulate CSV, tab-delimited and Excel data"
|
55
52
|
|
56
53
|
if s.respond_to? :specification_version then
|
57
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
58
54
|
s.specification_version = 3
|
59
55
|
|
60
56
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
+
s.add_runtime_dependency(%q<ruby-ole>, [">= 0"])
|
58
|
+
s.add_runtime_dependency(%q<spreadsheet>, [">= 0"])
|
59
|
+
s.add_runtime_dependency(%q<jeweler>, [">= 0"])
|
61
60
|
else
|
61
|
+
s.add_dependency(%q<ruby-ole>, [">= 0"])
|
62
|
+
s.add_dependency(%q<spreadsheet>, [">= 0"])
|
63
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
62
64
|
end
|
63
65
|
else
|
66
|
+
s.add_dependency(%q<ruby-ole>, [">= 0"])
|
67
|
+
s.add_dependency(%q<spreadsheet>, [">= 0"])
|
68
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
64
69
|
end
|
65
70
|
end
|
66
71
|
|
data/test/column_test.rb
CHANGED
@@ -3,37 +3,78 @@ require "helper"
|
|
3
3
|
module Tabular
|
4
4
|
class ColumnTest < Test::Unit::TestCase
|
5
5
|
def test_new_nil
|
6
|
-
column = Column.new
|
7
|
-
assert_equal
|
6
|
+
column = Column.new(nil, nil)
|
7
|
+
assert_equal "", column.to_s, "blank column to_s"
|
8
8
|
assert_equal nil, column.key, "blank column key"
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def test_new
|
12
|
-
assert_equal :date, Column.new("date").key, "column key"
|
13
|
-
assert_equal :date, Column.new(:date).key, "column key"
|
14
|
-
assert_equal :date, Column.new("Date").key, "column key"
|
15
|
-
assert_equal :date, Column.new(" Date ").key, "column key"
|
16
|
-
assert_equal :date, Column.new("DATE").key, "column key"
|
17
|
-
assert_equal :start_date, Column.new("StartDate").key, "column key"
|
18
|
-
assert_equal :start_date, Column.new("Start Date").key, "column key"
|
19
|
-
end
|
20
|
-
|
12
|
+
assert_equal :date, Column.new(nil, nil, "date").key, "column key"
|
13
|
+
assert_equal :date, Column.new(nil, nil, :date).key, "column key"
|
14
|
+
assert_equal :date, Column.new(nil, nil, "Date").key, "column key"
|
15
|
+
assert_equal :date, Column.new(nil, nil, " Date ").key, "column key"
|
16
|
+
assert_equal :date, Column.new(nil, nil, "DATE").key, "column key"
|
17
|
+
assert_equal :start_date, Column.new(nil, nil, "StartDate").key, "column key"
|
18
|
+
assert_equal :start_date, Column.new(nil, nil, "Start Date").key, "column key"
|
19
|
+
end
|
20
|
+
|
21
21
|
def test_mapping
|
22
|
-
assert_equal :city, Column.new(:location, :location => :city).key, "column key"
|
22
|
+
assert_equal :city, Column.new(nil, nil, :location, :location => :city).key, "column key"
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def test_type
|
26
|
-
column = Column.new("name")
|
26
|
+
column = Column.new(nil, nil, "name")
|
27
27
|
assert_equal :name, column.key, "key"
|
28
28
|
assert_equal :string, column.column_type, "column_type"
|
29
|
-
|
30
|
-
column = Column.new("date")
|
29
|
+
|
30
|
+
column = Column.new(nil, nil, "date")
|
31
31
|
assert_equal :date, column.key, "key"
|
32
32
|
assert_equal :date, column.column_type, "column_type"
|
33
33
|
|
34
|
-
column = Column.new("phone", :phone => { :column_type => :integer })
|
34
|
+
column = Column.new(nil, nil, "phone", :phone => { :column_type => :integer })
|
35
35
|
assert_equal :phone, column.key, "key"
|
36
36
|
assert_equal :integer, column.column_type, "column_type"
|
37
37
|
end
|
38
|
+
|
39
|
+
def test_cells
|
40
|
+
data = [
|
41
|
+
{ :place => "1", :name => "Bernard Hinault" },
|
42
|
+
{ :place => "2", :name => "Greg Lemond" }
|
43
|
+
]
|
44
|
+
table = Table.new(data)
|
45
|
+
column = table.columns[:place]
|
46
|
+
assert_equal [ "1", "2" ], column.cells
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_max
|
50
|
+
data = [
|
51
|
+
{ :place => "1", :name => "Bernard Hinault" },
|
52
|
+
{ :place => "2", :name => "Greg Lemond" }
|
53
|
+
]
|
54
|
+
table = Table.new(data)
|
55
|
+
|
56
|
+
assert_equal "2", table.columns[:place].max
|
57
|
+
assert_equal "Greg Lemond", table.columns[:name].max
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_precision
|
61
|
+
data = [
|
62
|
+
{ :place => "1", :age => 22, :points => 10.75 },
|
63
|
+
{ :place => "2", :age => 30, :points => 12.000 }
|
64
|
+
]
|
65
|
+
table = Table.new(data)
|
66
|
+
|
67
|
+
assert_equal 0, table.columns[:place].precision
|
68
|
+
assert_equal 0, table.columns[:age].precision
|
69
|
+
assert_equal 2, table.columns[:points].precision
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_precision_with_mixed_zeros
|
73
|
+
data = [
|
74
|
+
{ :place => "1", :age => 22, :points => 12.001 }
|
75
|
+
]
|
76
|
+
table = Table.new(data)
|
77
|
+
assert_equal 3, table.columns[:points].precision
|
78
|
+
end
|
38
79
|
end
|
39
80
|
end
|
data/test/columns_test.rb
CHANGED
@@ -3,7 +3,7 @@ require "helper"
|
|
3
3
|
module Tabular
|
4
4
|
class ColumnsTest < Test::Unit::TestCase
|
5
5
|
def test_new_blank
|
6
|
-
columns = Columns.new([])
|
6
|
+
columns = Columns.new(nil, [])
|
7
7
|
assert_equal false, columns.has_key?(:name), "has_key? :name"
|
8
8
|
assert_equal nil, columns[:name], "[:name]"
|
9
9
|
assert_equal nil, columns.index(nil), "index"
|
@@ -11,36 +11,54 @@ module Tabular
|
|
11
11
|
assert_equal nil, columns.index(:name), "index"
|
12
12
|
columns.each { |c| c.nil? }
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def test_new
|
16
|
-
columns = Columns.new(["date", "first name", "LastName"])
|
16
|
+
columns = Columns.new(nil, ["date", "first name", "LastName"])
|
17
17
|
assert_equal false, columns.has_key?(:location), "has_key? :location"
|
18
18
|
assert_equal true, columns.has_key?(:date), "has_key? :date"
|
19
19
|
assert_equal true, columns.has_key?(:first_name), "has_key? :first_name"
|
20
20
|
assert_equal true, columns.has_key?(:last_name), "has_key? :last_name"
|
21
21
|
assert_equal false, columns.has_key?("first name"), "has_key? 'first name'"
|
22
|
-
|
22
|
+
|
23
23
|
column = columns[:first_name]
|
24
24
|
assert_equal :first_name, column.key, "column[:first_name] Column key"
|
25
25
|
|
26
26
|
assert_equal 1, columns.index(:first_name), "index of :first_name"
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def test_columns_map
|
30
|
-
columns = Columns.new(["date"], :start_date => :date)
|
30
|
+
columns = Columns.new(nil, ["date"], :start_date => :date)
|
31
31
|
assert_equal true, columns.has_key?(:date), "has_key? :date"
|
32
32
|
assert_equal false, columns.has_key?(:start_date), "has_key? :start_date"
|
33
33
|
end
|
34
|
-
|
35
|
-
def
|
36
|
-
columns = Columns.new(["date", "first name", "LastName"])
|
34
|
+
|
35
|
+
def test_render
|
36
|
+
columns = Columns.new(nil, ["date", "first name", "LastName"])
|
37
|
+
assert_equal "date", columns.first.render
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_renderer
|
41
|
+
columns = Columns.new(nil, ["date", "first name", "LastName"])
|
42
|
+
columns.renderer = TestRenderer
|
43
|
+
assert_equal "Date", columns.first.render
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_delete
|
47
|
+
columns = Columns.new(nil, ["date", "first name", "LastName"])
|
48
|
+
columns.delete :date
|
49
|
+
|
37
50
|
columns_from_each = []
|
38
51
|
columns.each { |c| columns_from_each << c.key }
|
39
|
-
assert_equal [ :
|
52
|
+
assert_equal [ :first_name, :last_name ], columns_from_each, "column keys from #each"
|
53
|
+
|
54
|
+
assert_equal false, columns.has_key?(:date), "has_key? :date"
|
55
|
+
assert_equal true, columns.has_key?(:first_name), "has_key? :first_name"
|
56
|
+
assert_equal 0, columns.index(:first_name), "index of :first_name"
|
57
|
+
assert_equal 1, columns.index(:last_name), "index of :last_name"
|
40
58
|
end
|
41
|
-
|
59
|
+
|
42
60
|
def test_push_onto_blank
|
43
|
-
columns = Columns.new([])
|
61
|
+
columns = Columns.new(nil, [])
|
44
62
|
columns << "city state"
|
45
63
|
assert_equal true, columns.has_key?(:city_state), "has_key? :city_state"
|
46
64
|
assert_equal 0, columns.index(:city_state), "index of new column"
|
@@ -48,9 +66,9 @@ module Tabular
|
|
48
66
|
column = columns[:city_state]
|
49
67
|
assert_equal :city_state, column.key, "column[:city_state] Column key"
|
50
68
|
end
|
51
|
-
|
69
|
+
|
52
70
|
def test_push
|
53
|
-
columns = Columns.new(["first", "second"])
|
71
|
+
columns = Columns.new(nil, ["first", "second"])
|
54
72
|
columns << "third"
|
55
73
|
assert_equal true, columns.has_key?(:third), "has_key? :third"
|
56
74
|
assert_equal 0, columns.index(:first), "index of existing column"
|
@@ -60,5 +78,12 @@ module Tabular
|
|
60
78
|
column = columns[:third]
|
61
79
|
assert_equal :third, column.key, "column[:third] Column key"
|
62
80
|
end
|
81
|
+
|
82
|
+
class TestRenderer
|
83
|
+
def self.render_header(column)
|
84
|
+
key = column.key.to_s
|
85
|
+
(key.slice(0) || key.chars('')).upcase + (key.slice(1..-1) || key.chars('')).downcase
|
86
|
+
end
|
87
|
+
end
|
63
88
|
end
|
64
89
|
end
|