travis_check_rubies 0.2.5 → 0.3.0

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
  SHA1:
3
- metadata.gz: ab21a220cbfd5c324fed2b89437b745669ac522e
4
- data.tar.gz: 535c8f939a7d062574f920c0c1f42d28e06ed754
3
+ metadata.gz: d0565043dfa3d7dcaed61f34261c1af5b367d923
4
+ data.tar.gz: fd91c41ce727573376e72d29f05e6c5e68751553
5
5
  SHA512:
6
- metadata.gz: 849c73497444dfcf8e1aa7c0303e6169c8ee823d8ae072ec7190016468857cad59618cbdaaeb232b0e67c523a6eef3b3b5bcad03913baabfeb54aa88c98025ac
7
- data.tar.gz: 010526626e47ad0a788cf4b15acf2849318ba4c5f4d0936ee6d729679a0f496a5f180b8a8c104af8201e4f7e4c58ef04d9853de89c7ff1f7e347c2329d2c4988
6
+ metadata.gz: acedf4996dadb6720dfffa8444ad5def2e4b5076ff1a09a5a56bd770c14a051ea242f6a8769035d8335f023f9191f49f340fc8632e830d6db4f9800ca866e17d
7
+ data.tar.gz: 70f4e50e8362147dcd4ec8a0deed8ef07acacffcc97235e336263e6dd96818ebaf4a7a56c0d32595371cc74971298639f7e241d7f69ef799aa38c2fd9b820e7c
data/.travis.yml CHANGED
@@ -1,12 +1,9 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - '2.0.0-p648'
5
- - '2.1.10'
6
- - '2.2.8'
7
- - '2.3.5'
8
- - '2.4.2'
9
- - 'jruby-9.0.5.0'
4
+ - '2.3.8'
5
+ - '2.4.5'
6
+ - '2.5.3'
10
7
  - 'jruby-9.1.9.0'
11
8
  before_script:
12
9
  - env
@@ -14,10 +11,3 @@ before_script:
14
11
  script:
15
12
  - bundle exec rspec
16
13
  - bundle exec travis_check_rubies
17
- matrix:
18
- allow_failures:
19
- - rvm: 'jruby-9.0.5.0'
20
- exclude:
21
- - rvm: 'jruby-9.0.5.0'
22
- include:
23
- - rvm: 'jruby-9.0.5.0'
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017 Ivan Kuchin
1
+ Copyright (c) 2017-2018 Ivan Kuchin
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.markdown CHANGED
@@ -43,4 +43,4 @@ bundle exec travis_check_rubies
43
43
 
44
44
  ## Copyright
45
45
 
46
- Copyright (c) 2017 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
46
+ Copyright (c) 2017-2018 Ivan Kuchin. See [LICENSE.txt](LICENSE.txt) for details.
@@ -6,6 +6,8 @@ require 'yaml'
6
6
 
7
7
  CONFIG_FILE = '.travis_check_rubies.yml'
8
8
 
9
+ update = false
10
+
9
11
  options = if File.exist?(CONFIG_FILE)
10
12
  yaml = YAML.load_file(CONFIG_FILE) || {}
11
13
  fail "#{CONFIG_FILE} doesn't contain options hash" unless yaml.is_a?(Hash)
@@ -36,11 +38,18 @@ end
36
38
  op.on('--exclude V,V,V', Array, 'Exclude matching versions') do |exclude|
37
39
  options[:exclude] = exclude
38
40
  end
41
+ op.on('-u', '--update', 'Update versions') do
42
+ update = true
43
+ end
39
44
  begin
40
45
  op.parse!
41
46
  rescue => e
42
47
  abort "#{e}\n\n#{op.help}"
43
48
  end
44
49
 
45
- suggestions = TravisCheckRubies::TravisYml.new.suggestions(options)
46
- abort suggestions if suggestions
50
+ travis_yml = TravisCheckRubies::TravisYml.new(options: options)
51
+ if update
52
+ abort unless travis_yml.update
53
+ else
54
+ abort unless travis_yml.suggest
55
+ end
@@ -1,66 +1,152 @@
1
+ require 'fspath'
1
2
  require 'yaml'
