data_list_converter 0.1 → 0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f397cef319fe34bff9143714c69003b6e9f2cb2f
4
- data.tar.gz: 96708c56c0bfd2a4e48ebff454ec004c3becda34
3
+ metadata.gz: 3d8949f31a68b057f6914e873ec2ebe6e38b5dc4
4
+ data.tar.gz: a3d47489bc2874afde23b3281e3d792a6d05cc0b
5
5
  SHA512:
6
- metadata.gz: 20f9d84f520b6039d6c4a2b7bb1c7e9367a16a18b30818170a98637742bccf9524d5a2fefaa919730eb4b736e66571f13ab9acea08ade8c29200d996b9a7f80a
7
- data.tar.gz: 957d4cc8c98c9dc7cad99814ee4acaaa18cf222481c043926649b86241d4e5deb4db74d31e9631f97a428e10952a46ac152759a9ee24acaeda54a774cac11c62
6
+ metadata.gz: 9679c6942102474e5100153518b93b91352881a980ad8da94417b6a7adbe052a26500ac41ce4e5937521c9e3fdec0902de0776cb9d01e5caf28d05f8b0876e6c
7
+ data.tar.gz: 9e93b4ed6e0459f410378196a9e2486c0d6ee07b9339a5383a39641577e1eba1cb7ad7359d6aef8539693e38fde49ea0e1c20ea34646554580fa9ba0b3850d4c
data/Rakefile CHANGED
@@ -5,6 +5,12 @@ Bundler::GemHelper.install_tasks
5
5
 
6
6
  task :default => :test
7
7
 
8
+ task :console do
9
+ require 'pry'
10
+ require 'data_list_converter'
11
+ Pry.start
12
+ end
13
+
8
14
  task :test do
9
15
  load 'test/data_list_converter_test.rb'
10
16
  end
@@ -2,7 +2,7 @@ $:.push File.expand_path("../lib", __FILE__)
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "data_list_converter"
5
- s.version = "0.1"
5
+ s.version = "0.2"
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.authors = ["linjunhalida"]
8
8
  s.email = ["linjunhalida@gmail.com"]
@@ -14,6 +14,8 @@ Gem::Specification.new do |s|
14
14
  s.add_development_dependency 'rake', '~> 10.4'
15
15
  s.add_development_dependency 'minitest', '~> 5.7'
16
16
  s.add_development_dependency 'spreadsheet', '~> 1.0'
17
+ s.add_development_dependency 'rubyXL', '~> 3.3'
18
+ s.add_development_dependency 'pry'
17
19
 
18
20
  s.files = `git ls-files`.split("\n")
19
21
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -1,353 +1,11 @@
1
- require 'csv'
2
-
3
- # this class is used for convert data between types,
4
- # so we can use it to easily export and import data.
5
- #
6
- class DataListConverter
7
- CONVERTERS = {}
8
- FILTERS = {}
9
-
10
- class << self
11
-
12
- def types
13
- CONVERTERS.keys.flatten.uniq.sort
14
- end
15
-
16
- def register_converter(from_type, to_type, method)
17
- @route_map = nil # clear cache
18
- CONVERTERS[[from_type, to_type]] = method
19
- end
20
-
21
- def register_filter(type, name, method)
22
- FILTERS[type] ||= {}
23
- FILTERS[type][name] = method
24
- end
25
-
26
- # Example:
27
- # convert(:item_iterator, :item_data, iter)
28
- # convert(:item_iterator, :csv_file, iter, csv_file: {filename: 'result.csv'})
29
- # convert(:csv_file, :item_data, {filename: 'result.csv'})
30
- #
31
- # can add filter:
32
- # filter = :limit
33
- # filter = {limit: {size: 2}}
34
- # filter = [{limit: {size: 12}}, {count: {size: 4}}]
35
- # convert(:item_iterator, :table_data, iter, table_iterator: {filter: filter})
36
- def convert(from_type, to_type, from_value, options={})
37
- methods = []
38
- add_filter = lambda { |type|
39
- filters = (options[type] || {}).delete(:filter)
40
- return unless filters
41
- methods += normalize_filters(type, filters)
42
- }
43
-
44
- route = find_route(from_type, to_type)
45
- add_filter.call(route[0])
46
-
47
- (0..(route.length-2)).map do |i|
48
- from_type, to_type = route[i], route[i+1]
49
- method = CONVERTERS[[from_type, to_type]]
50
- raise "cannot find converter #{from_type} -> #{to_type}" unless method
51
- methods.push([method, options[to_type] || {}])
52
- add_filter.call(to_type)
53
- end
54
-
55
- methods.inject(from_value) do |v, method|
56
- method, args = method
57
- method.call(v, args)
58
- end
59
- end
60
-
61
- def normalize_filters(type, filters)
62
- # filter list as array
63
- filters = [filters] unless filters.kind_of?(Array)
64
- filters.map do |v|
65
- # fix filter arguments
66
- case v
67
- # {:limit, {count: 12}} => [:limit, {count: 12}]
68
- when Hash; v.first
69
- # :debug => [:debug, {}]
70
- when Symbol, String; [v, {}]
71
- else; v
72
- end
73
- end.map do |name, args|
74
- method = FILTERS[type][name] rescue raise("cannot find method for type #{type} filter #{name}")
75
- [method, args]
76
- end
77
- end
78
-
79
- # One type of data can be converted into any other types,
80
- # we have a list of convert methods: CONVERTERS
81
- # If we want to convert between types, like: convert item_data into csv_file,
82
- # we need find all the intermidate data type, like: [:item_data, :item_iterator, :table_iterator, :csv_file]
83
- def find_route(from_type, to_type)
84
- raise Exception, "from_type should not equal to to_type: #{from_type}" if from_type == to_type
85
- out = find_next_node([], [from_type], route_map, to_type)
86
- raise Exception, "Route not found: #{from_type} -> #{to_type}" unless out
87
- out
88
- end
89
-
90
- # iterate through the type convert graph, and find the route
91
- def find_next_node (out, nodes, map, end_node)
92
- nodes.each do |node|
93
- return out + [node] if node == end_node
94
- next unless next_nodes = map[node]
95
-
96
- new_map = map.dup
97
- new_map.delete(node)
98
- result = find_next_node(out + [node], next_nodes, new_map, end_node)
99
- return result if result
100
- end
101
- nil
102
- end
103
- private :find_next_node
104
-
105
- # convert adjacency list into quick lookup hash
106
- def route_map
107
- @route_map ||= \
108
- begin
109
- CONVERTERS.keys.
110
- inject({}) do |map, item|
111
- map[item.first] ||= []
112
- map[item.first] += [item[1]]
113
- map
114
- end
115
- end
116
- end
117
-
118
- end
119
- end
120
-
121
- # register types
122
-
123
- # item_data: [{name: 'xx', value: 12}, ...]
124
- # table_data: [['name', 'value'], ['xx', '12'], ..]
125
- # item_iterator and table_iterator are iterators which yield each row
126
- class DataListConverter
127
- self.register_converter(
128
- :item_iterator, :table_iterator, lambda{ |proc, options|
129
- lambda { |&block|
130
- columns = nil
131
- proc.call do |item|
132
- unless columns
133
- columns = item.keys.map(&:to_sym)
134
- block.call(columns.map(&:to_s))
135
- end
136
- # item_iterator key can be symbol or string
137
- block.call(columns.map{ |c| item[c] || item[c.to_s] })
138
- end
139
- }
140
- })
141
- self.register_converter(
142
- :table_iterator, :item_iterator, lambda{ |proc, options|
143
- lambda {|&block|
144
- columns = nil
145
- proc.call do |row|
146
- unless columns
147
- columns = row.map(&:to_sym)
148
- else
149
- block.call(columns.zip(row).to_h)
150
- end
151
- end
152
- }
153
- })
154
-
155
- def self.iterator_to_data(proc, options={})
156
- out = []
157
- proc.call { |d| out << d }
158
- out
159
- end
160
- self.register_converter(
161
- :item_iterator, :item_data, self.method(:iterator_to_data))
162
- self.register_converter(
163
- :table_iterator, :table_data, self.method(:iterator_to_data))
164
-
165
- def self.data_to_iterator(data, options={})
166
- lambda { |&block|
167
- data.each do |d|
168
- block.call(d)
169
- end
170
- }
171
- end
172
- self.register_converter(
173
- :item_data, :item_iterator, self.method(:data_to_iterator))
174
- self.register_converter(
175
- :table_data, :table_iterator, self.method(:data_to_iterator))
176
- end
177
-
178
- # records
179
- class DataListConverter
180
- self.register_converter(
181
- :records, :item_iterator, lambda { |records, options|
182
- columns = options[:columns]
183
- lambda { |&block|
184
- records.find_each do |record|
185
- item = columns.map do |column|
186
- [column.first.to_sym, record.send(column[1])]
187
- end.to_h
188
- block.call(item)
189
- end
190
- }
191
- })
192
- end
193
-
194
- # csv_file
195
- class DataListConverter
196
- self.register_converter(
197
- :csv_file, :table_iterator, lambda { |input, options|
198
- lambda { |&block|
199
- CSV.open(input[:filename]) do |csv|
200
- csv.each do |row|
201
- block.call(row)
202
- end
203
- end
204
- }
205
- })
206
- self.register_converter(
207
- :table_iterator, :csv_file, lambda { |proc, options|
208
- CSV.open(options[:filename], 'wb', force_quotes: true) do |csv|
209
- proc.call do |row|
210
- csv << row
211
- end
212
- end
213
- })
214
- end
215
-
216
- # xls_file only works when installed spreadsheet gem
217
- # multi_sheet_data = {
218
- # 'sheet1' => [columns, row, row, ...],
219
- # 'sheet2' => [columns, row, row, ...],
220
- # }
221
- begin
222
- require 'spreadsheet'
223
-
224
- class DataListConverter
225
- self.register_converter(
226
- :xls_file, :table_iterator, lambda { |input, options|
227
- lambda { |&block|
228
- book = Spreadsheet.open(input[:filename])
229
- sheet = book.worksheet input[:sheet] || 0
230
- sheet.each do |row|
231
- block.call(row.to_a)
232
- end
233
- }
234
- })
235
- self.register_converter(
236
- :table_iterator, :xls_file, lambda { |proc, options|
237
- book = Spreadsheet::Workbook.new
238
- sheet = book.create_worksheet(name: (options[:sheet] || "Sheet1"))
239
- i = 0
240
- proc.call do |row|
241
- sheet.row(i).push *row
242
- i += 1
243
- end
244
- book.write(options[:filename])
245
- options[:filename]
246
- })
247
-
248
- self.register_converter(
249
- :multi_sheet_iterator, :multi_sheet_data, lambda { |data, options|
250
- data.map do |k, iter|
251
- data = self.convert(:table_iterator, :table_data, iter)
252
- [k, data]
253
- end.to_h
254
- })
255
- self.register_converter(
256
- :multi_sheet_data, :multi_sheet_iterator, lambda { |data, options|
257
- data.map do |k, v|
258
- iter = self.convert(:table_data, :table_iterator, v)
259
- [k, iter]
260
- end.to_h
261
- })
262
- self.register_converter(
263
- :multi_sheet_iterator, :xls_file, lambda { |data, options|
264
- book = Spreadsheet::Workbook.new
265
- data.each do |name, table_iterator|
266
- sheet = book.create_worksheet(name: name)
267
- i = 0
268
- table_iterator.call do |row|
269
- sheet.row(i).concat(row)
270
- i += 1
271
- end
272
- end
273
- filename = options[:filename]
274
- book.write(filename)
275
- filename
276
- })
277
- self.register_converter(
278
- :xls_file, :multi_sheet_iterator, lambda { |data, options|
279
- book = Spreadsheet.open(data[:filename])
280
- book.worksheets.map do |sheet|
281
- iterator = lambda { |&block|
282
- sheet.each do |row|
283
- block.call(row.to_a)
284
- end
285
- }
286
- [sheet.name, iterator]
287
- end.to_h
288
- })
289
- end
290
- rescue LoadError
291
- nil
292
- end
293
-
294
-
295
- # filters
296
- class DataListConverter
297
- self.register_filter(
298
- :table_iterator, :remove_debug, lambda{ |proc, options|
299
- lambda { |&block|
300
- columns = nil
301
- debug_index = nil
302
- proc.call do |row|
303
- unless columns
304
- columns = row
305
- debug_index = columns.index('debug')
306
- end
307
- block.call(row[0..(debug_index-1)])
308
- end
309
- }
310
- })
311
-
312
- def self.iterator_limit(proc, options)
313
- limit_size = options[:size] || 10
314
- lambda { |&block|
315
- limit = 0
316
- proc.call do |item|
317
- block.call(item)
318
- limit += 1
319
- break if limit >= limit_size
320
- end
321
- }
322
- end
323
- self.register_filter(
324
- :item_iterator, :limit, self.method(:iterator_limit))
325
- self.register_filter(
326
- :table_iterator, :limit, self.method(:iterator_limit))
327
-
328
- self.register_filter(
329
- :item_iterator, :count, lambda{ |proc, options|
330
- count = 0
331
- size = options[:size] || 100
332
- msg_format = options[:msg] || "on %{count}"
333
- total_format = options[:total] || "total: %{total}"
334
- out = options[:out] || STDOUT
335
- total = 1
336
- lambda { |&block|
337
- proc.call do |item|
338
- if item.keys == [:total]
339
- total = item[:total]
340
- out.write(total_format % {total: total})
341
- out.write("\n")
342
- else
343
- block.call(item)
344
- count += 1
345
- msg = msg_format % {count: count, percent: (count / total.to_f * 100).round(2)}
346
- out.write(msg + "\n") if count % size == 0
347
- end
348
- end
349
- }
350
- })
351
- end
1
+ require 'data_list_converter/base'
352
2
 
3
+ require 'data_list_converter/types/basic'
4
+ require 'data_list_converter/types/csv_file'
5
+ require 'data_list_converter/types/records'
6
+ require 'data_list_converter/types/xls_file' rescue LoadError
7
+ require 'data_list_converter/types/xlsx_file' rescue LoadError
353
8
 
9
+ require 'data_list_converter/filters/count'
10
+ require 'data_list_converter/filters/limit'
11
+ require 'data_list_converter/filters/remove_debug'
@@ -0,0 +1,148 @@
1
+ # this class is used for convert data between types,
2
+ # so we can use it to easily export and import data.
3
+ #
4
+ class DataListConverter
5
+ CONVERTERS = {}
6
+ FILTERS = {}
7
+
8
+ class << self
9
+
10
+ attr_accessor :debug
11
+
12
+ def on_debug
13
+ self.debug = true
14
+ yield
15
+ ensure
16
+ self.debug = false
17
+ end
18
+
19
+ def log(msg)
20
+ return unless debug
21
+ puts "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}\t#{msg}"
22
+ end
23
+
24
+ def types
25
+ CONVERTERS.keys.flatten.uniq.sort
26
+ end
27
+
28
+ def register_converter(from_type, to_type, method)
29
+ @route_map = nil # clear cache
30
+ CONVERTERS[[from_type, to_type]] = method
31
+ end
32
+
33
+ def register_filter(type, name, method)
34
+ FILTERS[type] ||= {}
35
+ FILTERS[type][name] = method
36
+ end
37
+
38
+ # Example:
39
+ # convert(:item_iterator, :item_data, iter)
40
+ # convert(:item_iterator, :csv_file, iter, csv_file: {filename: 'result.csv'})
41
+ # convert(:csv_file, :item_data, {filename: 'result.csv'})
42
+ #
43
+ # can add filter:
44
+ # filter = :limit
45
+ # filter = {limit: {size: 2}}
46
+ # filter = [{limit: {size: 12}}, {count: {size: 4}}]
47
+ # convert(:item_iterator, :table_data, iter, table_iterator: {filter: filter})
48
+ def convert(from_type, to_type, from_value, options={})
49
+ methods = []
50
+ add_filter = lambda { |type|
51
+ filters = (options[type] || {}).delete(:filter)
52
+ return unless filters
53
+ methods += normalize_filters(type, filters)
54
+ }
55
+
56
+ route = find_route(from_type, to_type)
57
+ add_filter.call(route[0])
58
+
59
+ self.log("route: #{route}")
60
+ (0..(route.length-2)).map do |i|
61
+ from_type, to_type = route[i], route[i+1]
62
+ method = CONVERTERS[[from_type, to_type]]
63
+ raise "cannot find converter #{from_type} -> #{to_type}" unless method
64
+ methods.push([method, options[to_type] || {}])
65
+ add_filter.call(to_type)
66
+ end
67
+
68
+ self.log("methods: #{methods}")
69
+ methods.inject(from_value) do |v, method|
70
+ method, args = method
71
+ method.call(v, args)
72
+ end
73
+ end
74
+
75
+ def normalize_filters(type, filters)
76
+ # filter list as array
77
+ filters = [filters] unless filters.kind_of?(Array)
78
+ filters.map do |v|
79
+ # fix filter arguments
80
+ case v
81
+ # {:limit, {count: 12}} => [:limit, {count: 12}]
82
+ when Hash; v.first
83
+ # :debug => [:debug, {}]
84
+ when Symbol, String; [v, {}]
85
+ else; v
86
+ end
87
+ end.map do |name, args|
88
+ method = FILTERS[type][name] rescue raise("cannot find method for type #{type} filter #{name}")
89
+ [method, args]
90
+ end
91
+ end
92
+
93
+ # One type of data can be converted into any other types,
94
+ # we have a list of convert methods: CONVERTERS
95
+ # If we want to convert between types, like: convert item_data into csv_file,
96
+ # we need find all the intermidate data type, like: [:item_data, :item_iterator, :table_iterator, :csv_file]
97
+ def find_route(from_type, to_type)
98
+ raise Exception, "from_type should not equal to to_type: #{from_type}" if from_type == to_type
99
+ # map wide search
100
+ checked = Set.new
101
+ checking = Set.new([from_type])
102
+ directions = {}
103
+
104
+ while not checking.empty?
105
+ current_node = checking.first
106
+
107
+ next_nodes = route_map[current_node]
108
+ # mark direction from from_type
109
+ next_nodes.each do |node|
110
+ # first marked is the shortest
111
+ directions[node] ||= current_node
112
+ end
113
+
114
+ if next_nodes.include?(to_type)
115
+ # get route
116
+ start = to_type
117
+ route = [start]
118
+ while start != from_type
119
+ previous = directions[start]
120
+ raise "cannot find previous for #{start} in #{directions}" if not previous
121
+ route.push(previous)
122
+ start = previous
123
+ end
124
+ return route.reverse
125
+ else
126
+ checking.delete(current_node)
127
+ checked.add(current_node)
128
+ checking += Set.new(next_nodes) - checked
129
+ end
130
+ end
131
+ raise Exception, "Route not found: #{from_type} -> #{to_type}"
132
+ end
133
+
134
+ # convert adjacency list into quick lookup hash
135
+ def route_map
136
+ @route_map ||= \
137
+ begin
138
+ CONVERTERS.keys.
139
+ inject({}) do |map, item|
140
+ map[item.first] ||= []
141
+ map[item.first] += [item[1]]
142
+ map
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,27 @@
1
+ class DataListConverter
2
+ self.register_filter(
3
+ :item_iterator, :count, lambda{ |proc, options|
4
+ count = 0
5
+ size = options[:size] || 100
6
+ msg_format = options[:msg] || "on %{count}"
7
+ total_format = options[:total] || "total: %{total}"
8
+ out = options[:out] || STDOUT
9
+ total = 1
10
+ lambda { |&block|
11
+ proc.call do |item|
12
+ if item.keys == [:total]
13
+ total = item[:total]
14
+ out.write(total_format % {total: total})
15
+ out.write("\n")
16
+ else
17
+ block.call(item)
18
+ count += 1
19
+ msg = msg_format % {count: count, percent: (count / total.to_f * 100).round(2)}
20
+ out.write(msg + "\n") if count % size == 0
21
+ end
22
+ end
23
+ }
24
+ })
25
+ end
26
+
27
+
@@ -0,0 +1,17 @@
1
+ class DataListConverter
2
+ def self.iterator_limit(proc, options)
3
+ limit_size = options[:size] || 10
4
+ lambda { |&block|
5
+ limit = 0
6
+ proc.call do |item|
7
+ block.call(item)
8
+ limit += 1
9
+ break if limit >= limit_size
10
+ end
11
+ }
12
+ end
13
+ self.register_filter(
14
+ :item_iterator, :limit, self.method(:iterator_limit))
15
+ self.register_filter(
16
+ :table_iterator, :limit, self.method(:iterator_limit))
17
+ end
@@ -0,0 +1,16 @@
1
+ class DataListConverter
2
+ self.register_filter(
3
+ :table_iterator, :remove_debug, lambda{ |proc, options|
4
+ lambda { |&block|
5
+ columns = nil
6
+ debug_index = nil
7
+ proc.call do |row|
8
+ unless columns
9
+ columns = row
10
+ debug_index = columns.index('debug')
11
+ end
12
+ block.call(row[0..(debug_index-1)])
13
+ end
14
+ }
15
+ })
16
+ end
@@ -0,0 +1,54 @@
1
+ # item_data: [{name: 'xx', value: 12}, ...]
2
+ # table_data: [['name', 'value'], ['xx', '12'], ..]
3
+ # item_iterator and table_iterator are iterators which yield each row
4
+ class DataListConverter
5
+ self.register_converter(
6
+ :item_iterator, :table_iterator, lambda{ |proc, options|
7
+ lambda { |&block|
8
+ columns = nil
9
+ proc.call do |item|
10
+ unless columns
11
+ columns = item.keys.map(&:to_sym)
12
+ block.call(columns.map(&:to_s))
13
+ end
14
+ # item_iterator key can be symbol or string
15
+ block.call(columns.map{ |c| item[c] || item[c.to_s] })
16
+ end
17
+ }
18
+ })
19
+ self.register_converter(
20
+ :table_iterator, :item_iterator, lambda{ |proc, options|
21
+ lambda {|&block|
22
+ columns = nil
23
+ proc.call do |row|
24
+ unless columns
25
+ columns = row.map(&:to_sym)
26
+ else
27
+ block.call(columns.zip(row).to_h)
28
+ end
29
+ end
30
+ }
31
+ })
32
+
33
+ def self.iterator_to_data(proc, options={})
34
+ out = []
35
+ proc.call { |d| out << d }
36
+ out
37
+ end
38
+ self.register_converter(
39
+ :item_iterator, :item_data, self.method(:iterator_to_data))
40
+ self.register_converter(
41
+ :table_iterator, :table_data, self.method(:iterator_to_data))
42
+
43
+ def self.data_to_iterator(data, options={})
44
+ lambda { |&block|
45
+ data.each do |d|
46
+ block.call(d)
47
+ end
48
+ }
49
+ end
50
+ self.register_converter(
51
+ :item_data, :item_iterator, self.method(:data_to_iterator))
52
+ self.register_converter(
53
+ :table_data, :table_iterator, self.method(:data_to_iterator))
54
+ end
@@ -0,0 +1,23 @@
1
+ require 'csv'
2
+
3
+ # csv_file
4
+ class DataListConverter
5
+ self.register_converter(
6
+ :csv_file, :table_iterator, lambda { |input, options|
7
+ lambda { |&block|
8
+ CSV.open(input[:filename]) do |csv|
9
+ csv.each do |row|
10
+ block.call(row)
11
+ end
12
+ end
13
+ }
14
+ })
15
+ self.register_converter(
16
+ :table_iterator, :csv_file, lambda { |proc, options|
17
+ CSV.open(options[:filename], 'wb', force_quotes: true) do |csv|
18
+ proc.call do |row|
19
+ csv << row
20
+ end
21
+ end
22
+ })
23
+ end
@@ -0,0 +1,15 @@
1
+ # records
2
+ class DataListConverter
3
+ self.register_converter(
4
+ :records, :item_iterator, lambda { |records, options|
5
+ columns = options[:columns]
6
+ lambda { |&block|
7
+ records.find_each do |record|
8
+ item = columns.map do |column|
9
+ [column.first.to_sym, record.send(column[1])]
10
+ end.to_h
11
+ block.call(item)
12
+ end
13
+ }
14
+ })
15
+ end
@@ -0,0 +1,73 @@
1
+ # xls_file only works when installed spreadsheet gem
2
+ # multi_sheet_data = {
3
+ # 'sheet1' => [columns, row, row, ...],
4
+ # 'sheet2' => [columns, row, row, ...],
5
+ # }
6
+ require 'spreadsheet'
7
+
8
+ class DataListConverter
9
+ self.register_converter(
10
+ :xls_file, :table_iterator, lambda { |input, options|
11
+ lambda { |&block|
12
+ book = Spreadsheet.open(input[:filename])
13
+ sheet = book.worksheet input[:sheet] || 0
14
+ sheet.each do |row|
15
+ block.call(row.to_a)
16
+ end
17
+ }
18
+ })
19
+ self.register_converter(
20
+ :table_iterator, :xls_file, lambda { |proc, options|
21
+ book = Spreadsheet::Workbook.new
22
+ sheet = book.create_worksheet(name: (options[:sheet] || "Sheet1"))
23
+ i = 0
24
+ proc.call do |row|
25
+ sheet.row(i).push *row
26
+ i += 1
27
+ end
28
+ book.write(options[:filename])
29
+ options[:filename]
30
+ })
31
+
32
+ self.register_converter(
33
+ :multi_sheet_iterator, :multi_sheet_data, lambda { |data, options|
34
+ data.map do |k, iter|
35
+ data = self.convert(:table_iterator, :table_data, iter)
36
+ [k, data]
37
+ end.to_h
38
+ })
39
+ self.register_converter(
40
+ :multi_sheet_data, :multi_sheet_iterator, lambda { |data, options|
41
+ data.map do |k, v|
42
+ iter = self.convert(:table_data, :table_iterator, v)
43
+ [k, iter]
44
+ end.to_h
45
+ })
46
+ self.register_converter(
47
+ :multi_sheet_iterator, :xls_file, lambda { |data, options|
48
+ book = Spreadsheet::Workbook.new
49
+ data.each do |name, table_iterator|
50
+ sheet = book.create_worksheet(name: name)
51
+ i = 0
52
+ table_iterator.call do |row|
53
+ sheet.row(i).concat(row)
54
+ i += 1
55
+ end
56
+ end
57
+ filename = options[:filename]
58
+ book.write(filename)
59
+ filename
60
+ })
61
+ self.register_converter(
62
+ :xls_file, :multi_sheet_iterator, lambda { |data, options|
63
+ book = Spreadsheet.open(data[:filename])
64
+ book.worksheets.map do |sheet|
65
+ iterator = lambda { |&block|
66
+ sheet.each do |row|
67
+ block.call(row.to_a)
68
+ end
69
+ }
70
+ [sheet.name, iterator]
71
+ end.to_h
72
+ })
73
+ end
@@ -0,0 +1,66 @@
1
+ # xlsx_file only works when installed rubyXL gem
2
+ # multi_sheet_data = {
3
+ # 'sheet1' => [columns, row, row, ...],
4
+ # 'sheet2' => [columns, row, row, ...],
5
+ # }
6
+ require 'rubyXL'
7
+
8
+ class DataListConverter
9
+ self.register_converter(
10
+ :xlsx_file, :table_iterator, lambda { |input, options|
11
+ lambda { |&block|
12
+ book = RubyXL::Parser.parse(input[:filename])
13
+ sheet = book.worksheets[input[:sheet] || 0]
14
+ sheet.each do |row|
15
+ block.call(row.cells.map(&:value))
16
+ end
17
+ }
18
+ })
19
+ self.register_converter(
20
+ :table_iterator, :xlsx_file, lambda { |proc, options|
21
+ book = RubyXL::Workbook.new
22
+ sheet = book.worksheets[0]
23
+ sheet.sheet_name = options[:sheet] if options[:sheet]
24
+ i = 0
25
+ proc.call do |row|
26
+ row.each_with_index do |v, j|
27
+ sheet.add_cell(i, j, v)
28
+ end
29
+ i += 1
30
+ end
31
+ filename = options[:filename]
32
+ book.write(filename)
33
+ filename
34
+ })
35
+
36
+ self.register_converter(
37
+ :multi_sheet_iterator, :xlsx_file, lambda { |data, options|
38
+ book = RubyXL::Workbook.new
39
+ book.worksheets.pop
40
+ data.each do |name, table_iterator|
41
+ sheet = book.add_worksheet(name)
42
+ i = 0
43
+ table_iterator.call do |row|
44
+ row.each_with_index do |v, j|
45
+ sheet.add_cell(i, j, v)
46
+ end
47
+ i += 1
48
+ end
49
+ end
50
+ filename = options[:filename]
51
+ book.write(filename)
52
+ filename
53
+ })
54
+ self.register_converter(
55
+ :xlsx_file, :multi_sheet_iterator, lambda { |data, options|
56
+ book = RubyXL::Parser.parse(data[:filename])
57
+ book.worksheets.map do |sheet|
58
+ iterator = lambda { |&block|
59
+ sheet.each do |row|
60
+ block.call(row.cells.map(&:value))
61
+ end
62
+ }
63
+ [sheet.sheet_name, iterator]
64
+ end.to_h
65
+ })
66
+ end
@@ -25,7 +25,7 @@ describe DataListConverter do
25
25
  it 'works' do
26
26
  DataListConverter.types.must_equal(
27
27
  [:csv_file, :item_data, :item_iterator, :multi_sheet_data, :multi_sheet_iterator,
28
- :records, :table_data, :table_iterator, :xls_file])
28
+ :records, :table_data, :table_iterator, :xls_file, :xlsx_file])
29
29
  end
30
30
  end
31
31
 
@@ -85,6 +85,22 @@ describe DataListConverter do
85
85
  FileUtils.rm_f("test.xls")
86
86
  end
87
87
  end
88
+
89
+ it 'has type xlsx_file' do
90
+ begin
91
+ @c.convert(:item_data, :xlsx_file, ITEM_DATA, xlsx_file: {filename: "test.xlsx"})
92
+ @c.convert(:xlsx_file, :item_data, {filename: "test.xlsx"}).must_equal ITEM_DATA
93
+
94
+ multi_sheet = {
95
+ "sheet1" => [['name'], ['james'], ['bob']],
96
+ "sheet2" => [['value'], ['21'], ['12']],
97
+ }
98
+ @c.convert(:multi_sheet_data, :xlsx_file, multi_sheet, xlsx_file: {filename: 'test.xlsx'})
99
+ @c.convert(:xlsx_file, :multi_sheet_data, {filename: 'test.xlsx'}).must_equal multi_sheet
100
+ ensure
101
+ FileUtils.rm_f("test.xlsx")
102
+ end
103
+ end
88
104
  end
89
105
 
90
106
  describe :filters do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: data_list_converter
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - linjunhalida
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-04 00:00:00.000000000 Z
11
+ date: 2015-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubyXL
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  description: Data List Converter is a tool to convert data between different formats.
56
84
  email:
57
85
  - linjunhalida@gmail.com
@@ -66,6 +94,15 @@ files:
66
94
  - Rakefile
67
95
  - data_list_converter.gemspec
68
96
  - lib/data_list_converter.rb
97
+ - lib/data_list_converter/base.rb
98
+ - lib/data_list_converter/filters/count.rb
99
+ - lib/data_list_converter/filters/limit.rb
100
+ - lib/data_list_converter/filters/remove_debug.rb
101
+ - lib/data_list_converter/types/basic.rb
102
+ - lib/data_list_converter/types/csv_file.rb
103
+ - lib/data_list_converter/types/records.rb
104
+ - lib/data_list_converter/types/xls_file.rb
105
+ - lib/data_list_converter/types/xlsx_file.rb
69
106
  - test/data_list_converter_test.rb
70
107
  homepage: http://github.com/halida/data_list_converter
71
108
  licenses: