synvert-core 1.6.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +6 -4
- data/lib/synvert/core/configuration.rb +16 -8
- data/lib/synvert/core/rewriter/gem_spec.rb +2 -2
- data/lib/synvert/core/rewriter/instance.rb +72 -6
- data/lib/synvert/core/rewriter/ruby_version.rb +3 -3
- data/lib/synvert/core/rewriter.rb +38 -14
- data/lib/synvert/core/version.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/synvert/core/rewriter/instance_spec.rb +49 -0
- data/spec/synvert/core/rewriter_spec.rb +41 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00661750a3522a9983d5eec1f064f62bf0d335e05e3b2a9cb7bb7222b04e20a2
|
4
|
+
data.tar.gz: 5a35b831ca678486f4a51db2ae8a57bed58617299a0c26c9cbc0c462351ffd3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b18bcf0f9f344c2c8661d7e4edf1a4f768d67175f45b34d2bb37b321f38681840c29b8e9a0fa61ee295329909f3093de4b7703e6dec21066190336ee8ceb1b5a
|
7
|
+
data.tar.gz: 0f334dc34d82d15235381f3a9be7651707b5a7a0e5614e29f48310407c70384ac6f1594f2b8643dc97577e0e55a4b6165f3174071e2887f424a76ed4d5f3b3cb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.8.0 (2022-09-17)
|
4
|
+
|
5
|
+
* Rename config `path` to `root_path`
|
6
|
+
* Rename config `skip_files` to `skip_paths`
|
7
|
+
* Add config `only_paths`
|
8
|
+
* Change dir to `root_path`
|
9
|
+
|
10
|
+
## 1.7.0 (2022-09-16)
|
11
|
+
|
12
|
+
* Add `Rewriter#test`
|
13
|
+
* Use option `run_instance` instead of `sandbox`
|
14
|
+
|
3
15
|
## 1.6.0 (2022-09-15)
|
4
16
|
|
5
17
|
* Make use of `NodeQuery` to query nodes
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
synvert-core (1.
|
4
|
+
synvert-core (1.8.0)
|
5
5
|
activesupport (< 7.0.0)
|
6
6
|
erubis
|
7
7
|
node_mutation
|
@@ -23,6 +23,7 @@ GEM
|
|
23
23
|
concurrent-ruby (1.1.10)
|
24
24
|
diff-lcs (1.5.0)
|
25
25
|
erubis (2.7.0)
|
26
|
+
fakefs (1.8.0)
|
26
27
|
ffi (1.15.5)
|
27
28
|
formatador (1.1.0)
|
28
29
|
guard (2.18.0)
|
@@ -48,10 +49,10 @@ GEM
|
|
48
49
|
method_source (1.0.0)
|
49
50
|
minitest (5.16.3)
|
50
51
|
nenv (0.3.0)
|
51
|
-
node_mutation (1.
|
52
|
-
activesupport
|
52
|
+
node_mutation (1.3.3)
|
53
|
+
activesupport (< 7.0.0)
|
53
54
|
erubis
|
54
|
-
node_query (1.
|
55
|
+
node_query (1.6.0)
|
55
56
|
activesupport (< 7.0.0)
|
56
57
|
notiffany (0.1.3)
|
57
58
|
nenv (~> 0.1)
|
@@ -90,6 +91,7 @@ PLATFORMS
|
|
90
91
|
ruby
|
91
92
|
|
92
93
|
DEPENDENCIES
|
94
|
+
fakefs
|
93
95
|
guard
|
94
96
|
guard-rspec
|
95
97
|
rake
|
@@ -4,23 +4,31 @@ module Synvert::Core
|
|
4
4
|
# Synvert global configuration.
|
5
5
|
class Configuration
|
6
6
|
class << self
|
7
|
-
# @!attribute [w]
|
8
|
-
# @!attribute [w]
|
7
|
+
# @!attribute [w] root_path
|
8
|
+
# @!attribute [w] skip_paths
|
9
|
+
# @!attribute [w] only_paths
|
9
10
|
# @!attribute [w] show_run_process
|
10
|
-
attr_writer :
|
11
|
+
attr_writer :root_path, :skip_paths, :only_paths, :show_run_process
|
11
12
|
|
12
13
|
# Get the path.
|
13
14
|
#
|
14
15
|
# @return [String] default is '.'
|
15
|
-
def
|
16
|
-
@
|
16
|
+
def root_path
|
17
|
+
@root_path || '.'
|
17
18
|
end
|
18
19
|
|
19
|
-
# Get a list of skip
|
20
|
+
# Get a list of skip paths.
|
20
21
|
#
|
21
22
|
# @return [Array<String>] default is [].
|
22
|
-
def
|
23
|
-
@
|
23
|
+
def skip_paths
|
24
|
+
@skip_paths || []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a list of only paths.
|
28
|
+
#
|
29
|
+
# @return [Array<String>] default is [].
|
30
|
+
def only_paths
|
31
|
+
@only_paths || []
|
24
32
|
end
|
25
33
|
|
26
34
|
# Check if show run process.
|
@@ -22,12 +22,12 @@ module Synvert::Core
|
|
22
22
|
#
|
23
23
|
# @return [Boolean] true if matches, otherwise false.
|
24
24
|
def match?
|
25
|
-
gemfile_lock_path = File.expand_path(File.join(Configuration.
|
25
|
+
gemfile_lock_path = File.expand_path(File.join(Configuration.root_path, 'Gemfile.lock'))
|
26
26
|
|
27
27
|
# if Gemfile.lock does not exist, just ignore this check
|
28
28
|
return true unless File.exist?(gemfile_lock_path)
|
29
29
|
|
30
|
-
ENV['BUNDLE_GEMFILE'] = Configuration.
|
30
|
+
ENV['BUNDLE_GEMFILE'] = Configuration.root_path # make sure bundler reads Gemfile.lock in the correct path
|
31
31
|
parser = Bundler::LockfileParser.new(File.read(gemfile_lock_path))
|
32
32
|
parser.specs.any? { |spec| Gem::Dependency.new(@name, @version).match?(spec) }
|
33
33
|
end
|
@@ -30,14 +30,19 @@ module Synvert::Core
|
|
30
30
|
|
31
31
|
# Process the instance.
|
32
32
|
# It finds specified files, for each file, it executes the block code, rewrites the original code,
|
33
|
-
# then
|
33
|
+
# then writes the code back to the original file.
|
34
34
|
def process
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
get_file_paths.each do |file_path|
|
36
|
+
process_file(file_path)
|
37
|
+
end
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
40
|
+
# Test the instance.
|
41
|
+
# It finds specified files, for each file, it executes the block code, tests the original code,
|
42
|
+
# then returns the actions.
|
43
|
+
def test
|
44
|
+
get_file_paths.each do |file_path|
|
45
|
+
test_file(file_path)
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
@@ -378,6 +383,67 @@ module Synvert::Core
|
|
378
383
|
end
|
379
384
|
end
|
380
385
|
|
386
|
+
# Test one file.
|
387
|
+
#
|
388
|
+
# @param file_path [String]
|
389
|
+
def test_file(file_path)
|
390
|
+
@current_file = file_path
|
391
|
+
source = read_source(file_path)
|
392
|
+
@current_mutation = NodeMutation.new(source)
|
393
|
+
begin
|
394
|
+
node = parse_code(file_path, source)
|
395
|
+
|
396
|
+
process_with_node(node) do
|
397
|
+
instance_eval(&@block)
|
398
|
+
rescue NoMethodError => e
|
399
|
+
puts [
|
400
|
+
"error: #{e.message}",
|
401
|
+
"file: #{file_path}",
|
402
|
+
"source: #{source}",
|
403
|
+
"line: #{current_node.line}"
|
404
|
+
].join("\n")
|
405
|
+
raise
|
406
|
+
end
|
407
|
+
|
408
|
+
result = @current_mutation.test
|
409
|
+
result.file_path = file_path
|
410
|
+
result
|
411
|
+
rescue Parser::SyntaxError
|
412
|
+
puts "[Warn] file #{file_path} was not parsed correctly."
|
413
|
+
# do nothing, iterate next file
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Get file paths.
|
418
|
+
# @return [Array<String>] file paths
|
419
|
+
def get_file_paths
|
420
|
+
Dir.chdir(Configuration.root_path) do
|
421
|
+
only_paths = Configuration.only_paths.size > 0 ? Configuration.only_paths : ["."]
|
422
|
+
only_paths.flat_map do |only_path|
|
423
|
+
@file_patterns.flat_map do |file_pattern|
|
424
|
+
pattern = only_path == "." ? file_pattern : File.join(only_path, file_pattern)
|
425
|
+
Dir.glob(pattern) - get_skip_files
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Get skip files.
|
432
|
+
# @return [Array<String>] skip files
|
433
|
+
def get_skip_files
|
434
|
+
@skip_files ||= Configuration.skip_paths.flat_map do |skip_path|
|
435
|
+
if File.directory?(skip_path)
|
436
|
+
Dir.glob(File.join(skip_path, "**/*"))
|
437
|
+
elsif File.file?(skip_path)
|
438
|
+
[skip_path]
|
439
|
+
elsif skip_path.end_with?("**") || skip_path.end_with?("**/")
|
440
|
+
Dir.glob(File.join(skip_path, "*"))
|
441
|
+
else
|
442
|
+
Dir.glob(skip_path)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
381
447
|
# Read file source.
|
382
448
|
# @param file_path [String] file path
|
383
449
|
# @return [String] file source
|
@@ -16,14 +16,14 @@ module Synvert::Core
|
|
16
16
|
#
|
17
17
|
# @return [Boolean] true if matches, otherwise false.
|
18
18
|
def match?
|
19
|
-
if File.exist?(File.join(Configuration.
|
19
|
+
if File.exist?(File.join(Configuration.root_path, '.ruby-version'))
|
20
20
|
version_file = '.ruby-version'
|
21
|
-
elsif File.exist?(File.join(Configuration.
|
21
|
+
elsif File.exist?(File.join(Configuration.root_path, '.rvmrc'))
|
22
22
|
version_file = '.rvmrc'
|
23
23
|
end
|
24
24
|
return true unless version_file
|
25
25
|
|
26
|
-
version = File.read(File.join(Configuration.
|
26
|
+
version = File.read(File.join(Configuration.root_path, version_file))
|
27
27
|
Gem::Version.new(version) >= Gem::Version.new(@version)
|
28
28
|
end
|
29
29
|
end
|
@@ -72,15 +72,16 @@ module Synvert::Core
|
|
72
72
|
#
|
73
73
|
# @param group [String] the rewriter group.
|
74
74
|
# @param name [String] the rewriter name.
|
75
|
-
# @param
|
75
|
+
# @param options [Hash]
|
76
|
+
# @option options [Boolean] :run_instance (true) process the instance.
|
76
77
|
# @return [Synvert::Core::Rewriter] the registered rewriter.
|
77
78
|
# @raise [Synvert::Core::RewriterNotFound] if the registered rewriter is not found.
|
78
|
-
def call(group, name,
|
79
|
+
def call(group, name, options = { run_instance: true })
|
79
80
|
rewriter = fetch(group, name)
|
80
|
-
if
|
81
|
-
rewriter.process_with_sandbox
|
82
|
-
else
|
81
|
+
if options[:run_instance]
|
83
82
|
rewriter.process
|
83
|
+
else
|
84
|
+
rewriter.process_with_sandbox
|
84
85
|
end
|
85
86
|
rewriter
|
86
87
|
end
|
@@ -137,6 +138,8 @@ module Synvert::Core
|
|
137
138
|
@warnings = []
|
138
139
|
@affected_files = Set.new
|
139
140
|
@redo_until_no_change = false
|
141
|
+
@options = { run_instance: true, write_to_file: true }
|
142
|
+
@test_results = []
|
140
143
|
self.class.register(@group, @name, self)
|
141
144
|
end
|
142
145
|
|
@@ -152,14 +155,29 @@ module Synvert::Core
|
|
152
155
|
# Process rewriter with sandbox mode.
|
153
156
|
# It will call the block but doesn't change any file.
|
154
157
|
def process_with_sandbox
|
155
|
-
@
|
158
|
+
@options[:run_instance] = false
|
156
159
|
begin
|
157
160
|
process
|
158
161
|
ensure
|
159
|
-
@
|
162
|
+
@options[:run_instance] = true
|
160
163
|
end
|
161
164
|
end
|
162
165
|
|
166
|
+
def test
|
167
|
+
@options[:write_to_file] = false
|
168
|
+
begin
|
169
|
+
@affected_files = Set.new
|
170
|
+
instance_eval(&@block)
|
171
|
+
|
172
|
+
if !@affected_files.empty? && @redo_until_no_change # redo
|
173
|
+
test
|
174
|
+
end
|
175
|
+
ensure
|
176
|
+
@options[:write_to_file] = true
|
177
|
+
end
|
178
|
+
@test_results
|
179
|
+
end
|
180
|
+
|
163
181
|
# Add a warning.
|
164
182
|
#
|
165
183
|
# @param warning [Synvert::Core::Rewriter::Warning]
|
@@ -225,12 +243,18 @@ module Synvert::Core
|
|
225
243
|
# @param file_patterns [String|Array<String>] string pattern or list of string pattern to find files, e.g. ['spec/**/*_spec.rb']
|
226
244
|
# @param block [Block] the block to rewrite code in the matching files.
|
227
245
|
def within_files(file_patterns, &block)
|
228
|
-
return
|
246
|
+
return unless @options[:run_instance]
|
229
247
|
|
230
248
|
return if @ruby_version && !@ruby_version.match?
|
231
249
|
return if @gem_spec && !@gem_spec.match?
|
232
250
|
|
233
|
-
Rewriter::Instance.new(self, Array(file_patterns), &block)
|
251
|
+
instance = Rewriter::Instance.new(self, Array(file_patterns), &block)
|
252
|
+
if @options[:write_to_file]
|
253
|
+
instance.process
|
254
|
+
else
|
255
|
+
results = instance.test
|
256
|
+
@test_results += results.select { |result| result.affected? }
|
257
|
+
end
|
234
258
|
end
|
235
259
|
|
236
260
|
# Parse +within_file+ dsl, it finds a specifiled file.
|
@@ -248,9 +272,9 @@ module Synvert::Core
|
|
248
272
|
# @param filename [String] file name of newly created file.
|
249
273
|
# @param content [String] file body of newly created file.
|
250
274
|
def add_file(filename, content)
|
251
|
-
return
|
275
|
+
return unless @options[:run_instance]
|
252
276
|
|
253
|
-
filepath = File.join(Configuration.
|
277
|
+
filepath = File.join(Configuration.root_path, filename)
|
254
278
|
if File.exist?(filepath)
|
255
279
|
puts "File #{filepath} already exists."
|
256
280
|
return
|
@@ -267,9 +291,9 @@ module Synvert::Core
|
|
267
291
|
# end
|
268
292
|
# @param filename [String] file name.
|
269
293
|
def remove_file(filename)
|
270
|
-
return
|
294
|
+
return unless @options[:run_instance]
|
271
295
|
|
272
|
-
file_path = File.join(Configuration.
|
296
|
+
file_path = File.join(Configuration.root_path, filename)
|
273
297
|
File.delete(file_path) if File.exist?(file_path)
|
274
298
|
end
|
275
299
|
|
@@ -293,7 +317,7 @@ module Synvert::Core
|
|
293
317
|
# @param group [String] group of another rewriter.
|
294
318
|
# @param name [String] name of another rewriter.
|
295
319
|
def add_snippet(group, name)
|
296
|
-
@sub_snippets << self.class.call(group.to_s, name.to_s, @
|
320
|
+
@sub_snippets << self.class.call(group.to_s, name.to_s, @options)
|
297
321
|
end
|
298
322
|
|
299
323
|
# Parse +helper_method+ dsl, it defines helper method for {Synvert::Core::Rewriter::Instance}.
|
data/lib/synvert/core/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
4
|
|
5
5
|
require 'synvert/core'
|
6
|
+
require 'fakefs/spec_helpers'
|
6
7
|
|
7
8
|
Dir[File.join(File.dirname(__FILE__), 'support', '*')].each do |path|
|
8
9
|
require path
|
@@ -10,6 +11,7 @@ end
|
|
10
11
|
|
11
12
|
RSpec.configure do |config|
|
12
13
|
config.include ParserHelper
|
14
|
+
config.include FakeFS::SpecHelpers, fakefs: true
|
13
15
|
|
14
16
|
config.run_all_when_everything_filtered = true
|
15
17
|
config.filter_run :focus
|
@@ -267,6 +267,55 @@ module Synvert::Core
|
|
267
267
|
end
|
268
268
|
end
|
269
269
|
|
270
|
+
describe '#test' do
|
271
|
+
let(:rewriter) { Rewriter.new('foo', 'bar') }
|
272
|
+
|
273
|
+
it 'writes new code to file' do
|
274
|
+
instance =
|
275
|
+
Rewriter::Instance.new rewriter, ['spec/**/*_spec.rb'] do
|
276
|
+
with_node type: 'send', receiver: 'FactoryGirl', message: 'create' do
|
277
|
+
replace_with 'create {{arguments}}'
|
278
|
+
end
|
279
|
+
end
|
280
|
+
input = <<~EOS
|
281
|
+
it 'uses factory_girl' do
|
282
|
+
user = FactoryGirl.create :user
|
283
|
+
post = FactoryGirl.create :post, user: user
|
284
|
+
assert post.valid?
|
285
|
+
end
|
286
|
+
EOS
|
287
|
+
expect(Dir).to receive(:glob).with('./spec/**/*_spec.rb').and_return(['spec/models/post_spec.rb'])
|
288
|
+
expect(File).to receive(:read).with('spec/models/post_spec.rb', encoding: 'UTF-8').and_return(input)
|
289
|
+
results = instance.test
|
290
|
+
expect(results[0].file_path).to eq 'spec/models/post_spec.rb'
|
291
|
+
expect(results[0].actions).to eq [
|
292
|
+
OpenStruct.new(start: 35, end: 59, new_code: 'create :user'),
|
293
|
+
OpenStruct.new(start: 69, end: 105, new_code: 'create :post, user: user')
|
294
|
+
]
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'does not write if file content is not changed' do
|
298
|
+
instance =
|
299
|
+
Rewriter::Instance.new rewriter, ['spec/spec_helper.rb'] do
|
300
|
+
with_node type: 'block', caller: { receiver: 'RSpec', message: 'configure' } do
|
301
|
+
unless_exist_node type: 'send', message: 'include', arguments: ['FactoryGirl::Syntax::Methods'] do
|
302
|
+
insert '{{arguments.first}}.include FactoryGirl::Syntax::Methods'
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
input = <<~EOS
|
307
|
+
RSpec.configure do |config|
|
308
|
+
config.include FactoryGirl::Syntax::Methods
|
309
|
+
end
|
310
|
+
EOS
|
311
|
+
expect(Dir).to receive(:glob).with('./spec/spec_helper.rb').and_return(['spec/spec_helper.rb'])
|
312
|
+
expect(File).to receive(:read).with('spec/spec_helper.rb', encoding: 'UTF-8').and_return(input)
|
313
|
+
result = instance.test
|
314
|
+
expect(result[0].file_path).to eq 'spec/spec_helper.rb'
|
315
|
+
expect(result[0].actions).to eq []
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
270
319
|
describe '#process_with_node' do
|
271
320
|
it 'resets current_node' do
|
272
321
|
node1 = double
|
@@ -37,6 +37,46 @@ module Synvert::Core
|
|
37
37
|
rewriter.process
|
38
38
|
end
|
39
39
|
|
40
|
+
describe '#process' do
|
41
|
+
it 'rewrites the file' do
|
42
|
+
rewriter = Rewriter.new('group', 'name') do
|
43
|
+
within_files '**/*.rb' do
|
44
|
+
with_node node_type: 'class', name: 'Foobar' do
|
45
|
+
replace :name, with: 'Synvert'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
input = "class Foobar\nend"
|
50
|
+
output = "class Synvert\nend"
|
51
|
+
FakeFS do
|
52
|
+
File.write("code.rb", input)
|
53
|
+
rewriter.process
|
54
|
+
expect(File.read("code.rb")).to eq output
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#test' do
|
60
|
+
it 'gets test results' do
|
61
|
+
rewriter = Rewriter.new('group', 'name') do
|
62
|
+
within_files '**/*.rb' do
|
63
|
+
with_node node_type: 'class', name: 'Foobar' do
|
64
|
+
replace :name, with: 'Synvert'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
input = "class Foobar\nend"
|
69
|
+
FakeFS do
|
70
|
+
File.write("code.rb", input)
|
71
|
+
results = rewriter.test
|
72
|
+
expect(results[0].file_path).to eq '/code.rb'
|
73
|
+
expect(results[0].affected?).to be_truthy
|
74
|
+
expect(results[0].conflicted?).to be_falsey
|
75
|
+
expect(results[0].actions).to eq [OpenStruct.new(start: 6, end: 12, new_code: 'Synvert')]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
40
80
|
describe 'parses within_file' do
|
41
81
|
it 'does nothing if if_ruby does not match' do
|
42
82
|
expect(File).to receive(:exist?).with('./.ruby-version').and_return(true)
|
@@ -245,7 +285,7 @@ module Synvert::Core
|
|
245
285
|
it 'registers and calls rewriter in sandbox mode' do
|
246
286
|
rewriter = Rewriter.new 'group', 'rewriter'
|
247
287
|
expect(rewriter).to receive(:process_with_sandbox)
|
248
|
-
Rewriter.call 'group', 'rewriter',
|
288
|
+
Rewriter.call 'group', 'rewriter', run_instance: false
|
249
289
|
end
|
250
290
|
|
251
291
|
it 'raises RewriterNotFound if rewriter not found' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: synvert-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-09-
|
11
|
+
date: 2022-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|