3
+ require 'travis_check_rubies/updater'
2
4
  require 'travis_check_rubies/version'
3
5
 
4
6
  module TravisCheckRubies
5
7
  class TravisYml
6
- attr_reader :path
7
- attr_reader :versions, :allow_failures_versions, :exclude_versions, :include_versions
8
+ Suggestion = Struct.new(:section, :from, :choices, :to)
8
9
 
9
- def initialize(path = '.travis.yml')
10
- @path = path
10
+ attr_reader :path, :options
11
11
 
12
- yaml = YAML.load_file(path)
13
- @versions = Array(yaml['rvm']).map(&Version.method(:new))
14
- @allow_failures_versions = matrix_versions(yaml, 'allow_failures')
15
- @exclude_versions = matrix_versions(yaml, 'exclude')
16
- @include_versions = matrix_versions(yaml, 'include')
12
+ def initialize(path: '.travis.yml', options: {})
13
+ @path = FSPath(path)
14
+ @options = options
17
15
  end
18
16
 
19
- def suggestions(options)
20
- suggestions = Hash.new{ |h, k| h[k] = [] }
17
+ def warnings
18
+ return @warnings if @warnings
21
19
 
22
- updates = Version.updates(versions, options)
20
+ @warnings = []
23
21
 
24
- versions.each do |version|
25
- next unless (for_version = updates[version])
26
- suggestions['rvm'] << "#{version} -> #{for_version.join(', ')}"
22
+ rvm_versions.group_by(&:itself).select{ |_, versions| versions.count > 1 }.each do |version, _|
23
+ @warnings << "#{version} in rvm is repeating"
27
24
  end
28
25
 
29
- allow_failures_versions.each do |version|
30
- next if versions.include?(version)
31
- next if include_versions.include?(version)
32
- suggestions['matrix.allow_failures'] << "#{version} in matrix.allow_failures is not in rvm or include list"
26
+ (allow_failures_versions - rvm_versions - include_versions).each do |version|
27
+ @warnings << "#{version} in matrix.allow_failures is not in rvm or include list"
33
28
  end
34
29
 
35
- exclude_versions.each do |version|
36
- next if versions.include?(version)
37
- suggestions['matrix.exclude'] << "#{version} in matrix.exclude is not in rvm list"
30
+ (exclude_versions - rvm_versions).each do |version|
31
+ @warnings << "#{version} in matrix.exclude is not in rvm list"
32
+ end
33
+
34
+ @warnings
35
+ end
36
+
37
+ def suggestions
38
+ return @suggestions if @suggestions
39
+
40
+ @suggestions = []
41
+
42
+ updates = Version.updates(rvm_versions, options)
43
+ rvm_versions.each do |version|
44
+ next unless (suggestions = updates[version])
45
+ @suggestions << Suggestion.new('rvm', version, suggestions)
38
46
  end
39
47
 
40
48
  {
41
- 'matrix.allow_failures' => allow_failures_versions,
42
- 'matrix.exclude' => exclude_versions,
49
+ 'allow_failures' => allow_failures_versions,
50
+ 'exclude' => exclude_versions,
51
+ 'include' => include_versions,
43
52
  }.each do |section, versions|
44
53
  versions.each do |version|
45
- next unless (for_version = updates[version])
46
- suggestions[section] << "#{version} -> #{for_version.join(', ')}"
54
+ next unless (suggestions = Version.update(version, options))
55
+ next if suggestions.include?(version)
56
+ to = section == 'include' ? suggestions.last : suggestions.first
57
+ @suggestions << Suggestion.new(section, version, suggestions, to)
47
58
  end
48
59
  end
49
60
 
50
- include_versions.each do |version|
51
- next unless (for_version = Version.update(version, options))
52
- next if for_version.include?(version)
53
- suggestions['matrix.include'] << "#{version} -> #{for_version.join(', ')}"
61
+ @suggestions
62
+ end
63
+
64
+ def suggest
65
+ puts warnings
66
+
67
+ suggestions.group_by(&:section).each do |section, section_suggestions|
68
+ puts "#{section}:"
69
+ section_suggestions.each do |suggestion|
70
+ puts " #{suggestion.from} -> #{suggestion.choices.join(', ')}"
71
+ end
54
72
  end
