diff --git a/README.md b/README.md index a1f43bca..545bfb28 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,8 @@ cannot be found by ascending the current working directory (i.e., against a temporary file buffer in your editor), you can specify the config location with `--config path/to/.standard.yml`. +You can also set a global configuration by creating a `.standard_global.yml` in your home directory. + ## What you might do if you're REALLY clever Because StandardRB is essentially a wrapper on top of diff --git a/lib/standard/loads_yaml_config.rb b/lib/standard/loads_yaml_config.rb index 1aeb840d..b4c09efe 100644 --- a/lib/standard/loads_yaml_config.rb +++ b/lib/standard/loads_yaml_config.rb @@ -7,16 +7,36 @@ module Standard class LoadsYamlConfig def initialize @parses_cli_option = ParsesCliOption.new + @global_config_dir = ENV["HOME"] end def call(argv, search_path) yaml_path = @parses_cli_option.call(argv, "--config") || FileFinder.new.call(".standard.yml", search_path) - construct_config(yaml_path, load_standard_yaml(yaml_path)) + + # global config + global_yaml_path = FileFinder.new.call(".standard_global.yml", @global_config_dir) + + standard_yaml = resolve_config(load_standard_yaml(global_yaml_path), load_standard_yaml(yaml_path)) + construct_config(yaml_path, standard_yaml) end private + def resolve_config(global_config, custom_config) + keys = %w[ruby_version fix format parallel default_ignores] + config = global_config.slice(*keys).merge(custom_config.slice(*keys)) + + return config unless global_config["ignore"] || custom_config["ignore"] + # merge `ignore` + config["ignore"] = arrayify(custom_config["ignore"]).to_set + .merge(arrayify(global_config["ignore"]).to_set) + .to_a + .compact + + config + end + def load_standard_yaml(yaml_path) if yaml_path YAML.load_file(yaml_path) || {} diff --git a/test/fixture/config/t/.standard.yml b/test/fixture/config/t/.standard.yml new file mode 100644 index 00000000..a4c291aa --- /dev/null +++ b/test/fixture/config/t/.standard.yml @@ -0,0 +1,9 @@ +fix: false +ruby_version: 2.2 +ignore: + - hello/**/* + - neat/cool.rb: + - Fake/Hi + - Fake/Lol + - nest/file.rb: + - Fake/T \ No newline at end of file diff --git a/test/fixture/config/t/.standard_global.yml b/test/fixture/config/t/.standard_global.yml new file mode 100644 index 00000000..b79680be --- /dev/null +++ b/test/fixture/config/t/.standard_global.yml @@ -0,0 +1,10 @@ +fix: true +parallel: true +format: progress +ruby_version: 1.8.7 +default_ignores: false +ignore: + - monkey/**/* + - neat/cool.rb: + - Fake/Lol + - Fake/Kek diff --git a/test/fixture/config/u/.standard.yml b/test/fixture/config/u/.standard.yml new file mode 100644 index 00000000..e69de29b diff --git a/test/fixture/config/u/.standard_global.yml b/test/fixture/config/u/.standard_global.yml new file mode 100644 index 00000000..a37ec7fe --- /dev/null +++ b/test/fixture/config/u/.standard_global.yml @@ -0,0 +1,11 @@ +fix: true +parallel: true +format: progress +ruby_version: 1.8.7 +default_ignores: false +ignore: + - monkey/**/* + - neat/cool.rb: + - Fake/Lol + - Fake/Kek + diff --git a/test/standard/builds_config_test.rb b/test/standard/builds_config_test.rb index 5d0b0ee8..ae4cee3f 100644 --- a/test/standard/builds_config_test.rb +++ b/test/standard/builds_config_test.rb @@ -92,6 +92,55 @@ def test_specified_standard_yaml_raises assert_match(/Configuration file ".*fake\.file" not found/, err.message) end + def test_global_standard_yaml + loads_yaml_config = @subject.instance_variable_get(:@loads_yaml_config) + assert_equal ENV["HOME"], loads_yaml_config.instance_variable_get(:@global_config_dir) + + loads_yaml_config.instance_variable_set(:@global_config_dir, path("test/fixture/config/u")) + result = @subject.call([], path("test/fixture/config/u")) + + assert_equal DEFAULT_OPTIONS.merge( + auto_correct: true, + safe_auto_correct: true, + parallel: true, + formatters: [["progress", nil]] + ), result.rubocop_options + + expected_config = RuboCop::ConfigStore.new.tap do |config_store| + config_store.options_config = path("config/ruby-1.8.yml") + options_config = config_store.instance_variable_get("@options_config") + options_config["AllCops"]["Exclude"] |= [path("test/fixture/config/u/monkey/**/*")] + options_config["Fake/Lol"] = {"Exclude" => [path("test/fixture/config/u/neat/cool.rb")]} + options_config["Fake/Kek"] = {"Exclude" => [path("test/fixture/config/u/neat/cool.rb")]} + end.for("").to_h + assert_equal expected_config, result.rubocop_config_store.for("").to_h + end + + def test_specified_standard_yaml_overrides_global + loads_yaml_config = @subject.instance_variable_get(:@loads_yaml_config) + assert_equal ENV["HOME"], loads_yaml_config.instance_variable_get(:@global_config_dir) + + loads_yaml_config.instance_variable_set(:@global_config_dir, path("test/fixture/config/t")) + result = @subject.call([], path("test/fixture/config/t")) + + assert_equal DEFAULT_OPTIONS.merge( + safe_auto_correct: false, + parallel: true, + formatters: [["progress", nil]] + ), result.rubocop_options + + expected_config = RuboCop::ConfigStore.new.tap do |config_store| + config_store.options_config = path("config/ruby-2.2.yml") + options_config = config_store.instance_variable_get("@options_config") + options_config["AllCops"]["Exclude"] |= [path("test/fixture/config/t/hello/**/*"), path("test/fixture/config/t/monkey/**/*")] + options_config["Fake/Hi"] = {"Exclude" => [path("test/fixture/config/t/neat/cool.rb")]} + options_config["Fake/Lol"] = {"Exclude" => [path("test/fixture/config/t/neat/cool.rb")]} + options_config["Fake/T"] = {"Exclude" => [path("test/fixture/config/t/nest/file.rb")]} + options_config["Fake/Kek"] = {"Exclude" => [path("test/fixture/config/t/neat/cool.rb")]} + end.for("").to_h + assert_equal expected_config, result.rubocop_config_store.for("").to_h + end + private def config_store(config_root = nil, rubocop_yml = "config/base.yml", ruby_version = RUBY_VERSION)