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 +4 -4
- data/Rakefile +6 -0
- data/data_list_converter.gemspec +3 -1
- data/lib/data_list_converter.rb +9 -351
- data/lib/data_list_converter/base.rb +148 -0
- data/lib/data_list_converter/filters/count.rb +27 -0
- data/lib/data_list_converter/filters/limit.rb +17 -0
- data/lib/data_list_converter/filters/remove_debug.rb +16 -0
- data/lib/data_list_converter/types/basic.rb +54 -0
- data/lib/data_list_converter/types/csv_file.rb +23 -0
- data/lib/data_list_converter/types/records.rb +15 -0
- data/lib/data_list_converter/types/xls_file.rb +73 -0
- data/lib/data_list_converter/types/xlsx_file.rb +66 -0
- data/test/data_list_converter_test.rb +17 -1
- metadata +39 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d8949f31a68b057f6914e873ec2ebe6e38b5dc4
|
4
|
+
data.tar.gz: a3d47489bc2874afde23b3281e3d792a6d05cc0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9679c6942102474e5100153518b93b91352881a980ad8da94417b6a7adbe052a26500ac41ce4e5937521c9e3fdec0902de0776cb9d01e5caf28d05f8b0876e6c
|
7
|
+
data.tar.gz: 9e93b4ed6e0459f410378196a9e2486c0d6ee07b9339a5383a39641577e1eba1cb7ad7359d6aef8539693e38fde49ea0e1c20ea34646554580fa9ba0b3850d4c
|
data/Rakefile
CHANGED
data/data_list_converter.gemspec
CHANGED
@@ -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.
|
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")
|
data/lib/data_list_converter.rb
CHANGED
@@ -1,353 +1,11 @@
|
|
1
|
-
require '
|
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.
|
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-
|
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:
|