csv_madness 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ 0.0.4
2
+ =====
3
+
4
+ * Fixed serious bug for default column names. record.middle_name instead of record.middlename
5
+ * Spreadsheet can be re-read from file by calling @sheet.reload_spreadsheet(opts)
6
+
7
+
8
+ 0.0.3
9
+ =====
10
+
11
+ * Feature: can add and drop columns from spreadsheets
12
+
13
+
14
+ 0.0.2
15
+ =====
16
+
17
+ * Aw, hell. I don't remember.
data/Gemfile CHANGED
@@ -5,6 +5,9 @@ source "http://rubygems.org"
5
5
 
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
8
+
9
+ gem "fun_with_files"
10
+
8
11
  group :development do
9
12
  gem "shoulda", ">= 0"
10
13
  gem "rdoc", "~> 3.12"
data/README.rdoc CHANGED
@@ -121,9 +121,9 @@ sheet.write_to_file( "~/data/people.clean.csv", force_quotes: true ) # save the
121
121
  You could do something similar to clean and standardize phone numbers, detect and delete/complete invalid emails, etc.
122
122
 
123
123
 
124
- === Adding and removing columns
124
+ === Adding, removing, and renaming columns ===
125
125
 
126
- # Add 72 years to the date born.
126
+ # Add 72 years to the date born.
127
127
  sheet.set_column_type( :born, :date ) # replace date strings with Time objects
128
128
 
129
129
  sheet.add_column( :expected_death_date ) do |date, record|
@@ -135,8 +135,10 @@ puts sheet[0].expected_death_date # should be in 2058
135
135
  # But that's just morbid, so we drop the column
136
136
  sheet.drop_column( :expected_death_date )
137
137
 
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 )
138
140
 
139
- === Using columns
141
+ === Using columns ===
140
142
 
141
143
  sheet.set_column_type( :id, :integer )
142
144
 
data/Rakefile CHANGED
@@ -33,9 +33,11 @@ Jeweler::Tasks.new do |gem|
33
33
  "./VERSION",
34
34
  "./README.rdoc",
35
35
  "./Rakefile",
36
+ "./CHANGELOG.markdown",
36
37
  "./test/csv/simple.csv",
37
38
  "./test/helper.rb",
38
- "./test/test_csv_madness.rb" ]
39
+ "./test/test_csv_madness.rb",
40
+ "./test/test_sheet.rb" ]
39
41
  end
40
42
 
41
43
  Jeweler::RubygemsDotOrgTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
@@ -13,11 +13,21 @@ module CsvMadness
13
13
  end
14
14
 
15
15
  def [] key
16
- @csv_data[key]
16
+ case key
17
+ when String, Integer
18
+ @csv_data[key]
19
+ when Symbol
20
+ @csv_data[key.to_s]
21
+ end
17
22
  end
18
23
 
19
24
  def []= key, val
20
- @csv_data[key] = val
25
+ case key
26
+ when String, Integer
27
+ @csv_data[key] = val
28
+ when Symbol
29
+ @csv_data[key.to_s] = val
30
+ end
21
31
  end
22
32
 
23
33
  def columns
@@ -37,7 +47,7 @@ module CsvMadness
37
47
  end
38
48
 
39
49
  def blank?( col )
40
- (self.send( col ).to_s || "").strip.length == 0
50
+ (self.send( col.to_sym ).to_s || "").strip.length == 0
41
51
  end
42
52
  end
43
53
  end
@@ -30,7 +30,8 @@ module CsvMadness
30
30
  # Used to make getter/setter names out of the original header strings.
31
31
  # " hello;: world! " => :hello_world
32
32
  def self.getter_name( name )
33
- name = name.strip.gsub(/\s+/,"_").gsub(/(\W|_)+/, "" ).downcase
33
+ name = name.strip.gsub( /\s+/, "_" ).gsub( /(\W|_)+/, "_" ).downcase
34
+ name = name.gsub( /_+$/, "" )
34
35
  if name.match( /^\d/ )
35
36
  name = "_#{name}"
36
37
  end
@@ -43,9 +44,17 @@ module CsvMadness
43
44
  def self.add_search_path( path )
44
45
  @search_paths ||= []
45
46
  path = Pathname.new( path ).expand_path
