csv_madness 0.0.4 → 0.0.9
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.
- checksums.yaml +7 -0
- data/CHANGELOG.markdown +21 -3
- data/Gemfile +9 -7
- data/README.rdoc +136 -95
- data/Rakefile +79 -60
- data/VERSION +1 -1
- data/lib/csv_madness/builder.rb +100 -0
- data/lib/csv_madness/data_accessor_module.rb +59 -24
- data/lib/csv_madness/exceptions.rb +16 -0
- data/lib/csv_madness/gem_api.rb +12 -0
- data/lib/csv_madness/record.rb +41 -2
- data/lib/csv_madness/sheet.rb +101 -292
- data/lib/csv_madness/sheet_methods/base.rb +6 -0
- data/lib/csv_madness/sheet_methods/class_methods.rb +77 -0
- data/lib/csv_madness/sheet_methods/column_methods.rb +187 -0
- data/lib/csv_madness/sheet_methods/file_methods.rb +41 -0
- data/lib/csv_madness/sheet_methods/indexing.rb +52 -0
- data/lib/csv_madness/sheet_methods/record_methods.rb +99 -0
- data/lib/csv_madness.rb +3 -13
- data/test/csv/forbidden_column.csv +2 -0
- data/test/csv/splitter.csv +11 -0
- data/test/csv/test_column_types.csv +3 -0
- data/test/csv/with_nils.csv +5 -0
- data/test/helper.rb +31 -19
- data/test/test_builder.rb +37 -0
- data/test/test_csv_madness.rb +8 -10
- data/test/test_merging_columns.rb +40 -0
- data/test/test_reloading_spreadsheet.rb +31 -0
- data/test/test_sheet.rb +117 -8
- metadata +52 -93
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4f4f9b06af750b458f7eb43b10053dfce2b60a69cc55ea4410d4466c72680f0c
|
|
4
|
+
data.tar.gz: 23ddbf472f0b52973769e21eb72939622bee0e5b13e43e8c292ae28203106145
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1e44f1666c4118cf68a16f254ea5ad8808f9a0036b346755d187c74baf24a65cff718323d81741cd5478b036fcea3518daa3b2b5f72f6de2bf89b3b435392de8
|
|
7
|
+
data.tar.gz: b44d0fa470ec59317f5a93b0e73d1ec6a032f300083734e2b0aef27b2077b51303dce1b504e77102e6cfb5f79cc2c229d136d4a5d7eca049402bb88e740e711a
|
data/CHANGELOG.markdown
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
0.0.6
|
|
5
|
+
-----
|
|
6
|
+
|
|
7
|
+
* CsvMadness::Builder added (easily generate spreadsheets from an array of objects)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
0.0.5
|
|
11
|
+
-----
|
|
12
|
+
|
|
13
|
+
* Splitting spreadsheets, creating blank copies of spreadsheets, adding/removing records.
|
|
14
|
+
* Convert records to hashes/arrays.
|
|
15
|
+
* Created a dependency on `fun_with_testing`.
|
|
16
|
+
* CsvMadness.version
|
|
17
|
+
|
|
18
|
+
|
|
1
19
|
0.0.4
|
|
2
|
-
|
|
20
|
+
-----
|
|
3
21
|
|
|
4
22
|
* Fixed serious bug for default column names. record.middle_name instead of record.middlename
|
|
5
23
|
* Spreadsheet can be re-read from file by calling @sheet.reload_spreadsheet(opts)
|
|
6
24
|
|
|
7
25
|
|
|
8
26
|
0.0.3
|
|
9
|
-
|
|
27
|
+
-----
|
|
10
28
|
|
|
11
29
|
* Feature: can add and drop columns from spreadsheets
|
|
12
30
|
|
|
13
31
|
|
|
14
32
|
0.0.2
|
|
15
|
-
|
|
33
|
+
-----
|
|
16
34
|
|
|
17
35
|
* Aw, hell. I don't remember.
|
data/Gemfile
CHANGED
|
@@ -6,12 +6,14 @@ source "http://rubygems.org"
|
|
|
6
6
|
# Add dependencies to develop your gem here.
|
|
7
7
|
# Include everything needed to run rake, tests, features, etc.
|
|
8
8
|
|
|
9
|
-
gem "fun_with_files"
|
|
10
|
-
|
|
11
9
|
group :development do
|
|
12
|
-
gem "shoulda", ">=
|
|
13
|
-
gem "rdoc", "~> 3.12"
|
|
14
|
-
gem "bundler", "~> 1.
|
|
15
|
-
gem "jeweler", "~>
|
|
16
|
-
gem "
|
|
10
|
+
# gem "shoulda", ">= 3.5"
|
|
11
|
+
# gem "rdoc", "~> 3.12"
|
|
12
|
+
# gem "bundler", "~> 1.5"
|
|
13
|
+
# gem "jeweler", "~> 2"
|
|
14
|
+
gem "fun_with_testing"
|
|
15
|
+
# gem "debugger"
|
|
17
16
|
end
|
|
17
|
+
|
|
18
|
+
gem "csv"
|
|
19
|
+
gem 'fun_with_gems', '~> 0.0', ">= 0.0.7"
|
data/README.rdoc
CHANGED
|
@@ -7,150 +7,191 @@ CSV Madness tries to remove what little pain is left from Ruby's CSV class. Loa
|
|
|
7
7
|
|
|
8
8
|
== Why should I use it?
|
|
9
9
|
|
|
10
|
-
I like brief code and I cannot lie.
|
|
10
|
+
I like brief code and I cannot lie.
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
== Examples
|
|
14
14
|
|
|
15
15
|
CsvMadness makes some assumptions about your CSV file. It does assume headers, for example.
|
|
16
16
|
|
|
17
|
-
The simplest case is when your columns are nicely named. For example, if you have a csv file named
|
|
17
|
+
The simplest case is when your columns are nicely named. For example, if you have a csv file named `~/data/people.csv`:
|
|
18
|
+
|
|
19
|
+
"id","fname","lname","age","born"
|
|
20
|
+
"1","Mary","Moore","27","1986-04-08 15:06:10"
|
|
21
|
+
"2","Bill","Paxton","39","1974-02-22"
|
|
22
|
+
"3","Charles","Darwin","72",""
|
|
23
|
+
"4","Chuck","Norris","57","1901-03-02"
|
|
18
24
|
|
|
19
|
-
<code>
|
|
20
|
-
"id","fname","lname","age","born"
|
|
21
|
-
"1","Mary","Moore","27","1986-04-08 15:06:10"
|
|
22
|
-
"2","Bill","Paxton","39","1974-02-22"
|
|
23
|
-
"3","Charles","Darwin","72",""
|
|
24
|
-
"4","Chuck","Norris","57","1901-03-02"
|
|
25
|
-
</code>
|
|
26
25
|
|
|
27
26
|
... then you can write code like so:
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
sheet
|
|
32
|
-
sheet.
|
|
33
|
-
sheet.
|
|
34
|
-
sheet.
|
|
35
|
-
sheet.
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
require 'csv_madness'
|
|
29
|
+
|
|
30
|
+
sheet = CsvMadness.load( "~/data/people.csv" )
|
|
31
|
+
sheet.columns # => [:id, :fname, :lname, :age, :born]
|
|
32
|
+
sheet.records.map(&:id) # => ["1", "2", "3", "4"]
|
|
33
|
+
sheet.set_column_type(:id, :float)
|
|
34
|
+
sheet.records.map(&:id) # => [1.0, 2.0, 3.0, 4.0]
|
|
35
|
+
sheet.alter_column(:id) do |id|
|
|
36
|
+
id + rand() - 0.5
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
sheet.records.map(&:id) # => [0.7186, 2.30134, 2.90132, 4.30124] (your results may vary)
|
|
40
|
+
mary = sheet[0]
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
mary = sheet[0]
|
|
42
|
+
"#{mary.lname}, #{mary.fname} (#{mary.id})" # => "Moore, Mary (1)"
|
|
41
43
|
|
|
42
|
-
"#{mary.lname}, #{mary.fname} (#{mary.id})" # => "Moore, Mary (1)"
|
|
43
|
-
'''
|
|
44
44
|
|
|
45
45
|
If you're not satisfied with your column names, you can send your own, in the column order. An index can also be provided, which will allow you to use <tt>.fetch()</tt> to find specific records quickly:
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
sheet = CsvMadness.load( "~/data/people.csv",
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
require 'csv_madness'
|
|
48
|
+
sheet = CsvMadness.load( "~/data/people.csv",
|
|
49
|
+
columns: [:uid, :first_name, :last_name, :years_on_planet, :birthday],
|
|
50
|
+
index: :uid )
|
|
51
|
+
|
|
52
|
+
sheet.fetch("2").years_on_planet # => "39"
|
|
51
53
|
|
|
52
|
-
sheet.fetch("2").years_on_planet # => "39"
|
|
53
|
-
'''
|
|
54
54
|
|
|
55
55
|
**Note:** you can provide multiple indexes as an array. However many columns you index, you'll run into trouble if the index isn't unique for each record. (that may change in the future)
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
It's useful to clean up your files. Say you have:
|
|
59
59
|
|
|
60
|
-
"
|
|
61
|
-
"1 ","Mary ","Moore","27","1986-04-08 15:06:10"
|
|
62
|
-
""," Bill ","Paxton",," Feb. 22, 1974 "
|
|
63
|
-
,"Charles "," Darwin","72 "
|
|
64
|
-
"4","Chuck","Norris",," 2 March 1901 "
|
|
65
|
-
|
|
60
|
+
"id","fname","lname","age"," born "
|
|
61
|
+
"1 ","Mary ","Moore","27","1986-04-08 15:06:10"
|
|
62
|
+
""," Bill ","Paxton",," Feb. 22, 1974 "
|
|
63
|
+
"3","Charles "," Darwin","72 "
|
|
64
|
+
"4","Chuck","Norris",," 2 March 1901 "
|
|
65
|
+
|
|
66
66
|
|
|
67
67
|
Ick. Missing IDs, inconsistent date formats, leading and trailing whitespace... We can fix this.
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
sheet.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
69
|
+
require 'csv_madness'
|
|
70
|
+
|
|
71
|
+
sheet = CsvMadness.load( "~/data/people.csv" )
|
|
72
|
+
|
|
73
|
+
# remove leading and trailing whitespace, and turns nils into ""
|
|
74
|
+
sheet.alter_cells do |cell, record|
|
|
75
|
+
(cell || "").strip
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# the last argument provides a default for blank records.
|
|
79
|
+
sheet.set_column_type(:id, :integer, nil)
|
|
80
|
+
|
|
81
|
+
# assumes the missing ids can be filled in sequentially.
|
|
82
|
+
# While .alter_column() does take a default (second argument),
|
|
83
|
+
# which will fill in the blank cells,
|
|
84
|
+
# that's not what we want here.
|
|
85
|
+
#
|
|
86
|
+
# If a blank is provided, your
|
|
87
|
+
# code will never see the records with blank entries.
|
|
88
|
+
sheet.alter_column(:id) do |id, record|
|
|
89
|
+
@count ||= 1
|
|
90
|
+
if id.nil?
|
|
91
|
+
id = @count
|
|
92
|
+
@count += 1
|
|
93
|
+
else
|
|
94
|
+
@count = id + 1
|
|
95
|
+
end
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
end
|
|
97
|
+
id
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
sheet.column(:id) # => [1, 2, 3, 4]
|
|
101
|
+
|
|
102
|
+
# Reformat a column of dates.
|
|
103
|
+
require 'time'
|
|
104
|
+
sheet.alter_column(:born) do |date_string|
|
|
105
|
+
begin
|
|
106
|
+
Time.parse( date_string ).strftime("%Y-%m-%d")
|
|
107
|
+
rescue ArgumentError
|
|
108
|
+
""
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sheet.column(:date) # => ["1986-04-08", "1974-02-22", "", "1901-03-02"]
|
|
113
|
+
|
|
114
|
+
# The same thing can be accomplished more simply by saying <tt>sheet.set_column_type(:date)</tt>.
|
|
115
|
+
# Even better, record.date is then a Time object
|
|
116
|
+
|
|
117
|
+
# Now calculate everyone's age (ignoring and overriding the existing data)
|
|
118
|
+
sheet.alter_column(:age) do |age, record|
|
|
119
|
+
if record.blank?(:born)
|
|
120
|
+
""
|
|
121
|
+
else
|
|
122
|
+
((Time.now - Time.parse(record.born)) / 365.25 / 24 / 60 / 60).to_i # not production-worthy code
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# save the cleaned data for the next stage of your process
|
|
127
|
+
sheet.write_to_file( "~/data/people.clean.csv", force_quotes: true )
|
|
92
128
|
|
|
93
|
-
|
|
129
|
+
You could do something similar to clean and standardize phone numbers, detect and delete/complete invalid emails, etc.
|
|
94
130
|
|
|
95
|
-
require 'time'
|
|
96
|
-
sheet.alter_column(:born) do |date_string|
|
|
97
|
-
begin
|
|
98
|
-
Time.parse( date_string ).strftime("%Y-%m-%d")
|
|
99
|
-
rescue ArgumentError
|
|
100
|
-
""
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
131
|
|
|
104
|
-
|
|
132
|
+
=== Adding, removing, and renaming columns ===
|
|
105
133
|
|
|
106
|
-
# The same thing can be accomplished more simply by saying <tt>sheet.set_column_type(:date)</tt>.
|
|
107
|
-
# Even better, record.date is then a Time object
|
|
108
134
|
|
|
109
|
-
#
|
|
110
|
-
sheet.
|
|
111
|
-
if record.blank?(:born)
|
|
112
|
-
""
|
|
113
|
-
else
|
|
114
|
-
((Time.now - Time.parse(record.born)) / 365.25 / 24 / 60 / 60).to_i # not production-worthy code
|
|
115
|
-
end
|
|
116
|
-
end
|
|
135
|
+
# Add 72 years to the date born.
|
|
136
|
+
sheet.set_column_type( :born, :date ) # replace date strings with Time objects
|
|
117
137
|
|
|
118
|
-
sheet.
|
|
119
|
-
|
|
138
|
+
sheet.add_column( :expected_death_date ) do |date, record|
|
|
139
|
+
record.born + ( 72 * 365 * 24 * 60 * 60 )
|
|
140
|
+
end
|
|
120
141
|
|
|
121
|
-
|
|
142
|
+
puts sheet[0].expected_death_date # should be in 2058
|
|
122
143
|
|
|
144
|
+
# But that's just morbid, so we drop the column
|
|
145
|
+
sheet.drop_column( :expected_death_date )
|
|
123
146
|
|
|
124
|
-
|
|
147
|
+
# Or, if you think you need the information, but need to be a bit more euphemistic about it
|
|
148
|
+
sheet.rename_column( :expected_death_date, :expiration_date )
|
|
125
149
|
|
|
126
|
-
# Add 72 years to the date born.
|
|
127
|
-
sheet.set_column_type( :born, :date ) # replace date strings with Time objects
|
|
128
150
|
|
|
129
|
-
|
|
130
|
-
record.born + ( 72 * 365 * 24 * 60 * 60 )
|
|
131
|
-
end
|
|
151
|
+
=== Using columns ===
|
|
132
152
|
|
|
133
|
-
puts sheet[0].expected_death_date # should be in 2058
|
|
134
153
|
|
|
135
|
-
# But that's just morbid, so we drop the column
|
|
136
|
-
sheet.drop_column( :expected_death_date )
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
sheet.rename_column( :expected_death_date, :expiration_date )
|
|
155
|
+
sheet.set_column_type( :id, :integer )
|
|
140
156
|
|
|
141
|
-
=== Using columns ===
|
|
142
157
|
|
|
143
|
-
|
|
158
|
+
# Returns each record's id as an array of integers
|
|
159
|
+
sheet.column( :id ).max # ==> 4
|
|
144
160
|
|
|
145
161
|
|
|
146
|
-
|
|
147
|
-
sheet.column( :id ).max # ==> 4
|
|
162
|
+
=== Builder ===
|
|
148
163
|
|
|
164
|
+
You have an array of objects. You want to write them to a spreadsheet.
|
|
149
165
|
|
|
166
|
+
sb = CsvMadness::Builder.new do |sb|
|
|
167
|
+
sb.column( :id )
|
|
168
|
+
sb.column( :addressee, "addressee_custom" )
|
|
169
|
+
sb.column( :street, "primary_address.street_address" )
|
|
170
|
+
sb.column( :street2, "primary_address.supplemental_address_1" )
|
|
171
|
+
sb.column( :city, "primary_address.city" )
|
|
172
|
+
sb.column( :state_code, "primary_address.state_code" )
|
|
173
|
+
sb.column( :formatted_zip, "primary_address.formatted_zip" )
|
|
174
|
+
sb.column( :phone, "phones.first.phone" )
|
|
175
|
+
sb.column( :contact, "congregation_fieldset.congregation_leader" )
|
|
176
|
+
sb.column( :email, "emails.first.email" )
|
|
177
|
+
sb.column( :greeting ) do |contact|
|
|
178
|
+
if contact.business?
|
|
179
|
+
"To our friends at #{contact.organization_name},"
|
|
180
|
+
elsif contact.person?
|
|
181
|
+
"Dear #{ contact.title } #{ contact.last_name }"
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
end
|
|
150
186
|
|
|
187
|
+
sheet = sb.build( [address1, address2, address3...] )
|
|
188
|
+
|
|
189
|
+
outfile = FunWith::Files::FilePath.home.join( "Documents", "addresses", "address_records.csv" )
|
|
190
|
+
sheet.write_to_file( outfile )
|
|
151
191
|
|
|
192
|
+
=== Documentation is incomplete ===
|
|
152
193
|
|
|
153
|
-
There are
|
|
194
|
+
There are other features, but they'll take time to test and document.
|
|
154
195
|
|
|
155
196
|
|
|
156
197
|
|
data/Rakefile
CHANGED
|
@@ -1,70 +1,89 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
|
+
require 'fun_with_gems'
|
|
3
|
+
require_relative File.join( "lib", "csv_madness" )
|
|
4
|
+
self.extend( FunWith::Gems::Rakefile)
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
require 'bundler'
|
|
6
|
+
rakefile_setup CsvMadness
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
require 'jeweler'
|
|
8
|
+
gem_specification do |gem|
|
|
9
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
|
10
|
+
# dependencies defined in Gemfile
|
|
11
|
+
gem.name = "csv_madness"
|
|
12
|
+
gem.homepage = "http://github.com/darthschmoo/csv_madness"
|
|
13
|
+
gem.license = "MIT"
|
|
14
|
+
gem.summary = "CSV Madness turns your CSV rows into happycrazy objects."
|
|
15
|
+
gem.description = "CSV Madness removes what little pain is left from Ruby's CSV class. Load a CSV file, and get back an array of objects with customizable getter/setter methods."
|
|
16
|
+
gem.email = "keeputahweird@gmail.com"
|
|
17
|
+
gem.authors = ["Bryce Anderson"]
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
gem.summary = %Q{CSV Madness turns your CSV rows into happycrazy objects.}
|
|
24
|
-
gem.description = %Q{CSV Madness removes what little pain is left from Ruby's CSV class. Load a CSV file, and get back an array of objects with customizable getter/setter methods.}
|
|
25
|
-
gem.email = "keeputahweird@gmail.com"
|
|
26
|
-
gem.authors = ["Bryce Anderson"]
|
|
27
|
-
# dependencies defined in Gemfile
|
|
28
|
-
gem.files = [ "./lib/csv_madness/data_accessor_module.rb",
|
|
29
|
-
"./lib/csv_madness/record.rb",
|
|
30
|
-
"./lib/csv_madness/sheet.rb",
|
|
31
|
-
"./lib/csv_madness.rb",
|
|
32
|
-
"./Gemfile",
|
|
33
|
-
"./VERSION",
|
|
34
|
-
"./README.rdoc",
|
|
35
|
-
"./Rakefile",
|
|
36
|
-
"./CHANGELOG.markdown",
|
|
37
|
-
"./test/csv/simple.csv",
|
|
38
|
-
"./test/helper.rb",
|
|
39
|
-
"./test/test_csv_madness.rb",
|
|
40
|
-
"./test/test_sheet.rb" ]
|
|
19
|
+
# dependencies defined in Gemfile
|
|
20
|
+
gem.files = Dir.glob( File.join( ".", "lib", "**", "*.rb" ) ) +
|
|
21
|
+
Dir.glob( File.join( ".", "test", "**", "*" ) ) +
|
|
22
|
+
%w( Gemfile Rakefile LICENSE.txt README.rdoc VERSION CHANGELOG.markdown )
|
|
23
|
+
# csv_madness.gemspec )
|
|
41
24
|
end
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
setup_gem_boilerplate
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
28
|
+
#
|
|
29
|
+
#
|
|
30
|
+
# require 'rubygems'
|
|
31
|
+
# require 'bundler'
|
|
32
|
+
#
|
|
33
|
+
# begin
|
|
34
|
+
# Bundler.setup(:default, :development)
|
|
35
|
+
# rescue Bundler::BundlerError => e
|
|
36
|
+
# $stderr.puts e.message
|
|
37
|
+
# $stderr.puts "Run `bundle install` to install missing gems"
|
|
38
|
+
# exit e.status_code
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# require 'rake'
|
|
42
|
+
#
|
|
43
|
+
# require 'jeweler'
|
|
44
|
+
#
|
|
45
|
+
# Jeweler::Tasks.new do |gem|
|
|
46
|
+
# # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
|
47
|
+
# gem.name = "csv_madness"
|
|
48
|
+
# gem.homepage = "http://github.com/darthschmoo/csv_madness"
|
|
49
|
+
# gem.license = "MIT"
|
|
50
|
+
# gem.summary = "CSV Madness turns your CSV rows into happycrazy objects."
|
|
51
|
+
# gem.description = "CSV Madness removes what little pain is left from Ruby's CSV class. Load a CSV file, and get back an array of objects with customizable getter/setter methods."
|
|
52
|
+
# gem.email = "keeputahweird@gmail.com"
|
|
53
|
+
# gem.authors = ["Bryce Anderson"]
|
|
54
|
+
#
|
|
55
|
+
# # dependencies defined in Gemfile
|
|
56
|
+
# gem.files = Dir.glob( File.join( ".", "lib", "**", "*.rb" ) ) +
|
|
57
|
+
# Dir.glob( File.join( ".", "test", "**", "*" ) ) +
|
|
58
|
+
# %w( Gemfile Rakefile LICENSE.txt README.rdoc VERSION CHANGELOG.markdown csv_madness.gemspec )
|
|
59
|
+
#
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# Jeweler::RubygemsDotOrgTasks.new
|
|
63
|
+
#
|
|
64
|
+
# require 'rake/testtask'
|
|
65
|
+
# Rake::TestTask.new(:test) do |test|
|
|
66
|
+
# test.libs << 'lib' << 'test'
|
|
55
67
|
# test.pattern = 'test/**/test_*.rb'
|
|
56
68
|
# test.verbose = true
|
|
57
|
-
# test.rcov_opts << '--exclude "gems/*"'
|
|
58
69
|
# end
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
#
|
|
71
|
+
# # require 'rcov/rcovtask'
|
|
72
|
+
# # Rcov::RcovTask.new do |test|
|
|
73
|
+
# # test.libs << 'test'
|
|
74
|
+
# # test.pattern = 'test/**/test_*.rb'
|
|
75
|
+
# # test.verbose = true
|
|
76
|
+
# # test.rcov_opts << '--exclude "gems/*"'
|
|
77
|
+
# # end
|
|
78
|
+
#
|
|
79
|
+
# task :default => :test
|
|
80
|
+
#
|
|
81
|
+
# require 'rdoc/task'
|
|
82
|
+
# Rake::RDocTask.new do |rdoc|
|
|
83
|
+
# version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
84
|
+
#
|
|
85
|
+
# rdoc.rdoc_dir = 'rdoc'
|
|
86
|
+
# rdoc.title = "csv_madness #{version}"
|
|
87
|
+
# rdoc.rdoc_files.include('README*')
|
|
88
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
|
89
|
+
# end
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.9
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
class Builder
|
|
3
|
+
attr_accessor :label
|
|
4
|
+
|
|
5
|
+
def initialize( &block )
|
|
6
|
+
@columns = {}
|
|
7
|
+
@column_syms = []
|
|
8
|
+
|
|
9
|
+
@module = Module.new # for extending
|
|
10
|
+
self.extend( @module )
|
|
11
|
+
yield self if block_given?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def column( sym, method_path = nil, &block )
|
|
15
|
+
warn( "#{sym} already defined. Overwriting." ) if @column_syms.include?( sym )
|
|
16
|
+
|
|
17
|
+
@column_syms << sym
|
|
18
|
+
@columns[sym] = if block_given?
|
|
19
|
+
block
|
|
20
|
+
elsif method_path
|
|
21
|
+
method_path
|
|
22
|
+
else
|
|
23
|
+
Proc.new(&sym)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Three :on_error values:
|
|
28
|
+
# :print => Put an error message in the cell instead of a value
|
|
29
|
+
# :raise => Raise the error, halting the process
|
|
30
|
+
# :ignore => Hand back an empty cell
|
|
31
|
+
#
|
|
32
|
+
# Although ideally it should be configurable by column...
|
|
33
|
+
def build( objects, opts = { :on_error => :print } )
|
|
34
|
+
spreadsheet = CsvMadness::Sheet.new( @column_syms )
|
|
35
|
+
|
|
36
|
+
for object in objects
|
|
37
|
+
STDOUT << "."
|
|
38
|
+
record = {}
|
|
39
|
+
for sym in @column_syms
|
|
40
|
+
record[sym] = build_cell( object, sym, opts )
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
spreadsheet.add_record( record ) # hash form
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
spreadsheet
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build_cell( object, sym, opts = { :on_error => :print } )
|
|
50
|
+
column = @columns[sym]
|
|
51
|
+
|
|
52
|
+
case column
|
|
53
|
+
when String
|
|
54
|
+
build_cell_by_pathstring( object, column, opts )
|
|
55
|
+
when Proc
|
|
56
|
+
build_cell_by_proc( object, column, opts )
|
|
57
|
+
else
|
|
58
|
+
"no idea what to do"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def def( method_name, &block )
|
|
63
|
+
@module.send( :define_method, method_name, &block )
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
protected
|
|
67
|
+
def build_cell_by_pathstring( object, str, opts )
|
|
68
|
+
handle_cell_build_error( :build_cell_by_pathstring, opts ) do
|
|
69
|
+
for method in str.split(".").map(&:to_sym)
|
|
70
|
+
object = object.send(method)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
object
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_cell_by_proc( object, proc, opts )
|
|
78
|
+
handle_cell_build_error( :build_cell_by_proc, opts ) do
|
|
79
|
+
proc.call(object)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def handle_cell_build_error( caller, opts, &block )
|
|
84
|
+
begin
|
|
85
|
+
yield
|
|
86
|
+
end
|
|
87
|
+
rescue Exception => e
|
|
88
|
+
case opts[:on_error]
|
|
89
|
+
when nil, :print
|
|
90
|
+
puts "error #{e.message} #{caller}" if opts[:verbose]
|
|
91
|
+
"ERROR: #{e.message} (#{caller}())"
|
|
92
|
+
when :raise
|
|
93
|
+
puts "Re-raisinge error #{e.message}. Set opts[:on_error] to :print or :ignore if you want Builder to continue on errors."
|
|
94
|
+
raise e
|
|
95
|
+
when :ignore
|
|
96
|
+
""
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|