csv_madness 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGQ4MTA4MGJlYjA1MzIwOTU4NzEyODViMmFhYmYwMTA2YjQzMmY2MQ==
5
+ data.tar.gz: !binary |-
6
+ ZTRkOWNjMjYyNTNmMjA3YzMwZThlMzAyOWRjMTM0ZWYwMTQ3NWE0MA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzAzNTJmNjU5YmM2NjUxODY4YjRkMDkwMDc5NTQxOWIxZWRjMjRmYmU1YzFh
10
+ Zjg2ZjA5ZGZlZTEyNjg1NjFmNzE5NzY3NTFhNzY3ZTIyNDFhZGIzZTEzOGI5
11
+ ZDUzMjZjYjJkODdiM2M1M2JiMjZiMGNlMTViNWE2Yzk1ZjU1YjI=
12
+ data.tar.gz: !binary |-
13
+ MjJlYzI1YTQxMzU4NzA0MmVlNzFkYzVmNDE3NjYyMDM5YmI3Y2JmMmQyOWZk
14
+ OGQ0NzFhODUyMzRjNWI3ZWRlZDQ3YmQ0NGU0Zjc4ZDFmNWRkMWY4MDc4OTdi
15
+ YjkwY2U1MGY3MmVhNTY2NTYzYzc1ZDViZWViMTg0OTE5OGU1ZTE=
@@ -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,15 @@ 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", ">= 0"
13
- gem "rdoc", "~> 3.12"
14
- gem "bundler", "~> 1.3.0"
15
- gem "jeweler", "~> 1.8.4"
16
- gem "debugger"
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 "fun_with_files", "~> 0.0", ">= 0.0.7"
19
+ # gem "fun_with_version_strings", "~> 0.0"
20
+ gem 'fun_with_gems', '~> 0.0', ">= 0.0.2"
@@ -66,89 +66,122 @@ It's useful to clean up your files. Say you have:
66
66
 
67
67
  Ick. Missing IDs, inconsistent date formats, leading and trailing whitespace... We can fix this.
68
68
 
69
- """require 'csv_madness'
70
- sheet = CsvMadness.load( "~/data/people.csv" )
71
- sheet.alter_cells do |cell, record|
72
- (cell || "").strip
73
- end # removes leading and trailing whitespace, and turns nils into ""
74
-
75
- sheet.set_column_type(:id, :integer, nil) # the last argument provides a default for blank records.
76
-
77
- # assumes the missing ids can be filled in sequentially.
78
- # While .alter_column() does take a blank as a second argument,
79
- # that's not what we want here. If a blank is provided, your
80
- # code will never see the records with blank entries.
81
- sheet.alter_column(:id) do |id, record|
82
- @count ||= 1
83
- if id.nil?
84
- id = @count
85
- @count += 1
86
- else
87
- @count = id + 1
88
- end
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
- id
91
- end
92
-
93
- sheet.records.map(&:id) # => [1, 2, 3, 4]
94
-
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
-
104
- sheet.records.map(&:date) # => ["1986-04-08", "1974-02-22", "", "1901-03-02"]
105
-
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
-
109
- # Now calculate everyone's age (ignoring and overriding the existing data)
110
- sheet.alter_column(:age) do |age, record|
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
117
-
118
- sheet.write_to_file( "~/data/people.clean.csv", force_quotes: true ) # save the cleaned data for the next stage of your process
119
- """
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 )
120
128
 
121
129
  You could do something similar to clean and standardize phone numbers, detect and delete/complete invalid emails, etc.
122
130
 
123
131
 
124
132
  === Adding, removing, and renaming columns ===
125
133
 
126
- # Add 72 years to the date born.
127
- sheet.set_column_type( :born, :date ) # replace date strings with Time objects
128
134
 
129
- sheet.add_column( :expected_death_date ) do |date, record|
130
- record.born + ( 72 * 365 * 24 * 60 * 60 )
131
- end
135
+ # Add 72 years to the date born.
136
+ sheet.set_column_type( :born, :date ) # replace date strings with Time objects
137
+
138
+ sheet.add_column( :expected_death_date ) do |date, record|
139
+ record.born + ( 72 * 365 * 24 * 60 * 60 )
140
+ end
141
+
142
+ puts sheet[0].expected_death_date # should be in 2058
132
143
 
133
- puts sheet[0].expected_death_date # should be in 2058
144
+ # But that's just morbid, so we drop the column
145
+ sheet.drop_column( :expected_death_date )
134
146
 
135
- # But that's just morbid, so we drop the column
136
- sheet.drop_column( :expected_death_date )
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 )
137
149
 
