tallty_import_export 1.1.0 → 1.1.3

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
  SHA256:
3
- metadata.gz: 72c586f1a3e13625520d1ce6378e478ee8dc6cf0055a16528f63ead86c20794f
4
- data.tar.gz: 857a44c2a36c69347a2baed91855d943a16581427bdd1b562dd02e54d4a7d97c
3
+ metadata.gz: 284928193ebed0e079f44ba98ee7a2f0d51c05f0c1ea5100dd68973940f56369
4
+ data.tar.gz: 75dbf1351c7a08ce445b508ea60aba31da811fcfc87e9355c5f8c710f66be47a
5
5
  SHA512:
6
- metadata.gz: 9ae472d4119ebb57fe5066615d49001ba99a417a067d0586f25010338e7bba1d5fdda737cf8a7892483e68b079af065e21862e87225409f3a7f2e7e0683f8d5f
7
- data.tar.gz: fd6af86be0af8b552d2d5170e86b807a301550e7b7f3e604fa0cef539fe6f4630083a4117a494071bc734f83fb29950425f6cdb147b82e130b978c0a2fc8af6f
6
+ metadata.gz: 8d335d50b6059608dcdb41cc3f44e16f75746d900d698ff1ea3960de8d2541b0fdfadf9b7fceeadef33f70906a3c4673f8247c6aa9406195826f0b45a9977d5e
7
+ data.tar.gz: e72f4ddf2a9bb43f00d031aa74c56723870b63ce0b548110d6c3112a0945dbd21f789759a88834fb1101f24c61e30ce179bd14dea19001c2e4bb81f5e7721c9e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tallty_import_export (1.0.35)
4
+ tallty_import_export (1.1.1)
5
5
  activesupport
6
6
  attr_json
7
7
  caxlsx
@@ -27,27 +27,31 @@ GEM
27
27
  tzinfo (~> 2.0)
28
28
  attr_json (1.4.0)
29
29
  activerecord (>= 5.0.0, < 7.1)
30
- caxlsx (3.2.0)
30
+ caxlsx (3.3.0)
31
31
  htmlentities (~> 4.3, >= 4.3.4)
32
32
  marcel (~> 1.0)
33
33
  nokogiri (~> 1.10, >= 1.10.4)
34
34
  rubyzip (>= 1.3.0, < 3)
35
35
  concurrent-ruby (1.1.10)
36
+ connection_pool (2.3.0)
36
37
  diff-lcs (1.4.4)
37
38
  htmlentities (4.3.4)
38
- i18n (1.10.0)
39
+ i18n (1.12.0)
39
40
  concurrent-ruby (~> 1.0)
40
41
  marcel (1.0.2)
41
42
  mini_portile2 (2.8.0)
42
- minitest (5.15.0)
43
- nokogiri (1.13.4)
43
+ minitest (5.16.3)
44
+ nokogiri (1.13.9)
44
45
  mini_portile2 (~> 2.8.0)
45
46
  racc (~> 1.4)
46
47
  racc (1.6.0)
47
48
  rake (12.3.3)
48
- redis (4.6.0)
49
- redis-objects (1.5.1)
50
- redis (~> 4.2)
49
+ redis (5.0.5)
50
+ redis-client (>= 0.9.0)
51
+ redis-client (0.10.0)
52
+ connection_pool
53
+ redis-objects (1.7.0)
54
+ redis
51
55
  roo (2.9.0)
52
56
  nokogiri (~> 1)
53
57
  rubyzip (>= 1.3.0, < 3.0.0)
@@ -77,7 +81,7 @@ GEM
77
81
  activesupport (>= 5.0)
78
82
  tallty_form (1.0.0)
79
83
  tallty_duck_record
80
- tzinfo (2.0.4)
84
+ tzinfo (2.0.5)
81
85
  concurrent-ruby (~> 1.0)
82
86
  zip-zip (0.3)
83
87
  rubyzip (>= 1.0.0)
@@ -14,7 +14,7 @@ module TalltyImportExport
14
14
  attr_json :json, :string
15
15
  attr_json :select, ActiveModel::Type::Value.new, array: true
16
16
  attr_json :source, :boolean
17
- attr_json :proc, ActiveModel::Type::Value.new, array: true
17
+ attr_json :proc, ActiveModel::Type::Value.new
18
18
  attr_json :children, self.to_type, array: true
19
19
 
20
20
  attr_accessor :depth, :parent_path, :seq
@@ -6,14 +6,14 @@ module TalltyImportExport
6
6
  end
7
7
 
8
8
  module ClassMethods
9
- def model_headers **args
9
+ def model_headers(**args)
10
10
  columns.map do |column|
11
11
  {
12
12
  key: column.name,
13
13
  name: column.comment || column.name,
14
14
  attr_type: column.type,
15
15
  format: column.type == :string ? :string : nil,
16
- primary_key: column.name == 'id' ? true : false
16
+ primary_key: column.name == 'id',
17
17
  }
18
18
  end
19
19
  end
@@ -2,6 +2,7 @@ module TalltyImportExport
2
2
  class Excel
3
3
  require 'redis-objects'
4
4
  require 'roo'
5
+ require 'roo-xls'
5
6
 
6
7
  attr_reader :uid, :cache
7
8
 
@@ -2,7 +2,7 @@ module TalltyImportExport
2
2
  class Export
3
3
  attr_reader :klass, :context
4
4
 
5
- def initialize klass
5
+ def initialize(klass)
6
6
  @klass = klass
7
7
  @context = Context.new({})
8
8
  end
@@ -34,7 +34,7 @@ module TalltyImportExport
34
34
  # proc: proc或者lamda,支持call,传入 record 和 context
35
35
  # list: 对于list布局的,进行嵌套,生成子表格
36
36
 
37
- def export_xlsx records, **options
37
+ def export_xlsx(records, **options)
38
38
  records = with_scope records
39
39
  process_options(options)
40
40
 
@@ -46,14 +46,16 @@ module TalltyImportExport
46
46
 
47
47
  if @group_by.present?
48
48
  if records.is_a?(Array)
49
- records.group_by { |record| record.send(@group_by)}.each do |key, group_records|
49
+ records.group_by { |record| record.send(@group_by) }.each do |key, group_records|
50
50
  next unless key.present?
51
+
51
52
  @group_key = key
52
53
  export_workbook workbook, group_records, **options
53
54
  end
54
55
  else
55
56
  records.group(@group_by).count.keys.each do |key|
56
57
  next unless key.present?
58
+
57
59
  @group_key = key
58
60
  export_workbook workbook, records.ransack("#{@group_where}" => key).result, **options
59
61
  end
@@ -72,16 +74,16 @@ module TalltyImportExport
72
74
  end
73
75
  end
74
76
 
75
- def export_workbook workbook, association_records, **options
77
+ def export_workbook(workbook, association_records, **options)
76
78
  # excel导出样式
77
79
  alignment = { vertical: :center, horizontal: :center }
78
80
  border = { color: '969696', style: :thin }
79
81
  title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 12, b: true)
80
- title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 12, fg_color: "fffffb")
82
+ title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: '2a5caa', sz: 12, fg_color: 'fffffb')
81
83
  title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 10)
82
- headers = export_headers_result **options
84
+ headers = export_headers_result(**options)
83
85
 
84
- _sheet_name = respond_to?(:sheet_name) ? self.sheet_name : nil
86
+ _sheet_name = respond_to?(:sheet_name) ? sheet_name : nil
85
87
 
86
88
  workbook.add_worksheet(name: _sheet_name) do |sheet|
87
89
  if respond_to?(:first_header)
@@ -90,7 +92,7 @@ module TalltyImportExport
90
92
  sheet.add_row [first_header], style: title1, height: 30
91
93
  end
92
94
 
93
- sheet.add_row headers.map{|header| header[:name]}, style: title2, height: 25
95
+ sheet.add_row headers.map { |header| header[:name] }, style: title2, height: 25
94
96
 
95
97
  last_row = nil
96
98
  merge_column_hash = {}
@@ -133,12 +135,12 @@ module TalltyImportExport
133
135
  merge_column_hash.each do |col_index, row_arr|
134
136
  row_arr.each do |arr|
135
137
  sheet.merge_cells(
136
- Axlsx::cell_r(col_index, arr.first) + ':' + Axlsx::cell_r(col_index, arr.last)
138
+ Axlsx.cell_r(col_index, arr.first) + ':' + Axlsx.cell_r(col_index, arr.last),
137
139
  )
138
140
  end
139
141
  end
140
142
  end
141
- sheet.column_widths(*headers.map{|header| (header[:width] || @width).to_f})
143
+ sheet.column_widths(*headers.map { |header| (header[:width] || @width).to_f })
142
144
  end
143
145
  end
144
146
 
@@ -147,7 +149,7 @@ module TalltyImportExport
147
149
  {}
148
150
  end
149
151
 
150
- def process_options options = {}
152
+ def process_options(options = {})
151
153
  options = export_options.merge(options).with_indifferent_access
152
154
 
153
155
  @row_height ||= options.delete(:row_height) || 25
@@ -161,30 +163,30 @@ module TalltyImportExport
161
163
  context.params = @params
162
164
  end
163
165
 
164
- def with_scope records
166
+ def with_scope(records)
165
167
  records
166
168
  end
167
169
 
168
- def export_headers_result **options
170
+ def export_headers_result(**options)
169
171
  if @headers.present? && @group_key.blank?
170
172
  headers_hash = @headers.to_h { |header| [header.with_indifferent_access[:key], header] }.with_indifferent_access
171
173
  export_headers(**options.symbolize_keys).select do |_header|
172
174
  _header.with_indifferent_access[:key].to_s.in?(headers_hash.keys)
173
175
  end.map do |_header|
174
176
  _header = _header.with_indifferent_access
175
- _header.merge(headers_hash[_header[:key]].delete_if { |k, v| v.blank? })
177
+ _header.merge(headers_hash[_header[:key]].delete_if { |_k, v| v.blank? })
176
178
  end
177
179
  else
178
180
  @headers = export_headers(**options.symbolize_keys)
179
181
  end
180
182
  end
181
183
 
182
- def export_headers **args
184
+ def export_headers(**args)
183
185
  @headers || klass.try(:headers) || klass.try(:model_headers)
184
186
  end
185
187
 
186
188
  # 处理一个记录的数据
187
- def handle_data record, header, index=0, **opts
189
+ def handle_data(record, header, index = 0, **opts)
188
190
  data =
189
191
  if header[:key] == '_index'
190
192
  index
@@ -200,28 +202,36 @@ module TalltyImportExport
200
202
  data = handle_format(data, header, **opts)
201
203
  data = handle_data_type(data, **opts)
202
204
  data = handle_select(data, header, **opts)
203
- rescue
205
+ rescue StandardError
204
206
  ''
205
207
  end
206
208
 
207
- def try_chain record, arr
209
+ def try_chain(record, arr)
208
210
  arr.reduce(record) do |r, m|
209
211
  if r.is_a?(Array)
210
212
  r.try(m) || r.try(:[], m.to_i)
211
213
  else
212
- r.try(:[], m) || r.try(:[], m.to_sym) || r.try(m)
214
+ begin
215
+ r.try(:[], m)
216
+ rescue StandardError
217
+ nil
218
+ end || begin
219
+ r.try(:[], m.to_sym)
220
+ rescue StandardError
221
+ nil
222
+ end || r.try(m)
213
223
  end
214
224
  end
215
225
  end
216
226
 
217
- def try_method record, method, prefix: nil
227
+ def try_method(record, method, prefix: nil)
218
228
  prefix_arr = prefix.to_s.split(/\./)
219
229
  arr = method.to_s.split(/\./)
220
230
  try_chain record, prefix_arr + arr
221
231
  end
222
232
 
223
233
  # 根据数据类型 attr_type 进行数据的格式化
224
- def handle_format data, header, **opts
234
+ def handle_format(data, header, **opts)
225
235
  case header[:attr_type].to_s
226
236
  when 'string'
227
237
  data.to_s
@@ -234,7 +244,7 @@ module TalltyImportExport
234
244
  end
235
245
  end
236
246
 
237
- def handle_data_type data, **opts
247
+ def handle_data_type(data, **opts)
238
248
  if data.is_a?(Time)
239
249
  data.in_time_zone.strftime('%F %H:%M')
240
250
  elsif data.is_a?(Date)
@@ -257,7 +267,7 @@ module TalltyImportExport
257
267
  end
258
268
  end
259
269
 
260
- def handle_select data, header, **opts
270
+ def handle_select(data, header, **opts)
261
271
  if header[:select].present?
262
272
  select_option = header[:select].find { |option| option[:value].to_s == data.to_s }
263
273
  select_option.present? ? select_option[:label] : data
@@ -126,7 +126,7 @@ module TalltyImportExport
126
126
 
127
127
  def export_headers_result **options
128
128
  @headers = options.symbolize_keys[:headers]
129
- super(**options)
129
+ @headers = super(**options)
130
130
  @headers = TalltyImportExport::Attr::ExportHeader.new({ items: @headers })
131
131
  end
132
132
 
@@ -1,9 +1,10 @@
1
1
  module TalltyImportExport
2
2
  class Import
3
3
  require 'roo'
4
+ require 'roo-xls'
4
5
  attr_reader :klass, :context, :primary_keys, :associations
5
6
 
6
- def initialize klass
7
+ def initialize(klass)
7
8
  @klass = klass
8
9
  @context = Context.new({})
9
10
  end
@@ -20,13 +21,41 @@ module TalltyImportExport
20
21
  # skip: 用来综合使用的数据,但是不导入
21
22
 
22
23
  # xlsx_file 为 file path or file object or TalltyImportExport::Excel.new
23
- def import_xlsx xlsx_file, associations, **options
24
+ def import_xlsx(xlsx_file, associations, **options)
24
25
  process_xlsx_line_info(xlsx_file, associations, **options) do |line_info, associations|
25
26
  process_line_info(line_info, associations)
26
27
  end
27
28
  end
28
29
 
29
- def exchange_to_ids xlsx_file, associations, **options
30
+ # 导出excel的空模版
31
+ def export_template_xlsx(**options)
32
+ origin_options = options.dup
33
+ process_options(options)
34
+
35
+ Axlsx::Package.new do |pack|
36
+ pack.use_shared_strings = true
37
+ workbook = pack.workbook
38
+
39
+ export_workbook workbook, **origin_options
40
+
41
+ file_path = File.join(Rails.root, 'public', 'import')
42
+ FileUtils.mkdir_p(file_path) unless Dir.exist?(file_path)
43
+ file_name = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{@filename}.xlsx"
44
+ file_path_with_name = File.join(file_path, file_name)
45
+ pack.serialize(file_path_with_name)
46
+ return file_path_with_name
47
+ end
48
+ end
49
+
50
+ def export_workbook(workbook, **options)
51
+ export_instance = TalltyImportExport::Export.new(@klass)
52
+ opts = options.symbolize_keys.merge({ headers: @headers })
53
+ Rails.logger.info opts
54
+ export_instance.process_options(opts)
55
+ export_instance.export_workbook(workbook, [], **opts)
56
+ end
57
+
58
+ def exchange_to_ids(xlsx_file, associations, **options)
30
59
  errors = []
31
60
  ids = []
32
61
  process_xlsx_line_info(xlsx_file, associations, **options) do |line_info, associations|
@@ -37,33 +66,34 @@ module TalltyImportExport
37
66
  error_msg = errors.map do |line_info|
38
67
  "【#{@primary_keys.map { |key| line_info[key] }.join(' - ')}】"
39
68
  end.join(' ')
40
- raise RecordNotFountError.new("以下内容未找到: #{error_msg}")
69
+ raise RecordNotFountError, "以下内容未找到: #{error_msg}"
41
70
  end
42
- return ids.compact.uniq
71
+ ids.compact.uniq
43
72
  end
44
73
 
45
- def process_xlsx_line_info xlsx_file, associations, **options
74
+ def process_xlsx_line_info(xlsx_file, associations, **options)
46
75
  @associations = associations
47
76
  # 先处理获取出来Excel每行的数据, line_info
48
77
  process_options(options)
49
78
 
50
- if TalltyImportExport::Excel === xlsx_file
79
+ if xlsx_file.is_a?(TalltyImportExport::Excel)
51
80
  xlsx_file.rows.each_with_excel_hash(@excel_hash) do |line_info|
52
81
  yield line_info.with_indifferent_access, associations
53
82
  end
54
83
  else
55
84
  file_path = xlsx_file.is_a?(String) ? xlsx_file : xlsx_file.path
56
- xlsx = ::Roo::Excelx.new(file_path)
85
+ xlsx = ::Roo::Spreadsheet.open(file_path, extension: File.extname(file_path) == '.xls' ? :xls : :xlsx)
57
86
  xlsx.each_with_pagename do |_sheetname, sheet|
58
87
  sheet.each(**@excel_hash).with_index do |line_info, index|
59
88
  next if index == 0
89
+
60
90
  yield line_info.with_indifferent_access, associations
61
91
  end
62
92
  end
63
93
  end
64
94
  end
65
95
 
66
- def import_data data, associations, **options
96
+ def import_data(data, associations, **options)
67
97
  process_options(options)
68
98
  TalltyImportExport::Excel::Rows.new(data).each_with_excel_hash(@excel_hash) do |line_info|
69
99
  process_line_info(line_info.with_indifferent_access, associations)
@@ -74,25 +104,25 @@ module TalltyImportExport
74
104
  TalltyImportExport::Excel
75
105
  end
76
106
 
77
- def process_options options
107
+ def process_options(options)
78
108
  options = import_options.merge(options).with_indifferent_access
79
- @headers = options.delete(:headers) || import_headers
109
+ @headers = options.delete(:headers) || import_headers(**options)
80
110
  @primary_keys = options.delete(:primary_keys) || @headers.map { |header| header[:primary_key] ? header[:key].to_sym : nil }.compact
81
111
  @skip_keys = options.delete(:skip_keys) || @headers.map { |header| header[:skip] ? header[:key].to_sym : nil }.compact
82
112
  @params = options
83
113
  context.params = @params
84
114
 
85
- @excel_hash = @headers.reduce({}) do |h, header|
115
+ @excel_hash = @headers.each_with_object({}) do |header, h|
86
116
  h[header[:key].to_sym] = header[:name]
87
- h
88
117
  end
89
118
 
90
119
  options
91
120
  end
92
121
 
93
- def process_line_info line_info, associations
122
+ def process_line_info(line_info, associations)
94
123
  # 去除空行内容
95
124
  return unless line_info.values.any?(&:present?)
125
+
96
126
  context.line_info = line_info
97
127
  # 转换处理导入的数据格式
98
128
  line_info = convert_data(line_info)
@@ -104,14 +134,16 @@ module TalltyImportExport
104
134
  context.last_line_info = line_info
105
135
  end
106
136
 
107
- def convert_data line_info
137
+ def convert_data(line_info)
108
138
  info = line_info.with_indifferent_access
109
- import_headers_result.reduce({}) do |h, header|
139
+ import_headers_result.each_with_object({}.with_indifferent_access) do |header, h|
110
140
  k = header[:key]
141
+ k_arr = k.to_s.split(/\./)
111
142
  v = info[k]
112
143
  # header[:convert] = handle_xxx
113
144
  # handle_xxx(val, processing_line_info, raw_line_info)
114
145
  val = header[:convert] ? send(header[:convert], v, h, info) : v
146
+ val.strip! if val.is_a?(String)
115
147
  if header[:json]
116
148
  h[header[:json]] ||= {}
117
149
  h[header[:json]][k] = val
@@ -120,15 +152,20 @@ module TalltyImportExport
120
152
  elsif header[:finder]
121
153
  # $SAFE = 2
122
154
  h[k.to_sym] = eval header[:finder]
