csv_importer 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.
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-11-04
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,16 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ Rakefile
5
+ README.rdoc
6
+ lib/csv_importer.rb
7
+ lib/csv_importer/csv_importer.rb
8
+ script/console
9
+ script/destroy
10
+ script/generate
11
+ script/txt2html
12
+ spec/csv/sample.csv
13
+ spec/csv_importer_spec.rb
14
+ spec/spec.opts
15
+ spec/spec_helper.rb
16
+ tasks/rspec.rake
@@ -0,0 +1 @@
1
+ For more information on csv_importer, see http://csv_importer.rubyforge.org
@@ -0,0 +1,69 @@
1
+ == DESCRIPTION:
2
+
3
+ http://github.com/sparkboxx/csv-importer
4
+
5
+ Ever needed to import csv files where every row needs to be converted into a model?
6
+
7
+ The CSV importer turns every row of a CSV file into an object. Each column is matched and tested against a given class.
8
+ You can provide a dictionary with translations between the CSV column names and the object properties.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * Probably no windows support right now because of different line endings.
13
+
14
+ == SYNOPSIS:
15
+
16
+ require 'csv_importer'
17
+
18
+ class Product < Struct.new(:title, :price, :brand, :image); end
19
+ file = File.open("filename.csv", "rb")
20
+ importer = CSV_Importer::Importer.new(file, Product)
21
+ products = importer.objects
22
+
23
+ or
24
+
25
+ require 'csv_importer'
26
+
27
+ class Product < Struct.new(:title, :price, :brand, :image); end
28
+ csv_string = "title, the price, the brand \n jeans, 10, fashionable"
29
+ dictionary = {"the brand"=>"brand", "the price"=>"price"}
30
+ importer = CSV_Importer::Importer.new(csv_string, Product, dictionary)
31
+ products = importer.objects
32
+
33
+ For use with ActiveRecord objects you can provide an argument to call save on every product when created
34
+
35
+ products = importer.object(true)
36
+
37
+
38
+ == REQUIREMENTS:
39
+
40
+ * This gem uses the standard ruby CSV libary. Ruby 1.9 will have a new library which will probably be compatible.
41
+
42
+ == INSTALL:
43
+
44
+ sudo gem install csv_importer
45
+
46
+ == LICENSE:
47
+
48
+ (The MIT License)
49
+
50
+ Copyright (c) 2009 Sparkboxx
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining
53
+ a copy of this software and associated documentation files (the
54
+ 'Software'), to deal in the Software without restriction, including
55
+ without limitation the rights to use, copy, modify, merge, publish,
56
+ distribute, sublicense, and/or sell copies of the Software, and to
57
+ permit persons to whom the Software is furnished to do so, subject to
58
+ the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be
61
+ included in all copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
64
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
65
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
66
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
67
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
68
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
69
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ gem 'hoe', '>= 2.1.0'
3
+ require 'hoe'
4
+ require 'fileutils'
5
+ require './lib/csv_importer'
6
+
7
+ Hoe.plugin :newgem
8
+ # Generate all the Rake tasks
9
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
10
+ $hoe = Hoe.spec 'csv_importer' do
11
+ self.developer 'Sparkboxx', 'wilco@sparkboxx.com'
12
+ end
13
+
14
+ require 'newgem/tasks'
15
+ Dir['tasks/**/*.rake'].each { |t| load t }
16
+
17
+ # TODO - want other tests/tasks run by default? Add them to the list
18
+ # remove_task :default
19
+ # task :default => [:spec, :features]
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'csv_importer/csv_importer'
@@ -0,0 +1,79 @@
1
+ module CSVImporter
2
+ VERSION = "0.0.1"
3
+ require 'csv'
4
+
5
+
6
+ class Importer
7
+ attr_reader :reader, :delimiter, :csv_columns, :columns, :klass, :dictionary, :objects, :save
8
+
9
+ def initialize(str_or_readable, klass=nil, dictionary={})
10
+ @klass = klass
11
+ @save = save
12
+ @dictionary = {}
13
+ dictionary.each_pair{|k,v| @dictionary[k.downcase] = v.downcase}
14
+ @delimiter, @csv_columns = determine_delimiter_and_columns(str_or_readable)
15
+ @columns = match_columns
16
+ @reader = CSV::Reader.create(str_or_readable, @delimiter)
17
+ end
18
+
19
+ def objects(save=false)
20
+ return @objects if !@objects.nil?
21
+ @objects = Array.new if @object.nil?
22
+ first = true
23
+ @reader.each do |row|
24
+ #TODO: Ruby 1.9 has header option would be nicer instead of this hack.
25
+ if !first
26
+ object = @klass.new
27
+ @columns.each_pair do |column, row_number|
28
+ object.send("#{column}=", row[row_number].strip)
29
+ end
30
+ object.save if save
31
+ @objects << object
32
+ else
33
+ first = false
34
+ end
35
+ end
36
+ return @objects
37
+ end
38
+
39
+ private
40
+ def determine_delimiter_and_columns(str_or_readable)
41
+ case str_or_readable
42
+ when IO
43
+ first_line = str_or_readable.gets
44
+ str_or_readable.pos = 0 #Reset IO cursor position, else the first line will be skipped
45
+ when String
46
+ #TODO: Find efficient way to get the first line by stoping at first newline character
47
+ #TODO: Test for different newline characters on windows machines
48
+ first_line = str_or_readable.split("\n")[0]
49
+ else
50
+ #TODO: What's the best exception to raise?
51
+ raise ArgumentException, "No String or IO argument detected"
52
+ end
53
+
54
+ #TODO: Fix repitition in determine_delimiter_and_columns
55
+ comma = first_line.split(",")
56
+ semicolon = first_line.split(";")
57
+ if comma.count > semicolon.count
58
+ return ",",comma.map!{|c| c.strip.downcase}
59
+ else
60
+ return ";",semicolon.map!{|c| c.strip.downcase}
61
+ end
62
+ end
63
+
64
+ def match_columns
65
+ columns = {} if @columns.nil?
66
+ klass = @klass.new
67
+ @csv_columns.each_with_index do |column, index|
68
+ if klass.respond_to?("#{column}=")
69
+ columns[column] = index
70
+ elsif klass.respond_to?("#{@dictionary[column.downcase]}=")
71
+ columns[@dictionary[column.downcase]] = index
72
+ end
73
+ end
74
+ return columns
75
+ end
76
+
77
+
78
+ end
79
+ end
@@ -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_importer.rb'}"
9
+ puts "Loading csv_importer gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -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)
@@ -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,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ load File.dirname(__FILE__) + "/../Rakefile"
4
+ require 'rubyforge'
5
+ require 'redcloth'
6
+ require 'syntax/convertors/html'
7
+ require 'erb'
8
+
9
+ download = "http://rubyforge.org/projects/#{$hoe.rubyforge_name}"
10
+ version = $hoe.version
11
+
12
+ def rubyforge_project_id
13
+ RubyForge.new.configure.autoconfig["group_ids"][$hoe.rubyforge_name]
14
+ end
15
+
16
+ class Fixnum
17
+ def ordinal
18
+ # teens
19
+ return 'th' if (10..19).include?(self % 100)
20
+ # others
21
+ case self % 10
22
+ when 1: return 'st'
23
+ when 2: return 'nd'
24
+ when 3: return 'rd'
25
+ else return 'th'
26
+ end
27
+ end
28
+ end
29
+
30
+ class Time
31
+ def pretty
32
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
33
+ end
34
+ end
35
+
36
+ def convert_syntax(syntax, source)
37
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
38
+ end
39
+
40
+ if ARGV.length >= 1
41
+ src, template = ARGV
42
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb')
43
+ else
44
+ puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html")
45
+ exit!
46
+ end
47
+
48
+ template = ERB.new(File.open(template).read)
49
+
50
+ title = nil
51
+ body = nil
52
+ File.open(src) do |fsrc|
53
+ title_text = fsrc.readline
54
+ body_text_template = fsrc.read
55
+ body_text = ERB.new(body_text_template).result(binding)
56
+ syntax_items = []
57
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
58
+ ident = syntax_items.length
59
+ element, syntax, source = $1, $2, $3
60
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
61
+ "syntax-temp-#{ident}"
62
+ }
63
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
64
+ body = RedCloth.new(body_text).to_html
65
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
66
+ end
67
+ stat = File.stat(src)
68
+ created = stat.ctime
69
+ modified = stat.mtime
70
+
71
+ $stdout << template.result(binding)
@@ -0,0 +1,6 @@
1
+ Description;Brand;SellPrice;SalePrice;Image;Category;ArticleCode
2
+ SINGLE JERSEY L.S R;PME Jeans; 59.95; 59.95;http://www.example.com/images/JTS91573.JPG;T-shirts;JTS91573
3
+ MONTANA L.SL. GRAND;PME legend; 59.95; 59.95;http://www.example.com/images/PTS88510.JPG;T-shirts;PTS88510
4
+ CO DOUBLE JERSEY, 1;Cast Iron; 29.95; 29.95;http://www.example.com/images/CAC91101.JPG;Belts/ Keyholders;CAC91101
5
+ PLAYER L.SLV. VEST;Cast Iron; 99.95; 99.95;http://www.example.com/images/CKW91406.JPG;Knitwear;CKW91406
6
+ SLUBOR L.SL POLO;Cast Iron; 79.95; 79.95;http://www.example.com/images/CPS91301.JPG;Polo l.sl;CPS91301
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe "Initialze CSV" do
4
+ include CSVImporter
5
+
6
+ it "should open a reader on the given CSV file" do
7
+ importer = new_importer
8
+ importer.reader.class == CSV::Reader
9
+ end
10
+
11
+ it "should open a reader given a string" do
12
+ importer = Importer.new("foo,bar,foo,bar", Product)
13
+ importer.reader.class == CSV::Reader
14
+ end
15
+
16
+ it "should detect the ; file delimiter" do
17
+ importer = new_importer
18
+ importer.delimiter.should == ";"
19
+ end
20
+
21
+ it "should detect the , file delimiter" do
22
+ importer = Importer.new("foo,bar,foo,bar,foo", Product)
23
+ importer.delimiter.should == ","
24
+ end
25
+
26
+ it "should pick the delimiter with the maximum split count" do
27
+ importer = Importer.new("column,col;umn,column,col;umn,column,column", Product)
28
+ importer.delimiter.should == ","
29
+ end
30
+
31
+ it "should set the columns of the CSV file" do
32
+ importer = Importer.new("a,b,c,d,e\nfoo,bar,foo,bar,foo", Product)
33
+ importer.csv_columns.should == ["a","b","c","d","e"]
34
+ end
35
+
36
+ it "should strip the column names" do
37
+ importer = Importer.new("a, b, c , d ", Product)
38
+ importer.csv_columns.should == ["a","b","c","d"]
39
+ end
40
+
41
+ it "should memorize the class to handle the rows" do
42
+ importer = Importer.new("a,b", Product)
43
+ importer.klass.should == Product
44
+ end
45
+
46
+ it "should match the CSV columns with the Object columns" do
47
+ importer = Importer.new("title, price, brand, foo, barred", Product)
48
+ importer.columns.should == {"title"=>0, "price"=>1, "brand"=>2}
49
+ end
50
+
51
+ it "should match the CSV columns with the Object columns through the dictionary" do
52
+ dictionary = {"the title"=>"title", "the price"=>"price", "the brand" => "brand"}
53
+ importer = Importer.new("the title, the price, the brand", Product, dictionary)
54
+ importer.columns.should == {"title"=>0, "price"=>1, "brand"=>2}
55
+ end
56
+
57
+ it "should ignore case differences in the dictionary" do
58
+ dictionary = {"THE title"=>"title", "the PRice"=>"price", "The Brand" => "brand"}
59
+ importer = Importer.new("the title, the price, the brand", Product, dictionary)
60
+ importer.columns.should == {"title"=>0, "price"=>1, "brand"=>2}
61
+ end
62
+
63
+ it "should be able to cope with partial dictionaries" do
64
+ dictionary = {"Description"=>"title", "Saleprice"=>"Price"}
65
+ importer = Importer.new(File.open(File.dirname(__FILE__)+ "/csv/sample.csv", "rb"), Product, dictionary)
66
+ importer.columns.should == {"title"=>0, "price"=>3, "brand"=>1, "image"=>4}
67
+ end
68
+
69
+ it "should create the collection of objects" do
70
+ dictionary = {"Description"=>"title", "Saleprice"=>"Price"}
71
+ importer = Importer.new(File.open(File.dirname(__FILE__)+ "/csv/sample.csv", "rb"), Product, dictionary)
72
+ importer.objects.count.should == 5 #See spec/csv/sample.csv
73
+ end
74
+
75
+ it "shoud not turn the title line into an object" do
76
+ importer = Importer.new("title,brand\nThis is a nice title!, belonging to a brand", Product)
77
+ importer.objects[0].title.should == "This is a nice title!"
78
+ importer.objects[0].brand.should == "belonging to a brand"
79
+ importer.objects.count.should == 1
80
+ end
81
+
82
+
83
+ end
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'csv_importer'
11
+ include CSVImporter
12
+
13
+ # Define a Product Struct for test purposes
14
+ class Product < Struct.new(:title, :price, :brand, :image); end
15
+
16
+ def new_importer
17
+ Importer.new(File.open(File.dirname(__FILE__)+ "/csv/sample.csv", "rb"), Product)
18
+ end
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_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,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csv_importer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sparkboxx
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-04 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.3
24
+ version:
25
+ description: |-
26
+ http://github.com/sparkboxx/csv-importer
27
+
28
+ Ever needed to import csv files where every row needs to be converted into a model?
29
+
30
+ The CSV importer turns every row of a CSV file into an object. Each column is matched and tested against a given class.
31
+ You can provide a dictionary with translations between the CSV column names and the object properties.
32
+ email:
33
+ - wilco@sparkboxx.com
34
+ executables: []
35
+
36
+ extensions: []
37
+
38
+ extra_rdoc_files:
39
+ - History.txt
40
+ - Manifest.txt
41
+ - PostInstall.txt
42
+ files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - PostInstall.txt
46
+ - Rakefile
47
+ - README.rdoc
48
+ - lib/csv_importer.rb
49
+ - lib/csv_importer/csv_importer.rb
50
+ - script/console
51
+ - script/destroy
52
+ - script/generate
53
+ - script/txt2html
54
+ - spec/csv/sample.csv
55
+ - spec/csv_importer_spec.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ - tasks/rspec.rake
59
+ has_rdoc: true
60
+ homepage: http://github.com/sparkboxx/csv-importer
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --main
66
+ - README.rdoc
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project: csv_importer
84
+ rubygems_version: 1.3.5
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: http://github.com/sparkboxx/csv-importer Ever needed to import csv files where every row needs to be converted into a model? The CSV importer turns every row of a CSV file into an object
88
+ test_files: []
89
+