diff --git a/lib/herb/configuration.rb b/lib/herb/configuration.rb index 4fbb64beb..d80042346 100644 --- a/lib/herb/configuration.rb +++ b/lib/herb/configuration.rb @@ -28,12 +28,13 @@ class Configuration DEFAULTS_PATH = File.expand_path("defaults.yml", __dir__ || __FILE__).freeze DEFAULTS = YAML.safe_load_file(DEFAULTS_PATH).freeze - attr_reader :config, :config_path, :project_root + attr_reader :config, :user_config, :config_path, :project_root def initialize(project_path = nil) @start_path = project_path ? Pathname.new(project_path) : Pathname.pwd @config_path, @project_root = find_config_file - @config = load_config + @user_config = load_user_config + @config = deep_merge(DEFAULTS, @user_config) end def [](key) @@ -256,26 +257,26 @@ def project_root?(path) end end - def load_config - return deep_merge(DEFAULTS, {}) unless @config_path&.exist? + def load_user_config + return {} unless @config_path&.exist? begin - user_config = YAML.safe_load_file(@config_path, permitted_classes: [Symbol]) || {} - deep_merge(DEFAULTS, user_config) + YAML.safe_load_file(@config_path, permitted_classes: [Symbol]) || {} rescue Psych::SyntaxError => e warn "Warning: Invalid YAML in #{@config_path}: #{e.message}" - deep_merge(DEFAULTS, {}) + + {} end end def deep_merge(base, override, additive_keys: ["include", "exclude"]) - base.merge(override) do |key, old_val, new_val| - if old_val.is_a?(Hash) && new_val.is_a?(Hash) - deep_merge(old_val, new_val, additive_keys: additive_keys) - elsif old_val.is_a?(Array) && new_val.is_a?(Array) && additive_keys.include?(key) - old_val + new_val + base.merge(override) do |key, old_value, new_value| + if old_value.is_a?(Hash) && new_value.is_a?(Hash) + deep_merge(old_value, new_value, additive_keys: additive_keys) + elsif old_value.is_a?(Array) && new_value.is_a?(Array) && additive_keys.include?(key) + old_value + new_value else - new_val + new_value end end end diff --git a/sig/herb/configuration.rbs b/sig/herb/configuration.rbs index 9b37706c4..3b4f6f697 100644 --- a/sig/herb/configuration.rbs +++ b/sig/herb/configuration.rbs @@ -20,6 +20,8 @@ module Herb attr_reader config: untyped + attr_reader user_config: untyped + attr_reader config_path: untyped attr_reader project_root: untyped @@ -99,7 +101,7 @@ module Herb def project_root?: (untyped path) -> untyped - def load_config: () -> untyped + def load_user_config: () -> untyped def deep_merge: (untyped base, untyped override, ?additive_keys: untyped) -> untyped end diff --git a/test/configuration_test.rb b/test/configuration_test.rb index b209ca930..53d8c00b4 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -766,4 +766,65 @@ def write_config(content, filename = ".herb.yml") assert_equal engine, config.template_engine end end + + test "user_config is empty when no config file exists" do + config = Herb::Configuration.load(@temp_dir) + + assert_equal({}, config.user_config) + end + + test "user_config contains only user-specified values" do + write_config(<<~YAML) + framework: actionview + YAML + + config = Herb::Configuration.load(@temp_dir) + + assert_equal({ "framework" => "actionview" }, config.user_config) + refute config.user_config.key?("template_engine") + refute config.user_config.key?("files") + end + + test "user_config does not include defaults" do + write_config(<<~YAML) + framework: actionview + YAML + + config = Herb::Configuration.load(@temp_dir) + + assert config.user_config.key?("framework") + refute config.user_config.key?("engine") + + assert config.config.key?("engine") + assert config.config.key?("files") + end + + test "user_config reflects nested values" do + write_config(<<~YAML) + engine: + optimize: true + parser_options: + strict: false + YAML + + config = Herb::Configuration.load(@temp_dir) + + assert_equal true, config.user_config.dig("engine", "optimize") + assert_equal false, config.user_config.dig("engine", "parser_options", "strict") + assert_nil config.user_config.dig("engine", "validators") + end + + test "user_config key? can distinguish explicit values from defaults" do + write_config(<<~YAML) + framework: ruby + YAML + + config = Herb::Configuration.load(@temp_dir) + + assert config.user_config.key?("framework") + refute config.user_config.key?("template_engine") + + assert_equal "ruby", config.framework + assert_equal "erubi", config.template_engine + end end