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
|
@@ -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
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
module SheetMethods
|
|
3
|
+
module ColumnMethods
|
|
4
|
+
|
|
5
|
+
COLUMN_TYPES = {
|
|
6
|
+
number: Proc.new do |cell, record|
|
|
7
|
+
rval = cell
|
|
8
|
+
|
|
9
|
+
unless cell.nil? || (cell.is_a?(String) && cell.length == 0)
|
|
10
|
+
|
|
11
|
+
begin
|
|
12
|
+
rval = Integer(cell)
|
|
13
|
+
rescue
|
|
14
|
+
# do nothing
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless rval.is_a?(Integer)
|
|
18
|
+
begin
|
|
19
|
+
rval = Float(cell)
|
|
20
|
+
rescue
|
|
21
|
+
# do nothing
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
rval
|
|
27
|
+
end,
|
|
28
|
+
|
|
29
|
+
integer: Proc.new do |cell, record|
|
|
30
|
+
begin
|
|
31
|
+
Integer(cell)
|
|
32
|
+
rescue
|
|
33
|
+
cell
|
|
34
|
+
end
|
|
35
|
+
end,
|
|
36
|
+
|
|
37
|
+
float: Proc.new do |cell, record|
|
|
38
|
+
begin
|
|
39
|
+
Float(cell)
|
|
40
|
+
rescue
|
|
41
|
+
cell
|
|
42
|
+
end
|
|
43
|
+
end,
|
|
44
|
+
|
|
45
|
+
date: Proc.new do |cell, record|
|
|
46
|
+
begin
|
|
47
|
+
parse = Time.parse( cell || "" )
|
|
48
|
+
rescue ArgumentError
|
|
49
|
+
if cell =~ /^Invalid Time Format: /
|
|
50
|
+
parse = cell
|
|
51
|
+
else
|
|
52
|
+
parse = "Invalid Time Format: <#{cell}>"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
parse
|
|
57
|
+
end
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
FORBIDDEN_COLUMN_NAMES = [:to_s] # breaks things hard when you use them. Probably not comprehensive, sadly.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def column col
|
|
64
|
+
@records.map(&col)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def has_column?( col )
|
|
68
|
+
self.columns.include?( col.to_sym )
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# retrieve multiple columns. Returns an array of the form
|
|
72
|
+
# [ [record1:col1, record1:col2...], [record2:col1, record2:col2...] [...] ]
|
|
73
|
+
def multiple_columns(*args)
|
|
74
|
+
@records.inject([]){ |memo, record|
|
|
75
|
+
memo << args.map{ |arg| record.send(arg) }
|
|
76
|
+
memo
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def alter_column( column, blank = :undefined, &block )
|
|
81
|
+
raise_on_missing_columns( column )
|
|
82
|
+
|
|
83
|
+
if cindex = @columns.index( column )
|
|
84
|
+
for record in @records
|
|
85
|
+
if record.blank?(column) && blank != :undefined
|
|
86
|
+
record[cindex] = blank
|
|
87
|
+
else
|
|
88
|
+
record[cindex] = yield( record[cindex], record )
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# If no block given, adds an empty column.
|
|
95
|
+
# If block given, fills the new column cells when the block is run on each record
|
|
96
|
+
# TODO: Should be able to specify the index of the column
|
|
97
|
+
def add_column( column, &block )
|
|
98
|
+
raise_on_forbidden_column_names( column )
|
|
99
|
+
raise ColumnExistsError.new( "Column already exists: #{column}" ) if @columns.include?( column )
|
|
100
|
+
|
|
101
|
+
@columns << column
|
|
102
|
+
|
|
103
|
+
# add empty column to each row
|
|
104
|
+
@records.map{ |r|
|
|
105
|
+
r.csv_data << {column => ""}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
update_data_accessor_module
|
|
109
|
+
|
|
110
|
+
if block_given?
|
|
111
|
+
alter_column( column ) do |val, record|
|
|
112
|
+
yield val, record
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def drop_column( column )
|
|
118
|
+
raise_on_missing_columns( column )
|
|
119
|
+
|
|
120
|
+
@columns.delete( column )
|
|
121
|
+
|
|
122
|
+
key = column.to_s
|
|
123
|
+
|
|
124
|
+
@records.map{ |r|
|
|
125
|
+
r.csv_data.delete( key )
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
update_data_accessor_module
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def rename_column( column, new_name )
|
|
132
|
+
@columns[@columns.index(column)] = new_name
|
|
133
|
+
rename_index_column( column, new_name ) if @index_columns.include?( column )
|
|
134
|
+
update_data_accessor_module
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def set_column_type( column, type, blank = :undefined )
|
|
138
|
+
alter_column( column, blank, &COLUMN_TYPES[type] )
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# If :reverse_merge is true, then the dest column is only overwritten for records where :dest is blank
|
|
143
|
+
def merge_columns( source, dest, opts = {} )
|
|
144
|
+
opts = { :drop_source => true, :reverse_merge => false, :default => "" }.merge( opts )
|
|
145
|
+
raise_on_missing_columns( source, dest )
|
|
146
|
+
|
|
147
|
+
self.records.each do |record|
|
|
148
|
+
if opts[:reverse_merge] == false || record.blank?( dest )
|
|
149
|
+
record[dest] = record.blank?(source) ? opts[:default] : record[source]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
self.drop_column( source ) if opts[:drop_source]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# By default, the
|
|
157
|
+
def concat_columns( col1, col2, opts = {} )
|
|
158
|
+
opts = {:separator => '', :out => col1}.merge( opts )
|
|
159
|
+
|
|
160
|
+
raise_on_missing_columns( col1, col2 )
|
|
161
|
+
self.add_column( opts[:out] ) unless self.columns.include?( opts[:out] )
|
|
162
|
+
|
|
163
|
+
for record in self.records
|
|
164
|
+
record[ opts[:out] ] = "#{record[col1]}#{opts[:separator]}#{record[col2]}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
alias :concatenate :concat_columns
|
|
169
|
+
|
|
170
|
+
protected
|
|
171
|
+
def raise_on_missing_columns( *cols )
|
|
172
|
+
missing_columns = cols.flatten.reject{ |col| self.has_column?( col ) }
|
|
173
|
+
|
|
174
|
+
raise MissingColumnError.new( "#{caller[0]}: required_column(s) :#{missing_columns.inspect} not found." ) unless missing_columns.fwf_blank?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def forbidden_column_name?( col )
|
|
178
|
+
FORBIDDEN_COLUMN_NAMES.include?( col )
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def raise_on_forbidden_column_names( *cols )
|
|
182
|
+
forbidden_columns = cols.flatten.select{ |col| self.forbidden_column_name?( col ) }
|
|
183
|
+
raise ForbiddenColumnNameError.new( "forbidden names: #{forbidden_columns.inspect}" ) unless forbidden_columns.fwf_blank?
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
module SheetMethods
|
|
3
|
+
module FileMethods
|
|
4
|
+
def write_to_file( file, opts = {} )
|
|
5
|
+
self.class.write_to_file( self, file, opts )
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# pass an arbitrary filepath to save sheet to that filepath
|
|
9
|
+
# call save() with no arguments to overwrite the current spreadsheet file
|
|
10
|
+
# call save(:timestamp) to make a timestamped copy of the spreadsheet file
|
|
11
|
+
#
|
|
12
|
+
# Obviously, the last two require self.spreadsheet_file to exist and be writable
|
|
13
|
+
def save( file = self.spreadsheet_file )
|
|
14
|
+
if file == :timestamp
|
|
15
|
+
if self.spreadsheet_file
|
|
16
|
+
file = self.spreadsheet_file.fwf_filepath.timestamp
|
|
17
|
+
else
|
|
18
|
+
raise ArgumentError.new( "CsvMadness.save(:timestamp) - Spreadsheet must be specified by @spreadsheet_file" )
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if file
|
|
23
|
+
self.write_to_file( self.spreadsheet_file, @opts || {} )
|
|
24
|
+
else
|
|
25
|
+
raise "CsvMadness.save(:timestamp) - Spreadsheet must be specified by spreadsheet_file"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def reload_spreadsheet( opts = @opts )
|
|
31
|
+
load_csv if @spreadsheet_file
|
|
32
|
+
set_initial_columns( opts[:columns] )
|
|
33
|
+
create_record_class
|
|
34
|
+
package
|
|
35
|
+
|
|
36
|
+
set_index_columns( opts[:index] )
|
|
37
|
+
reindex
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
module SheetMethods
|
|
3
|
+
module Indexing
|
|
4
|
+
def set_index_columns( index_columns )
|
|
5
|
+
@index_columns = case index_columns
|
|
6
|
+
when NilClass
|
|
7
|
+
[]
|
|
8
|
+
when Symbol
|
|
9
|
+
[ index_columns ]
|
|
10
|
+
when Array
|
|
11
|
+
index_columns
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_to_index( col, key, record )
|
|
16
|
+
(@indexes[col] ||= {})[key] = record
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_to_indexes( records )
|
|
20
|
+
if records.is_a?( Array )
|
|
21
|
+
for record in records
|
|
22
|
+
add_to_indexes( record )
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
record = records
|
|
26
|
+
for col in @index_columns
|
|
27
|
+
add_to_index( col, record.send(col), record )
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remove_from_index( record )
|
|
33
|
+
for col in @index_columns
|
|
34
|
+
@indexes[col].delete( record.send(col) )
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Reindexes the record lookup tables.
|
|
39
|
+
def reindex
|
|
40
|
+
@indexes = {}
|
|
41
|
+
add_to_indexes( @records )
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# shouldn't require reindex
|
|
45
|
+
def rename_index_column( column, new_name )
|
|
46
|
+
@index_columns[ @index_columns.index( column ) ] = new_name
|
|
47
|
+
@indexes[new_name] = @indexes[column]
|
|
48
|
+
@indexes.delete(column)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module CsvMadness
|
|
2
|
+
module SheetMethods
|
|
3
|
+
module RecordMethods
|
|
4
|
+
def add_record( record )
|
|
5
|
+
case record
|
|
6
|
+
when Array
|
|
7
|
+
# CSV::Row.new( column names, column_entries ) (in same order as columns, natch)
|
|
8
|
+
record = CSV::Row.new( self.columns, record )
|
|
9
|
+
when Hash
|
|
10
|
+
header = []
|
|
11
|
+
fields = []
|
|
12
|
+
|
|
13
|
+
for col in self.columns
|
|
14
|
+
header << col
|
|
15
|
+
fields << record[col]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
record = CSV::Row.new( header, fields )
|
|
19
|
+
when CSV::Row
|
|
20
|
+
# do nothing
|
|
21
|
+
else
|
|
22
|
+
raise "sheet.add_record() doesn't take objects of type #{record.inspect}" unless record.respond_to?(:csv_data)
|
|
23
|
+
record = record.csv_data
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
record = @record_class.new( record, self.column_accessors_map )
|
|
27
|
+
@records << record
|
|
28
|
+
add_to_indexes( record )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias :<< :add_record
|
|
32
|
+
|
|
33
|
+
def add_blank_record( &block )
|
|
34
|
+
self.add_record( {} )
|
|
35
|
+
if block_given?
|
|
36
|
+
yield self.records.last
|
|
37
|
+
else
|
|
38
|
+
self.records.last
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# record can be the row number (integer from 0...@records.length)
|
|
43
|
+
# record can be the record itself (anonymous class)
|
|
44
|
+
def remove_record( record )
|
|
45
|
+
record = @records[record] if record.is_a?(Integer)
|
|
46
|
+
return if record.nil?
|
|
47
|
+
|
|
48
|
+
self.remove_from_index( record )
|
|
49
|
+
@records.delete( record )
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Here's the deal: you hand us a block, and we'll remove the records for which
|
|
53
|
+
# it yields _true_.
|
|
54
|
+
def remove_records( records = nil, &block )
|
|
55
|
+
if block_given?
|
|
56
|
+
for record in @records
|
|
57
|
+
remove_record( record ) if yield( record ) == true
|
|
58
|
+
end
|
|
59
|
+
else # records should be an array
|
|
60
|
+
for record in records
|
|
61
|
+
self.remove_record( record )
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Fetches an indexed record based on the column indexed and the keying object.
|
|
67
|
+
# If key is an array of keying objects, returns an array of records in the
|
|
68
|
+
# same order that the keying objects appear.
|
|
69
|
+
# Index column should yield a different, unique value for each record.
|
|
70
|
+
def fetch( index_col, key )
|
|
71
|
+
if key.is_a?(Array)
|
|
72
|
+
key.map{ |k| @indexes[index_col][k] }
|
|
73
|
+
else
|
|
74
|
+
@indexes[index_col][key]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# function should take an object, and return either true or false
|
|
79
|
+
# returns an array of objects that respond true when put through the
|
|
80
|
+
# meat grinder
|
|
81
|
+
def filter( &block )
|
|
82
|
+
rval = []
|
|
83
|
+
@records.each do |record|
|
|
84
|
+
rval << record if ( yield record )
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
rval
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# removes rows which fail the given test from the spreadsheet.
|
|
91
|
+
# TODO! Should be returning self rather than the records.a
|
|
92
|
+
def filter!( &block )
|
|
93
|
+
@records = self.filter( &block )
|
|
94
|
+
reindex
|
|
95
|
+
@records
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/csv_madness.rb
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
require 'csv'
|
|
2
|
-
require '
|
|
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
|
-
|
|
7
|
-
require_relative 'csv_madness/sheet'
|
|
8
|
-
require_relative 'csv_madness/record'
|
|
6
|
+
FunWith::Gems.make_gem_fun( "CsvMadness" )
|
|
9
7
|
|
|
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,11 @@
|
|
|
1
|
+
"id","fname","lname","party"
|
|
2
|
+
"1","Mary","Moore","D"
|
|
3
|
+
"2","Bill","Paxton","R"
|
|
4
|
+
"3","Charles","Darwin","I"
|
|
5
|
+
"4","Chuck","Norris","D"
|
|
6
|
+
"5","Annabelle","Lecter","R"
|
|
7
|
+
"6","Mortimer","Bradford","D"
|
|
8
|
+
"7","Wilford","Brimley","I"
|
|
9
|
+
"8","Cala","Wilcox","R"
|
|
10
|
+
"9","Horace","Wilcox","R"
|
|
11
|
+
"10","Jacob","Buford","D"
|
data/test/helper.rb
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'bundler'
|
|
3
|
-
|
|
4
|
-
begin
|
|
5
|
-
|
|
6
|
-
rescue Bundler::BundlerError => e
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
require 'test/unit'
|
|
13
|
-
require 'shoulda'
|
|
1
|
+
# require 'rubygems'
|
|
2
|
+
# require 'bundler'
|
|
3
|
+
#
|
|
4
|
+
# begin
|
|
5
|
+
# Bundler.setup(:default, :development)
|
|
6
|
+
# rescue Bundler::BundlerError => e
|
|
7
|
+
# $stderr.puts e.message
|
|
8
|
+
# $stderr.puts "Run `bundle install` to install missing gems"
|
|
9
|
+
# exit e.status_code
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# require 'test/unit'
|
|
13
|
+
# require 'shoulda'
|
|
14
14
|
|
|
15
15
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
16
16
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
17
17
|
|
|
18
|
+
require 'fun_with_testing'
|
|
18
19
|
require 'csv_madness'
|
|
19
20
|
|
|
20
|
-
class Test::Unit::TestCase
|
|
21
|
-
end
|
|
21
|
+
# class Test::Unit::TestCase
|
|
22
|
+
# end
|
|
22
23
|
|
|
23
|
-
class MadTestCase < Test::Unit::TestCase
|
|
24
|
+
class MadTestCase < FunWith::Testing::TestCase # Test::Unit::TestCase
|
|
25
|
+
include FunWith::Testing::Assertions::Basics
|
|
26
|
+
|
|
24
27
|
MARY_ID = "1"
|
|
25
28
|
BILL_ID = "2"
|
|
26
29
|
DARWIN_ID = "3"
|
|
27
30
|
CHUCK_ID = "4"
|
|
31
|
+
|
|
32
|
+
def setup
|
|
33
|
+
set_spreadsheet_paths
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load_sheet( *args )
|
|
37
|
+
@sheet = CsvMadness.load( *args )
|
|
38
|
+
end
|
|
28
39
|
|
|
29
40
|
def load_mary
|
|
30
41
|
id = @simple.index_columns.first
|
|
@@ -54,6 +65,7 @@ class MadTestCase < Test::Unit::TestCase
|
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
def muck_up_spreadsheet
|
|
68
|
+
debugger
|
|
57
69
|
@simple.add_column(:scrambled_name) do |val, record|
|
|
58
70
|
record.fname.chars.map(&:to_s).zip( record.lname.chars.map(&:to_s) ).flatten.compact.join
|
|
59
71
|
end
|
|
@@ -78,9 +90,9 @@ class MadTestCase < Test::Unit::TestCase
|
|
|
78
90
|
end
|
|
79
91
|
|
|
80
92
|
def set_spreadsheet_paths
|
|
81
|
-
@
|
|
82
|
-
@csv_output_path =
|
|
83
|
-
CsvMadness::Sheet.add_search_path( @
|
|
93
|
+
@csv_load_path = CsvMadness.root( "test", "csv" )
|
|
94
|
+
@csv_output_path = CsvMadness.root( "test", "csv", "out" )
|
|
95
|
+
CsvMadness::Sheet.add_search_path( @csv_load_path )
|
|
84
96
|
CsvMadness::Sheet.add_search_path( @csv_output_path )
|
|
85
97
|
end
|
|
86
98
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
|
|
3
|
+
class TestBuilder < MadTestCase
|
|
4
|
+
context "testing simple cases" do
|
|
5
|
+
should "spreadsheetize integers" do
|
|
6
|
+
integers = [65, 66, 67, 68, 69, 70]
|
|
7
|
+
|
|
8
|
+
sb = CsvMadness::Builder.new do |s|
|
|
9
|
+
s.column( :even, "even?" ) # calls the .even?() method on each object
|
|
10
|
+
s.column( :odd, "odd?" )
|
|
11
|
+
s.column( :hashh, "hash" )
|
|
12
|
+
s.column( :hashhash, "hash.hash" ) # calls .hash(), then calls .hash() on result
|
|
13
|
+
s.column( :chr )
|
|
14
|
+
s.column( :not_a_valid_method )
|
|
15
|
+
s.column( :square ) do |i| # Stores the block as a proc, runs on each object.
|
|
16
|
+
i * i
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ss = sb.build( integers )
|
|
21
|
+
|
|
22
|
+
for record in ss.records
|
|
23
|
+
assert_kind_of( CsvMadness::Record, ss.records.first )
|
|
24
|
+
for col in [:even, :odd, :hashh, :hashhash, :chr]
|
|
25
|
+
assert_respond_to record, col
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
assert_matches ss.records.first.not_a_valid_method, /^ERROR: undefined method `not_a_valid_method'/
|
|
30
|
+
|
|
31
|
+
ss = sb.build( integers, :on_error => :ignore )
|
|
32
|
+
|
|
33
|
+
assert_equal "", ss.records.first.not_a_valid_method
|
|
34
|
+
assert_equal 4225, ss.records.first.square
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/test/test_csv_madness.rb
CHANGED
|
@@ -3,7 +3,6 @@ require 'helper'
|
|
|
3
3
|
class TestCsvMadness < MadTestCase
|
|
4
4
|
context "all:" do
|
|
5
5
|
setup do
|
|
6
|
-
set_spreadsheet_paths
|
|
7
6
|
load_simple_spreadsheet
|
|
8
7
|
end
|
|
9
8
|
|
|
@@ -14,12 +13,12 @@ class TestCsvMadness < MadTestCase
|
|
|
14
13
|
context "testing sheet basics" do
|
|
15
14
|
should "not accept duplicate search paths" do
|
|
16
15
|
@path_count = CsvMadness::Sheet.search_paths.length
|
|
17
|
-
CsvMadness::Sheet.add_search_path(
|
|
16
|
+
CsvMadness::Sheet.add_search_path( @csv_load_path )
|
|
18
17
|
assert_equal @path_count, CsvMadness::Sheet.search_paths.length
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
should "load a simple spreadsheet" do
|
|
22
|
-
simple = CsvMadness::Sheet.from("simple.csv")
|
|
21
|
+
simple = CsvMadness::Sheet.from( "simple.csv" )
|
|
23
22
|
assert_equal "Mary", simple[0].fname
|
|
24
23
|
assert_equal "Paxton", simple[1].lname
|
|
25
24
|
assert_equal "72", simple[2].age
|
|
@@ -111,7 +110,7 @@ class TestCsvMadness < MadTestCase
|
|
|
111
110
|
assert_in_delta Time.parse("1986-04-08"), born1, 3600 * 24
|
|
112
111
|
|
|
113
112
|
assert_kind_of String, born3
|
|
114
|
-
assert_match /Invalid Time Format/, born3
|
|
113
|
+
assert_match( /Invalid Time Format/, born3 )
|
|
115
114
|
end
|
|
116
115
|
|
|
117
116
|
should "successfully decorate record objects with new functionality" do
|
|
@@ -213,19 +212,18 @@ class TestCsvMadness < MadTestCase
|
|
|
213
212
|
|
|
214
213
|
should "to_csv properly" do
|
|
215
214
|
@to_csv = @nilsheet.to_csv( force_quotes: true )
|
|
216
|
-
assert_match /"Moore"/, @to_csv
|
|
217
|
-
assert_match /"age","born"/, @to_csv
|
|
215
|
+
assert_match( /"Moore"/, @to_csv )
|
|
216
|
+
assert_match( /"age","born"/, @to_csv )
|
|
218
217
|
end
|
|
219
218
|
|
|
220
219
|
should "write to an output file properly" do
|
|
221
|
-
# debugger
|
|
222
220
|
@outfile = @csv_output_path.join("output_nilfile.csv")
|
|
223
221
|
@nilsheet.write_to_file( @outfile, force_quotes: true )
|
|
224
222
|
|
|
225
223
|
assert File.exist?( @outfile )
|
|
226
224
|
@to_csv = File.read( @outfile )
|
|
227
|
-
assert_match /"Moore"/, @to_csv
|
|
228
|
-
assert_match /"age","born"/, @to_csv
|
|
225
|
+
assert_match( /"Moore"/, @to_csv )
|
|
226
|
+
assert_match( /"age","born"/, @to_csv )
|
|
229
227
|
end
|
|
230
228
|
end
|
|
231
229
|
|
|
@@ -237,7 +235,7 @@ class TestCsvMadness < MadTestCase
|
|
|
237
235
|
|
|
238
236
|
should "add column" do
|
|
239
237
|
@simple.add_column( :compound ) do |h, record|
|
|
240
|
-
|
|
238
|
+
"#{record.fname} #{record.lname} #{record.id}"
|
|
241
239
|
end
|
|
242
240
|
|
|
243
241
|
load_mary
|