csv_madness 0.0.3 → 0.0.4

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.
@@ -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: