Skip to content

Commit

Permalink
Refactor plugin code to use Config class directly
Browse files Browse the repository at this point in the history
The previous implementation operated directly on the underlying hash
object contained within the `Config` class. Adjust so actual `Config`
objects are passed around instead of `Hash`es.
  • Loading branch information
sds committed May 26, 2015
1 parent 55505e5 commit 1d9d7ee
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 133 deletions.
4 changes: 4 additions & 0 deletions lib/scss_lint/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CLI
config: 78, # Configuration error
no_files: 80, # No files matched by specified glob patterns
files_filtered: 81, # All matched files were filtered by exclusions
plugin: 82, # Plugin loading error
}

def run(args)
Expand Down Expand Up @@ -84,6 +85,9 @@ def handle_runtime_exception(exception, options) # rubocop:disable Metrics/AbcSi
when SCSSLint::Exceptions::NoFilesError
puts exception.message
halt :no_files
when SCSSLint::Exceptions::PluginGemLoadError
puts exception.message
halt :plugin
when Errno::ENOENT
puts exception.message
halt :no_input
Expand Down
26 changes: 24 additions & 2 deletions lib/scss_lint/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,35 @@ def initialize(options)
validate_linters
end

def [](key)
@options[key]
end

# Compares this configuration with another.
#
# @param other [SCSSLint::Config]
# @return [true,false]
def ==(other)
super || @options == other.options
end
alias_method :eql?, :==

# Extend this {Config} with another configuration.
#
# @return [SCSSLint::Config]
def extend(config)
self.class.send(:smart_merge, @options, config.options)
self
end

def load_plugins
load_plugins_and_merge_config.tap { ensure_plugins_have_default_options }
end

def load_plugins_and_merge_config
Plugins.new(@options).load.each do |plugin|
@options = self.class.send(:smart_merge, plugin.config_options, @options)
Plugins.new(self).load.each do |plugin|
# Have the plugin options be overrideable by the local configuration
@options = self.class.send(:smart_merge, plugin.config.options, @options)
end
end

Expand Down
12 changes: 5 additions & 7 deletions lib/scss_lint/plugins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
require_relative 'plugins/linter_dir'

module SCSSLint
# Loads plugin directories and gems
# Loads external linter plugins.
class Plugins
def initialize(config_options)
@config_options = config_options
def initialize(config)
@config = config
end

def load
Expand All @@ -14,20 +14,18 @@ def load

private

attr_reader :config_options

def all
[plugin_gems, plugin_directories].flatten
end

def plugin_gems
config_options.fetch('plugin_gems', []).map do |gem_name|
Array(@config['plugin_gems']).map do |gem_name|
LinterGem.new(gem_name)
end
end

def plugin_directories
config_options.fetch('plugin_directories', []).map do |directory|
Array(@config['plugin_directories']).map do |directory|
LinterDir.new(directory)
end
end
Expand Down
10 changes: 4 additions & 6 deletions lib/scss_lint/plugins/linter_dir.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module SCSSLint
class Plugins
# Load ruby files from linter plugin directories
# Load ruby files from linter plugin directories.
class LinterDir
attr_reader :config_options
attr_reader :config

def initialize(dir)
@dir = dir
@config_options = {}
@config = SCSSLint::Config.new({}) # Will always be empty
end

def load
Expand All @@ -16,10 +16,8 @@ def load

private

attr_reader :dir

def ruby_files
Dir.glob(File.expand_path(File.join(dir, '/**/*.rb')))
Dir.glob(File.expand_path(File.join(@dir, '**', '*.rb')))
end
end
end
Expand Down
64 changes: 30 additions & 34 deletions lib/scss_lint/plugins/linter_gem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,49 @@ module SCSSLint
class Plugins
# Load linter plugin gems
class LinterGem
attr_reader :config

def initialize(name)
@name = name
end

def load
require name
require @name
@config = plugin_config
self
rescue LoadError
raise_error
end

def config_options
if File.exist?(config_file)
load_config_from_file(config_file)
else
{}
end
rescue Gem::LoadError, LoadError
raise SCSSLint::Exceptions::PluginGemLoadError,
"Unable to load linter plugin gem '#{@name}'. Try running " \
"`gem install #{@name}`, or adding it to your Gemfile and " \
'running `bundle install`. See the `plugin_gems` section of ' \
'your .scss-lint.yml file to add/remove gem plugins.'
end

private

attr_reader :name

def load_config_from_file(config_file)
Config.send(:load_options_hash_from_file, config_file)
end

def config_file
File.join(gem_dir, Config::FILE_NAME)
end

def gem_specification
Gem::Specification.find_by_name(name)
rescue Gem::LoadError
raise_error
# Returns the {SCSSLint::Config} for this plugin.
#
# This is intended to be merged with the configuration that loaded this
# plugin.
#
# @return [SCSSLint::Config]
def plugin_config
file = plugin_config_file

if File.exist?(file)
Config.load(file, merge_with_default: false)
else
Config.new({})
end
end

def gem_dir
gem_specification.gem_dir
end
# Path of the configuration file to attempt to load for this plugin.
#
# @return [String]
def plugin_config_file
gem_specification = Gem::Specification.find_by_name(@name)

def raise_error
raise SCSSLint::Exceptions::PluginGemLoadError,
"Unable to load linter plugin gem '#{name}' as it's not " \
'part of the bundle, linter_plugin_gems are specified in ' \
"your scss_lint.yml configuration. Try running 'gem install " \
"#{name}'"
File.join(gem_specification.gem_dir, Config::FILE_NAME)
end
end
end
Expand Down
7 changes: 3 additions & 4 deletions spec/scss_lint/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,9 @@ class FakeLinter2 < SCSSLint::Linter; end
}
end

context 'no plugins' do
it 'will return an empty Array' do
expect(subject).to be_instance_of Array
expect(subject).to be_empty
context 'when no plugins are specified' do
it 'returns an empty array' do
subject.should == []
end
end
end
Expand Down
12 changes: 4 additions & 8 deletions spec/scss_lint/plugins/linter_dir_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
let(:plugin_directory) { File.expand_path('../../fixtures/plugins', __FILE__) }
let(:subject) { described_class.new(plugin_directory) }

describe '#config_options' do
it 'will return a Hash' do
expect(subject.config_options).to be_instance_of Hash
end

it 'will be empty' do
expect(subject.config_options.empty?).to be true
describe '#config' do
it 'returns empty configuration' do
subject.config.should == SCSSLint::Config.new({})
end
end

describe '#load' do
it 'will require each file in the dir' do
it 'requires each file in the plugin directory' do
subject.should_receive(:require)
.with(File.join(plugin_directory, 'linter_plugin.rb')).once

Expand Down
62 changes: 35 additions & 27 deletions spec/scss_lint/plugins/linter_gem_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,53 @@ module SCSSLint
let(:subject) { described_class.new('a_gem') }

describe '#load' do
it 'will raise an exception if the gem is not installed' do
expect do
subject.load
end.to raise_exception Exceptions::PluginGemLoadError
end
let(:gem_dir) { '/gem_dir' }
let(:config_file) { File.join(gem_dir, '.scss-lint.yml') }
let(:config_file_exists) { false }

it 'will require the gem' do
subject.should_receive(:require).with('a_gem').once
subject.load
before do
File.stub(:exist?).with(config_file).and_return(config_file_exists)
end
end

describe '#config_options' do
it 'will raise an exception if the gem is not installed' do
expect do
subject.config_options
end.to raise_exception Exceptions::PluginGemLoadError
context 'when the gem does not exist' do
it 'raises an exception' do
expect { subject.load }.to raise_error Exceptions::PluginGemLoadError
end
end

context 'gem loaded' do
context 'when the gem exists' do
before do
described_class.any_instance.stub(:require).and_return true
subject.stub(:gem_dir).and_return '/dir'
subject.stub(:require).with('a_gem').and_return(true)
Gem::Specification.stub(:find_by_name)
.with('a_gem')
.and_return(double(gem_dir: gem_dir))
end

context 'with config file' do
it 'will load the config' do
File.stub(:exist?).and_return true
subject.should_receive(:load_config_from_file)
.with('/dir/.scss-lint.yml').and_return({})
it 'requires the gem' do
subject.should_receive(:require).with('a_gem').once
subject.load
end

subject.config_options
context 'when the gem does not include a configuration file' do
it 'loads an empty configuration' do
subject.load
subject.config.should == Config.new({})
end
end

context 'no config file' do
it 'will return an empty hash' do
expect(subject.config_options).to be_a Hash
expect(subject.config_options.empty?).to be true
context 'when a config file exists in the gem' do
let(:config_file_exists) { true }
let(:fake_config) { Config.new('linters' => { 'FakeLinter' => {} }) }

before do
Config.should_receive(:load)
.with(config_file, merge_with_default: false)
.and_return(fake_config)
end

it 'loads the configuration' do
subject.load
subject.config.should == fake_config
end
end
end
Expand Down
Loading

0 comments on commit 1d9d7ee

Please sign in to comment.