155
+ elsif k_arr.size > 1
156
+ # hash设置
157
+ attr = k_arr.first
158
+ val = { k_arr[1..-1].join('.') => v }.to_nested_hash
159
+ h[attr.to_sym] ||= {}
160
+ h[attr.to_sym].merge! val
123
161
  else
124
162
  h[k.to_sym] = val
125
163
  end
126
- h
127
164
  end.with_indifferent_access
128
165
  end
129
166
 
130
167
  # 通过转换后,数据是否合法,如果不合法,则直接跳过不处理这个数据
131
- def valid? line_info
168
+ def valid?(line_info)
132
169
  true
133
170
  end
134
171
 
@@ -141,12 +178,12 @@ module TalltyImportExport
141
178
  _header.with_indifferent_access[:key].to_s.in?(headers_hash.keys)
142
179
  end.map do |_header|
143
180
  _header = _header.with_indifferent_access
144
- _header.merge(headers_hash[_header[:key]].delete_if { |k, v| v.blank? })
181
+ _header.merge(headers_hash[_header[:key]].delete_if { |_k, v| v.blank? })
145
182
  end
146
183
  else
147
- @headers = import_headers(**options.symbolize_keys)
184
+ @headers = import_headers
148
185
  end
149
- rescue
186
+ rescue StandardError
150
187
  @headers
151
188
  end
152
189
 
@@ -154,8 +191,8 @@ module TalltyImportExport
154
191
  {}
155
192
  end
156
193
 
157
- def import_headers **args
158
- @headers || klass.try(:headers) || klass.try(:model_headers) || (raise ArgumentError.new('missing import_headers'))
194
+ def import_headers(**args)
195
+ @headers || klass.try(:headers) || klass.try(:model_headers) || (raise ArgumentError, 'missing import_headers')
159
196
  end
160
197
 
161
198
  # # 只保留 key, name, json, 合并到 import_header
@@ -182,12 +219,12 @@ module TalltyImportExport
182
219
  # @headers = result
183
220
  # end
184
221
 
185
- def skip val, processing_line_info, raw_line_info
222
+ def skip(val, processing_line_info, raw_line_info)
186
223
  # do nothing there, use for header[:convert]
187
224
  end
188
225
 
189
226
  ### 这个方法是可以由复杂业务进行重载的 ###
190
- def import_record line_info, associations
227
+ def import_record(line_info, associations)
191
228
  if primary_keys.present?
192
229
  _record = associations.find_or_initialize_by(line_info.clone.extract!(*primary_keys))
193
230
  _record.update!(line_info.clone.except!(*primary_keys, *@skip_keys))
@@ -196,23 +233,26 @@ module TalltyImportExport
196
233
  end
197
234
  end
198
235
 
199
- def exchange_line_info_to_id line_info, associations, errors
236
+ def exchange_line_info_to_id(line_info, associations, errors)
200
237
  # 去除空行内容
201
238
  return unless line_info.values.any?(&:present?)
239
+
202
240
  context.line_info = line_info
203
241
 
204
242
  return unless primary_keys.present?
243
+
205
244
  ids = associations.where(line_info.clone.extract!(*primary_keys)).pluck(:id)
206
245
 
207
246
  context.last_line_info = line_info
208
247
 
209
248
  errors << line_info unless ids[0]
210
- return ids[0]
249
+ ids[0]
211
250
  end
212
251
 
213
252
  class RecordNotFountError < StandardError
214
253
  attr_accessor :message
215
- def initialize message
254
+
255
+ def initialize(message)
216
256
  @message = message
217
257
  super()
218
258
  end
@@ -28,6 +28,10 @@ module TalltyImportExport
28
28
  def exchange_to_ids xlsx_file, associations, **options
29
29
  import_instance.exchange_to_ids(xlsx_file, associations, **options)
30
30
  end
31
+
32
+ def export_template_xlsx **options
33
+ import_instance.export_template_xlsx(**options)
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -1,3 +1,3 @@
1
1
  module TalltyImportExport
2
- VERSION = "1.1.0"
2
+ VERSION = '1.1.3'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tallty_import_export
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - liyijie
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-10 00:00:00.000000000 Z
11
+ date: 2022-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zip-zip