duty_free 1.0.7 → 1.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/duty_free/column.rb +2 -2
- data/lib/duty_free/extensions.rb +603 -446
- data/lib/duty_free/suggest_template.rb +381 -170
- data/lib/duty_free/util.rb +129 -11
- data/lib/duty_free/version_number.rb +1 -1
- data/lib/duty_free.rb +227 -52
- data/lib/generators/duty_free/model_generator.rb +349 -0
- data/lib/generators/duty_free/templates/create_versions.rb.erb +2 -2
- metadata +10 -29
@@ -0,0 +1,349 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
require 'fancy_gets'
|
6
|
+
|
7
|
+
module DutyFree
|
8
|
+
# Auto-generates an IMPORT_TEMPLATE entry for a model
|
9
|
+
class ModelGenerator < ::Rails::Generators::Base
|
10
|
+
include FancyGets
|
11
|
+
# include ::Rails::Generators::Migration
|
12
|
+
|
13
|
+
# # source_root File.expand_path('templates', __dir__)
|
14
|
+
# class_option(
|
15
|
+
# :with_changes,
|
16
|
+
# type: :boolean,
|
17
|
+
# default: false,
|
18
|
+
# desc: 'Add IMPORT_TEMPLATE to model'
|
19
|
+
# )
|
20
|
+
|
21
|
+
desc 'Adds an appropriate IMPORT_TEMPLATE entry into a model of your choosing so that' \
|
22
|
+
' DutyFree can perform exports, and with the Pro version, the same template also' \
|
23
|
+
' does imports.'
|
24
|
+
|
25
|
+
def df_model_template
|
26
|
+
# %%% If Apartment is active, ask which schema they want
|
27
|
+
|
28
|
+
# Load all models
|
29
|
+
Rails.configuration.eager_load_namespaces.select { |ns| ns < Rails::Application }.each(&:eager_load!)
|
30
|
+
|
31
|
+
# Generate a list of viable models that can be chosen
|
32
|
+
longest_length = 0
|
33
|
+
model_info = Hash.new { |h, k| h[k] = {} }
|
34
|
+
tableless = Hash.new { |h, k| h[k] = [] }
|
35
|
+
models = ActiveRecord::Base.descendants.reject do |m|
|
36
|
+
trouble = if m.abstract_class?
|
37
|
+
true
|
38
|
+
elsif !m.table_exists?
|
39
|
+
tableless[m.table_name] << m.name
|
40
|
+
' (No Table)'
|
41
|
+
else
|
42
|
+
this_f_keys = (model_info[m][:f_keys] = m.reflect_on_all_associations.select { |a| a.macro == :belongs_to }) || []
|
43
|
+
column_names = (model_info[m][:column_names] = m.columns.map(&:name) - [m.primary_key, 'created_at', 'updated_at', 'deleted_at'] - this_f_keys.map(&:foreign_key))
|
44
|
+
if column_names.empty? && this_f_keys && !this_f_keys.empty?
|
45
|
+
fk_message = ", although #{this_f_keys.length} foreign keys"
|
46
|
+
" (No columns#{fk_message})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# puts "#{m.name}#{trouble}" if trouble&.is_a?(String)
|
50
|
+
trouble
|
51
|
+
end
|
52
|
+
models.sort! do |a, b| # Sort first to separate namespaced stuff from the rest, then alphabetically
|
53
|
+
is_a_namespaced = a.name.include?('::')
|
54
|
+
is_b_namespaced = b.name.include?('::')
|
55
|
+
if is_a_namespaced && !is_b_namespaced
|
56
|
+
1
|
57
|
+
elsif !is_a_namespaced && is_b_namespaced
|
58
|
+
-1
|
59
|
+
else
|
60
|
+
a.name <=> b.name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
models.each do |m| # Find longest name in the list for future use to show lists on the right side of the screen
|
64
|
+
# Strangely this can't be inlined since it assigns to "len"
|
65
|
+
if longest_length < (len = m.name.length)
|
66
|
+
longest_length = len
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
model_name = ARGV[0]&.camelize
|
71
|
+
unless (starting = models.find { |m| m.name == model_name })
|
72
|
+
puts "#{"Couldn't find #{model_name}. " if model_name}Pick a model to start from:"
|
73
|
+
starting = gets_list(
|
74
|
+
list: models,
|
75
|
+
on_select: proc do |item|
|
76
|
+
selected = item[:selected] || item[:focused]
|
77
|
+
this_model_info = model_info[selected]
|
78
|
+
selected.name + " (#{(this_model_info[:column_names] + this_model_info[:f_keys].map(&:name).map(&:upcase)).join(', ')})"
|
79
|
+
end
|
80
|
+
)
|
81
|
+
end
|
82
|
+
puts "\nThinking..."
|
83
|
+
|
84
|
+
# %%% Find out how many hops at most we can go from this model
|
85
|
+
max_hm_nav = starting.suggest_template(-1, true, false)
|
86
|
+
max_bt_nav = starting.suggest_template(-1, false, false)
|
87
|
+
hops_with_hm, num_hm_hops_tables = calc_num_hops([[starting, max_hm_nav[:all]]], models)
|
88
|
+
hops, num_hops_tables = calc_num_hops([[starting, max_bt_nav[:all]]], models)
|
89
|
+
# print "\b" * 11
|
90
|
+
unless hops_with_hm.length == hops.length
|
91
|
+
starting_name = starting.name
|
92
|
+
unless (is_hm = ARGV[1]&.downcase)
|
93
|
+
puts "Navigate from #{starting_name} using:\n#{'=' * (21 + starting_name.length)}"
|
94
|
+
is_hm = gets_list(
|
95
|
+
["Only belongs_to (max of #{hops.length} hops and #{num_hops_tables} tables)",
|
96
|
+
"has_many as well as belongs_to (max of #{hops_with_hm.length} hops and #{num_hm_hops_tables} tables)"]
|
97
|
+
)
|
98
|
+
end
|
99
|
+
is_hm = is_hm.start_with?('has_many') || is_hm[0] == 'y'
|
100
|
+
hops = hops_with_hm if is_hm
|
101
|
+
end
|
102
|
+
|
103
|
+
unless (num_hops = ARGV[2]&.to_i)
|
104
|
+
puts "\nNow, how many hops total would you like to navigate?"
|
105
|
+
index = 0
|
106
|
+
cumulative = 0
|
107
|
+
hops_list = ['0'] + hops.map { |h| "#{index += 1} (#{cumulative += h.length} linkages)" }
|
108
|
+
num_hops = gets_list(
|
109
|
+
list: hops_list,
|
110
|
+
on_select: proc do |value|
|
111
|
+
associations = Hash.new { |h, k| h[k] = 0 }
|
112
|
+
index = (value[:selected] || value[:focused]).split(' ').first.to_i - 1
|
113
|
+
layer = hops[index] if index >= 0
|
114
|
+
layer ||= []
|
115
|
+
layer.each { |i| associations[i.last] += 1 }
|
116
|
+
associations.each { |k, v| associations.delete(k) if v == 1 }
|
117
|
+
layer.map do |l|
|
118
|
+
associations.keys.include?(l.last) ? "#{l.first.name.demodulize} #{l.last}" : l.last
|
119
|
+
end.join(', ')
|
120
|
+
# y = model_info[data[:focused].name]
|
121
|
+
# data[:focused].name + " (#{(y[:column_names] + y[:f_keys].map(&:name).map(&:upcase)).join(', ')})"
|
122
|
+
# layer.inspect
|
123
|
+
end
|
124
|
+
).split(' ').first.to_i
|
125
|
+
end
|
126
|
+
|
127
|
+
print "Navigating from #{starting_name}" if model_name
|
128
|
+
puts "\nOkay, #{num_hops} hops#{', including has_many,' if is_hm} it is!"
|
129
|
+
# Grab the console output from this:
|
130
|
+
original_stdout = $stdout
|
131
|
+
$stdout = StringIO.new
|
132
|
+
starting.suggest_template(num_hops, is_hm)
|
133
|
+
output = $stdout
|
134
|
+
$stdout = original_stdout
|
135
|
+
filename = nil
|
136
|
+
output.rewind
|
137
|
+
lines = output.each_line.each_with_object([]) do |line, s|
|
138
|
+
if line == "\n"
|
139
|
+
# Do nothing
|
140
|
+
elsif filename
|
141
|
+
s << line
|
142
|
+
elsif line.start_with?('# Place the following into ')
|
143
|
+
filename = line[27..-1]&.split(':')&.first
|
144
|
+
end
|
145
|
+
s
|
146
|
+
end
|
147
|
+
|
148
|
+
model_file = File.open(filename, 'r+')
|
149
|
+
insert_at = nil
|
150
|
+
starting_name = starting.name.demodulize
|
151
|
+
loop do
|
152
|
+
break if model_file.eof?
|
153
|
+
|
154
|
+
line_parts = model_file.readline.strip.gsub(/ +/, ' ').split(' ')
|
155
|
+
if line_parts.first == 'class' && line_parts[1] && (line_parts[1] == starting_name || line_parts[1].end_with?("::#{starting_name}"))
|
156
|
+
insert_at = model_file.pos
|
157
|
+
break
|
158
|
+
end
|
159
|
+
end
|
160
|
+
line = nil
|
161
|
+
import_template_blocks = []
|
162
|
+
import_template_block = nil
|
163
|
+
indentation = nil
|
164
|
+
# See if there's already any IMPORT_TEMPLATE entries in the model file.
|
165
|
+
# If there already is just one, we will comment it out if needs be before adding a fresh one.
|
166
|
+
loop do
|
167
|
+
break if model_file.eof?
|
168
|
+
|
169
|
+
line_parts = (line = model_file.readline).strip.split(/[\s=]+/)
|
170
|
+
indentation ||= line[0...(/\S/ =~ line)]
|
171
|
+
case line_parts[-2..-1]
|
172
|
+
when ['IMPORT_TEMPLATE', '{']
|
173
|
+
import_template_blocks << import_template_block if import_template_block
|
174
|
+
import_template_block = [model_file.pos - line.length, nil, line.strip[0] == '#', []]
|
175
|
+
when ['#', '------------------------------------------']
|
176
|
+
import_template_block[1] = model_file.pos if import_template_block # && import_template_block[1].nil?
|
177
|
+
end
|
178
|
+
next unless import_template_block
|
179
|
+
|
180
|
+
# Collect all the lines of any existing block
|
181
|
+
import_template_block[3] << line
|
182
|
+
# Cap this one if it's done
|
183
|
+
if import_template_block[1]
|
184
|
+
import_template_blocks << import_template_block
|
185
|
+
import_template_block = nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
import_template_blocks << import_template_block if import_template_block
|
189
|
+
comments = nil
|
190
|
+
is_add_cr = nil
|
191
|
+
if import_template_blocks.length > 1
|
192
|
+
# %%% maybe in the future: remove any older commented ones
|
193
|
+
puts 'Found multiple existing import template blocks. Will not attempt to automatically add yet another.'
|
194
|
+
insert_at = nil
|
195
|
+
elsif import_template_blocks.length == 1
|
196
|
+
# Get set up to add the new block after the existing one
|
197
|
+
insert_at = (import_template_block = import_template_blocks.first)[1]
|
198
|
+
if insert_at.nil?
|
199
|
+
puts "Found what looked like the start of an existing IMPORT_TEMPLATE block, but couldn't determine where it ends. Will not attempt to automatically add anything."
|
200
|
+
elsif import_template_block[2] # Already commented
|
201
|
+
is_add_cr = true
|
202
|
+
else # Needs to be commented
|
203
|
+
# Find what kind and how much indentation is present from the first commented line
|
204
|
+
indentation = import_template_block[3].first[0...(/\S/ =~ import_template_block[3].first)]
|
205
|
+
comments = import_template_block[3].map { |l| "#{l[0...indentation.length]}# #{l[indentation.length..-1]}" }
|
206
|
+
end
|
207
|
+
# else # Must be no IMPORT_TEMPLATE block yet
|
208
|
+
# insert_at = model_file.pos
|
209
|
+
end
|
210
|
+
if insert_at.nil?
|
211
|
+
puts "Please edit #{filename} manually and add this code:\n\n#{lines.join}"
|
212
|
+
else
|
213
|
+
is_good = ARGV[3]&.downcase&.start_with?('y')
|
214
|
+
args = [starting_name,
|
215
|
+
is_hm ? 'has_many' : 'no',
|
216
|
+
num_hops]
|
217
|
+
args << 'yes' if is_good
|
218
|
+
args = args.each_with_object(+'') do |v, s|
|
219
|
+
s << " #{v}"
|
220
|
+
s
|
221
|
+
end
|
222
|
+
lines.unshift("# Added #{DateTime.now.strftime('%b %d, %Y %I:%M%P')} by running `bin/rails g duty_free:model#{args}`\n")
|
223
|
+
# add a new one afterwards
|
224
|
+
print is_good ? 'Will' : 'OK to'
|
225
|
+
print "#{" comment #{comments.length} existing lines and" if comments} add #{lines.length} new lines to #{filename}"
|
226
|
+
puts is_good ? '.' : '?'
|
227
|
+
if is_good || gets_list(%w[Yes No]) == 'Yes'
|
228
|
+
# Store rest of file
|
229
|
+
model_file.pos = insert_at
|
230
|
+
rest_of_file = model_file.read
|
231
|
+
if comments
|
232
|
+
model_file.pos = import_template_block[0]
|
233
|
+
model_file.write("#{comments.join}\n")
|
234
|
+
puts "Commented #{comments.length} existing lines"
|
235
|
+
else
|
236
|
+
model_file.pos = insert_at
|
237
|
+
end
|
238
|
+
model_file.write("\n") if is_add_cr
|
239
|
+
model_file.write(lines.map { |l| "#{indentation}#{l}" }.join)
|
240
|
+
model_file.write(rest_of_file)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
model_file.close
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# def calc_num_hops(all, num = 0)
|
249
|
+
# max_num = num
|
250
|
+
# all.each do |item|
|
251
|
+
# if item.is_a?(Hash)
|
252
|
+
# item.each do |k, v|
|
253
|
+
# # puts "#{k} - #{num}"
|
254
|
+
# this_num = calc_num_hops(item[k], num + 1)
|
255
|
+
# max_num = this_num if this_num > max_num
|
256
|
+
# end
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
# max_num
|
260
|
+
# end
|
261
|
+
|
262
|
+
# Breadth first approach
|
263
|
+
def calc_num_hops(this_layer, models = nil)
|
264
|
+
seen_it = {}
|
265
|
+
layers = []
|
266
|
+
loop do
|
267
|
+
this_keys = []
|
268
|
+
next_layer = []
|
269
|
+
this_layer.each do |grouping|
|
270
|
+
klass = grouping.first
|
271
|
+
# binding.pry #unless klass.is_a?(Class)
|
272
|
+
grouping.last.each do |item|
|
273
|
+
next unless item.is_a?(Hash) && !seen_it.include?([klass, (k, v = item.first).first])
|
274
|
+
|
275
|
+
seen_it[[klass, k]] = nil
|
276
|
+
this_keys << [klass, k]
|
277
|
+
this_klass = klass.reflect_on_association(k)&.klass
|
278
|
+
if this_klass.nil? # Perhaps it's polymorphic
|
279
|
+
polymorphics = klass.reflect_on_all_associations.each_with_object([]) do |r, s|
|
280
|
+
prefix = "#{r.name}_"
|
281
|
+
if r.polymorphic? && k.to_s.start_with?(prefix)
|
282
|
+
suffix = k.to_s[prefix.length..-1]
|
283
|
+
possible_klass = models.find { |m| m.name.underscore == suffix }
|
284
|
+
s << [suffix, possible_klass] if possible_klass
|
285
|
+
end
|
286
|
+
s
|
287
|
+
end
|
288
|
+
# binding.pry if polymorphics.length != 1
|
289
|
+
this_klass = polymorphics.first&.last
|
290
|
+
end
|
291
|
+
next_layer << [this_klass, v.select { |ip| ip.is_a?(Hash) }] if this_klass
|
292
|
+
end
|
293
|
+
end
|
294
|
+
layers << this_keys unless this_keys.empty?
|
295
|
+
break if next_layer.empty?
|
296
|
+
|
297
|
+
this_layer = next_layer
|
298
|
+
end
|
299
|
+
# puts "#{k} - #{num}"
|
300
|
+
[layers, seen_it.keys.map(&:first).uniq.length]
|
301
|
+
end
|
302
|
+
|
303
|
+
# # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
|
304
|
+
# def item_type_options
|
305
|
+
# opt = { null: false }
|
306
|
+
# opt[:limit] = 191 if mysql?
|
307
|
+
# ", #{opt}"
|
308
|
+
# end
|
309
|
+
|
310
|
+
# def migration_version
|
311
|
+
# return unless (major = ActiveRecord::VERSION::MAJOR) >= 5
|
312
|
+
|
313
|
+
# "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
|
314
|
+
# end
|
315
|
+
|
316
|
+
# # Class names of MySQL adapters.
|
317
|
+
# # - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
|
318
|
+
# # - `Mysql2Adapter` - Used by `mysql2` gem.
|
319
|
+
# def mysql?
|
320
|
+
# [
|
321
|
+
# 'ActiveRecord::ConnectionAdapters::MysqlAdapter',
|
322
|
+
# 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'
|
323
|
+
# ].freeze.include?(ActiveRecord::Base.connection.class.name)
|
324
|
+
# end
|
325
|
+
|
326
|
+
# # Even modern versions of MySQL still use `latin1` as the default character
|
327
|
+
# # encoding. Many users are not aware of this, and run into trouble when they
|
328
|
+
# # try to use DutyFree in apps that otherwise tend to use UTF-8. Postgres, by
|
329
|
+
# # comparison, uses UTF-8 except in the unusual case where the OS is configured
|
330
|
+
# # with a custom locale.
|
331
|
+
# #
|
332
|
+
# # - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
|
333
|
+
# # - http://www.postgresql.org/docs/9.4/static/multibyte.html
|
334
|
+
# #
|
335
|
+
# # Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
|
336
|
+
# # to be fixed later by introducing a new charset, `utf8mb4`.
|
337
|
+
# #
|
338
|
+
# # - https://mathiasbynens.be/notes/mysql-utf8mb4
|
339
|
+
# # - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
|
340
|
+
# #
|
341
|
+
# def versions_table_options
|
342
|
+
# if mysql?
|
343
|
+
# ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
|
344
|
+
# else
|
345
|
+
# ''
|
346
|
+
# end
|
347
|
+
# end
|
348
|
+
end
|
349
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# This migration creates the `versions` table, the only schema
|
2
|
-
# All other migrations
|
1
|
+
# This migration creates the `versions` table, the only schema DF requires.
|
2
|
+
# All other migrations DF provides are optional.
|
3
3
|
class CreateVersions < ActiveRecord::Migration<%= migration_version %>
|
4
4
|
|
5
5
|
# The largest text column available in all supported RDBMS is
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duty_free
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -17,9 +17,6 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '3.0'
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '6.1'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,9 +24,6 @@ dependencies:
|
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '3.0'
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '6.1'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: appraisal
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,14 +128,14 @@ dependencies:
|
|
134
128
|
requirements:
|
135
129
|
- - "~>"
|
136
130
|
- !ruby/object:Gem::Version
|
137
|
-
version: 0.
|
131
|
+
version: '0.93'
|
138
132
|
type: :development
|
139
133
|
prerelease: false
|
140
134
|
version_requirements: !ruby/object:Gem::Requirement
|
141
135
|
requirements:
|
142
136
|
- - "~>"
|
143
137
|
- !ruby/object:Gem::Version
|
144
|
-
version: 0.
|
138
|
+
version: '0.93'
|
145
139
|
- !ruby/object:Gem::Dependency
|
146
140
|
name: rubocop-rspec
|
147
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,20 +150,6 @@ dependencies:
|
|
156
150
|
- - "~>"
|
157
151
|
- !ruby/object:Gem::Version
|
158
152
|
version: 1.42.0
|
159
|
-
- !ruby/object:Gem::Dependency
|
160
|
-
name: mysql2
|
161
|
-
requirement: !ruby/object:Gem::Requirement
|
162
|
-
requirements:
|
163
|
-
- - "~>"
|
164
|
-
- !ruby/object:Gem::Version
|
165
|
-
version: '0.5'
|
166
|
-
type: :development
|
167
|
-
prerelease: false
|
168
|
-
version_requirements: !ruby/object:Gem::Requirement
|
169
|
-
requirements:
|
170
|
-
- - "~>"
|
171
|
-
- !ruby/object:Gem::Version
|
172
|
-
version: '0.5'
|
173
153
|
- !ruby/object:Gem::Dependency
|
174
154
|
name: pg
|
175
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,7 +186,7 @@ dependencies:
|
|
206
186
|
version: '1.4'
|
207
187
|
description: 'Simplify data imports and exports with this slick ActiveRecord extension
|
208
188
|
|
209
|
-
'
|
189
|
+
'
|
210
190
|
email: lorint@gmail.com
|
211
191
|
executables: []
|
212
192
|
extensions: []
|
@@ -228,13 +208,14 @@ files:
|
|
228
208
|
- lib/duty_free/version_number.rb
|
229
209
|
- lib/generators/duty_free/USAGE
|
230
210
|
- lib/generators/duty_free/install_generator.rb
|
211
|
+
- lib/generators/duty_free/model_generator.rb
|
231
212
|
- lib/generators/duty_free/templates/add_object_changes_to_versions.rb.erb
|
232
213
|
- lib/generators/duty_free/templates/create_versions.rb.erb
|
233
214
|
homepage: https://github.com/lorint/duty_free
|
234
215
|
licenses:
|
235
216
|
- MIT
|
236
217
|
metadata: {}
|
237
|
-
post_install_message:
|
218
|
+
post_install_message:
|
238
219
|
rdoc_options: []
|
239
220
|
require_paths:
|
240
221
|
- lib
|
@@ -249,8 +230,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
249
230
|
- !ruby/object:Gem::Version
|
250
231
|
version: 1.3.6
|
251
232
|
requirements: []
|
252
|
-
rubygems_version: 3.
|
253
|
-
signing_key:
|
233
|
+
rubygems_version: 3.1.6
|
234
|
+
signing_key:
|
254
235
|
specification_version: 4
|
255
236
|
summary: Import and Export Data
|
256
237
|
test_files: []
|