47
+ unless path.directory?
48
+ raise "The given path does not exist"
49
+ end
50
+
46
51
  @search_paths << path unless @search_paths.include?( path )
47
52
  end
48
53
 
54
+ def self.search_paths
55
+ @search_paths
56
+ end
57
+
49
58
  def self.from( csv_file, opts = {} )
50
59
  if f = find_spreadsheet_in_filesystem( csv_file )
51
60
  Sheet.new( f, opts )
@@ -83,7 +92,7 @@ module CsvMadness
83
92
  end
84
93
 
85
94
  def self.write_to_file( spreadsheet, file, opts = {} )
86
- file = Pathname.new(file).expand_path
95
+ file = file.fwf_filepath.expand_path
87
96
  File.open( file, "w" ) do |f|
88
97
  f << spreadsheet.to_csv( opts )
89
98
  end
@@ -99,7 +108,7 @@ module CsvMadness
99
108
  end
100
109
  end
101
110
 
102
- attr_reader :columns, :records, :spreadsheet_file, :record_class
111
+ attr_reader :columns, :index_columns, :records, :spreadsheet_file, :record_class
103
112
  # opts:
104
113
  # index: ( [:id, :id2 ] )
105
114
  # columns you want mapped for quick
@@ -115,28 +124,37 @@ module CsvMadness
115
124
  # header: false
116
125
  # anything else, we assume the csv file has a header row
117
126
  def initialize( spreadsheet, opts = {} )
118
- @spreadsheet_file = self.class.find_spreadsheet_in_filesystem( spreadsheet )
127
+ if spreadsheet.is_a?(Array)
128
+ @spreadsheet_file = nil
129
+
130
+ else
131
+ @spreadsheet_file = self.class.find_spreadsheet_in_filesystem( spreadsheet )
132
+ end
119
133
  @opts = opts
120
134
  @opts[:header] = (@opts[:header] == false ? false : true) # true unless already explicitly set to false
121
135
 
136
+ reload_spreadsheet
137
+ end
138
+
139
+ def reload_spreadsheet( opts = @opts )
122
140
  load_csv
123
-
124
- set_initial_columns( @opts[:columns] )
125
-
126
-
141
+ set_initial_columns( opts[:columns] )
127
142
  create_record_class
128
143
  package
129
144
 
130
- @index_columns = case @opts[:index]
145
+ set_index_columns( opts[:index] )
146
+ reindex
147
+ end
148
+
149
+ def set_index_columns( index_columns )
150
+ @index_columns = case index_columns
131
151
  when NilClass
132
152
  []
133
153
  when Symbol
134
- [ @opts[:index] ]
154
+ [ index_columns ]
135
155
  when Array
136
- @opts[:index]
156
+ index_columns
137
157
  end
138
-
139
- reindex
140
158
  end
141
159
 
142
160
  def [] offset
@@ -196,8 +214,9 @@ module CsvMadness
196
214
  end
197
215
  end
198
216
 
199
- # if column doesn't exist, silently fails. Proper behavior? Dunno.
200
217
  def alter_column( column, blank = :undefined, &block )
218
+ raise "Column does not exist: #{column}" unless @columns.include?( column )
219
+
201
220
  if cindex = @columns.index( column )
202
221
  for record in @records
203
222
  if record.blank?(column) && blank != :undefined
@@ -209,8 +228,9 @@ module CsvMadness
209
228
  end
210
229
  end
211
230
 
231
+ # If no block given, adds an empty column
212
232
  def add_column( column, &block )
213
- raise "Column already exists" if @columns.include?( column )
233
+ raise "Column already exists: #{column}" if @columns.include?( column )
214
234
  @columns << column
215
235
 
216
236
  # add empty column to each row
@@ -241,10 +261,45 @@ module CsvMadness
241
261
  update_data_accessor_module
242
262
  end
243
263
 
264
+ def rename_column( column, new_name )
265
+ @columns[@columns.index(column)] = new_name
266
+ rename_index_column( column, new_name ) if @index_columns.include?( column )
267
+ update_data_accessor_module
268
+ end
269
+
244
270
  def set_column_type( column, type, blank = :undefined )
245
271
  alter_column( column, blank, &COLUMN_TYPES[type] )
246
272
  end
247
273
 
