scottmotte-csvmapper 0.4.0
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/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +36 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/csvmapper.rb +237 -0
- data/spec/csvmapper_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +82 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 scottmotte
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
= CsvMapper
|
2
|
+
|
3
|
+
Taken from Luke Pillow (pillowfactory's) gem and adjusted for my own needs very very slightly.
|
4
|
+
|
5
|
+
== INSTALL:
|
6
|
+
|
7
|
+
sudo gem install scottmotte-csvmapper
|
8
|
+
|
9
|
+
== DESCRIPTION:
|
10
|
+
|
11
|
+
CsvMapper is a small library intended to simplify the common steps involved with importing CSV files to a usable form in Ruby.
|
12
|
+
|
13
|
+
== EXAMPLES:
|
14
|
+
|
15
|
+
The following example will import a CSV file to an Array of OpenStruct[http://ruby-doc.org/core/classes/OpenStruct.html] instances.
|
16
|
+
|
17
|
+
==== Example CSV File Structure
|
18
|
+
|
19
|
+
First Name,Last Name,Age
|
20
|
+
John,Doe,27
|
21
|
+
Jane,Doe,26
|
22
|
+
Bat,Man,52
|
23
|
+
|
24
|
+
==== Simple Usage Example
|
25
|
+
include CsvMapper
|
26
|
+
|
27
|
+
results = CsvMapper.import('/path/to/file.csv') do
|
28
|
+
start_at_row 1
|
29
|
+
[first_name, last_name, age]
|
30
|
+
end
|
31
|
+
|
32
|
+
results.first.first_name # John
|
33
|
+
results.first.last_name # Doe
|
34
|
+
results.first.age # 27
|
35
|
+
|
36
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "csvmapper"
|
8
|
+
gem.summary = %Q{Stolen from pillowfactory's csv-mapper and adjusted slightly}
|
9
|
+
gem.email = "scott@scottmotte.com"
|
10
|
+
gem.homepage = "http://github.com/scottmotte/csvmapper"
|
11
|
+
gem.authors = ["scottmotte"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
|
14
|
+
gem.add_dependency('right_aws')
|
15
|
+
gem.add_dependency('mini_magick')
|
16
|
+
end
|
17
|
+
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
if File.exist?('VERSION.yml')
|
40
|
+
config = YAML.load(File.read('VERSION.yml'))
|
41
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
42
|
+
else
|
43
|
+
version = ""
|
44
|
+
end
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "csvmapper #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
51
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/lib/csvmapper.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'fastercsv'
|
3
|
+
|
4
|
+
# this is stolen from csv-mapper gem with slight modifcations by me and a cleaner module name
|
5
|
+
module CsvMapper
|
6
|
+
# Create a new RowMap instance from the definition in the given block.
|
7
|
+
def self.map_csv(&map_block)
|
8
|
+
CsvMapper::RowMap.new(self, &map_block)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Load CSV data and map the values according to the definition in the given block.
|
12
|
+
# Accepts either a file path, String, or IO as +data+. Defaults to file path.
|
13
|
+
#
|
14
|
+
# The following +options+ may be used:
|
15
|
+
# <tt>:type</tt>:: defaults to <tt>:file_path</tt>. Use <tt>:io</tt> to specify data as String or IO.
|
16
|
+
# <tt>:map</tt>:: Specify an instance of a RowMap to take presidence over a given block defintion.
|
17
|
+
#
|
18
|
+
def self.import(data, options={}, &map_block)
|
19
|
+
config = { :type => :file_path,
|
20
|
+
:map => map_csv(&map_block) }.merge!(options)
|
21
|
+
|
22
|
+
csv_data = config[:type] == :io ? data : File.new(data, 'r')
|
23
|
+
map = config[:map]
|
24
|
+
|
25
|
+
results = []
|
26
|
+
FasterCSV.new(csv_data, map.parser_options ).each_with_index do |row, i|
|
27
|
+
results << map.parse(row) if i >= map.start_at_row && i <= map.stop_at_row
|
28
|
+
end
|
29
|
+
|
30
|
+
results
|
31
|
+
end
|
32
|
+
|
33
|
+
# CsvMapper::RowMap provides a simple, DSL-like interface for constructing mappings.
|
34
|
+
# A CsvMapper::RowMap provides the main functionality of the library. It will mostly be used indirectly through the CsvMapper API,
|
35
|
+
# but may be useful to use directly for the dynamic CSV mappings.
|
36
|
+
class RowMap
|
37
|
+
#Start with a 'blank slate'
|
38
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__||instance_eval/ }
|
39
|
+
|
40
|
+
Infinity = 1.0/0
|
41
|
+
attr_reader :mapped_attributes
|
42
|
+
|
43
|
+
# Create a new instance with access to an evaluation context
|
44
|
+
def initialize(context, &map_block)
|
45
|
+
@context = context
|
46
|
+
@before_filters = []
|
47
|
+
@after_filters = []
|
48
|
+
@parser_options = {}
|
49
|
+
@start_at_row = 0
|
50
|
+
@stop_at_row = Infinity
|
51
|
+
@delimited_by = FasterCSV::DEFAULT_OPTIONS[:col_sep]
|
52
|
+
@mapped_attributes = []
|
53
|
+
|
54
|
+
self.instance_eval(&map_block) if block_given?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Each row of a CSV is parsed and mapped to a new instance of a Ruby class; OpenStruct by default.
|
58
|
+
# Use this method to change the what class each row is mapped to.
|
59
|
+
# The given class must respond to a parameter-less #new and all attribute mappings defined.
|
60
|
+
# Providing a hash of defaults will ensure that each resulting object will have the providing name and attribute values
|
61
|
+
# unless overridden by a mapping
|
62
|
+
def map_to(klass, defaults={})
|
63
|
+
@map_to_klass = klass
|
64
|
+
|
65
|
+
defaults.each do |name, value|
|
66
|
+
self.add_attribute(name, -99).map lambda{|row| value}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Specify a hash of FasterCSV options to be used for CSV parsing
|
71
|
+
#
|
72
|
+
# Can be anything FasterCSV::new()[http://fastercsv.rubyforge.org/classes/FasterCSV.html#M000018] accepts
|
73
|
+
def parser_options(opts=nil)
|
74
|
+
@parser_options = opts if opts
|
75
|
+
@parser_options.merge :col_sep => @delimited_by
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convenience method to 'move' the cursor skipping the current index.
|
79
|
+
def _SKIP_
|
80
|
+
self.move_cursor
|
81
|
+
end
|
82
|
+
|
83
|
+
# Specify the CSV column delimiter. Defaults to comma.
|
84
|
+
def delimited_by(delimiter=nil)
|
85
|
+
@delimited_by = delimiter if delimiter
|
86
|
+
@delimited_by
|
87
|
+
end
|
88
|
+
|
89
|
+
# Declare what row to begin parsing the CSV.
|
90
|
+
# This is useful for skipping headers and such.
|
91
|
+
def start_at_row(row_number=nil)
|
92
|
+
@start_at_row = row_number if row_number
|
93
|
+
@start_at_row
|
94
|
+
end
|
95
|
+
|
96
|
+
# Declare the last row to be parsed in a CSV.
|
97
|
+
def stop_at_row(row_number=nil)
|
98
|
+
@stop_at_row = row_number if row_number
|
99
|
+
@stop_at_row
|
100
|
+
end
|
101
|
+
|
102
|
+
# Declare method name symbols and/or lambdas to be executed before each row.
|
103
|
+
# Each method or lambda must accept to parameters: +csv_row+, +target_object+
|
104
|
+
# Methods names should refer to methods available within the RowMap's provided context
|
105
|
+
def before_row(*befores)
|
106
|
+
self.add_filters(@before_filters, *befores)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Declare method name symbols and/or lambdas to be executed before each row.
|
110
|
+
# Each method or lambda must accept to parameters: +csv_row+, +target_object+
|
111
|
+
# Methods names should refer to methods available within the RowMap's provided context
|
112
|
+
def after_row(*afters)
|
113
|
+
self.add_filters(@after_filters, *afters)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Add a new attribute to this map. Mostly used internally, but is useful for dynamic map creation.
|
117
|
+
# returns the newly created CsvMapper::AttributeMap
|
118
|
+
def add_attribute(name, index=nil)
|
119
|
+
attr_mapping = CsvMapper::AttributeMap.new(name.to_sym, index, @context)
|
120
|
+
self.mapped_attributes << attr_mapping
|
121
|
+
attr_mapping
|
122
|
+
end
|
123
|
+
|
124
|
+
# The current cursor location
|
125
|
+
def cursor # :nodoc:
|
126
|
+
@cursor ||= 0
|
127
|
+
end
|
128
|
+
|
129
|
+
# Move the cursor relative to it's current position
|
130
|
+
def move_cursor(positions=1) # :nodoc:
|
131
|
+
self.cursor += positions
|
132
|
+
end
|
133
|
+
|
134
|
+
# Given a CSV row return an instance of an object defined by this mapping
|
135
|
+
def parse(csv_row)
|
136
|
+
target = self.map_to_class.new
|
137
|
+
@before_filters.each {|filter| filter.call(csv_row, target) }
|
138
|
+
|
139
|
+
self.mapped_attributes.inject(target) do |result, attr_map|
|
140
|
+
result.send("#{attr_map.name}=".to_sym, attr_map.parse(csv_row))
|
141
|
+
result
|
142
|
+
end
|
143
|
+
|
144
|
+
@after_filters.each {|filter| filter.call(csv_row, target) }
|
145
|
+
|
146
|
+
return target
|
147
|
+
end
|
148
|
+
|
149
|
+
protected # :nodoc:
|
150
|
+
|
151
|
+
# The Hacktastic "magic"
|
152
|
+
# Used to dynamically create CsvMapper::AttributeMaps based on unknown method calls that
|
153
|
+
# should represent the names of mapped attributes.
|
154
|
+
#
|
155
|
+
# An optional first argument is used to move this maps cursor position and as the index of the
|
156
|
+
# new AttributeMap
|
157
|
+
def method_missing(name, *args) # :nodoc:
|
158
|
+
|
159
|
+
if index = args[0]
|
160
|
+
self.move_cursor(index - self.cursor)
|
161
|
+
else
|
162
|
+
index = self.cursor
|
163
|
+
self.move_cursor
|
164
|
+
end
|
165
|
+
|
166
|
+
add_attribute(name, index)
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_filters(to_hook, *filters) # :nodoc:
|
170
|
+
(to_hook << filters.collect do |filter|
|
171
|
+
filter.is_a?(Symbol) ? lambda{|row, target| @context.send(filter, row, target)} : filter
|
172
|
+
end).flatten!
|
173
|
+
end
|
174
|
+
|
175
|
+
def map_to_class # :nodoc:
|
176
|
+
@map_to_klass || OpenStruct
|
177
|
+
end
|
178
|
+
|
179
|
+
def cursor=(value) # :nodoc:
|
180
|
+
@cursor=value
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
# A CsvMapper::AttributeMap contains the instructions to parse a value from a CSV row and to know the
|
187
|
+
# name of the attribute it is targeting.
|
188
|
+
class AttributeMap
|
189
|
+
attr_reader :name, :index
|
190
|
+
|
191
|
+
# Creates a new instance using the provided attribute +name+, CSV row +index+, and evaluation +map_context+
|
192
|
+
def initialize(name, index, map_context)
|
193
|
+
@name, @index, @map_context = name, index, map_context
|
194
|
+
end
|
195
|
+
|
196
|
+
# Set the index that this map is targeting.
|
197
|
+
#
|
198
|
+
# Returns this AttributeMap for chainability
|
199
|
+
def at(index)
|
200
|
+
@index = index
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
# Provide a lambda or the symbol name of a method on this map's evaluation context to be used when parsing
|
205
|
+
# the value from a CSV row.
|
206
|
+
# Both the lambda or the method provided should accept a single +row+ parameter
|
207
|
+
#
|
208
|
+
# Returns this AttributeMap for chainability
|
209
|
+
def map(transform)
|
210
|
+
@transformer = transform
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
# Given a CSV row, return the value at this AttributeMap's index using any provided map transforms (see map)
|
215
|
+
def parse(csv_row)
|
216
|
+
@transformer ? parse_transform(csv_row) : csv_row[self.index]
|
217
|
+
end
|
218
|
+
|
219
|
+
# Access the raw value of the CSV row without any map transforms applied.
|
220
|
+
def raw_value(csv_row)
|
221
|
+
csv_row[self.index]
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def parse_transform(csv_row)
|
227
|
+
if @transformer.is_a? Symbol
|
228
|
+
transform_name = @transformer
|
229
|
+
@transformer = lambda{|row| @map_context.send(transform_name, row) }
|
230
|
+
end
|
231
|
+
|
232
|
+
@transformer.call(csv_row)
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scottmotte-csvmapper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- scottmotte
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-20 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: right_aws
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mini_magick
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: scott@scottmotte.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- lib/csvmapper.rb
|
52
|
+
- spec/csvmapper_spec.rb
|
53
|
+
- spec/spec_helper.rb
|
54
|
+
has_rdoc: false
|
55
|
+
homepage: http://github.com/scottmotte/csvmapper
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.2.0
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Stolen from pillowfactory's csv-mapper and adjusted slightly
|
80
|
+
test_files:
|
81
|
+
- spec/csvmapper_spec.rb
|
82
|
+
- spec/spec_helper.rb
|