tabular 0.0.7 → 0.2.0
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.
- 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
|