dir = File.dirname(__FILE__) $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir) require 'rubygems' # the following is slightly modified from Gregory Brown's # solution on the Ruport Blaag: # http://ruport.blogspot.com/2008/03/fastercsv-api-shim-for-19.html if RUBY_VERSION > "1.9" require "csv" unless defined? FCSV class Object FasterCSV = CSV alias_method :FasterCSV, :CSV end end else require "fastercsv" end # This module provides the main interface for importing CSV files & data to mapped Ruby objects. # = Usage # Including CsvMapper will provide two methods: # - +import+ # - +map_csv+ # # See csv-mapper.rb[link:files/lib/csv-mapper_rb.html] for method docs. # # === Import From File # results = import('/path/to/file.csv') do # # declare mapping here # end # # === Import From String or IO # results = import(csv_data, :type => :io) do # # declare mapping here # end # # === Mapping # Mappings are built inside blocks. All three of CsvMapper's main API methods accept a block containing a mapping. # Maps are defined by using +map_to+, +start_at_row+, +before_row+, and +after_row+ (methods on CsvMapper::RowMap) and # by defining your own mapping attributes. # A mapping block uses an internal cursor to keep track of the order the mapping attributes are declared and use that order to # know the corresponding CSV column index to associate with the attribute. # # ===== The Basics # * +map_to+ - Override the default Struct target. Accepts a class and an optional hash of default attribute names and values. # * +start_at_row+ - Specify what row to begin parsing at. Use this to skip headers. # * +before_row+ - Accepts an Array of method name symbols or lambdas to be invoked before parsing each row. # * +after_row+ - Accepts an Array of method name symbols or lambdas to be invoked after parsing each row. # * +delimited_by+ - Accepts a character to be used to delimit columns. Use this to specify pipe-delimited files. # * \_SKIP_ - Use as a placehold to skip a CSV column index. # * +parser_options+ - Accepts a hash of FasterCSV options. Can be anything FasterCSV::new()[http://fastercsv.rubyforge.org/classes/FasterCSV.html#M000018] understands # # ===== Attribute Mappings # Attribute mappings are created by using the name of the attribute to be mapped to. # The order in which attribute mappings are declared determines the index of the corresponding CSV row. # All mappings begin at the 0th index of the CSV row. # foo # maps the 0th CSV row position value to the value of the 'foo' attribute on the target object. # bar # maps the 1st row position to 'bar' # This could also be a nice one liner for easy CSV format conversion # [foo, bar] # creates the same attribute maps as above. # The mapping index may be specifically declared in two additional ways: # foo(2) # maps the 2nd CSV row position value to 'foo' and moves the cursor to 3 # bar # maps the 3rd CSV row position to 'bar' due to the current cursor position # baz.at(0) # maps the 0th CSV row position to 'baz' but only increments the cursor 1 position to 4 # Each attribute mapping may be configured to parse the record using a lambda or a method name # foo.map lambda{|row| row[2].strip } # maps the 2nd row position value with leading and trailing whitespace removed to 'foo'. # bar.map :clean_bar # maps the result of the clean_bar method to 'bar'. clean_bar must accept the row as a parameter. # Attribute mapping declarations and "modifiers" may be chained # foo.at(4).map :some_transform # # === Create Reusable Mappings # The +import+ method accepts an instance of RowMap as an optional mapping parameter. # The easiest way to create an instance of a RowMap is by using +map_csv+. # a_row_map = map_csv do # # declare mapping here # end # Then you can reuse the mapping # results = import(some_string, :type => :io, :map => a_row_map) # other_results = import('/path/to/file.csv', :map => a_row_map) # module CsvMapper # Create a new RowMap instance from the definition in the given block. def map_csv(&map_block) CsvMapper::RowMap.new(self, &map_block) end # Load CSV data and map the values according to the definition in the given block. # Accepts either a file path, String, or IO as +data+. Defaults to file path. # # The following +options+ may be used: # :type:: defaults to :file_path. Use :io to specify data as String or IO. # :map:: Specify an instance of a RowMap to take presidence over a given block defintion. # def import(data, options={}, &map_block) csv_data = options[:type] == :io ? data : File.new(data, 'r') config = { :type => :file_path, :map => map_csv_with_data(csv_data, &map_block) }.merge!(options) map = config[:map] results = [] FasterCSV.new(csv_data, map.parser_options ).each_with_index do |row, i| results << map.parse(row) if i >= map.start_at_row && i <= map.stop_at_row end results end protected # Create a new RowMap instance from the definition in the given block and pass the csv_data. def map_csv_with_data(csv_data, &map_block) # :nodoc: CsvMapper::RowMap.new(self, csv_data, &map_block) end extend self end require 'csv-mapper/row_map'