flydata 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -7,7 +7,7 @@ gem "activesupport", "~> 4.0.0"
7
7
  gem "json", "~> 1.8.0"
8
8
  gem "highline", "~> 1.6.19"
9
9
  gem "fluentd", "0.10.46"
10
- gem "ruby-binlog", ">= 1.0.0"
10
+ gem "ruby-binlog", ">= 1.0.1"
11
11
  gem "fluent-plugin-mysql-binlog", "~> 0.0.2"
12
12
  gem "mysql2", "~> 0.3.11"
13
13
 
data/Gemfile.lock CHANGED
@@ -94,7 +94,7 @@ GEM
94
94
  rspec-expectations (2.14.3)
95
95
  diff-lcs (>= 1.1.3, < 2.0)
96
96
  rspec-mocks (2.14.3)
97
- ruby-binlog (1.0.0)
97
+ ruby-binlog (1.0.1)
98
98
  ruby-prof (0.14.2)
99
99
  sigdump (0.2.2)
100
100
  sqlite3 (1.3.8)
@@ -122,7 +122,7 @@ DEPENDENCIES
122
122
  protected_attributes
123
123
  rest-client (~> 1.6.7)
124
124
  rspec
125
- ruby-binlog (>= 1.0.0)
125
+ ruby-binlog (>= 1.0.1)
126
126
  ruby-prof
127
127
  sqlite3
128
128
  timecop
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.6
1
+ 0.1.7
data/flydata.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "flydata"
8
- s.version = "0.1.6"
8
+ s.version = "0.1.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Koichi Fujikawa"]
12
- s.date = "2014-05-22"
12
+ s.date = "2014-06-01"
13
13
  s.description = "FlyData Command Line Interface"
14
14
  s.email = "sysadmin@flydata.co"
15
15
  s.executables = ["fdmysqldump", "flydata"]
@@ -100,7 +100,7 @@ Gem::Specification.new do |s|
100
100
  s.add_runtime_dependency(%q<json>, ["~> 1.8.0"])
101
101
  s.add_runtime_dependency(%q<highline>, ["~> 1.6.19"])
102
102
  s.add_runtime_dependency(%q<fluentd>, ["= 0.10.46"])
103
- s.add_runtime_dependency(%q<ruby-binlog>, [">= 1.0.0"])
103
+ s.add_runtime_dependency(%q<ruby-binlog>, [">= 1.0.1"])
104
104
  s.add_runtime_dependency(%q<fluent-plugin-mysql-binlog>, ["~> 0.0.2"])
105
105
  s.add_runtime_dependency(%q<mysql2>, ["~> 0.3.11"])
106
106
  s.add_development_dependency(%q<bundler>, [">= 0"])
@@ -119,7 +119,7 @@ Gem::Specification.new do |s|
119
119
  s.add_dependency(%q<json>, ["~> 1.8.0"])
120
120
  s.add_dependency(%q<highline>, ["~> 1.6.19"])
121
121
  s.add_dependency(%q<fluentd>, ["= 0.10.46"])
122
- s.add_dependency(%q<ruby-binlog>, [">= 1.0.0"])
122
+ s.add_dependency(%q<ruby-binlog>, [">= 1.0.1"])
123
123
  s.add_dependency(%q<fluent-plugin-mysql-binlog>, ["~> 0.0.2"])
124
124
  s.add_dependency(%q<mysql2>, ["~> 0.3.11"])
125
125
  s.add_dependency(%q<bundler>, [">= 0"])
@@ -139,7 +139,7 @@ Gem::Specification.new do |s|
139
139
  s.add_dependency(%q<json>, ["~> 1.8.0"])
140
140
  s.add_dependency(%q<highline>, ["~> 1.6.19"])
141
141
  s.add_dependency(%q<fluentd>, ["= 0.10.46"])
142
- s.add_dependency(%q<ruby-binlog>, [">= 1.0.0"])
142
+ s.add_dependency(%q<ruby-binlog>, [">= 1.0.1"])
143
143
  s.add_dependency(%q<fluent-plugin-mysql-binlog>, ["~> 0.0.2"])
144
144
  s.add_dependency(%q<mysql2>, ["~> 0.3.11"])
145
145
  s.add_dependency(%q<bundler>, [">= 0"])
