travis_check_rubies 0.2.5 → 0.3.0

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.
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