138
- # Or, if you think you need the information, but need to be a bit more euphemistic about it
139
- sheet.rename_column( :expected_death_date, :expiration_date )
140
150
 
141
151
  === Using columns ===
142
152
 
143
- sheet.set_column_type( :id, :integer )
144
153
 
145
154
 
146
- # Returns each record's id, as an array
147
- sheet.column( :id ).max # ==> 4
155
+ sheet.set_column_type( :id, :integer )
156
+
157
+
158
+ # Returns each record's id as an array of integers
159
+ sheet.column( :id ).max # ==> 4
160
+
161
+
162
+ === Builder ===
148
163
 
164
+ You have an array of objects. You want to write them to a spreadsheet.
149
165
 
166
+ ```ruby
167
+ sb = CsvMadness::Builder.new do |sb|
168
+ sb.column( :id )
169
+ sb.column( :addressee, "addressee_custom" )
170
+ sb.column( :street_address, "primary_address.street_address" )
171
+ sb.column( :supplemental_address_1, "primary_address.supplemental_address_1" )
172
+ sb.column( :city, "primary_address.city" )
173
+ sb.column( :state_code, "primary_address.state_code" )
174
+ sb.column( :formatted_zip, "primary_address.formatted_zip" )
175
+ sb.column( :phone, "phones.first.phone" )
176
+ sb.column( :leader, "congregation_fieldset.congregation_leader" )
177
+ sb.column( :denomination, "congregation_fieldset.denomination" )
178
+ sb.column( :email, "emails.first.email" )
179
+ end
150
180
 
181
+ sheet = sb.build( [address1, address2, address3...] )
182
+ ```
151
183
 
184
+ === Documentation is incomplete ===
152
185
 
153
186
  There are lots of other features, but they'll take time to test and document.
154
187
 
data/Rakefile CHANGED
@@ -20,24 +20,16 @@ Jeweler::Tasks.new do |gem|
20
20
  gem.name = "csv_madness"
21
21
  gem.homepage = "http://github.com/darthschmoo/csv_madness"
22
22
  gem.license = "MIT"
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.}
23
+ gem.summary = "CSV Madness turns your CSV rows into happycrazy objects."
24
+ 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."
25
25
  gem.email = "keeputahweird@gmail.com"
26
26
  gem.authors = ["Bryce Anderson"]
27
+
27
28
  # 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" ]
29
+ gem.files = Dir.glob( File.join( ".", "lib", "**", "*.rb" ) ) +
30
+ Dir.glob( File.join( ".", "test", "**", "*" ) ) +
31
+ %w( Gemfile Rakefile LICENSE.txt README.rdoc VERSION CHANGELOG.markdown )
32
+
41
33
  end
42
34
 
43
35
  Jeweler::RubygemsDotOrgTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.6
@@ -1,17 +1,8 @@
1
1
  require 'csv'
2
- require 'fun_with_files'
2
+ require 'fun_with_gems'
3
+ # require 'fun_with_version_strings'
3
4
  require 'time' # to use Time.parse to parse cells to get the date
4
- require 'debugger'
5
5
 
6
- require_relative 'csv_madness/data_accessor_module'
7
- require_relative 'csv_madness/sheet'
8
- require_relative 'csv_madness/record'
6
+ lib_dir = __FILE__.fwf_filepath.dirname
7
+ FunWith::Gems.make_gem_fun( "CsvMadness", :require => lib_dir.join( "csv_madness" ) )
9
8
 