@@ -107,6 +107,7 @@ class FlydataMysqlBinlogRecordHandler < MysqlBinlogRecordHandler
107
107
  'LONGLONG' => 8
108
108
  }
109
109
  SIGNLESS_INTEGER_PREFIX = '0SL'
110
+ SRC_POS = 'src_pos'
110
111
 
111
112
  def initialize(opts)
112
113
  mandatory_opts = [:database, :tables, :tag, :sync_fm]
@@ -119,10 +120,15 @@ class FlydataMysqlBinlogRecordHandler < MysqlBinlogRecordHandler
119
120
  @tables = opts[:tables]
120
121
  @tag = opts[:tag]
121
122
  @sync_fm = opts[:sync_fm]
122
-
123
+ @current_binlog_file = ""
124
+ @first_empty_binlog = true
123
125
  @query_handler = FlydataMysqlBinlogQueryHandler.new(record_handler: self)
124
126
  end
125
127
 
128
+ def on_rotate(record)
129
+ @current_binlog_file = record["binlog_file"]
130
+ end
131
+
126
132
  def on_write_rows(record)
127
133
  emit_insert(record)
128
134
  end
@@ -167,11 +173,14 @@ class FlydataMysqlBinlogRecordHandler < MysqlBinlogRecordHandler
167
173
  return unless acceptable?(record)
168
174
 
169
175
  table = record['table_name']
170
-
176
+ position = record['next_position'] - record['event_length']
177
+ check_empty_binlog
178
+
171
179
  records = record["rows"].collect do |row|
172
180
  row = yield(row) if block_given? # Give the caller a chance to generate the correct row
