tabular 0.0.1 → 0.0.2
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/.gitignore +1 -0
- data/README +18 -4
- data/VERSION +1 -1
- data/lib/tabular/columns.rb +21 -2
- data/lib/tabular/row.rb +22 -10
- data/lib/tabular/table.rb +46 -14
- data/tabular.gemspec +7 -5
- data/test/fixtures/sample.lif +5 -0
- data/test/row_test.rb +19 -11
- data/test/table_test.rb +16 -1
- metadata +19 -5
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/README
CHANGED
@@ -2,12 +2,22 @@ Tabular is a Ruby library for reading, writing, and manipulating CSV, tab-delimi
|
|
2
2
|
|
3
3
|
I extracted it from production code. Still extracting it, actually. I need to read structured data and manipulate it via a common interface before persisting it with ActiveRecord.
|
4
4
|
|
5
|
+
Install
|
6
|
+
-------
|
7
|
+
sudo gem install tabular
|
8
|
+
|
9
|
+
(The gem is hosted on Gemcutter, not RubyForge)
|
10
|
+
|
5
11
|
|
6
12
|
Dependencies
|
7
13
|
------------
|
8
14
|
For tab-delimited data: Ruby standard lib
|
9
|
-
|
10
|
-
For
|
15
|
+
|
16
|
+
For CSV: FasterCSV (http://fastercsv.rubyforge.org/)
|
17
|
+
sudo gem install fastercsv
|
18
|
+
|
19
|
+
For Excel: Spreadsheet gem (http://spreadsheet.rubyforge.org/)
|
20
|
+
sudo gem install spreadsheet
|
11
21
|
|
12
22
|
|
13
23
|
Examples
|
@@ -30,10 +40,14 @@ Table.read assumes that .txt files are tab-delimited, .csv files are comma-delim
|
|
30
40
|
|
31
41
|
Table.new accepts an Array of Arrays.
|
32
42
|
|
33
|
-
Table.new also accepts an options hash.
|
43
|
+
Table.new also accepts an options hash.
|
44
|
+
|
45
|
+
:columns option to map columns to a different key or type:
|
34
46
|
:city_state => :location -- Maps :city_state column to :location. A column with a "City State" header would be accessed as row[:location]
|
35
47
|
:flyer_approved => { :column_type => :boolean } -- Coerce :flyer_approved column cells to booleans.
|
36
48
|
|
49
|
+
:as => [:csv, :xls, :txt] to override file format
|
50
|
+
|
37
51
|
|
38
52
|
Tests
|
39
53
|
-----
|
@@ -43,4 +57,4 @@ There's basic test coverage. More comprehensive test coverage needs to be extrac
|
|
43
57
|
Copyright
|
44
58
|
---------
|
45
59
|
|
46
|
-
Copyright (c)
|
60
|
+
Copyright (c) 2010 Scott Willson. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/tabular/columns.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
module Tabular
|
2
2
|
# The Table's header: a list of Columns.
|
3
3
|
class Columns
|
4
|
+
include Enumerable
|
5
|
+
|
4
6
|
# +data+ -- array of header names
|
5
7
|
# +columns_map+ -- see Table. Maps column names and type conversion.
|
6
8
|
def initialize(data, columns_map = {})
|
7
|
-
|
9
|
+
columns_map ||= {}
|
10
|
+
@columns_map = normalize_columns_map(columns_map)
|
8
11
|
@column_indexes = {}
|
9
12
|
@columns_by_key = {}
|
10
13
|
index = 0
|
11
14
|
@columns = nil
|
12
15
|
@columns = data.map do |column|
|
13
|
-
new_column = Tabular::Column.new(column, columns_map)
|
16
|
+
new_column = Tabular::Column.new(column, @columns_map)
|
14
17
|
unless new_column.key.blank?
|
15
18
|
@column_indexes[new_column.key] = index
|
16
19
|
@columns_by_key[new_column.key] = new_column
|
@@ -51,5 +54,21 @@ module Tabular
|
|
51
54
|
end
|
52
55
|
@columns << column
|
53
56
|
end
|
57
|
+
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def normalize_columns_map(columns_map)
|
62
|
+
normalized_columns_map = {}
|
63
|
+
columns_map.each do |key, value|
|
64
|
+
case value
|
65
|
+
when Hash, Symbol
|
66
|
+
normalized_columns_map[key.to_sym] = value
|
67
|
+
else
|
68
|
+
normalized_columns_map[key.to_sym] = value.to_sym
|
69
|
+
end
|
70
|
+
end
|
71
|
+
normalized_columns_map
|
72
|
+
end
|
54
73
|
end
|
55
74
|
end
|
data/lib/tabular/row.rb
CHANGED
@@ -3,12 +3,15 @@ module Tabular
|
|
3
3
|
class Row
|
4
4
|
include Enumerable
|
5
5
|
|
6
|
-
|
6
|
+
attr_reader :index
|
7
|
+
|
8
|
+
# +table+ -- Table
|
7
9
|
# +cell+ -- array (not neccessarily Strings)
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
+
def initialize(table, cells = [])
|
11
|
+
@table = table
|
10
12
|
@array = cells
|
11
13
|
@hash = nil
|
14
|
+
@index = table.rows.size
|
12
15
|
end
|
13
16
|
|
14
17
|
# Cell value by symbol. E.g., row[:phone_number]
|
@@ -16,13 +19,13 @@ module Tabular
|
|
16
19
|
hash[key]
|
17
20
|
end
|
18
21
|
|
19
|
-
# Set cell value. Adds cell to end of Row and adds new Column if there is no Column
|
22
|
+
# Set cell value. Adds cell to end of Row and adds new Column if there is no Column for +key_
|
20
23
|
def []=(key, value)
|
21
|
-
if
|
22
|
-
@array[
|
24
|
+
if columns.has_key?(key)
|
25
|
+
@array[columns.index(key)] = value
|
23
26
|
else
|
24
27
|
@array << value
|
25
|
-
|
28
|
+
columns << key
|
26
29
|
end
|
27
30
|
hash[key] = value
|
28
31
|
end
|
@@ -37,6 +40,16 @@ module Tabular
|
|
37
40
|
@array.join(sep)
|
38
41
|
end
|
39
42
|
|
43
|
+
def previous
|
44
|
+
if index > 0
|
45
|
+
@table.rows[index - 1]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def columns
|
50
|
+
@table.columns
|
51
|
+
end
|
52
|
+
|
40
53
|
def to_hash
|
41
54
|
hash.dup
|
42
55
|
end
|
@@ -52,12 +65,11 @@ module Tabular
|
|
52
65
|
|
53
66
|
protected
|
54
67
|
|
55
|
-
|
56
68
|
def hash #:nodoc:
|
57
69
|
unless @hash
|
58
70
|
@hash = Hash.new
|
59
|
-
|
60
|
-
index =
|
71
|
+
columns.each do |column|
|
72
|
+
index = columns.index(column.key)
|
61
73
|
if index
|
62
74
|
case column.column_type
|
63
75
|
when :boolean
|
data/lib/tabular/table.rb
CHANGED
@@ -1,27 +1,44 @@
|
|
1
1
|
module Tabular
|
2
2
|
# Simple Enumerable list of Hashes. Use Table.read(file_path) to read file.
|
3
3
|
class Table
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :rows
|
5
5
|
|
6
6
|
# Assumes .txt = tab-delimited, .csv = CSV, .xls = Excel. Assumes first row is the header.
|
7
7
|
# Normalizes column names to lower-case with underscores.
|
8
8
|
def self.read(file_path, *options)
|
9
9
|
raise "Could not find '#{file_path}'" unless File.exists?(file_path)
|
10
|
+
options = extract_options(options)
|
11
|
+
as = options.delete(:as)
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
if as.present?
|
14
|
+
format = as
|
15
|
+
else
|
16
|
+
format = case File.extname(file_path)
|
17
|
+
when ".xls", ".xlsx"
|
18
|
+
:xls
|
19
|
+
when ".txt"
|
20
|
+
:txt
|
21
|
+
when ".csv"
|
22
|
+
:csv
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
case format
|
27
|
+
when :xls
|
13
28
|
require "spreadsheet"
|
14
29
|
# Row#to_a coerces Excel data to Strings, but we want Dates and Numbers
|
15
30
|
data = []
|
16
31
|
Spreadsheet.open(file_path).worksheets.first.each do |excel_row|
|
17
32
|
data << excel_row.inject([]) { |row, cell| row << cell; row }
|
18
33
|
end
|
19
|
-
when
|
34
|
+
when :txt
|
20
35
|
require "csv"
|
21
36
|
data = ::CSV.open(file_path, "r","\t").collect { |row| row }
|
22
|
-
when
|
37
|
+
when :csv
|
23
38
|
require "fastercsv"
|
24
39
|
data = FasterCSV.read(file_path)
|
40
|
+
else
|
41
|
+
raise "Cannot read '#{format}' format. Expected :xls, :xlsx, :txt, or :csv"
|
25
42
|
end
|
26
43
|
|
27
44
|
Table.new data, options
|
@@ -31,20 +48,16 @@ module Tabular
|
|
31
48
|
# Maps rows to Hash-like Tabular::Rows.
|
32
49
|
#
|
33
50
|
# Options:
|
34
|
-
# :
|
51
|
+
# :columns => { :original_name => :preferred_name, :column_name => { :column_type => :boolean } }
|
35
52
|
def initialize(rows = [], *options)
|
36
|
-
|
37
|
-
|
38
|
-
else
|
39
|
-
options = {}
|
40
|
-
end
|
53
|
+
options = Table.extract_options(options)
|
54
|
+
@rows = []
|
41
55
|
|
42
56
|
rows.each do |row|
|
43
|
-
if columns
|
44
|
-
|
57
|
+
if @columns
|
58
|
+
self << row
|
45
59
|
else
|
46
60
|
@columns = Tabular::Columns.new(row, options[:columns])
|
47
|
-
@rows = []
|
48
61
|
end
|
49
62
|
end
|
50
63
|
end
|
@@ -54,12 +67,31 @@ module Tabular
|
|
54
67
|
rows[index]
|
55
68
|
end
|
56
69
|
|
70
|
+
def <<(row)
|
71
|
+
@rows << Tabular::Row.new(self, row)
|
72
|
+
end
|
73
|
+
|
57
74
|
def inspect
|
58
75
|
rows.map { |row| row.join(",") }.join("\n")
|
59
76
|
end
|
60
77
|
|
78
|
+
def columns
|
79
|
+
@columns || Tabular::Columns.new([])
|
80
|
+
end
|
81
|
+
|
61
82
|
def to_s
|
62
83
|
"#<#{self.class} #{rows.size}>"
|
63
84
|
end
|
85
|
+
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def self.extract_options(options)
|
90
|
+
if options
|
91
|
+
options.flatten.first || {}
|
92
|
+
else
|
93
|
+
{}
|
94
|
+
end
|
95
|
+
end
|
64
96
|
end
|
65
97
|
end
|
data/tabular.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tabular}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.2"
|
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 = %q{
|
12
|
+
s.date = %q{2010-06-07}
|
13
13
|
s.description = %q{Tabular is a Ruby library for reading, writing, and manipulating CSV, tab-delimited and Excel data.}
|
14
14
|
s.email = %q{scott.willson@gmail.cpm}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -17,7 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
"README"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
|
-
"
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
21
22
|
"README",
|
22
23
|
"Rakefile",
|
23
24
|
"VERSION",
|
@@ -33,6 +34,7 @@ Gem::Specification.new do |s|
|
|
33
34
|
"test/fixtures/blank.txt",
|
34
35
|
"test/fixtures/excel.xls",
|
35
36
|
"test/fixtures/sample.csv",
|
37
|
+
"test/fixtures/sample.lif",
|
36
38
|
"test/helper.rb",
|
37
39
|
"test/row_test.rb",
|
38
40
|
"test/table_test.rb"
|
@@ -40,7 +42,7 @@ Gem::Specification.new do |s|
|
|
40
42
|
s.homepage = %q{http://github.com/scottwillson/tabular}
|
41
43
|
s.rdoc_options = ["--charset=UTF-8"]
|
42
44
|
s.require_paths = ["lib"]
|
43
|
-
s.rubygems_version = %q{1.3.
|
45
|
+
s.rubygems_version = %q{1.3.7}
|
44
46
|
s.summary = %q{Read, write, and manipulate CSV, tab-delimited and Excel data}
|
45
47
|
s.test_files = [
|
46
48
|
"test/column_test.rb",
|
@@ -54,7 +56,7 @@ Gem::Specification.new do |s|
|
|
54
56
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
57
|
s.specification_version = 3
|
56
58
|
|
57
|
-
if Gem::Version.new(Gem::
|
59
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
58
60
|
else
|
59
61
|
end
|
60
62
|
else
|
@@ -0,0 +1,5 @@
|
|
1
|
+
"Place ","Number","Last Name","First Name","Team","Category Raced"
|
2
|
+
"1","189","Willson","Scott","Gentle Lover","Senior Men 1/2/3","11",,"11"
|
3
|
+
"2","190","Phinney","Harry","CCCP","Senior Men 1/2/3","9",,
|
4
|
+
"3","10a","Holland","Steve","Huntair","Senior Men 1/2/3",,"3",
|
5
|
+
"dnf","100","Bourcier","Paul","Hutch's","Senior Men 1/2/3",,,"1"
|
data/test/row_test.rb
CHANGED
@@ -3,7 +3,7 @@ require "helper"
|
|
3
3
|
module Tabular
|
4
4
|
class RowTest < Test::Unit::TestCase
|
5
5
|
def test_new
|
6
|
-
row = Row.new(
|
6
|
+
row = Row.new(Table.new)
|
7
7
|
assert_equal nil, row[:city], "[]"
|
8
8
|
|
9
9
|
assert_equal "", row.join, "join"
|
@@ -16,8 +16,8 @@ module Tabular
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_set
|
19
|
-
|
20
|
-
row = Row.new(
|
19
|
+
table = Table.new([[ "planet", "star" ]])
|
20
|
+
row = Row.new(table, [ "Mars", "Sun" ])
|
21
21
|
|
22
22
|
assert_equal "Sun", row[:star], "row[:star]"
|
23
23
|
|
@@ -29,29 +29,37 @@ module Tabular
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_join
|
32
|
-
|
33
|
-
row = Row.new(
|
32
|
+
table = Table.new([[ "planet", "star" ]])
|
33
|
+
row = Row.new(table, [ "Mars", "Sun" ])
|
34
34
|
assert_equal "MarsSun", row.join, "join"
|
35
35
|
assert_equal "Mars-Sun", row.join("-"), "join '-'"
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_to_hash
|
39
|
-
|
40
|
-
row = Row.new(
|
39
|
+
table = Table.new([[ "planet", "star" ]])
|
40
|
+
row = Row.new(table, [ "Mars", "Sun" ])
|
41
41
|
assert_equal({ :planet => "Mars", :star => "Sun"}, row.to_hash, "to_hash")
|
42
42
|
end
|
43
43
|
|
44
44
|
def test_inspect
|
45
|
-
|
46
|
-
row = Row.new(
|
45
|
+
table = Table.new([[ "planet", "star" ]])
|
46
|
+
row = Row.new(table, [ "Mars", "Sun" ])
|
47
47
|
assert_equal "{:planet=>\"Mars\", :star=>\"Sun\"}", row.inspect, "inspect"
|
48
48
|
end
|
49
49
|
|
50
50
|
def test_to_s
|
51
|
-
|
52
|
-
row = Row.new(
|
51
|
+
table = Table.new([[ "planet", "star" ]])
|
52
|
+
row = Row.new(table, [ "Mars", "Sun" ])
|
53
53
|
assert_equal "Mars, Sun", row.to_s, "to_s"
|
54
54
|
end
|
55
|
+
|
56
|
+
def test_previous
|
57
|
+
table = Table.new([[ "planet", "star" ]])
|
58
|
+
table << [ "Mars", "Sun" ]
|
59
|
+
table << [ "Jupiter", "Sun" ]
|
60
|
+
assert_equal nil, table.rows.first.previous, "previous of first Row"
|
61
|
+
assert_equal "Mars", table.rows.last.previous[:planet], "previous"
|
62
|
+
end
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
data/test/table_test.rb
CHANGED
@@ -32,6 +32,21 @@ module Tabular
|
|
32
32
|
table = Table.read(File.expand_path(File.dirname(__FILE__) + "/fixtures/excel.xls"))
|
33
33
|
assert_equal Date.new(2006, 1, 20), table[0][:date], "0.0"
|
34
34
|
end
|
35
|
+
|
36
|
+
def test_read_as
|
37
|
+
table = Table.read(File.expand_path(File.dirname(__FILE__) + "/fixtures/sample.lif"), :as => :csv)
|
38
|
+
assert_equal 4, table.rows.size, "rows"
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_column_map
|
42
|
+
data = [
|
43
|
+
[ "nom", "equipe", "homme" ],
|
44
|
+
[ "Hinault", "Team Z", "true" ]
|
45
|
+
]
|
46
|
+
table = Table.new(data, :columns => { :nom => :name, :equipe => :team, :homme => { :column_type => :boolean } })
|
47
|
+
assert_equal "Hinault", table.rows.first[:name], ":name"
|
48
|
+
assert_equal "Team Z", table.rows.first[:team], ":team"
|
49
|
+
assert_equal true, table.rows.first[:homme], "boolean"
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
37
|
-
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tabular
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Scott Willson
|
@@ -9,7 +15,7 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2010-06-07 00:00:00 -07:00
|
13
19
|
default_executable:
|
14
20
|
dependencies: []
|
15
21
|
|
@@ -23,6 +29,7 @@ extra_rdoc_files:
|
|
23
29
|
- LICENSE
|
24
30
|
- README
|
25
31
|
files:
|
32
|
+
- .gitignore
|
26
33
|
- LICENSE
|
27
34
|
- README
|
28
35
|
- Rakefile
|
@@ -39,6 +46,7 @@ files:
|
|
39
46
|
- test/fixtures/blank.txt
|
40
47
|
- test/fixtures/excel.xls
|
41
48
|
- test/fixtures/sample.csv
|
49
|
+
- test/fixtures/sample.lif
|
42
50
|
- test/helper.rb
|
43
51
|
- test/row_test.rb
|
44
52
|
- test/table_test.rb
|
@@ -52,21 +60,27 @@ rdoc_options:
|
|
52
60
|
require_paths:
|
53
61
|
- lib
|
54
62
|
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
55
64
|
requirements:
|
56
65
|
- - ">="
|
57
66
|
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
58
70
|
version: "0"
|
59
|
-
version:
|
60
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
61
73
|
requirements:
|
62
74
|
- - ">="
|
63
75
|
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
64
79
|
version: "0"
|
65
|
-
version:
|
66
80
|
requirements: []
|
67
81
|
|
68
82
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.3.
|
83
|
+
rubygems_version: 1.3.7
|
70
84
|
signing_key:
|
71
85
|
specification_version: 3
|
72
86
|
summary: Read, write, and manipulate CSV, tab-delimited and Excel data
|