10
-
11
- FunWith::Files::RootPath.rootify( CsvMadness, __FILE__.fwf_filepath.dirname.up )
12
-
13
- CsvMadness.class_eval do
14
- def self.load( csv, opts = {} )
15
- CsvMadness::Sheet.from( csv, opts )
16
- end
17
- end
@@ -0,0 +1,97 @@
1
+ module CsvMadness
2
+ class Builder
3
+ def initialize( &block )
4
+ @columns = {}
5
+ @column_syms = []
6
+ @module = Module.new # for extending
7
+ self.extend( @module )
8
+ yield self
9
+ end
10
+
11
+ def column( sym, method_path = nil, &block )
12
+ warn( "#{sym} already defined. Overwriting." ) if @column_syms.include?( sym )
13
+
14
+ @column_syms << sym
15
+ @columns[sym] = if block_given?
16
+ block
17
+ elsif method_path
18
+ method_path
19
+ else
20
+ Proc.new(&sym)
21
+ end
22
+ end
23
+
24
+ # Three :on_error values:
25
+ # :print => Put an error message in the cell instead of a value
26
+ # :raise => Raise the error, halting the process
27
+ # :ignore => Hand back an empty cell
28
+ #
29
+ # Although ideally it should be configurable by column...
30
+ def build( objects, opts = { :on_error => :print } )
31
+ spreadsheet = CsvMadness::Sheet.new( @column_syms )
32
+
33
+ for object in objects
34
+ STDOUT << "."
35
+ record = {}
36
+ for sym in @column_syms
37
+ record[sym] = build_cell( object, sym, opts )
38
+ end
39
+
40
+ spreadsheet.add_record( record ) # hash form
41
+ end
42
+
43
+ spreadsheet
44
+ end
45
+
46
+ def build_cell( object, sym, opts = { :on_error => :print } )
47
+ column = @columns[sym]
48
+
49
+ case column
50
+ when String
51
+ build_cell_by_pathstring( object, column, opts )
52
+ when Proc
53
+ build_cell_by_proc( object, column, opts )
54
+ else
55
+ "no idea what to do"
56
+ end
57
+ end
58
+
59
+ def def( method_name, &block )
60
+ @module.send( :define_method, method_name, &block )
61
+ end
62
+
63
+ protected
64
+ def build_cell_by_pathstring( object, str, opts )
65
+ handle_cell_build_error( :build_cell_by_pathstring, opts ) do
66
+ for method in str.split(".").map(&:to_sym)
67
+ object = object.send(method)
68
+ end
69
+
70
+ object
71
+ end
72
+ end
73
+
74
+ def build_cell_by_proc( object, proc, opts )
75
+ handle_cell_build_error( :build_cell_by_proc, opts ) do
76
+ proc.call(object)
77
+ end
78
+ end
79
+
80
+ def handle_cell_build_error( caller, opts, &block )
81
+ begin
82
+ yield
83
+ end
84
+ rescue Exception => e
85
+ case opts[:on_error]
86
+ when nil, :print
87
+ puts "error #{e.message} #{caller}" if opts[:verbose]
88
+ "ERROR: #{e.message} (#{caller}())"
89
+ when :raise
90
+ puts "Re-raisinge error #{e.message}. Set opts[:on_error] to :print or :ignore if you want Builder to continue on errors."
91
+ raise e
92
+ when :ignore
93
+ ""
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,12 @@
1
+ module CsvMadness
2
+ module GemAPI
3
+ def load( csv, opts = {} )
4
+ CsvMadness::Sheet.from( csv, opts )
5
+ end
6
+
7
+ def build( objects, &block )
8
+ builder = CsvMadness::Builder.new(&block)
9
+ builder.build( objects )
10
+ end
11
+ end
12
+ end
@@ -8,8 +8,9 @@ module CsvMadness
8
8
  # symbol.
9
9
  class Record
10
10
  attr_accessor :csv_data
11
+
11
12
  def initialize( data )
12
- @csv_data = data
13
+ import_record_data( data )
13
14
  end
14
15
 
15
16
  def [] key
@@ -34,6 +35,10 @@ module CsvMadness
34
35
  self.class.spreadsheet.columns
35
36
  end
36
37
 
38
+ def self.columns
39
+ self.spreadsheet.columns
40
+ end
41
+
37
42
  def self.spreadsheet= sheet
38
43
  @spreadsheet = sheet
39
44
  end
@@ -46,8 +51,40 @@ module CsvMadness
46
51
  self.columns.map{|col| self.send(col) }.to_csv( opts )
47
52
  end
48
53
 
54
+ def to_hash
55
+ self.columns.inject({}){ |hash, col| hash[col] = self.send( col ); hash }
56
+ end
57
+
58
+ def to_a
59
+ self.to_hash.to_a
60
+ end
61
+
49
62
  def blank?( col )
50
63
  (self.send( col.to_sym ).to_s || "").strip.length == 0
51
64
  end
65
+
66
+ protected
67
+ def import_record_data( data )
68
+ case data
69
+ when Array
70
+ csv_data = CSV::Row.new( self.columns, data )
71
+ when Hash
72
+ fields = self.columns.map do |col|
73
+ data[col]
74
+ end
75
+ # for col in self.columns
76
+ # fields << data[col]
77
+ # end
78
+
79
+ csv_data = CSV::Row.new( self.columns, fields )
80
+ when CSV::Row
81
+ csv_data = data
82
+ else
83
+ raise "record.import_record_data() doesn't take objects of type #{data.inspect}" unless data.respond_to?(:csv_data)
84
+ csv_data = data.csv_data.clone
85
+ end
86
+
87
+ @csv_data = csv_data
88
+ end
52
89
  end
53
90
  end