173
181
  { TYPE => type, TABLE_NAME => table,
174
182
  RESPECT_ORDER => true, # Continuous sync needs record order to be kept
183
+ SRC_POS => "#{@current_binlog_file}\t#{position}",
175
184
  ROW => row.each.with_index(1).inject({}) do |h, (v, i)|
176
185
  if v.kind_of?(String)
177
186
  v = v.encode('utf-16', :undef => :replace, :invalid => :replace).encode('utf-8')
@@ -212,6 +221,18 @@ class FlydataMysqlBinlogRecordHandler < MysqlBinlogRecordHandler
212
221
  end
213
222
  end
214
223
  end
224
+
225
+ def check_empty_binlog
226
+ #Log one warning per consecutive records that have empty binlog filename
227
+ if @current_binlog_file.to_s.empty?
228
+ if @first_empty_binlog
229
+ $log.warn "Binlog file name is empty. Rotate event not received!"
230
+ @first_empty_binlog = false
231
+ end
232
+ else
233
+ @first_empty_binlog = true
234
+ end
235
+ end
215
236
  end
216
237
 
217
238
  class MysqlBinlogQueryHandler
@@ -108,6 +108,10 @@ EOT
108
108
  # la
109
109
  TEST_EVENT_INCIDENT = <<EOT
110
110
  {"marker"=>0, "timestamp"=>0, "type_code"=>26, "server_id"=>1, "event_length"=>40, "next_position"=>2883, "flags"=>32, "event_type"=>"Incident", "incident_type"=>175, "message"=>"Operation canceled"}
111
+ EOT
112
+ # - 04 ROTATE_EVENT
113
+ TEST_EVENT_ROTATE_MISSING_BINLOG_FILE = <<EOT
114
+ {"marker"=>0, "timestamp"=>0, "type_code"=>4, "server_id"=>1, "event_length"=>43, "next_position"=>0, "flags"=>32, "event_type"=>"Rotate", "binlog_pos"=>2883}
111
115
  EOT
112
116
 
113
117
  describe MysqlBinlogFlydataInput do
@@ -130,10 +134,10 @@ EOT
130
134
  plugin.event_listener(event)
131
135
  end
132
136
 
133
- def expect_emitted_records_with_rows(event, type, table, rows)
137
+ def expect_emitted_records_with_rows(event, type, table, position, binlog_file, rows)
134
138
  rows = [rows] unless rows.kind_of?(Array)
135
139
  records = rows.collect do |row|
136
- { "type"=>type, "table_name"=>table, "respect_order"=>true, "seq"=>2, "row"=>row }
140
+ { "type"=>type, "table_name"=>table, "respect_order"=>true, "seq"=>2, "src_pos"=>"#{binlog_file}\t#{position}", "row"=>row }
137
141
  end
138
142
  expect_emitted_records(event, records)
139
143
  end
@@ -152,7 +156,9 @@ EOT
152
156
  let(:query_event) { create_event(TEST_EVENT_QUERY_CREATE_DATABSE) }
153
157
  let(:table_map_event) { create_event(TEST_EVENT_TABLE_MAP) }
154
158
  let(:xid_event) { create_event(TEST_EVENT_XID) }
155
-
159
+ let(:rotate_event){ create_event(TEST_EVENT_ROTATE) }
160
+ let(:rotate_event_corrupt){ create_event(TEST_EVENT_ROTATE_MISSING_BINLOG_FILE) }
161
+
156
162
  let(:now) { Time.now }
157
163
 
158
164
  let(:table_seq_file) {
@@ -176,103 +182,164 @@ EOT
176
182
  end
177
183
 
178
184
  describe '#event_listener' do
179
- before { Test.configure_plugin(plugin, TEST_CONFIG) }
180
185
 
181
- context 'when received insert event' do
182
- it do
183
- table_seq_file.should_receive(:write).exactly(3).with(2)
184
- expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE,
185
- [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
186
+ context 'when received rotate event' do
187
+ before do
188
+ Test.configure_plugin(plugin, TEST_CONFIG)
189
+ plugin.event_listener(rotate_event)
186
190
  end
187
- end
188
191
 
189
- context 'when received insert event containing two byte UTF8 chars' do
190
- it do
191
- table_seq_file.should_receive(:write).exactly(3).with(2)
192
- expect_emitted_records_with_rows(insert_two_byte_event, :insert, TEST_TABLE,
193
- [{"1"=>"0SL00000001", "2"=>"føø"}, {"1"=>"0SL00000002", "2"=>"vår"}, {"1"=>"0SL00000003", "2"=>"høgé"}])
192
+ context 'when received insert event' do
193
+ it do
194
+ table_seq_file.should_receive(:write).exactly(3).with(2)
195
+ expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "mysql-bin.000048",
196
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
197
+ end
194
198
  end
195
- end
196
199
 
197
- context 'when received insert event containing three byte UTF8 chars' do
198
- it do
199
- table_seq_file.should_receive(:write).exactly(3).with(2)
200
- expect_emitted_records_with_rows(insert_three_byte_event, :insert, TEST_TABLE,
201
- [{"1"=>"0SL00000001", "2"=>"富无无"}, {"1"=>"0SL00000002", "2"=>"易变的"}, {"1"=>"0SL00000003", "2"=>"切实切实"}])
200
+ context 'when received insert event containing two byte UTF8 chars' do
201
+ it do
202
+ table_seq_file.should_receive(:write).exactly(3).with(2)
203
+ expect_emitted_records_with_rows(insert_two_byte_event, :insert, TEST_TABLE, 628, "mysql-bin.000048",
204
+ [{"1"=>"0SL00000001", "2"=>"føø"}, {"1"=>"0SL00000002", "2"=>"vår"}, {"1"=>"0SL00000003", "2"=>"høgé"}])
205
+ end
202
206
  end
203
- end
204
207
 
205
- context 'when received delete event' do
206
- it do
207
- table_seq_file.should_receive(:write).twice.with(2)
208
- expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE,
209
- [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
208
+ context 'when received insert event containing three byte UTF8 chars' do
209
+ it do
210
+ table_seq_file.should_receive(:write).exactly(3).with(2)
211
+ expect_emitted_records_with_rows(insert_three_byte_event, :insert, TEST_TABLE, 628, "mysql-bin.000048",
212
+ [{"1"=>"0SL00000001", "2"=>"富无无"}, {"1"=>"0SL00000002", "2"=>"易变的"}, {"1"=>"0SL00000003", "2"=>"切实切实"}])
213
+ end
210
214
  end
211
- end
212
215
 
213
- context 'when received delete event containing two byte UTF8 chars' do
214
- it do
215
- table_seq_file.should_receive(:write).twice.with(2)
216
- expect_emitted_records_with_rows(delete_two_byte_event, :delete, TEST_TABLE,
217
- [{"1"=>"0SL00000002", "2"=>"vår"}, {"1"=>"0SL00000003", "2"=>"høgé"}])
216
+ context 'when received delete event' do
217
+ it do
218
+ table_seq_file.should_receive(:write).twice.with(2)
219
+ expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE, 5324, "mysql-bin.000048",
220
+ [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
221
+ end
218
222
  end
219
- end
220
223
 
221
- context 'when received delete event with containing byte UTF8 chars' do
222
- it do
223
- table_seq_file.should_receive(:write).twice.with(2)
224
- expect_emitted_records_with_rows(delete_three_byte_event, :delete, TEST_TABLE,
225
- [{"1"=>"0SL00000002", "2"=>"易变的"}, {"1"=>"0SL00000003", "2"=>"切实切实"}])
224
+ context 'when received delete event containing two byte UTF8 chars' do
225
+ it do
226
+ table_seq_file.should_receive(:write).twice.with(2)
227
+ expect_emitted_records_with_rows(delete_two_byte_event, :delete, TEST_TABLE, 5324, "mysql-bin.000048",
228
+ [{"1"=>"0SL00000002", "2"=>"vår"}, {"1"=>"0SL00000003", "2"=>"høgé"}])
229
+ end
226
230
  end
227
- end
228
231
 
229
- context 'when received update event' do
230
- it do
231
- table_seq_file.should_receive(:write).twice.with(2)
232
- expect_emitted_records_with_rows(update_event, :update, TEST_TABLE,
233
- [{"1"=>"0SL00000001", "2"=>"wow"}, {"1"=>"0SL00000003", "2"=>"fuga"}])
232
+ context 'when received delete event with containing byte UTF8 chars' do
233
+ it do
234
+ table_seq_file.should_receive(:write).twice.with(2)
235
+ expect_emitted_records_with_rows(delete_three_byte_event, :delete, TEST_TABLE, 5324, "mysql-bin.000048",
236
+ [{"1"=>"0SL00000002", "2"=>"易变的"}, {"1"=>"0SL00000003", "2"=>"切实切实"}])
237
+ end
234
238
  end
235
- end
236
239
 
237
- context 'when received update event with two byte utf8 chars' do
238
- it do
239
- table_seq_file.should_receive(:write).twice.with(2)
240
- expect_emitted_records_with_rows(update_two_byte_event, :update, TEST_TABLE,
241
- [{"1"=>"0SL00000001", "2"=>"∑ø∑"}, {"1"=>"0SL00000003", "2"=>"fügå"}])
240
+ context 'when received update event' do
241
+ it do
242
+ table_seq_file.should_receive(:write).twice.with(2)
243
+ expect_emitted_records_with_rows(update_event, :update, TEST_TABLE, 2528, "mysql-bin.000048",
244
+ [{"1"=>"0SL00000001", "2"=>"wow"}, {"1"=>"0SL00000003", "2"=>"fuga"}])
245
+ end
242
246
  end
243
- end
244
247
 
245
- context 'when received update event with three byte utf8 chars' do
246
- it do
247
- table_seq_file.should_receive(:write).twice.with(2)
248
- expect_emitted_records_with_rows(update_three_byte_event, :update, TEST_TABLE,
249
- [{"1"=>"0SL00000001", "2"=>"很兴奋"}, {"1"=>"0SL00000003", "2"=>"興奮虎"}])
248
+ context 'when received update event with two byte utf8 chars' do
249
+ it do
250
+ table_seq_file.should_receive(:write).twice.with(2)
251
+ expect_emitted_records_with_rows(update_two_byte_event, :update, TEST_TABLE, 2528, "mysql-bin.000048",
252
+ [{"1"=>"0SL00000001", "2"=>"∑ø∑"}, {"1"=>"0SL00000003", "2"=>"fügå"}])
253
+ end
254
+ end
255
+
256
+ context 'when received update event with three byte utf8 chars' do
257
+ it do
258
+ table_seq_file.should_receive(:write).twice.with(2)
259
+ expect_emitted_records_with_rows(update_three_byte_event, :update, TEST_TABLE, 2528, "mysql-bin.000048",
260
+ [{"1"=>"0SL00000001", "2"=>"很兴奋"}, {"1"=>"0SL00000003", "2"=>"興奮虎"}])
261
+ end
262
+ end
263
+
264
+ context 'when received event with another database name' do
265
+ it do
266
+ event = insert_event
267
+ event['db_name'] = 'another_db'
268
+ expect_no_emitted_record(event)
269
+ end
270
+ end
271
+
272
+ context 'when received event with unsupported table name' do
273
+ it do
274
+ event = insert_event
275
+ event['table_name'] = 'another_table'
276
+ expect_no_emitted_record(event)
277
+ end
250
278
  end
251
- end
252
279
 
253
- context 'when received event with another database name' do
254
- it do
255
- event = insert_event
256
- event['db_name'] = 'another_db'
257
- expect_no_emitted_record(event)
280
+ context 'when received unsupported event' do
281
+ it do
282
+ expect_no_emitted_record(query_event)
283
+ expect_no_emitted_record(table_map_event)
284
+ expect_no_emitted_record(xid_event)
285
+ end
258
286
  end
259
287
  end
288
+
289
+ context 'when rotate event is not received' do
290
+ before do
291
+ Test.configure_plugin(plugin, TEST_CONFIG)
292
+ end
260
293
 
261
- context 'when received event with unsupported table name' do
262
- it do
263
- event = insert_event
264
- event['table_name'] = 'another_table'
265
- expect_no_emitted_record(event)
294
+ it 'logs a warning and emits FET with a blank binlog file name, when it receives an insert event' do
295
+ table_seq_file.should_receive(:write).exactly(3).with(2)
296
+ expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
297
+ expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "",
298
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
266
299
  end
300
+
301
+ it 'logs a warning and emits FET with a blank binlog file name, when it receives an update event' do
302
+ table_seq_file.should_receive(:write).twice.with(2)
303
+ expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
304
+ expect_emitted_records_with_rows(update_event, :update, TEST_TABLE, 2528, "",
305
+ [{"1"=>"0SL00000001", "2"=>"wow"}, {"1"=>"0SL00000003", "2"=>"fuga"}])
306
+ end
307
+
308
+ it 'logs a warning emits FET with a blank binlog file name, when it receives a delete event' do
309
+ table_seq_file.should_receive(:write).twice.with(2)
310
+ expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
311
+ expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE, 5324, "",
312
+ [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
313
+ end
314
+
315
+ it 'logs warning once when it receives consecutive events' do
316
+ table_seq_file.should_receive(:write).exactly(10).with(2)
317
+ expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
318
+ expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "",
319
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
320
+ expect_emitted_records_with_rows(update_event, :update, TEST_TABLE, 2528, "",
321
+ [{"1"=>"0SL00000001", "2"=>"wow"}, {"1"=>"0SL00000003", "2"=>"fuga"}])
322
+ expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "",
323
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
324
+ expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE, 5324, "",
325
+ [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
326
+ end
267
327
  end
268
328
 
269
- context 'when received unsupported event' do
270
- it do
271
- expect_no_emitted_record(query_event)
272
- expect_no_emitted_record(table_map_event)
273
- expect_no_emitted_record(xid_event)
329
+ context 'when received rotate event with missing binlog file' do
330
+ before do
331
+ Test.configure_plugin(plugin, TEST_CONFIG)
332
+ plugin.event_listener(rotate_event_corrupt)
274
333
  end
334
+
335
+ it 'logs a warning and emits FET with a blank binlog file name, when it receives an insert event' do
336
+ table_seq_file.should_receive(:write).exactly(3).with(2)
337
+ expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
338
+ expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "",
339
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
340
+ end
275
341
  end
342
+
276
343
  end
277
344
  end
278
345
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flydata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-22 00:00:00.000000000 Z
12
+ date: 2014-06-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
@@ -114,7 +114,7 @@ dependencies:
114
114
  requirements:
115
115
  - - ! '>='
116
116
  - !ruby/object:Gem::Version
117
- version: 1.0.0
117
+ version: 1.0.1
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
@@ -122,7 +122,7 @@ dependencies:
122
122
  requirements:
123
123
  - - ! '>='
124
124
  - !ruby/object:Gem::Version
125
- version: 1.0.0
125
+ version: 1.0.1
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: fluent-plugin-mysql-binlog
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -391,7 +391,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
391
391
  version: '0'
392
392
  segments:
393
393
  - 0
394
- hash: -2211529075960565235
394
+ hash: 4300353101989721136
395
395
  required_rubygems_version: !ruby/object:Gem::Requirement
396
396
  none: false
397
397
  requirements: