csv-mapper 0.0.1

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.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-12-05
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,15 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/csv-mapper.rb
6
+ script/console
7
+ script/destroy
8
+ script/generate
9
+ spec/csv-mapper_spec.rb
10
+ spec/csv-mapper_row_map_spec.rb
11
+ spec/csv-mapper_attribute_map_spec.rb
12
+ spec/test.csv
13
+ spec/spec.opts
14
+ spec/spec_helper.rb
15
+ tasks/rspec.rake
data/README.rdoc ADDED
@@ -0,0 +1,82 @@
1
+ = README
2
+
3
+ by Luke Pillow
4
+
5
+ == DESCRIPTION:
6
+
7
+ CsvMapper is a small library intended to simplify the common steps involved with importing CSV files to a usable form in Ruby.
8
+
9
+ == EXAMPLES:
10
+
11
+ The following example will import a CSV file to an Array of OpenStruct[http://ruby-doc.org/core/classes/OpenStruct.html] instances.
12
+
13
+ ==== Example CSV File Structure
14
+
15
+ First Name,Last Name,Age
16
+ John,Doe,27
17
+ Jane,Doe,26
18
+ Bat,Man,52
19
+ ...etc...
20
+
21
+ ==== Simple Usage Example
22
+ include CsvMapper
23
+
24
+ results = import('/path/to/file.csv') do
25
+ start_at_row 1
26
+ [first_name, last_name, age]
27
+ end
28
+
29
+ results.first.first_name # John
30
+ results.first.last_name # Doe
31
+ results.first.age # 27
32
+
33
+ ==== Import to ActiveRecord Example
34
+ Although CsvMapper has no dependency on ActiveRecord; it's easy to import a CSV file to ActiveRecord models and save them.
35
+
36
+ # Define an ActiveRecord model
37
+ class Person < ActiveRecord::Base; end
38
+
39
+ include CsvMapper
40
+
41
+ results = import('/path/to/file.csv') do
42
+ map_to Person # Map to the Person ActiveRecord class (defined above) instead of the default OpenStruct.
43
+ after_row lambda{|row, person| person.save } # Call this lambda and save each record after it's parsed.
44
+
45
+ start_at_row 1
46
+ [first_name, last_name, age]
47
+ end
48
+
49
+ See CsvMapper for a more detailed description
50
+
51
+ == REQUIREMENTS:
52
+
53
+ FasterCSV[http://fastercsv.rubyforge.org/]
54
+
55
+ == INSTALL:
56
+
57
+ * sudo gem install csv-mapper
58
+
59
+ == LICENSE:
60
+
61
+ (The MIT License)
62
+
63
+ Copyright (c) 2008 Luke Pillow
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining
66
+ a copy of this software and associated documentation files (the
67
+ 'Software'), to deal in the Software without restriction, including
68
+ without limitation the rights to use, copy, modify, merge, publish,
69
+ distribute, sublicense, and/or sell copies of the Software, and to
70
+ permit persons to whom the Software is furnished to do so, subject to
71
+ the following conditions:
72
+
73
+ The above copyright notice and this permission notice shall be
74
+ included in all copies or substantial portions of the Software.
75
+
76
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
77
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/csv-mapper'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('csv-mapper', CsvMapper::VERSION) do |p|
7
+ p.developer('Luke Pillow', 'lpillow@gmail.com')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ # p.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
10
+ p.rubyforge_name = p.name # TODO this is default value
11
+ p.extra_deps = [
12
+ ['fastercsv','>= 1.4.0'],
13
+ ]
14
+ p.extra_dev_deps = [
15
+ ['newgem', ">= #{::Newgem::VERSION}"]
16
+ ]
17
+
18
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
19
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
20
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
21
+ p.rsync_args = '-av --delete --ignore-errors'
22
+ end
23
+
24
+ require 'newgem/tasks' # load /tasks/*.rake
25
+ Dir['tasks/**/*.rake'].each { |t| load t }
26
+
27
+ # TODO - want other tests/tasks run by default? Add them to the list
28
+ # task :default => [:spec, :features]
data/lib/csv-mapper.rb ADDED
@@ -0,0 +1,294 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'ostruct'
5
+ require 'fastercsv'
6
+
7
+ # This module provides the main interface for importing CSV files & data to mapped Ruby objects.
8
+ # = Usage
9
+ # Including CsvMapper will provide two methods:
10
+ # - +import+
11
+ # - +map_csv+
12
+ #
13
+ # See csv-mapper.rb[link:files/lib/csv-mapper_rb.html] for method docs.
14
+ #
15
+ # === Import From File
16
+ # results = import('/path/to/file.csv') do
17
+ # # declare mapping here
18
+ # end
19
+ #
20
+ # === Import From String or IO
21
+ # results = import(csv_data, :type => :io) do
22
+ # # declare mapping here
23
+ # end
24
+ #
25
+ # === Mapping
26
+ # Mappings are built inside blocks. All three of CsvMapper's main API methods accept a block containing a mapping.
27
+ # Maps are defined by using +map_to+, +start_at_row+, +before_row+, and +after_row+ (methods on CsvMapper::RowMap) and
28
+ # by defining your own mapping attributes.
29
+ # A mapping block uses an internal cursor to keep track of the order the mapping attributes are declared and use that order to
30
+ # know the corresponding CSV column index to associate with the attribute.
31
+ #
32
+ # ===== The Basics
33
+ # * +map_to+ - Override the default OpenStruct target. Accepts a class and an optional hash of default attribute names and values.
34
+ # * +start_at_row+ - Specify what row to begin parsing at. Use this to skip headers.
35
+ # * +before_row+ - Accepts an Array of method name symbols or lambdas to be invoked before parsing each row.
36
+ # * +after_row+ - Accepts an Array of method name symbols or lambdas to be invoked after parsing each row.
37
+ # * +delimited_by+ - Accepts a character to be used to delimit columns. Use this to specify pipe-delimited files.
38
+ # * <tt>\_SKIP_</tt> - Use as a placehold to skip a CSV column index.
39
+ # * +parser_options+ - Accepts a hash of FasterCSV options. Can be anything FasterCSV::new()[http://fastercsv.rubyforge.org/classes/FasterCSV.html#M000018] understands
40
+ #
41
+ # ===== Attribute Mappings
42
+ # Attribute mappings are created by using the name of the attribute to be mapped to.
43
+ # The order in which attribute mappings are declared determines the index of the corresponding CSV row.
44
+ # All mappings begin at the 0th index of the CSV row.
45
+ # foo # maps the 0th CSV row position value to the value of the 'foo' attribute on the target object.
46
+ # bar # maps the 1st row position to 'bar'
47
+ # This could also be a nice one liner for easy CSV format conversion
48
+ # [foo, bar] # creates the same attribute maps as above.
49
+ # The mapping index may be specifically declared in two additional ways:
50
+ # foo(2) # maps the 2nd CSV row position value to 'foo' and moves the cursor to 3
51
+ # bar # maps the 3rd CSV row position to 'bar' due to the current cursor position
52
+ # baz.at(0) # maps the 0th CSV row position to 'baz' but only increments the cursor 1 position to 4
53
+ # Each attribute mapping may be configured to parse the record using a lambda or a method name
54
+ # foo.map lambda{|row| row[2].strip } # maps the 2nd row position value with leading and trailing whitespace removed to 'foo'.
55
+ # bar.map :clean_bar # maps the result of the clean_bar method to 'bar'. clean_bar must accept the row as a parameter.
56
+ # Attribute mapping declarations and "modifiers" may be chained
57
+ # foo.at(4).map :some_transform
58
+ #
59
+ # === Create Reusable Mappings
60
+ # Both +import_csv+ and +import_string+ accept an instance of RowMap as an optional mapping parameter.
61
+ # The easiest way to create an instance of a RowMap is by using +map_csv+.
62
+ # a_row_map = map_csv do
63
+ # # declare mapping here
64
+ # end
65
+ # Then you can reuse the mapping
66
+ # results = import(some_string, :type => :io, :map => a_row_map)
67
+ # other_results = import('/path/to/file.csv', :map => a_row_map)
68
+ #
69
+ module CsvMapper
70
+ VERSION = '0.0.1'
71
+
72
+ # Create a new RowMap instance from the definition in the given block.
73
+ def map_csv(&map_block)
74
+ CsvMapper::RowMap.new(self, &map_block)
75
+ end
76
+
77
+ # Load CSV data and map the values according to the definition in the given block.
78
+ # Accepts either a file path, String, or IO as +data+. Defaults to file path.
79
+ #
80
+ # The following +options+ may be used:
81
+ # <tt>:type</tt>:: defaults to <tt>:file_path</tt>. Use <tt>:io</tt> to specify data as String or IO.
82
+ # <tt>:map</tt>:: Specify an instance of a RowMap to take presidence over a given block defintion.
83
+ #
84
+ def import(data, options={}, &map_block)
85
+ config = { :type => :file_path,
86
+ :map => map_csv(&map_block) }.merge!(options)
87
+
88
+ csv_data = config[:type] == :io ? data : File.new(data, 'r')
89
+ map = config[:map]
90
+
91
+ results = []
92
+ FasterCSV.new(csv_data, map.parser_options ).each_with_index do |row, i|
93
+ results << map.parse(row) if i >= map.start_at_row
94
+ end
95
+
96
+ results
97
+ end
98
+
99
+ # CsvMapper::RowMap provides a simple, DSL-like interface for constructing mappings.
100
+ # A CsvMapper::RowMap provides the main functionality of the library. It will mostly be used indirectly through the CsvMapper API,
101
+ # but may be useful to use directly for the dynamic CSV mappings.
102
+ class RowMap
103
+ #Start with a 'blank slate'
104
+ instance_methods.each { |m| undef_method m unless m =~ /^__||instance_eval/ }
105
+
106
+ attr_reader :mapped_attributes
107
+
108
+ # Create a new instance with access to an evaluation context
109
+ def initialize(context, &map_block)
110
+ @context = context
111
+ @before_filters = []
112
+ @after_filters = []
113
+ @parser_options = {}
114
+ @start_at_row = 0
115
+ @delimited_by = FasterCSV::DEFAULT_OPTIONS[:col_sep]
116
+ @mapped_attributes = []
117
+
118
+ self.instance_eval(&map_block) if block_given?
119
+ end
120
+
121
+ # Each row of a CSV is parsed and mapped to a new instance of a Ruby class; OpenStruct by default.
122
+ # Use this method to change the what class each row is mapped to.
123
+ # The given class must respond to a parameter-less #new and all attribute mappings defined.
124
+ # Providing a hash of defaults will ensure that each resulting object will have the providing name and attribute values
125
+ # unless overridden by a mapping
126
+ def map_to(klass, defaults={})
127
+ @map_to_klass = klass
128
+
129
+ defaults.each do |name, value|
130
+ self.add_attribute(name, -99).map lambda{|row| value}
131
+ end
132
+ end
133
+
134
+ # Specify a hash of FasterCSV options to be used for CSV parsing
135
+ #
136
+ # Can be anything FasterCSV::new()[http://fastercsv.rubyforge.org/classes/FasterCSV.html#M000018] accepts
137
+ def parser_options(opts=nil)
138
+ @parser_options = opts if opts
139
+ @parser_options.merge :col_sep => @delimited_by
140
+ end
141
+
142
+ # Convenience method to 'move' the cursor skipping the current index.
143
+ def _SKIP_
144
+ self.move_cursor
145
+ end
146
+
147
+ # Specify the CSV column delimiter. Defaults to comma.
148
+ def delimited_by(delimiter=nil)
149
+ @delimited_by = delimiter if delimiter
150
+ @delimited_by
151
+ end
152
+
153
+ # Declare what row to begin parsing the CSV.
154
+ # This is useful for skipping headers and such.
155
+ def start_at_row(row_number=nil)
156
+ @start_at_row = row_number if row_number
157
+ @start_at_row
158
+ end
159
+
160
+ # Declare method name symbols and/or lambdas to be executed before each row.
161
+ # Each method or lambda must accept to parameters: +csv_row+, +target_object+
162
+ # Methods names should refer to methods available within the RowMap's provided context
163
+ def before_row(*befores)
164
+ self.add_filters(@before_filters, *befores)
165
+ end
166
+
167
+ # Declare method name symbols and/or lambdas to be executed before each row.
168
+ # Each method or lambda must accept to parameters: +csv_row+, +target_object+
169
+ # Methods names should refer to methods available within the RowMap's provided context
170
+ def after_row(*afters)
171
+ self.add_filters(@after_filters, *afters)
172
+ end
173
+
174
+ # Add a new attribute to this map. Mostly used internally, but is useful for dynamic map creation.
175
+ # returns the newly created CsvMapper::AttributeMap
176
+ def add_attribute(name, index=nil)
177
+ attr_mapping = CsvMapper::AttributeMap.new(name.to_sym, index, @context)
178
+ self.mapped_attributes << attr_mapping
179
+ attr_mapping
180
+ end
181
+
182
+ # The current cursor location
183
+ def cursor # :nodoc:
184
+ @cursor ||= 0
185
+ end
186
+
187
+ # Move the cursor relative to it's current position
188
+ def move_cursor(positions=1) # :nodoc:
189
+ self.cursor += positions
190
+ end
191
+
192
+ # Given a CSV row return an instance of an object defined by this mapping
193
+ def parse(csv_row)
194
+ target = self.map_to_class.new
195
+ @before_filters.each {|filter| filter.call(csv_row, target) }
196
+
197
+ self.mapped_attributes.inject(target) do |result, attr_map|
198
+ result.send("#{attr_map.name}=".to_sym, attr_map.parse(csv_row))
199
+ result
200
+ end
201
+
202
+ @after_filters.each {|filter| filter.call(csv_row, target) }
203
+
204
+ return target
205
+ end
206
+
207
+ protected # :nodoc:
208
+
209
+ # The Hacktastic "magic"
210
+ # Used to dynamically create CsvMapper::AttributeMaps based on unknown method calls that
211
+ # should represent the names of mapped attributes.
212
+ #
213
+ # An optional first argument is used to move this maps cursor position and as the index of the
214
+ # new AttributeMap
215
+ def method_missing(name, *args) # :nodoc:
216
+
217
+ if index = args[0]
218
+ self.move_cursor(index - self.cursor)
219
+ else
220
+ index = self.cursor
221
+ self.move_cursor
222
+ end
223
+
224
+ add_attribute(name, index)
225
+ end
226
+
227
+ def add_filters(to_hook, *filters) # :nodoc:
228
+ (to_hook << filters.collect do |filter|
229
+ filter.is_a?(Symbol) ? lambda{|row, target| @context.send(filter, row, target)} : filter
230
+ end).flatten!
231
+ end
232
+
233
+ def map_to_class # :nodoc:
234
+ @map_to_klass || OpenStruct
235
+ end
236
+
237
+ def cursor=(value) # :nodoc:
238
+ @cursor=value
239
+ end
240
+
241
+
242
+ end
243
+
244
+ # A CsvMapper::AttributeMap contains the instructions to parse a value from a CSV row and to know the
245
+ # name of the attribute it is targeting.
246
+ class AttributeMap
247
+ attr_reader :name, :index
248
+
249
+ # Creates a new instance using the provided attribute +name+, CSV row +index+, and evaluation +map_context+
250
+ def initialize(name, index, map_context)
251
+ @name, @index, @map_context = name, index, map_context
252
+ end
253
+
254
+ # Set the index that this map is targeting.
255
+ #
256
+ # Returns this AttributeMap for chainability
257
+ def at(index)
258
+ @index = index
259
+ self
260
+ end
261
+
262
+ # Provide a lambda or the symbol name of a method on this map's evaluation context to be used when parsing
263
+ # the value from a CSV row.
264
+ # Both the lambda or the method provided should accept a single +row+ parameter
265
+ #
266
+ # Returns this AttributeMap for chainability
267
+ def map(transform)
268
+ @transformer = transform
269
+ self
270
+ end
271
+
272
+ # Given a CSV row, return the value at this AttributeMap's index using any provided map transforms (see map)
273
+ def parse(csv_row)
274
+ @transformer ? parse_transform(csv_row) : csv_row[self.index]
275
+ end
276
+
277
+ # Access the raw value of the CSV row without any map transforms applied.
278
+ def raw_value(csv_row)
279
+ csv_row[self.index]
280
+ end
281
+
282
+ private
283
+
284
+ def parse_transform(csv_row)
285
+ if @transformer.is_a? Symbol
286
+ transform_name = @transformer
287
+ @transformer = lambda{|row| @map_context.send(transform_name, row) }
288
+ end
289
+
290
+ @transformer.call(csv_row)
291
+ end
292
+
293
+ end
294
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/csv-mapper.rb'}"
9
+ puts "Loading csv-mapper gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe CsvMapper::AttributeMap do
4
+
5
+ class TestContext
6
+ def transform_it(row)
7
+ :transform_it_success
8
+ end
9
+ end
10
+
11
+ before(:each) do
12
+ @row_attr = CsvMapper::AttributeMap.new('foo', 1, TestContext.new)
13
+ @csv_row = ['first_name', 'last_name']
14
+ end
15
+
16
+ it "should map a destination attribute name" do
17
+ @row_attr.name.should == 'foo'
18
+ end
19
+
20
+ it "should map a CSV column index" do
21
+ @row_attr.index.should be(1)
22
+ end
23
+
24
+ it "should map a transformation between the CSV value and destination value and chain method calls" do
25
+ @row_attr.map(:named_transform).should be(@row_attr)
26
+ end
27
+
28
+ it "should provide ability to set the index and chain method calls" do
29
+ @row_attr.at(9).should be(@row_attr)
30
+ @row_attr.index.should be(9)
31
+ end
32
+
33
+ it "should parse values" do
34
+ @row_attr.parse(@csv_row).should == @csv_row[1]
35
+ end
36
+
37
+ it "should parse values using mapped transformer" do
38
+ @row_attr.map( lambda{|row| :success } )
39
+ @row_attr.parse(@csv_row).should == :success
40
+ end
41
+
42
+ it "should parse values using a named method on the context" do
43
+ @row_attr.map(:transform_it).parse(@csv_row).should == :transform_it_success
44
+ end
45
+
46
+ it "should provide access to the raw value" do
47
+ @row_attr.raw_value(@csv_row).should be(@csv_row[@row_attr.index])
48
+ end
49
+
50
+ end
@@ -0,0 +1,121 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe CsvMapper::RowMap do
4
+
5
+ class TestMapToClass
6
+ attr_accessor :foo, :bar, :baz
7
+ end
8
+
9
+ class TestMapContext
10
+ def transform(row)
11
+ :transform_success
12
+ end
13
+
14
+ def change_name(row, target)
15
+ row[0] = :changed_name
16
+ end
17
+ end
18
+
19
+ before(:each) do
20
+ @row_map = CsvMapper::RowMap.new(TestMapContext.new)
21
+ @csv_row = ['first_name', 'last_name']
22
+ end
23
+
24
+ it "should parse a CSV row" do
25
+ @row_map.parse(@csv_row).should_not be_nil
26
+ end
27
+
28
+ it "should map to a OpenStruct by default" do
29
+ @row_map.parse(@csv_row).should be_instance_of(OpenStruct)
30
+ end
31
+
32
+ it "should parse a CSV row returning the mapped result" do
33
+ @row_map.fname
34
+ @row_map.lname
35
+
36
+ result = @row_map.parse(@csv_row)
37
+ result.fname.should == @csv_row[0]
38
+ result.lname.should == @csv_row[1]
39
+ end
40
+
41
+ it "should map to a ruby class with optional default attribute values" do
42
+ @row_map.map_to TestMapToClass, :baz => :default_baz
43
+
44
+ @row_map.foo
45
+ @row_map.bar
46
+
47
+ (result = @row_map.parse(@csv_row)).should be_instance_of(TestMapToClass)
48
+ result.foo.should == @csv_row[0]
49
+ result.bar.should == @csv_row[1]
50
+ result.baz.should == :default_baz
51
+ end
52
+
53
+ it "should start at the specified CSV row" do
54
+ @row_map.start_at_row.should be(0)
55
+ @row_map.start_at_row(1)
56
+ @row_map.start_at_row.should be(1)
57
+ end
58
+
59
+ it "should allow before row processing" do
60
+ @row_map.before_row :change_name, lambda{|row, target| row[1] = 'bar'}
61
+
62
+ @row_map.first_name
63
+ @row_map.foo
64
+
65
+ result = @row_map.parse(@csv_row)
66
+ result.first_name.should == :changed_name
67
+ result.foo.should == 'bar'
68
+ end
69
+
70
+ it "should allow after row processing" do
71
+ @row_map.after_row lambda{|row, target| target.bam = :woot}
72
+
73
+ @row_map.parse(@csv_row).bam.should == :woot
74
+ end
75
+
76
+ it "should have a moveable cursor" do
77
+ @row_map.cursor.should be(0)
78
+ @row_map.move_cursor
79
+ @row_map.cursor.should be(1)
80
+ @row_map.move_cursor 3
81
+ @row_map.cursor.should be(4)
82
+ end
83
+
84
+ it "should skip indexes" do
85
+ pre_cursor = @row_map.cursor
86
+ @row_map._SKIP_
87
+ @row_map.cursor.should be(pre_cursor + 1)
88
+ end
89
+
90
+ it "should accept FasterCSV parser options" do
91
+ @row_map.parser_options :row_sep => :auto
92
+ @row_map.parser_options[:row_sep].should == :auto
93
+ end
94
+
95
+ it "should have a configurable the column delimiter" do
96
+ @row_map.delimited_by '|'
97
+ @row_map.delimited_by.should == '|'
98
+ end
99
+
100
+ it "should maintain a collection of attribute mappings" do
101
+ @row_map.mapped_attributes.should be_kind_of(Enumerable)
102
+ end
103
+
104
+ it "should lazy initialize attribute maps and move the cursor" do
105
+ pre_cursor = @row_map.cursor
106
+ (attr_map = @row_map.first_name).should be_instance_of(CsvMapper::AttributeMap)
107
+ attr_map.index.should be(pre_cursor)
108
+ @row_map.cursor.should be(pre_cursor + 1)
109
+ end
110
+
111
+ it "should lazy initialize attribute maps with optional cursor position" do
112
+ pre_cursor = @row_map.cursor
113
+ @row_map.last_name(1).index.should be(1)
114
+ @row_map.cursor.should be(1)
115
+ end
116
+
117
+ it "should share it context with its mappings" do
118
+ @row_map.first_name.map(:transform)
119
+ @row_map.parse(@csv_row).first_name.should == :transform_success
120
+ end
121
+ end
@@ -0,0 +1,54 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe CsvMapper do
4
+
5
+ before(:each) do
6
+ @mapped_klass = Class.new { include CsvMapper }
7
+ @mapped = @mapped_klass.new
8
+ end
9
+
10
+ it "should allow the creation of CSV mappings" do
11
+ mapping = @mapped.map_csv do
12
+ start_at_row 2
13
+ end
14
+
15
+ mapping.should be_instance_of(CsvMapper::RowMap)
16
+ mapping.start_at_row.should == 2
17
+ end
18
+
19
+ it "should import a CSV IO" do
20
+ io = 'foo,bar,00,01'
21
+ results = @mapped.import(io, :type => :io) do
22
+ first
23
+ second
24
+ end
25
+
26
+ results.should be_kind_of(Enumerable)
27
+ results.should have(1).things
28
+ results[0].first.should == 'foo'
29
+ results[0].second.should == 'bar'
30
+ end
31
+
32
+ it "should import a CSV File IO" do
33
+ results = import(File.dirname(__FILE__) + '/test.csv') do
34
+ start_at_row 1
35
+ [first_name, last_name, age]
36
+ end
37
+
38
+ results.size.should be(3)
39
+ end
40
+
41
+ it "should import non-comma delimited files" do
42
+ piped_io = 'foo|bar|00|01'
43
+
44
+ results = import(piped_io, :type => :io) do
45
+ delimited_by '|'
46
+ [first, second]
47
+ end
48
+
49
+ results.should have(1).things
50
+ results[0].first.should == 'foo'
51
+ results[0].second.should == 'bar'
52
+ end
53
+
54
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'csv-mapper'
data/spec/test.csv ADDED
@@ -0,0 +1,4 @@
1
+ First Name, Last Name, Age
2
+ John,Doe,27
3
+ Jane,Doe,26
4
+ Bat,Man,52
data/tasks/rspec.rake ADDED
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'spec'
6
+ end
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
17
+ desc "Run the specs under spec/models"
18
+ Spec::Rake::SpecTask.new do |t|
19
+ t.spec_opts = ['--options', "spec/spec.opts"]
20
+ t.spec_files = FileList['spec/**/*_spec.rb']
21
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csv-mapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Luke Pillow
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-05 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fastercsv
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.4.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: newgem
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.0
44
+ version:
45
+ description: CsvMapper is a small library intended to simplify the common steps involved with importing CSV files to a usable form in Ruby.
46
+ email:
47
+ - lpillow@gmail.com
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - History.txt
54
+ - Manifest.txt
55
+ - README.rdoc
56
+ files:
57
+ - History.txt
58
+ - Manifest.txt
59
+ - README.rdoc
60
+ - Rakefile
61
+ - lib/csv-mapper.rb
62
+ - script/console
63
+ - script/destroy
64
+ - script/generate
65
+ - spec/csv-mapper_spec.rb
66
+ - spec/csv-mapper_row_map_spec.rb
67
+ - spec/csv-mapper_attribute_map_spec.rb
68
+ - spec/test.csv
69
+ - spec/spec.opts
70
+ - spec/spec_helper.rb
71
+ - tasks/rspec.rake
72
+ has_rdoc: true
73
+ homepage: by Luke Pillow
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --main
77
+ - README.rdoc
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ version:
92
+ requirements: []
93
+
94
+ rubyforge_project: csv-mapper
95
+ rubygems_version: 1.3.1
96
+ signing_key:
97
+ specification_version: 2
98
+ summary: CsvMapper is a small library intended to simplify the common steps involved with importing CSV files to a usable form in Ruby.
99
+ test_files: []
100
+