from_excel 1.0.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/README.rdoc ADDED
@@ -0,0 +1,168 @@
1
+ == Overview
2
+
3
+ Import excel spreadsheet rows to ruby objects.
4
+ For how to use it, see examples below or see unit tests
5
+
6
+ == Install
7
+
8
+ gem install from_excel
9
+
10
+ == Dependencies
11
+
12
+ gem 'nokogiri'
13
+ gem 'roo'
14
+ gem 'rubyzip'
15
+ gem 'spreadsheet'
16
+ gem 'google-spreadsheet-ruby'
17
+
18
+ (also require 'active_support/inflector')
19
+
20
+ == Usage
21
+
22
+ data = Model.from_excel(spreadsheet_file, options)
23
+
24
+ Include module in your model : include ExcelImport
25
+
26
+
27
+
28
+ spreadsheet_file : File object as spreadsheet (ie :File.new ("path_to_spreadsheet"))
29
+
30
+ options :
31
+ :mapping => hash mapping attribute <=> <column name or column index>
32
+ :title => true|false . Pass false to indicate that columns have no title. True by default.
33
+ :limits => :start => [first_row, first_column], :end => [last_row, last_column]
34
+ :rules => Procs rules to convert data in cell sheet to desired data for object attribute.
35
+ For example, transform a string to association id (see example below)
36
+
37
+
38
+ <b>Note: If columns have title, by default the row of columns titles is supposed to be the first row before the first row
39
+ of data which will be convert to ruby object.</b>
40
+
41
+ == Basic spreadsheet
42
+
43
+ Suppose you have this kind of Excel sheet:
44
+ A B C
45
+ 1 First name Last name Age
46
+ 2 Albert Einstein 131
47
+ 3 Leonard De Vinci 558
48
+
49
+ and a class:
50
+
51
+ User (:first_name, last_name, :age)
52
+
53
+ You can retrieve users, the simple way like that :
54
+
55
+ users = User.from_excel(File.new ("path_to_spreadsheet"))
56
+
57
+ You get theses users :
58
+
59
+ [User.new(:first_name => 'Albert', :last_name => 'Einstein', :age => 131),
60
+ User.new(:first_name => 'Leonard', :last_name => 'De Vinci', :age => 558),
61
+ User.new(:first_name => 'Martin', :last_name => 'Heidegger', :age => 121)]
62
+
63
+ By default, attributes model are retrieved from columns title with the following simple rule :
64
+ o spaces are converted to "_"
65
+ o words are downcased
66
+
67
+ First Name => :first_name
68
+ Last Name => :last_name
69
+
70
+
71
+ == Mapping attribute - column names
72
+
73
+ If a column name can't be simply mapped to attribute model, you can pass a map to.
74
+ If, with the last spreadsheet example, "First name" column had "Prenom" instead as title and you want this column
75
+ to map :first_name attribute of User model, do like this :
76
+
77
+ users = User.from_excel sheet, :mapping => {:first_name => 'Prenom'}
78
+
79
+ == Spreadsheet whithout columns title
80
+
81
+ Suppose your spreadsheet has no columns title.
82
+ You can then map columns <=> model attribute via index of column.
83
+ You can also pass the starting cell of data (the first cell of data (excluded title when there is one))
84
+
85
+ users = User.from_excel sheet, :mapping => {:first_name => 1, :last_name => 2, :age => 3}, :limits => {:start => [2, 1]}
86
+
87
+ == Limits : Extraction of part of the spreadsheet
88
+
89
+ If you want to extract just a part of the spreadsheet, just pass start and end limits.
90
+ users = User.from_excel(sheet, :limits => {:start => [3, 3], :end => [5, 5] })
91
+
92
+ <b>Note</b> : The row which contains the columns titles (for default attributes mapping) is, here, supposed to be begining
93
+ at [2,3] and end at [2,5] (one row before the first row of data).
94
+ If not, you must pass a mapping with :attribute => column_index
95
+
96
+ == Object associations handling
97
+
98
+ Suppose user has one adress of class Adress(:street, :town, :zip_code)
99
+ And you have this spreadsheet :
100
+ A B C D E F
101
+ 1 First name Last name Age Street Town Zip Code
102
+ 2 Albert Einstein 131 17 rue de Brest Quimper 29000
103
+ So, you want user with user.adress = Adress.new(:stret => ..., :town => ... , :zip_code => ....)
104
+ Just pass the following mapping :
105
+
106
+ mapping = {[:adress, :street] => 'Street', [:adress, :town] => 'Town', [:adress, :zip_code] => 'Zip Code'}
107
+
108
+ users = User.from_excel sheet, :mapping => mapping
109
+
110
+ And you will get :
111
+
112
+ users.first.first_name => 'Albert'
113
+ users.first.adress.street => '17 rue de Brest'
114
+
115
+ users.first.adress => Adress.new(:street => '17 rue de Brest', :town => 'Quimper' .....)
116
+
117
+ == Rules
118
+
119
+
120
+ Suppose you have to transform value of cells column to value for your object attribute, make a rule!
121
+
122
+ For example, in the sheet you have a value that represents an association attribute id:
123
+ You have roles predifined in your database :
124
+ Roles :
125
+ id : 1 , name => 'Admin'
126
+ id : 2 , name => 'Reader' ....
127
+
128
+ Sheet :
129
+
130
+ First name Last name Rôle
131
+ Bobby Lapointe Admin
132
+ Gaston Lagaffe Reader
133
+
134
+
135
+ 'Admin' value must be retrieved as a Role with id 1
136
+ Make the rules like that :
137
+
138
+ rules = {[:role, :id] => rule}
139
+
140
+ Where rule is a Proc.
141
+
142
+ mapping = {[:role, :id] => 'Rôle'}
143
+ rules = {[:role, :id] => rule}
144
+ users = User.from_excel sheet, :mapping => mapping, :rules => rules
145
+
146
+
147
+ def rule
148
+ lambda do |cell_value|
149
+ case cell_value
150
+ when 'Admin' then 1
151
+ when 'Reader' then 2
152
+ end
153
+ end
154
+ end
155
+
156
+
157
+ That's it !
158
+ You get user.first.role => Role(:id => 1)
159
+
160
+ You may use rules for other purpose. Rule proc aim to transform value sheet cell to value you need for
161
+ your model.
162
+
163
+
164
+
165
+ == TO DO
166
+
167
+ Option for other spreadsheet than Excel (LibreOffice ...)
168
+ Handle has_many associations
@@ -0,0 +1,166 @@
1
+ module ExcelImport
2
+
3
+ class Bounds
4
+ DEFAULT_OFFSET_FROM_TITLE = 1
5
+
6
+ attr_accessor :first_row, :last_row, :first_column, :last_column, :offset_from_title
7
+
8
+ def initialize sheet, options={}
9
+ @options = options
10
+ @offset_from_title = title_offset
11
+ bounds(sheet)
12
+ end
13
+
14
+ def bounds sheet
15
+ limits = @options[:limits] || limits_from(sheet)
16
+ @first_column = limits[:start] ? limits[:start][1] : sheet.first_column
17
+ @first_row = limits[:start] ? limits[:start][0] : sheet.first_row + offset_from_title
18
+ @last_column = limits[:end] ? limits[:end][1] : sheet.last_column
19
+ @last_row = limits[:end] ? limits[:end][0] : sheet.last_row
20
+ end
21
+
22
+ def limits_from sheet
23
+ {:start => [sheet.first_row + offset_from_title, sheet.first_column], :end => [sheet.last_row, sheet.last_column]}
24
+ end
25
+
26
+ def title_offset
27
+ @options[:title] = true if @options[:title].nil?
28
+ @options[:title] ? @options[:offset_from_title] || DEFAULT_OFFSET_FROM_TITLE : 0
29
+ end
30
+
31
+ end
32
+
33
+ class Mapping
34
+
35
+ attr_accessor :map, :has_one_associations
36
+
37
+ def initialize sheet, map, bounds
38
+ @map = map
39
+ @sheet = sheet
40
+ @bounds = bounds
41
+ @map ? indexation : default
42
+ @has_one_associations = extract_has_one_associations
43
+ end
44
+
45
+ def extract_has_one_associations
46
+ @map.inject({}) do |associations, (attribute, index)|
47
+ next(associations) unless attribute.is_a?(Array)
48
+ association = attribute.first
49
+ next(associations) if associations.has_key?(association)
50
+ associations.merge({association => klass_of(association)})
51
+ end
52
+ end
53
+
54
+ def attributes_of association
55
+ @map.select {|attribute,| attribute.is_a?(Array) && attribute.first == association}
56
+ end
57
+
58
+ def indexation
59
+ return self if @map.all? {|key, value| value.is_a?(Integer)}
60
+ @bounds.first_column.upto(@bounds.last_column) do |index|
61
+ @map[attribute column_name_at(index)] = index
62
+ end
63
+ self
64
+ end
65
+
66
+ def default
67
+ @map = {}
68
+ @bounds.first_column.upto(@bounds.last_column) do |index|
69
+ @map[Mapping.default_attribute_for column_name_at(index)] = index
70
+ end
71
+ end
72
+
73
+ def attribute column
74
+ @map.each_pair {|key, value| return key if value == column}
75
+ Mapping.default_attribute_for(column)
76
+ end
77
+
78
+ def column_name_at column_index
79
+ @sheet.cell(@bounds.first_row - @bounds.offset_from_title, column_index)
80
+ end
81
+
82
+ def self.default_attribute_for column_name
83
+ column_name.downcase.gsub(' ', '_').to_sym
84
+ end
85
+
86
+ private
87
+
88
+ def klass_of attribute
89
+ attribute.to_s.camelize.constantize
90
+ end
91
+
92
+ end
93
+
94
+ class Rules
95
+
96
+ def initialize rules
97
+ @rules = rules || {}
98
+ end
99
+
100
+ def for attribute
101
+ @rules[attribute] || default
102
+ end
103
+
104
+ def default
105
+ lambda {|value| value}
106
+ end
107
+
108
+ end
109
+
110
+ class Integrator
111
+
112
+ def initialize sheet, klass, options
113
+ @sheet = sheet
114
+ @klass = klass
115
+ @bounds = Bounds.new(sheet, options)
116
+ @rules = Rules.new(options[:rules])
117
+ @mapping = Mapping.new(sheet, options[:mapping], @bounds)
118
+ end
119
+
120
+ def cell_value row_index, column_index
121
+ @rules.for(@mapping.attribute(column_index)).call(@sheet.cell(row_index, column_index))
122
+ end
123
+
124
+ def primary_attributes_hash row_index
125
+ @mapping.map.select{|attribute,| attribute.is_a? Symbol}.inject({}) do |hash, (attribute, index)|
126
+ hash[attribute] = cell_value(row_index, index)
127
+ hash
128
+ end
129
+ end
130
+
131
+ def object_from_columns_for_association association, row_index, klass
132
+ attributes = @mapping.attributes_of(association)
133
+ attributes.inject(klass.new) do |object, ((association, attribute), index)|
134
+ object.send(attribute.to_s + '=', cell_value(row_index, index))
135
+ object
136
+ end
137
+ end
138
+
139
+ def has_one_associations_attributes_hash row_index
140
+ @mapping.has_one_associations.inject({}) do |hash, (association, klass)|
141
+ hash[association] = object_from_columns_for_association(association, row_index, klass)
142
+ hash
143
+ end
144
+ end
145
+
146
+ def row_to_hash row_index
147
+ hash = primary_attributes_hash(row_index)
148
+ hash.merge(has_one_associations_attributes_hash row_index)
149
+ end
150
+
151
+ def rows_to_objects
152
+ @bounds.first_row.upto(@bounds.last_row).inject([]) {|objects, row_index| objects << @klass.new(row_to_hash(row_index)); objects}
153
+ end
154
+
155
+ end
156
+
157
+ def self.included(base)
158
+ class << base
159
+ def from_excel spreadsheet, opt={}
160
+ integrator = Integrator.new(Excel.new(spreadsheet.path), self, opt)
161
+ integrator.rows_to_objects
162
+ end
163
+ end
164
+ end
165
+
166
+ end
data/lib/from_excel.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'nokogiri'
2
+ require 'roo'
3
+ require 'rubygems'
4
+ require 'active_support/inflector'
5
+ require 'excel_import'
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), '../lib/from_excel')
4
+
5
+ require 'model/user'
6
+ require 'model/adress'
7
+ require 'model/role'
8
+
9
+
10
+ require 'mocha'
@@ -0,0 +1,17 @@
1
+ class Adress
2
+ attr_accessor :street, :town, :zip_code
3
+
4
+ def initialize args={}
5
+ @street = args[:street]
6
+ @town = args[:town]
7
+ @zip_code = args[:zip_code]
8
+ end
9
+
10
+ def ==(other)
11
+ return false unless other
12
+ street == other.street &&
13
+ town == other.town &&
14
+ zip_code == other.zip_code
15
+ end
16
+
17
+ end
@@ -0,0 +1,13 @@
1
+ class Role
2
+ attr_accessor :id, :title
3
+
4
+ def initialize args={}
5
+ @id = args[:id]
6
+ @name = args[:title]
7
+ end
8
+
9
+ def ==(other)
10
+ id == other.id
11
+ end
12
+
13
+ end
@@ -0,0 +1,21 @@
1
+ class User
2
+ include ExcelImport
3
+ attr_accessor :first_name, :last_name, :age, :adress, :role
4
+
5
+ def initialize args={}
6
+ @first_name = args[:first_name]
7
+ @last_name = args[:last_name]
8
+ @age = args[:age]
9
+ @adress = args[:adress]
10
+ @role = args[:role]
11
+ end
12
+
13
+ def ==(other)
14
+ first_name == other.first_name &&
15
+ last_name == other.last_name &&
16
+ age == other.age &&
17
+ adress == other.adress &&
18
+ role == other.role
19
+ end
20
+
21
+ end
@@ -0,0 +1,68 @@
1
+ require 'helper'
2
+
3
+ class TestBounds < Test::Unit::TestCase
4
+
5
+
6
+ def test_default_bounds
7
+ sheet = stubbed_sheet_with_bounds
8
+ bounds = ExcelImport::Bounds.new(sheet)
9
+ assert_equal 1, bounds.first_column
10
+ assert_equal 10, bounds.last_column
11
+ assert_equal 2, bounds.first_row #have a title line by default so first row is after title line
12
+ assert_equal 10, bounds.last_row
13
+ end
14
+
15
+ def test_bounds_with_limits_changed
16
+ sheet = stub
17
+ bounds = ExcelImport::Bounds.new(sheet, :limits => {:start => [3, 4], :end => [5, 20]})
18
+ assert_equal 4, bounds.first_column
19
+ assert_equal 20, bounds.last_column
20
+ assert_equal 3, bounds.first_row
21
+ assert_equal 5, bounds.last_row
22
+ end
23
+
24
+ def test_bounds_without_title_for_columns
25
+ sheet = stubbed_sheet_with_bounds
26
+ bounds = ExcelImport::Bounds.new(sheet, :title => false)
27
+ assert_equal 1, bounds.first_column
28
+ assert_equal 10, bounds.last_column
29
+ assert_equal 1, bounds.first_row # No title so first row is the first row of sheet
30
+ assert_equal 10, bounds.last_row
31
+ end
32
+
33
+ def test_bounds_with_only_start_key_should_take_sheet_end_cell_coordinates
34
+ sheet = stubbed_sheet_with_bounds
35
+ bounds = ExcelImport::Bounds.new(sheet, :limits => {:start => [3, 4]})
36
+ assert_equal 4, bounds.first_column
37
+ assert_equal 10, bounds.last_column
38
+ assert_equal 3, bounds.first_row
39
+ assert_equal 10, bounds.last_row
40
+ end
41
+
42
+ def test_bounds_with_only_end_key_should_take_sheet_start_cell_coordinates_with_1_added_to_first_row_if_title
43
+ sheet = stubbed_sheet_with_bounds
44
+ bounds = ExcelImport::Bounds.new(sheet, :limits => {:end => [10, 5]})
45
+ assert_equal 1, bounds.first_column
46
+ assert_equal 5, bounds.last_column
47
+ assert_equal 2, bounds.first_row
48
+ assert_equal 10, bounds.last_row
49
+ end
50
+
51
+ def test_bounds_with_offset_from_title
52
+ sheet = stubbed_sheet_with_bounds
53
+ bounds = ExcelImport::Bounds.new(sheet, :offset_from_title => 4)
54
+ assert_equal 1, bounds.first_column
55
+ assert_equal 10, bounds.last_column
56
+ assert_equal 5, bounds.first_row
57
+ assert_equal 10, bounds.last_row
58
+ end
59
+
60
+
61
+
62
+ private
63
+
64
+ def stubbed_sheet_with_bounds
65
+ stub(:first_column => 1, :last_column => 10, :first_row => 1, :last_row => 10)
66
+ end
67
+
68
+ end
@@ -0,0 +1,84 @@
1
+ require 'helper'
2
+
3
+ class TestFromExcel < Test::Unit::TestCase
4
+ SIMPLE_SHEET= File.join(File.dirname(__FILE__), 'spreadsheets/simple_sheet.xls')
5
+ SHEET_WITH_COLUMN_NAME_DIFFERENT_FROM_ATTRIBUTE = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_column_name_different_from_attribute.xls')
6
+ SHEET_WITHOUT_COLUMNS_NAMES = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_without_columns_names.xls')
7
+ SHEET_WITH_START_END_COLUMNS_ROWS = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_start_end_columns_rows.xls')
8
+ SHEET_WITH_COLUMNS_FROM_ASSOCIATION = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_columns_from_association.xls')
9
+ SHEET_WITH_COLUMN_VALUE_TO_ASSOCIATION_ID = File.join(File.dirname(__FILE__), 'spreadsheets/sheet_with_column_value_to_association_id.xls')
10
+
11
+ def test_import_with_simple_sheet
12
+ assert users = User.from_excel(File.new SIMPLE_SHEET)
13
+ assert_equal sheet_users, users
14
+ end
15
+
16
+ def test_import_with_simple_sheet_with_some_attributes_mapped_with_columns_names
17
+ sheet = File.new(SHEET_WITH_COLUMN_NAME_DIFFERENT_FROM_ATTRIBUTE)
18
+ users = User.from_excel sheet, :mapping => {:first_name => 'Prenom'}
19
+ assert_equal sheet_users, users
20
+ end
21
+
22
+ def test_should_be_able_to_import_with_attributes_mapped_with_columns_index
23
+ sheet = File.new(SHEET_WITHOUT_COLUMNS_NAMES)
24
+ users = User.from_excel sheet, :mapping => {:first_name => 1, :last_name => 2, :age => 3},
25
+ :limits => {:start => [2, 1]}
26
+ assert_equal sheet_users, users
27
+ end
28
+
29
+ def test_should_be_able_to_import_simple_sheet_with_given_start_and_end_columns_and_rows
30
+ assert users = User.from_excel(File.new(SHEET_WITH_START_END_COLUMNS_ROWS), :limits => {:start => [3, 3], :end => [5, 5] })
31
+ assert_equal sheet_users, users
32
+ end
33
+
34
+ def test_should_import_columns_on_associations_attributes_with_mapping_on_columns_names
35
+ sheet = File.new(SHEET_WITH_COLUMNS_FROM_ASSOCIATION)
36
+ mapping = {[:adress, :street] => 'Street', [:adress, :town] => 'Town', [:adress, :zip_code] => 'Zip Code'}
37
+ users = User.from_excel sheet, :mapping => mapping
38
+
39
+ assert_equal users_with_adresses, users
40
+ end
41
+
42
+ def test_should_be_able_to_apply_rule_on_column_cells_to_transform_data_from_sheet_to_object_attribute_value
43
+ sheet = File.new(SHEET_WITH_COLUMN_VALUE_TO_ASSOCIATION_ID)
44
+ mapping = {[:role, :id] => 'Rôle'}
45
+ rules = {[:role, :id] => rule_to_retrieve_id}
46
+ users = User.from_excel sheet, :mapping => mapping, :rules => rules
47
+
48
+ assert_equal users_with_role, users
49
+ end
50
+
51
+ private
52
+
53
+ def rule_to_retrieve_id
54
+ lambda do |cell_value|
55
+ case cell_value
56
+ when 'Admin' then 1
57
+ when 'Reader' then 2
58
+ end
59
+ end
60
+ end
61
+
62
+ def sheet_users
63
+ [User.new(:first_name => 'Albert', :last_name => 'Einstein', :age => 131),
64
+ User.new(:first_name => 'Leonard', :last_name => 'De Vinci', :age => 558),
65
+ User.new(:first_name => 'Martin', :last_name => 'Heidegger', :age => 121)]
66
+ end
67
+
68
+ def users_with_adresses
69
+ adresses = [ Adress.new(:street => '17 rue de Brest', :town => 'Quimper', :zip_code => 29000),
70
+ Adress.new(:street => '26 rue des Lilas', :town => 'Paris', :zip_code => 75017),
71
+ Adress.new(:street => '13 rue du port', :town => 'Vannes', :zip_code => 56000)]
72
+ users = sheet_users
73
+ users.each_with_index {|user, index| user.adress = adresses[index]}
74
+ users
75
+ end
76
+
77
+ def users_with_role
78
+ [ User.new(:first_name => 'Bobby', :last_name => 'Lapointe', :role => Role.new(:id => 1)),
79
+ User.new(:first_name => 'Gaston', :last_name => 'Lagaffe', :role => Role.new(:id => 2)) ]
80
+ end
81
+
82
+
83
+
84
+ end
@@ -0,0 +1,55 @@
1
+ require 'helper'
2
+
3
+ class TestMapping < Test::Unit::TestCase
4
+
5
+ def test_mapping_should_make_default_attribute_name_from_columns_title
6
+ sheet = stubbed_sheet
7
+ bounds = ExcelImport::Bounds.new(sheet)
8
+ mapping = ExcelImport::Mapping.new(sheet, {}, bounds )
9
+
10
+ assert_equal :first_name, mapping.attribute('First Name')
11
+ assert_equal :last_name, mapping.attribute('Last Name')
12
+ end
13
+
14
+ def test_mapping_should__attribute_name
15
+ sheet = stubbed_sheet
16
+ bounds = ExcelImport::Bounds.new(sheet)
17
+ mapping = ExcelImport::Mapping.new(sheet, {:name => 'First Name'}, bounds )
18
+
19
+ assert_equal :name, mapping.attribute(1)
20
+ end
21
+
22
+ def test_should_handle_association_attributes
23
+ sheet = stubbed_sheet_with_adress_columns
24
+ mapping = {[:adress, :street] => 'Street', [:adress, :town] => 'Town'}
25
+ bounds = ExcelImport::Bounds.new(sheet)
26
+ mapping = ExcelImport::Mapping.new(sheet, mapping, bounds )
27
+
28
+ assert_equal [:adress, :street], mapping.attribute(4)
29
+ assert_equal [:adress, :town], mapping.attribute(5)
30
+ assert_equal 5, mapping.map[[:adress, :town]]
31
+ assert_equal ({:adress => Adress}), mapping.has_one_associations
32
+
33
+ end
34
+
35
+ private
36
+
37
+ def stubbed_sheet
38
+ sheet = stub(:first_column => 1, :last_column => 3, :first_row => 1, :last_row => 2)
39
+ sheet.stubs(:cell).with(1,1).returns('First Name')
40
+ sheet.stubs(:cell).with(1,2).returns('Last Name')
41
+ sheet.stubs(:cell).with(1,3).returns('Age')
42
+ sheet
43
+ end
44
+
45
+ def stubbed_sheet_with_adress_columns
46
+ sheet = stub(:first_column => 1, :last_column => 5, :first_row => 1, :last_row => 2)
47
+ sheet.stubs(:cell).with(1,1).returns('First Name')
48
+ sheet.stubs(:cell).with(1,2).returns('Last Name')
49
+ sheet.stubs(:cell).with(1,3).returns('Age')
50
+ sheet.stubs(:cell).with(1,4).returns('Street')
51
+ sheet.stubs(:cell).with(1,5).returns('Town')
52
+ sheet
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: from_excel
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Philippe Cantin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-08 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Import Excel spreadsheet rows to ruby objects
23
+ email:
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.rdoc
30
+ files:
31
+ - lib/excel_import.rb
32
+ - lib/from_excel.rb
33
+ - README.rdoc
34
+ - test/helper.rb
35
+ - test/model/adress.rb
36
+ - test/model/role.rb
37
+ - test/model/user.rb
38
+ - test/test_bounds.rb
39
+ - test/test_from_excel.rb
40
+ - test/test_mapping.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/anoiaque/from_excel
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.7
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Import Excel spreadsheet rows to ruby objects
75
+ test_files:
76
+ - test/helper.rb
77
+ - test/model/adress.rb
78
+ - test/model/role.rb
79
+ - test/model/user.rb
80
+ - test/test_bounds.rb
81
+ - test/test_from_excel.rb
82
+ - test/test_mapping.rb