55
73
 
56
- return if suggestions.empty?
57
- suggestions.map do |section, lines|
58
- "#{section}:\n#{lines.map{ |line| " #{line}" }.join("\n")}"
59
- end.join("\n")
74
+ warnings.empty? && suggestions.empty?
75
+ end
76
+
77
+ def update
78
+ unless warnings.empty?
79
+ puts warnings
80
+
81
+ return false
82
+ end
83
+
84
+ unless YAML.load(updated_content) == expected_object
85
+ puts updated_content
86
+
87
+ return false
88
+ end
89
+
90
+ FSPath.temp_file(path.basename, path.dirname) do |f|
91
+ f.write(updated_content)
92
+ f.path.rename(path)
93
+ end
94
+
95
+ true
60
96
  end
61
97
 
62
98
  private
63
99
 
100
+ def original_content
101
+ @original_content ||= path.read
102
+ end
103
+
104
+ def original_object
105
+ @original_object ||= YAML.load(original_content)
106
+ end
107
+
108
+ def expected_object
109
+ @expected_object ||= YAML.load(original_content).tap do |expected|
110
+ suggestions.each do |suggestion|
111
+ if suggestion.section == 'rvm'
112
+ index = expected['rvm'].find_index{ |v| suggestion.from == v }
113
+ expected['rvm'][index, 1] = suggestion.choices.map(&:to_s)
114
+ else
115
+ entry = expected['matrix'][suggestion.section].find{ |attrs| suggestion.from == attrs['rvm'] }
116
+ entry['rvm'] = suggestion.to.to_s
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def updated_content
123
+ return @updated_content if @updated_content
124
+
125
+ updater = Updater.new(original_content)
126
+
127
+ suggestions.each do |suggestion|
128
+ updater = updater.apply_suggestion(suggestion)
129
+ end
130
+
131
+ @updated_content = updater.content
132
+ end
133
+
134
+ def rvm_versions
135
+ @rvm_versions ||= Array(original_object['rvm']).map(&Version.method(:new))
136
+ end
137
+
138
+ def allow_failures_versions
139
+ @allow_failures_versions ||= matrix_versions(original_object, 'allow_failures')
140
+ end
141
+
142
+ def exclude_versions
143
+ @exclude_versions ||= matrix_versions(original_object, 'exclude')
144
+ end
145
+
146
+ def include_versions
147
+ @include_versions ||= matrix_versions(original_object, 'include')
148
+ end
149
+
64
150
  def matrix_versions(yaml, key)
65
151
  return [] unless (matrix = yaml['matrix'])
66
152
  return [] unless (list = matrix[key])
@@ -0,0 +1,92 @@
1
+ require 'psych'
2
+
3
+ module TravisCheckRubies
4
+ class Updater
5
+ attr_reader :content
6
+
7
+ def initialize(content)
8
+ @content = content
9
+ end
10
+
11
+ def apply_suggestion(suggestion)
12
+ lines = content.lines
13
+
14
+ if suggestion.section == 'rvm'
15
+ apply_rvm_suggestion(lines, suggestion)
16
+ else
17
+ apply_matrix_suggestion(lines, suggestion)
18
+ end
19
+
20
+ self.class.new lines.join('')
21
+ end
22
+
23
+ private
24
+
25
+ def apply_rvm_suggestion(lines, suggestion)
26
+ node = rvm_node.children.find{ |node| suggestion.from == node.to_ruby }
27
+
28
+ lines[node.start_line] = suggestion.choices.map do |version|
29
+ line_with_version_change(lines, node, suggestion.from, version)
30
+ end.join('')
31
+ end
32
+
33
+ def apply_matrix_suggestion(lines, suggestion)
34
+ section_node = matrix_section_node(suggestion.section)
35
+
36
+ entry_node = section_node.children.find{ |node| suggestion.from == node.to_ruby['rvm'] }
37
+
38
+ node = fetch_node_mapping(entry_node, 'rvm')
39
+
40
+ lines[node.start_line] = line_with_version_change(lines, node, suggestion.from, suggestion.to)
41
+ end
42
+
43
+ def root
44
+ @root ||= Psych::Parser.new(Psych::TreeBuilder.new).parse(content).handler.root.children[0].children[0]
45
+ end
46
+
47
+ def rvm_node
48
+ fetch_node_mapping(root, 'rvm').tap do |rvm_node|
49
+ assert_type rvm_node, Psych::Nodes::Sequence
50
+ fail "Expected block style: #{rvm_node.to_ruby}" unless rvm_node.style == Psych::Nodes::Sequence::BLOCK
51
+ end
52
+ end
53
+
54
+ def matrix_section_node(section)
55
+ matrix_node = fetch_node_mapping(root, 'matrix')
56
+
57
+ fetch_node_mapping(matrix_node, section).tap do |section_node|
58
+ assert_type section_node, Psych::Nodes::Sequence
59
+ end
60
+ end
61
+
62
+ def fetch_node_mapping(mapping, key)
63
+ assert_type mapping, Psych::Nodes::Mapping
64
+
65
+ _, node = mapping.children.each_cons(2).find{ |key_node, _| key_node.to_ruby == key }
66
+
67
+ fail "Didn't find key #{key.inspect} in #{mapping.to_ruby}" unless node
68
+
69
+ node
70
+ end
71
+
72
+ def line_with_version_change(lines, node, from, to)
73
+ assert_type node, Psych::Nodes::Scalar
74
+
75
+ line = lines[node.start_line]
76
+
77
+ before = line[0...node.start_column]
78
+ excerpt = line[node.start_column...node.end_column]
79
+ after = line[node.end_column..-1]
80
+
81
+ fail "Didn't find #{from.to_s} in #{line}" unless excerpt.sub!(from.to_s, to.to_s)
82
+
83
+ "#{before}#{excerpt}#{after}"
84
+ end
85
+
86
+ def assert_type(node, expected_class)
87
+ return if node.is_a?(expected_class)
88
+
89
+ fail "Expected a #{expected_class}, got #{node.class}"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,116 @@
1
+ require 'rspec'
2
+ require 'travis_check_rubies/updater'
3
+
4
+ describe TravisCheckRubies::Updater do
5
+ describe '#apply_suggestion' do
6
+ subject{ described_class.new(content).apply_suggestion(suggestion) }
7
+
8
+ context 'for rvm change suggestion' do
9
+ let(:content) do
10
+ <<~YAML
11
+ language: ruby
12
+ rvm: # rvm
13
+ - '2.1.3' # foo
14
+ - '2.2.8' # bar
15
+ - '2.5.0' # baz
16
+ before_script:
17
+ - env # env
18
+ YAML
19
+ end
20
+
21
+ context 'removing version' do
22
+ let(:suggestion){ double(section: 'rvm', from: '2.2.8', choices: []) }
23
+
24
+ let(:expected) do
25
+ <<~YAML
26
+ language: ruby
27
+ rvm: # rvm
28
+ - '2.1.3' # foo
29
+ - '2.5.0' # baz
30
+ before_script:
31
+ - env # env
32
+ YAML
33
+ end
34
+
35
+ it{ is_expected.to have_attributes(content: expected) }
36
+ end
37
+
38
+ context 'changing to one version' do
39
+ let(:suggestion){ double(section: 'rvm', from: '2.2.8', choices: [double(to_s: '2.2.9')]) }
40
+
41
+ let(:expected) do
42
+ <<~YAML
43
+ language: ruby
44
+ rvm: # rvm
45
+ - '2.1.3' # foo
46
+ - '2.2.9' # bar
47
+ - '2.5.0' # baz
48
+ before_script:
49
+ - env # env
50
+ YAML
51
+ end
52
+
53
+ it{ is_expected.to have_attributes(content: expected) }
54
+ end
55
+
56
+ context 'changing to multiple versions' do
57
+ let(:suggestion){ double(section: 'rvm', from: '2.2.8', choices: [double(to_s: '2.2.9'), double(to_s: '2.3.4')]) }
58
+
59
+ let(:expected) do
60
+ <<~YAML
61
+ language: ruby
62
+ rvm: # rvm
63
+ - '2.1.3' # foo
64
+ - '2.2.9' # bar
65
+ - '2.3.4' # bar
66
+ - '2.5.0' # baz
67
+ before_script:
68
+ - env # env
69
+ YAML
70
+ end
71
+
72
+ it{ is_expected.to have_attributes(content: expected) }
73
+ end
74
+ end
75
+
76
+ context 'for matrix change suggestion' do
77
+ let(:content) do
78
+ <<~YAML
79
+ language: ruby
80
+ before_script:
81
+ - env # env
82
+ matrix:
83
+ allow_failures:
84
+ - rvm: '2.2.8' # foo
85
+ exclude:
86
+ - rvm: '2.2.8' # bar
87
+ include:
88
+ - rvm: '2.1.3' # foo
89
+ - rvm: '2.2.8' # bar
90
+ - rvm: '2.2.8' # baz
91
+ YAML
92
+ end
93
+
94
+ let(:suggestion){ double(section: 'include', from: '2.2.8', to: double(to_s: '2.2.9')) }
95
+
96
+ let(:expected) do
97
+ <<~YAML
98
+ language: ruby
99
+ before_script:
100
+ - env # env
101
+ matrix:
102
+ allow_failures:
103
+ - rvm: '2.2.8' # foo
104
+ exclude:
105
+ - rvm: '2.2.8' # bar
106
+ include:
107
+ - rvm: '2.1.3' # foo
108
+ - rvm: '2.2.9' # bar
109
+ - rvm: '2.2.8' # baz
110
+ YAML
111
+ end
112
+
113
+ it{ is_expected.to have_attributes(content: expected) }
114
+ end
115
+ end
116
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'travis_check_rubies'
5
- s.version = '0.2.5'
5
+ s.version = '0.3.0'
6
6
  s.summary = 'Are you using the latest rubies in .travis.yml?'
7
7
  s.description = 'Check if `.travis.yml` specifies latest available rubies from listed on https://rubies.travis-ci.org and propose changes'
8
8
  s.homepage = "http://github.com/toy/#{s.name}"
@@ -14,9 +14,10 @@ Gem::Specification.new do |s|
14
14
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
15
  s.require_paths = %w[lib]
16
16
 
17
- s.required_ruby_version = '>= 2.0.0'
17
+ s.required_ruby_version = '>= 2.3.0'
18
18
 
19
19
  s.add_runtime_dependency 'fspath', '~> 3.0'
20
+ s.add_runtime_dependency 'psych', '~> 3.0'
20
21
 
21
22
  s.add_development_dependency 'rspec', '~> 3.0'
22
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: travis_check_rubies
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Kuchin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-13 00:00:00.000000000 Z
11
+ date: 2018-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fspath
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: psych
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -54,7 +68,9 @@ files:
54
68
  - bin/travis_check_rubies
55
69
  - lib/travis_check_rubies.rb
56
70
  - lib/travis_check_rubies/travis_yml.rb
71
+ - lib/travis_check_rubies/updater.rb
57
72
  - lib/travis_check_rubies/version.rb
73
+ - spec/travis_check_rubies/updater_spec.rb
58
74
  - spec/travis_check_rubies/version_spec.rb
59
75
  - travis_check_rubies.gemspec
60
76
  homepage: http://github.com/toy/travis_check_rubies
@@ -69,7 +85,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
85
  requirements:
70
86
  - - ">="
71
87
  - !ruby/object:Gem::Version
72
- version: 2.0.0
88
+ version: 2.3.0
73
89
  required_rubygems_version: !ruby/object:Gem::Requirement
74
90
  requirements:
75
91
  - - ">="
@@ -77,9 +93,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
93
  version: '0'
78
94
  requirements: []
79
95
  rubyforge_project:
80
- rubygems_version: 2.6.14
96
+ rubygems_version: 2.6.14.3
81
97
  signing_key:
82
98
  specification_version: 4
83
99
  summary: Are you using the latest rubies in .travis.yml?
84
100
  test_files:
101
+ - spec/travis_check_rubies/updater_spec.rb
85
102
  - spec/travis_check_rubies/version_spec.rb