Skip to content

Commit

Permalink
Merge pull request #2306 from ptrippett/add-remote-config-file
Browse files Browse the repository at this point in the history
Add remote config files from http/https urls
  • Loading branch information
bbatsov committed Oct 30, 2015
2 parents 0ce2213 + ce2733b commit 40f9488
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* `Lint/LiteralInCondition` warns if a symbol or dynamic symbol is used as a condition. ([@alexdowad][])
* [#2369](https://github.com/bbatsov/rubocop/issues/2369): `Style/TrailingComma` doesn't add a trailing comma to a multiline method chain which is the only arg to a method call. ([@alexdowad][])
* `CircularArgumentReference` cop updated to lint for ordinal circular argument references on top of optional keyword arguments. ([@maxjacobson][])
* Added ability to download shared rubocop config files from remote urls. ([@ptrippett][])

### Bug Fixes

Expand Down Expand Up @@ -1687,3 +1688,4 @@
[@alexdowad]: https://github.com/alexdowad
[@minustehbare]: https://github.com/minustehbare
[@tansaku]: https://github.com/tansaku
[@ptrippett]: https://github.com/ptrippett
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ gemspec
group :test do
gem 'coveralls', require: false
gem 'safe_yaml', require: false
gem 'webmock', require: false
end

local_gemfile = 'Gemfile.local'
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,23 @@ inherit_from:
- ../conf/.rubocop.yml
```

### Inheriting configuration from a remote URL

The optional `inherit_from` directive can contain a full url to a remote
file. This makes it possible to have common project settings stored on a http
server and shared between many projects. You can inherit from both remote and
local files in the same config.

The same inheritance rules apply to remote URLs and inheriting from local
files where the first file in the list has the lowest precedence and the
last one has the highest. The format for multiple inheritance using URLs is:

```yaml
inherit_from:
- http://www.example.com/rubocop.yml
- ../.rubocop.yml
```

#### Inheriting configuration from a dependency gem

The optional `inherit_gem` directive is used to include configuration from
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,4 @@
require 'rubocop/cli'
require 'rubocop/options'
require 'rubocop/warning'
require 'rubocop/remote_config'
19 changes: 12 additions & 7 deletions lib/rubocop/config_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,20 @@ def merge(base_hash, derived_hash)

def base_configs(path, inherit_from)
configs = Array(inherit_from).compact.map do |f|
f = File.expand_path(f, File.dirname(path))
if f =~ URI.regexp
f = RemoteConfig.new(f).file
load_file(f)
else
f = File.expand_path(f, File.dirname(path))

if auto_gen_config?
next if f.include?(AUTO_GENERATED_FILE)
old_auto_config_file_warning if f.include?('rubocop-todo.yml')
end
if auto_gen_config?
next if f.include?(AUTO_GENERATED_FILE)
old_auto_config_file_warning if f.include?('rubocop-todo.yml')
end

print 'Inheriting ' if debug?
load_file(f)
print 'Inheriting ' if debug?
load_file(f)
end
end

configs.compact
Expand Down
60 changes: 60 additions & 0 deletions lib/rubocop/remote_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# encoding: utf-8

require 'net/http'

module RuboCop
# Common methods and behaviors for dealing with remote config files.
class RemoteConfig
CACHE_LIFETIME = 24 * 60 * 60

def initialize(url)
@uri = URI.parse(url)
end

def file
return cache_path unless cache_path_expired?

http = Net::HTTP.new(@uri.hostname, @uri.port)
http.use_ssl = true if @uri.instance_of? URI::HTTPS

request = Net::HTTP::Get.new(@uri.request_uri)
if cache_path_exists?
request['If-Modified-Since'] = File.stat(cache_path).mtime.rfc2822
end
response = http.request(request)

cache_path.tap do |f|
if response.is_a?(Net::HTTPSuccess)
open f, 'w' do |io|
io.write response.body
end
end
end
end

private

def cache_path
".rubocop-#{cache_name_from_uri}"
end

def cache_path_exists?
@cache_path_exists ||= File.exist?(cache_path)
end

def cache_path_expired?
return true unless cache_path_exists?

@cache_path_expired ||= begin
file_age = (Time.now - File.stat(cache_path).mtime).to_f
(file_age / CACHE_LIFETIME) > 1
end
end

def cache_name_from_uri
uri = @uri.clone
uri.query = nil
uri.to_s.gsub!(/[^0-9A-Za-z]/, '-')
end
end
end
16 changes: 16 additions & 0 deletions spec/rubocop/config_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,22 @@
.to be_superset(expected.to_set)
end
end

context 'when a file inherits from a url' do
let(:file_path) { '.rubocop.yml' }

before do
stub_request(:get, /example.com/)
.to_return(status: 200, body: "Style/Encoding:\n Enabled: true")

create_file('~/.rubocop.yml', [''])
create_file(file_path, ['inherit_from: http://example.com/rubocop.yml'])
end

it 'does not fail to load the resulting path' do
expect { configuration_from_file }.not_to raise_error
end
end
end

describe '.load_file', :isolated_environment do
Expand Down
42 changes: 42 additions & 0 deletions spec/rubocop/remote_config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# encoding: utf-8

require 'spec_helper'

describe RuboCop::RemoteConfig do
include FileHelper

let(:remote_config_url) { 'http://example.com/rubocop.yml' }
let(:cached_file_name) { '.rubocop-http---example-com-rubocop-yml' }

subject(:remote_config) { described_class.new(remote_config_url).file }

before do
stub_request(:get, /example.com/)
.to_return(status: 200, body: "Style/Encoding:\n Enabled: true")
end

after do
File.unlink cached_file_name if File.exist? cached_file_name
end

describe '.file' do
it 'downloads the file if the file does not exist' do
expect(subject).to eq(cached_file_name)
expect(File.exist? cached_file_name).to be_truthy
end

it 'does not download the file if cache lifetime has not been reached' do
FileUtils.touch cached_file_name, mtime: Time.now - ((60 * 60) * 20)

expect(subject).to eq(cached_file_name)
assert_not_requested :get, remote_config_url
end

it 'downloads the file if cache lifetime has been reached' do
FileUtils.touch cached_file_name, mtime: Time.now - ((60 * 60) * 30)

expect(subject).to eq(cached_file_name)
assert_requested :get, remote_config_url
end
end
end
5 changes: 5 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

require 'rubocop'

require 'webmock/rspec'

# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
Expand Down Expand Up @@ -48,3 +50,6 @@

# Disable colors in specs
Rainbow.enabled = false

# Disable network connections
WebMock.disable_net_connect!

0 comments on commit 40f9488

Please sign in to comment.