csv_madness 0.0.6 → 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 +5 -13
- data/Gemfile +2 -3
- data/README.rdoc +55 -47
- data/Rakefile +79 -52
- data/VERSION +1 -1
- data/lib/csv_madness/builder.rb +4 -1
- data/lib/csv_madness/data_accessor_module.rb +59 -24
- data/lib/csv_madness/exceptions.rb +16 -0
- data/lib/csv_madness/record.rb +3 -1
- data/lib/csv_madness/sheet.rb +33 -387
- 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 +1 -2
- data/test/helper.rb +5 -0
- data/test/test_builder.rb +8 -4
- data/test/test_csv_madness.rb +6 -7
- data/test/test_reloading_spreadsheet.rb +2 -1
- data/test/test_sheet.rb +28 -18
- metadata +54 -36
data/lib/csv_madness/sheet.rb
CHANGED
|
@@ -1,144 +1,16 @@
|
|
|
1
1
|
module CsvMadness
|
|
2
2
|
class Sheet
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
begin
|
|
10
|
-
rval = Integer(cell)
|
|
11
|
-
rescue
|
|
12
|
-
# do nothing
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
unless rval.is_a?(Integer)
|
|
16
|
-
begin
|
|
17
|
-
rval = Float(cell)
|
|
18
|
-
rescue
|
|
19
|
-
# do nothing
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
rval
|
|
25
|
-
end,
|
|
26
|
-
|
|
27
|
-
integer: Proc.new do |cell, record|
|
|
28
|
-
begin
|
|
29
|
-
Integer(cell)
|
|
30
|
-
rescue
|
|
31
|
-
cell
|
|
32
|
-
end
|
|
33
|
-
end,
|
|
34
|
-
|
|
35
|
-
float: Proc.new do |cell, record|
|
|
36
|
-
begin
|
|
37
|
-
Float(cell)
|
|
38
|
-
rescue
|
|
39
|
-
cell
|
|
40
|
-
end
|
|
41
|
-
end,
|
|
42
|
-
|
|
43
|
-
date: Proc.new do |cell, record|
|
|
44
|
-
begin
|
|
45
|
-
parse = Time.parse( cell || "" )
|
|
46
|
-
rescue ArgumentError
|
|
47
|
-
if cell =~ /^Invalid Time Format: /
|
|
48
|
-
parse = cell
|
|
49
|
-
else
|
|
50
|
-
parse = "Invalid Time Format: <#{cell}>"
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
parse
|
|
55
|
-
end
|
|
56
|
-
}
|
|
3
|
+
extend SheetMethods::ClassMethods
|
|
4
|
+
include SheetMethods::Base
|
|
5
|
+
include SheetMethods::Indexing
|
|
6
|
+
include SheetMethods::FileMethods
|
|
7
|
+
include SheetMethods::ColumnMethods
|
|
8
|
+
include SheetMethods::RecordMethods
|
|
57
9
|
|
|
58
|
-
FORBIDDEN_COLUMN_NAMES = [:to_s] # breaks things hard when you use them. Probably not comprehensive, sadly.
|
|
59
|
-
|
|
60
|
-
# Used to make getter/setter names out of the original header strings.
|
|
61
|
-
# " hello;: world! " => :hello_world
|
|
62
|
-
def self.getter_name( name )
|
|
63
|
-
name = name.strip.gsub( /\s+/, "_" ).gsub( /(\W|_)+/, "_" ).downcase
|
|
64
|
-
name = name.gsub( /_+$/, "" )
|
|
65
|
-
if name.match( /^\d/ )
|
|
66
|
-
name = "_#{name}"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
name.to_sym
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Paths to be searched when CsvMadness.load( "filename.csv" ) is called.
|
|
74
|
-
def self.add_search_path( path )
|
|
75
|
-
@search_paths ||= []
|
|
76
|
-
path = Pathname.new( path ).expand_path
|
|
77
|
-
unless path.directory?
|
|
78
|
-
raise "The given path does not exist"
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
@search_paths << path unless @search_paths.include?( path )
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def self.search_paths
|
|
85
|
-
@search_paths
|
|
86
|
-
end
|
|
87
10
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Sheet.new( f, opts )
|
|
91
|
-
else
|
|
92
|
-
raise "File not found."
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Search absolute/relative-to-current-dir before checking
|
|
97
|
-
# search paths.
|
|
98
|
-
def self.find_spreadsheet_in_filesystem( name )
|
|
99
|
-
@search_paths ||= []
|
|
100
|
-
|
|
101
|
-
expanded_path = Pathname.new( name ).expand_path
|
|
102
|
-
if expanded_path.exist?
|
|
103
|
-
return expanded_path
|
|
104
|
-
else # look for it in the search paths
|
|
105
|
-
@search_paths.each do |p|
|
|
106
|
-
file = p.join( name )
|
|
107
|
-
if file.exist? && file.file?
|
|
108
|
-
return p.join( name )
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
nil
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# opts are passed to underlying CSV (:row_sep, :encoding, :force_quotes)
|
|
117
|
-
def self.to_csv( spreadsheet, opts = {} )
|
|
118
|
-
out = spreadsheet.columns.to_csv( opts )
|
|
119
|
-
spreadsheet.records.inject( out ) do |output, record|
|
|
120
|
-
output << record.to_csv( opts )
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def self.write_to_file( spreadsheet, file, opts = {} )
|
|
125
|
-
file = file.fwf_filepath.expand_path
|
|
126
|
-
File.open( file, "w" ) do |f|
|
|
127
|
-
f << spreadsheet.to_csv( opts )
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def write_to_file( file, opts = {} )
|
|
132
|
-
self.class.write_to_file( self, file, opts )
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def to_csv( opts = {} )
|
|
136
|
-
self.records.inject( self.columns.to_csv( opts ) ) do |output, record|
|
|
137
|
-
output << record.to_csv( opts )
|
|
138
|
-
end
|
|
139
|
-
end
|
|
11
|
+
attr_reader :columns, :index_columns, :record_class, :opts
|
|
12
|
+
attr_accessor :spreadsheet_file
|
|
140
13
|
|
|
141
|
-
attr_reader :columns, :index_columns, :records, :spreadsheet_file, :record_class, :opts
|
|
142
14
|
# opts:
|
|
143
15
|
# index: ( [:id, :id2 ] )
|
|
144
16
|
# columns you want mapped for quick
|
|
@@ -154,6 +26,8 @@ module CsvMadness
|
|
|
154
26
|
# header: false
|
|
155
27
|
# anything else, we assume the csv file has a header row
|
|
156
28
|
def initialize( *args )
|
|
29
|
+
@csv = nil
|
|
30
|
+
|
|
157
31
|
if args.last.is_a?(Hash)
|
|
158
32
|
@opts = args.pop
|
|
159
33
|
else
|
|
@@ -178,117 +52,22 @@ module CsvMadness
|
|
|
178
52
|
reload_spreadsheet
|
|
179
53
|
end
|
|
180
54
|
|
|
181
|
-
def <<( record )
|
|
182
|
-
self.add_record( record )
|
|
183
|
-
end
|
|
184
55
|
|
|
185
|
-
def add_record( record )
|
|
186
|
-
case record
|
|
187
|
-
when Array
|
|
188
|
-
# CSV::Row.new( column names, column_entries ) (in same order as columns, natch)
|
|
189
|
-
record = CSV::Row.new( self.columns, record )
|
|
190
|
-
when Hash
|
|
191
|
-
header = []
|
|
192
|
-
fields = []
|
|
193
|
-
|
|
194
|
-
for col in self.columns
|
|
195
|
-
header << col
|
|
196
|
-
fields << record[col]
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
record = CSV::Row.new( header, fields )
|
|
200
|
-
when CSV::Row
|
|
201
|
-
# do nothing
|
|
202
|
-
else
|
|
203
|
-
raise "sheet.add_record() doesn't take objects of type #{record.inspect}" unless record.respond_to?(:csv_data)
|
|
204
|
-
record = record.csv_data
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
record = @record_class.new( record )
|
|
208
|
-
@records << record
|
|
209
|
-
add_to_indexes( record )
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# record can be the row number (integer from 0...@records.length)
|
|
213
|
-
# record can be the record itself (anonymous class)
|
|
214
|
-
def remove_record( record )
|
|
215
|
-
record = @records[record] if record.is_a?(Integer)
|
|
216
|
-
return if record.nil?
|
|
217
|
-
|
|
218
|
-
self.remove_from_index( record )
|
|
219
|
-
@records.delete( record )
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Here's the deal: you hand us a block, and we'll remove the records for which
|
|
223
|
-
# it yields _true_.
|
|
224
|
-
def remove_records( records = nil, &block )
|
|
225
|
-
if block_given?
|
|
226
|
-
for record in @records
|
|
227
|
-
remove_record( record ) if yield( record ) == true
|
|
228
|
-
end
|
|
229
|
-
else # records should be an array
|
|
230
|
-
for record in records
|
|
231
|
-
self.remove_record( record )
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def reload_spreadsheet( opts = @opts )
|
|
237
|
-
load_csv if @spreadsheet_file
|
|
238
|
-
set_initial_columns( opts[:columns] )
|
|
239
|
-
create_record_class
|
|
240
|
-
package
|
|
241
|
-
|
|
242
|
-
set_index_columns( opts[:index] )
|
|
243
|
-
reindex
|
|
244
|
-
end
|
|
245
56
|
|
|
246
|
-
def
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
[]
|
|
250
|
-
when Symbol
|
|
251
|
-
[ index_columns ]
|
|
252
|
-
when Array
|
|
253
|
-
index_columns
|
|
57
|
+
def to_csv( opts = {} )
|
|
58
|
+
self.records.inject( self.columns.to_csv( opts ) ) do |output, record|
|
|
59
|
+
output << record.to_csv( opts )
|
|
254
60
|
end
|
|
255
61
|
end
|
|
256
|
-
|
|
62
|
+
|
|
257
63
|
def [] offset
|
|
258
64
|
@records[offset]
|
|
259
65
|
end
|
|
260
|
-
|
|
261
|
-
# Fetches an indexed record based on the column indexed and the keying object.
|
|
262
|
-
# If key is an array of keying objects, returns an array of records in the
|
|
263
|
-
# same order that the keying objects appear.
|
|
264
|
-
# Index column should yield a different, unique value for each record.
|
|
265
|
-
def fetch( index_col, key )
|
|
266
|
-
if key.is_a?(Array)
|
|
267
|
-
key.map{ |key| @indexes[index_col][key] }
|
|
268
|
-
else
|
|
269
|
-
@indexes[index_col][key]
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
# function should take an object, and return either true or false
|
|
274
|
-
# returns an array of objects that respond true when put through the
|
|
275
|
-
# meat grinder
|
|
276
|
-
def filter( &block )
|
|
277
|
-
rval = []
|
|
278
|
-
@records.each do |record|
|
|
279
|
-
rval << record if ( yield record )
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
rval
|
|
283
|
-
end
|
|
284
66
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
@records = self.filter( &block )
|
|
288
|
-
reindex
|
|
289
|
-
@records
|
|
67
|
+
def records
|
|
68
|
+
@records ||= []
|
|
290
69
|
end
|
|
291
|
-
|
|
70
|
+
|
|
292
71
|
|
|
293
72
|
# give a copy of the current spreadsheet, but with no records
|
|
294
73
|
def blanked()
|
|
@@ -329,18 +108,6 @@ module CsvMadness
|
|
|
329
108
|
# records
|
|
330
109
|
end
|
|
331
110
|
|
|
332
|
-
def column col
|
|
333
|
-
@records.map(&col)
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
# retrieve multiple columns. Returns an array of the form
|
|
337
|
-
# [ [record1:col1, record1:col2...], [record2:col1, record2:col2...] [...] ]
|
|
338
|
-
def multiple_columns(*args)
|
|
339
|
-
@records.inject([]){ |memo, record|
|
|
340
|
-
memo << args.map{ |arg| record.send(arg) }
|
|
341
|
-
memo
|
|
342
|
-
}
|
|
343
|
-
end
|
|
344
111
|
|
|
345
112
|
# if blank is defined, only the records which are non-blank in that
|
|
346
113
|
# column will actually be yielded. The rest will be set to the provided
|
|
@@ -351,92 +118,6 @@ module CsvMadness
|
|
|
351
118
|
end
|
|
352
119
|
end
|
|
353
120
|
|
|
354
|
-
def alter_column( column, blank = :undefined, &block )
|
|
355
|
-
raise "Column does not exist: #{column}" unless @columns.include?( column )
|
|
356
|
-
|
|
357
|
-
if cindex = @columns.index( column )
|
|
358
|
-
for record in @records
|
|
359
|
-
if record.blank?(column) && blank != :undefined
|
|
360
|
-
record[cindex] = blank
|
|
361
|
-
else
|
|
362
|
-
record[cindex] = yield( record[cindex], record )
|
|
363
|
-
end
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
# If no block given, adds an empty column
|
|
369
|
-
def add_column( column, &block )
|
|
370
|
-
raise "Column already exists: #{column}" if @columns.include?( column )
|
|
371
|
-
raise "#{column} is in the list FORBIDDEN_COLUMN_NAMES" if FORBIDDEN_COLUMN_NAMES.include?(column)
|
|
372
|
-
@columns << column
|
|
373
|
-
|
|
374
|
-
# add empty column to each row
|
|
375
|
-
@records.map{ |r|
|
|
376
|
-
r.csv_data << {column => ""}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
update_data_accessor_module
|
|
380
|
-
|
|
381
|
-
if block_given?
|
|
382
|
-
alter_column( column ) do |val, record|
|
|
383
|
-
yield val, record
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def drop_column( column )
|
|
389
|
-
raise "Column does not exist" unless @columns.include?( column )
|
|
390
|
-
|
|
391
|
-
@columns.delete( column )
|
|
392
|
-
|
|
393
|
-
key = column.to_s
|
|
394
|
-
|
|
395
|
-
@records.map{ |r|
|
|
396
|
-
r.csv_data.delete( key )
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
update_data_accessor_module
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
def rename_column( column, new_name )
|
|
403
|
-
@columns[@columns.index(column)] = new_name
|
|
404
|
-
rename_index_column( column, new_name ) if @index_columns.include?( column )
|
|
405
|
-
update_data_accessor_module
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def set_column_type( column, type, blank = :undefined )
|
|
409
|
-
alter_column( column, blank, &COLUMN_TYPES[type] )
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
# If :reverse_merge is true, then the dest column is only overwritten for records where :dest is blank
|
|
414
|
-
def merge_columns( source, dest, opts = {} )
|
|
415
|
-
opts = { :drop_source => true, :reverse_merge => false, :default => "" }.merge( opts )
|
|
416
|
-
column_must_exist( source, dest )
|
|
417
|
-
|
|
418
|
-
self.records.each do |record|
|
|
419
|
-
if opts[:reverse_merge] == false || record.blank?( dest )
|
|
420
|
-
record[dest] = record.blank?(source) ? opts[:default] : record[source]
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
self.drop_column( source ) if opts[:drop_source]
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
# By default, the
|
|
428
|
-
def concat_columns( col1, col2, opts = {} )
|
|
429
|
-
opts = {:separator => '', :out => col1}.merge( opts )
|
|
430
|
-
|
|
431
|
-
column_must_exist( col1, col2 )
|
|
432
|
-
self.add_column( opts[:out] ) unless self.columns.include?( opts[:out] )
|
|
433
|
-
|
|
434
|
-
for record in self.records
|
|
435
|
-
record[ opts[:out] ] = "#{record[col1]}#{opts[:separator]}#{record[col2]}"
|
|
436
|
-
end
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
alias :concatenate :concat_columns
|
|
440
121
|
|
|
441
122
|
# Note: If a block is given, the mod arg will be ignored.
|
|
442
123
|
def add_record_methods( mod = nil, &block )
|
|
@@ -459,7 +140,7 @@ module CsvMadness
|
|
|
459
140
|
end
|
|
460
141
|
|
|
461
142
|
protected
|
|
462
|
-
attr_writer :columns, :index_columns, :records, :
|
|
143
|
+
attr_writer :columns, :index_columns, :records, :record_class, :opts
|
|
463
144
|
|
|
464
145
|
def load_csv
|
|
465
146
|
|
|
@@ -468,42 +149,6 @@ module CsvMadness
|
|
|
468
149
|
{ write_headers: true,
|
|
469
150
|
headers: ( @opts[:header] ? :first_row : false ) } )
|
|
470
151
|
end
|
|
471
|
-
|
|
472
|
-
def add_to_index( col, key, record )
|
|
473
|
-
(@indexes[col] ||= {})[key] = record
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def add_to_indexes( records )
|
|
477
|
-
if records.is_a?( Array )
|
|
478
|
-
for record in records
|
|
479
|
-
add_to_indexes( record )
|
|
480
|
-
end
|
|
481
|
-
else
|
|
482
|
-
record = records
|
|
483
|
-
for col in @index_columns
|
|
484
|
-
add_to_index( col, record.send(col), record )
|
|
485
|
-
end
|
|
486
|
-
end
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
def remove_from_index( record )
|
|
490
|
-
for col in @index_columns
|
|
491
|
-
@indexes[col].delete( record.send(col) )
|
|
492
|
-
end
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
# Reindexes the record lookup tables.
|
|
496
|
-
def reindex
|
|
497
|
-
@indexes = {}
|
|
498
|
-
add_to_indexes( @records )
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
# shouldn't require reindex
|
|
502
|
-
def rename_index_column( column, new_name )
|
|
503
|
-
@index_columns[ @index_columns.index( column ) ] = new_name
|
|
504
|
-
@indexes[new_name] = @indexes[column]
|
|
505
|
-
@indexes.delete(column)
|
|
506
|
-
end
|
|
507
152
|
|
|
508
153
|
# Each spreadsheet has its own anonymous record class, and each CSV row instantiates
|
|
509
154
|
# a record of this class. This is where the getters and setters come from.
|
|
@@ -547,14 +192,13 @@ module CsvMadness
|
|
|
547
192
|
else
|
|
548
193
|
unless !@csv || columns.length == csv_column_count
|
|
549
194
|
$stderr.puts "Warning <#{@spreadsheet_file}>: columns array does not match the number of columns in the spreadsheet."
|
|
195
|
+
@columns = columns
|
|
550
196
|
compare_columns_to_headers
|
|
551
197
|
end
|
|
552
198
|
end
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
end
|
|
557
|
-
|
|
199
|
+
|
|
200
|
+
raise_on_forbidden_column_names( columns )
|
|
201
|
+
|
|
558
202
|
@columns = columns
|
|
559
203
|
end
|
|
560
204
|
|
|
@@ -563,9 +207,10 @@ module CsvMadness
|
|
|
563
207
|
# column list.
|
|
564
208
|
def compare_columns_to_headers
|
|
565
209
|
headers = fetch_csv_headers
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
210
|
+
max_index = ([@columns, headers].map(&:length).max)
|
|
211
|
+
|
|
212
|
+
for i in 0 ... max_index
|
|
213
|
+
$stdout.puts "\t#{i}: #{@columns[i].inspect} ==> #{headers[i].inspect}"
|
|
569
214
|
end
|
|
570
215
|
end
|
|
571
216
|
|
|
@@ -574,7 +219,7 @@ module CsvMadness
|
|
|
574
219
|
def package
|
|
575
220
|
@records = []
|
|
576
221
|
(@csv || []).each do |row|
|
|
577
|
-
@records << @record_class.new( row )
|
|
222
|
+
@records << @record_class.new( row, self.column_accessors_map )
|
|
578
223
|
end
|
|
579
224
|
end
|
|
580
225
|
|
|
@@ -583,6 +228,8 @@ module CsvMadness
|
|
|
583
228
|
fetch_csv_headers.length
|
|
584
229
|
end
|
|
585
230
|
|
|
231
|
+
# returns a mapping based off the current ordered list of columns.
|
|
232
|
+
# A hash where the first column
|
|
586
233
|
def columns_to_mapping
|
|
587
234
|
@columns.each_with_index.inject({}){ |memo, item|
|
|
588
235
|
memo[item.first] = item.last
|
|
@@ -597,13 +244,12 @@ module CsvMadness
|
|
|
597
244
|
end
|
|
598
245
|
|
|
599
246
|
def update_data_accessor_module
|
|
247
|
+
debugger
|
|
600
248
|
@module.remap_accessors( columns_to_mapping )
|
|
601
249
|
end
|
|
602
|
-
|
|
603
|
-
def
|
|
604
|
-
|
|
605
|
-
raise ArgumentError.new( "#{caller[0]}: column :#{col} does not exist.") unless self.columns.include?(col)
|
|
606
|
-
end
|
|
250
|
+
|
|
251
|
+
def column_accessors_map
|
|
252
|
+
@module.column_accessors_map
|
|
607
253
|
end
|
|
608
254
|
end
|
|
609
255
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
module SheetMethods
|
|
3
|
+
module ClassMethods
|
|
4
|
+
# Used to make getter/setter names out of the original header strings.
|
|
5
|
+
# " hello;: world! " => :hello_world
|
|
6
|
+
def getter_name( name )
|
|
7
|
+
# replace any run of non-word characters with a single "_"
|
|
8
|
+
name = name.downcase.gsub( /(\W|_|\s)+/, "_" )
|
|
9
|
+
|
|
10
|
+
# snip trailing and leading "_"
|
|
11
|
+
name = name.gsub( /(^_+|_+$)/, "" )
|
|
12
|
+
|
|
13
|
+
# add leading "_" when we'd otherwise have a method name starting with a digit
|
|
14
|
+
name = "_#{name}" if name.match( /^\d/ )
|
|
15
|
+
|
|
16
|
+
name.to_sym
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Paths to be searched when CsvMadness.load( "filename.csv" ) is called.
|
|
21
|
+
def add_search_path( path )
|
|
22
|
+
@search_paths ||= []
|
|
23
|
+
path = Pathname.new( path ).expand_path
|
|
24
|
+
unless path.directory?
|
|
25
|
+
raise "The given path does not exist"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@search_paths << path unless @search_paths.include?( path )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def search_paths
|
|
32
|
+
@search_paths
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def from( csv_file, opts = {} )
|
|
36
|
+
if f = find_spreadsheet_in_filesystem( csv_file )
|
|
37
|
+
Sheet.new( f, opts )
|
|
38
|
+
else
|
|
39
|
+
raise "File not found."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Search absolute/relative-to-current-dir before checking
|
|
44
|
+
# search paths.
|
|
45
|
+
def find_spreadsheet_in_filesystem( name )
|
|
46
|
+
@search_paths ||= []
|
|
47
|
+
|
|
48
|
+
expanded_path = Pathname.new( name ).expand_path
|
|
49
|
+
if expanded_path.exist?
|
|
50
|
+
return expanded_path
|
|
51
|
+
else # look for it in the search paths
|
|
52
|
+
@search_paths.each do |p|
|
|
53
|
+
file = p.join( name )
|
|
54
|
+
if file.exist? && file.file?
|
|
55
|
+
return p.join( name )
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# opts are passed to underlying CSV (:row_sep, :encoding, :force_quotes)
|
|
64
|
+
def to_csv( spreadsheet, opts = {} )
|
|
65
|
+
out = spreadsheet.columns.to_csv( opts )
|
|
66
|
+
spreadsheet.records.inject( out ) do |output, record|
|
|
67
|
+
output << record.to_csv( opts )
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def write_to_file( spreadsheet, file, opts = {} )
|
|
72
|
+
file = file.fwf_filepath.expand_path
|
|
73
|
+
file.write( spreadsheet.to_csv( opts ) )
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|