274
+
275
+ # If :reverse_merge is true, then the dest column is only overwritten for records where :dest is blank
276
+ def merge_columns( source, dest, opts = {} )
277
+ opts = { :drop_source => true, :reverse_merge => false, :default => "" }.merge( opts )
278
+ column_must_exist( source, dest )
279
+
280
+ self.records.each do |record|
281
+ if opts[:reverse_merge] == false || record.blank?( dest )
282
+ record[dest] = record.blank?(source) ? opts[:default] : record[source]
283
+ end
284
+ end
285
+
286
+ self.drop_column( source ) if opts[:drop_source]
287
+ end
288
+
289
+ # By default, the
290
+ def concat_columns( col1, col2, opts = {} )
291
+ opts = {:separator => '', :out => col1}.merge( opts )
292
+
293
+ column_must_exist( col1, col2 )
294
+ self.add_column( opts[:out] ) unless self.columns.include?( opts[:out] )
295
+
296
+ for record in self.records
297
+ record[ opts[:out] ] = "#{record[col1]}#{opts[:separator]}#{record[col2]}"
298
+ end
299
+ end
300
+
301
+ alias :concatenate :concat_columns
302
+
248
303
  # Note: If a block is given, the mod arg will be ignored.
249
304
  def add_record_methods( mod = nil, &block )
250
305
  if block_given?
@@ -285,6 +340,13 @@ module CsvMadness
285
340
  end
286
341
  end
287
342
 
343
+ # shouldn't require reindex
344
+ def rename_index_column( column, new_name )
345
+ @index_columns[ @index_columns.index( column ) ] = new_name
346
+ @indexes[new_name] = @indexes[column]
347
+ @indexes.delete(column)
348
+ end
349
+
288
350
  # Each spreadsheet has its own anonymous record class, and each CSV row instantiates
289
351
  # a record of this class. This is where the getters and setters come from.
290
352
  def create_record_class
@@ -374,5 +436,11 @@ module CsvMadness
374
436
  def update_data_accessor_module
375
437
  @module.remap_accessors( columns_to_mapping )
376
438
  end
439
+
440
+ def column_must_exist( *cols )
441
+ for col in cols
442
+ raise ArgumentError.new( "#{caller[0]}: column :#{col} does not exist.") unless self.columns.include?(col)
443
+ end
444
+ end
377
445
  end
378
446
  end
data/lib/csv_madness.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'csv'
2
- require 'pathname'
2
+ require 'fun_with_files'
3
3
  require 'time' # to use Time.parse to parse cells to get the date
4
4
  require 'debugger'
5
5
 
@@ -7,6 +7,9 @@ require_relative 'csv_madness/data_accessor_module'
7
7
  require_relative 'csv_madness/sheet'
8
8
  require_relative 'csv_madness/record'
9
9
 
10
+
11
+ FunWith::Files::RootPath.rootify( CsvMadness, __FILE__.fwf_filepath.dirname.up )
12
+
10
13
  CsvMadness.class_eval do
11
14
  def self.load( csv, opts = {} )
12
15
  CsvMadness::Sheet.from( csv, opts )
data/test/helper.rb CHANGED
@@ -19,3 +19,85 @@ require 'csv_madness'
19
19
 
20
20
  class Test::Unit::TestCase
21
21
  end
22
+
23
+ class MadTestCase < Test::Unit::TestCase
24
+ MARY_ID = "1"
25
+ BILL_ID = "2"
26
+ DARWIN_ID = "3"
27
+ CHUCK_ID = "4"
28
+
29
+ def load_mary
30
+ id = @simple.index_columns.first
31
+ @mary = @simple.fetch( id, MARY_ID )
32
+ end
33
+
34
+ def load_bill
35
+ id = @simple.index_columns.first
36
+ @bill = @simple.fetch( id, BILL_ID )
37
+ end
38
+
39
+ def load_darwin
40
+ id = @simple.index_columns.first
41
+ @darwin = @simple.fetch( id, DARWIN_ID )
42
+ end
43
+
44
+ def load_chuck
45
+ id = @simple.index_columns.first
46
+ @chuck = @simple.fetch( id, CHUCK_ID )
47
+ end
48
+
49
+ def set_person_records
50
+ load_mary
51
+ load_darwin
52
+ load_bill
53
+ load_chuck
54
+ end
55
+
56
+ def muck_up_spreadsheet
57
+ @simple.add_column(:scrambled_name) do |val, record|
58
+ record.fname.chars.map(&:to_s).zip( record.lname.chars.map(&:to_s) ).flatten.compact.join
59
+ end
60
+
61
+ @simple.alter_column(:id) do |val|
62
+ (val.to_i << 8) % 27
63
+ end
64
+
65
+ @simple.set_column_type(:id, :float)
66
+ @simple.set_column_type(:born, :date)
67
+
68
+ @simple.alter_column(:born) do |val|
69
+ if val.is_a?(String)
70
+ -1.0
71
+ else
72
+ (Time.now - val).to_f
73
+ end
74
+ end
75
+
76
+ @simple.drop_column(:fname)
77
+ @simple.drop_column(:lname)
78
+ end
79
+
80
+ def set_spreadsheet_paths
81
+ @csv_search_path = Pathname.new( __FILE__ ).dirname.join("csv")
82
+ @csv_output_path = @csv_search_path.join("out")
83
+ CsvMadness::Sheet.add_search_path( @csv_search_path )
84
+ CsvMadness::Sheet.add_search_path( @csv_output_path )
85
+ end
86
+
87
+ def load_simple_spreadsheet
88
+ @simple = CsvMadness.load( "simple.csv", index: [:id] )
89
+ end
90
+
91
+ def unload_simple_spreadsheet
92
+ @simple = nil
93
+ end
94
+
95
+ def empty_output_folder
96
+ if defined?(FileUtils)
97
+ FileUtils.rm_rf( Dir.glob( @csv_output_path.join("**","*") ) )
98
+ else
99
+ puts "fileutils not defined"
100
+ `rm -rf #{@csv_output_path.join('*')}`
101
+ end
102
+ end
103
+ end
@@ -1,26 +1,21 @@
1
1
  require 'helper'
2
2
 
3
- class TestCsvMadness < Test::Unit::TestCase
3
+ class TestCsvMadness < MadTestCase
4
4
  context "all:" do
5
5
  setup do
6
- @csv_search_path = Pathname.new( __FILE__ ).dirname.join("csv")
7
- @csv_output_path = @csv_search_path.join("out")
8
- CsvMadness::Sheet.add_search_path( @csv_search_path )
6
+ set_spreadsheet_paths
7
+ load_simple_spreadsheet
9
8
  end
10
9
 
11
10
  teardown do
12
- if defined?(FileUtils)
13
- FileUtils.rm_rf( Dir.glob( @csv_output_path.join("**","*") ) )
14
- else
15
- puts "fileutils not defined"
16
- `rm -rf #{@csv_output_path.join('*')}`
17
- end
11
+ empty_output_folder
18
12
  end
19
13
 
20
14
  context "testing sheet basics" do
21
15
  should "not accept duplicate search paths" do
16
+ @path_count = CsvMadness::Sheet.search_paths.length
22
17
  CsvMadness::Sheet.add_search_path( Pathname.new( __FILE__ ).dirname.join("csv") )
23
- assert_equal 1, CsvMadness::Sheet.instance_variable_get("@search_paths").length
18
+ assert_equal @path_count, CsvMadness::Sheet.search_paths.length
24
19
  end
25
20
 
26
21
  should "load a simple spreadsheet" do
@@ -49,11 +44,11 @@ class TestCsvMadness < Test::Unit::TestCase
49
44
  context "testing transformations" do
50
45
  context "with a simple spreadsheet loaded" do
51
46
  setup do
52
- @simple = CsvMadness.load( "simple.csv", index: [:id] )
47
+ load_simple_spreadsheet
53
48
  end
54
49
 
55
50
  teardown do
56
- @simple = nil
51
+ unload_simple_spreadsheet
57
52
  end
58
53
 
59
54
  should "transform every cell" do
@@ -154,6 +149,49 @@ class TestCsvMadness < Test::Unit::TestCase
154
149
  assert_equal records.length, records.compact.length
155
150
  assert_equal 2, records.length
156
151
  assert_equal "1", records.first.id
152
+ end
153
+
154
+ should "rename columns" do
155
+ load_mary
156
+
157
+ assert_equal "Mary", @mary.fname
158
+ assert_equal "Moore", @mary.lname
159
+ assert_equal "Mary", @mary[1]
160
+ assert_equal "Moore", @mary[2]
161
+
162
+ @simple.rename_column( :fname, :first_name )
163
+ @simple.rename_column( :lname, :last_name )
164
+
165
+ assert_equal "Mary", @mary.first_name
166
+ assert_equal "Moore", @mary.last_name
167
+ assert_equal "Mary", @mary[1]
168
+ assert_equal "Moore", @mary[2]
169
+ end
170
+
171
+ should "rename an index column" do
172
+ @simple.rename_column( :id, :identifier )
173
+ @mary = @simple.fetch( :identifier, "1" )
174
+ assert_equal "1", @mary.identifier
175
+ end
176
+
177
+ should "rename an index column and ensure that the outputted spreadsheet has the new column name" do
178
+ @simple.rename_column( :id, :identifier )
179
+ @outfile = @csv_output_path.join("output.csv")
180
+ @simple.write_to_file( @outfile, force_quotes: true )
181
+
182
+ @simple = CsvMadness.load( @outfile, index: [:identifier] )
183
+
184
+ load_mary
185
+ assert_equal "Mary", @mary.fname
186
+ end
187
+
188
+ should "filter! records" do
189
+ @simple.filter! do |record|
190
+ record.id == BILL_ID || record.id == CHUCK_ID
191
+ end
192
+
193
+ assert_equal 2, @simple.records.length
194
+
157
195
  end
158
196
  end
159
197
  end
@@ -194,7 +232,7 @@ class TestCsvMadness < Test::Unit::TestCase
194
232
  context "testing add/remove column transformations" do
195
233
  context "with simple spreadsheet loaded" do
196
234
  setup do
197
- load_simple
235
+ load_simple_spreadsheet
198
236
  end
199
237
 
200
238
  should "add column" do
@@ -221,12 +259,4 @@ class TestCsvMadness < Test::Unit::TestCase
221
259
  end
222
260
  end
223
261
  end
224
-
225
- def load_simple
226
- @simple = CsvMadness.load( "simple.csv", index: [:id] )
227
- end
228
-
229
- def load_mary
230
- @mary = @simple.fetch( :id, "1" )
231
- end
232
262
  end
@@ -0,0 +1,24 @@
1
+ require 'helper'
2
+
3
+ class TestCsvMadness < MadTestCase
4
+ context "testing getter_name()" do
5
+ should "return proper function names" do
6
+ assert_equal :hello_world, CsvMadness::Sheet.getter_name( " heLLo __ world " )
7
+ assert_equal :_0_hello_world, CsvMadness::Sheet.getter_name( "0 heLLo __ worlD!!! " )
8
+ end
9
+ end
10
+
11
+ context "testing default spreadsheet paths" do
12
+ should "only load existing paths" do
13
+ assert_raise(RuntimeError) do
14
+ CsvMadness::Sheet.add_search_path( CsvMadness.root.join("rocaganthor") )
15
+ end
16
+ end
17
+
18
+ should "check a search path for files to load" do
19
+ CsvMadness::Sheet.add_search_path( CsvMadness.root.join("test", "csv") )
20
+ sheet = CsvMadness.load( "with_nils.csv" )
21
+ assert sheet.is_a?(CsvMadness::Sheet)
22
+ end
23
+ end
24
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csv_madness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-03 00:00:00.000000000 Z
12
+ date: 2013-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fun_with_files
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: shoulda
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +116,7 @@ extra_rdoc_files:
100
116
  - LICENSE.txt
101
117
  - README.rdoc
102
118
  files:
119
+ - ./CHANGELOG.markdown
103
120
  - ./Gemfile
104
121
  - ./README.rdoc
105
122
  - ./Rakefile
@@ -111,6 +128,7 @@ files:
111
128
  - ./test/csv/simple.csv
112
129
  - ./test/helper.rb
113
130
  - ./test/test_csv_madness.rb
131
+ - ./test/test_sheet.rb
114
132
  - LICENSE.txt
115
133
  - README.rdoc
116
134
  homepage: http://github.com/darthschmoo/csv_madness
@@ -128,7 +146,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
146
  version: '0'
129
147
  segments:
130
148
  - 0
131
- hash: 2889880782640461400
149
+ hash: 1185350757253474800
132
150
  required_rubygems_version: !ruby/object:Gem::Requirement
133
151
  none: false
134
152
  requirements: