data_list_converter 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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: