Hello community, here is the log from the commit of package rubygem-mixlib-config for openSUSE:Factory checked in at 2018-02-12 10:16:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-mixlib-config (Old) and /work/SRC/openSUSE:Factory/.rubygem-mixlib-config.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-mixlib-config" Mon Feb 12 10:16:04 2018 rev:9 rq:575440 version:2.2.5 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-mixlib-config/rubygem-mixlib-config.changes 2016-10-10 16:22:35.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-mixlib-config.new/rubygem-mixlib-config.changes 2018-02-12 10:16:04.343651390 +0100 @@ -1,0 +2,6 @@ +Sat Feb 10 05:30:12 UTC 2018 - factory-a...@kulow.org + +- updated to version 2.2.5 + no changelog found + +------------------------------------------------------------------- Old: ---- mixlib-config-2.2.4.gem New: ---- mixlib-config-2.2.5.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-mixlib-config.spec ++++++ --- /var/tmp/diff_new_pack.Fm9LRD/_old 2018-02-12 10:16:05.059625590 +0100 +++ /var/tmp/diff_new_pack.Fm9LRD/_new 2018-02-12 10:16:05.063625446 +0100 @@ -1,7 +1,7 @@ # # spec file for package rubygem-mixlib-config # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -24,16 +24,16 @@ # Name: rubygem-mixlib-config -Version: 2.2.4 +Version: 2.2.5 Release: 0 %define mod_name mixlib-config %define mod_full_name %{mod_name}-%{version} BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildRequires: %{ruby >= 2.2} BuildRequires: %{rubygem gem2rpm} -BuildRequires: %{ruby} BuildRequires: ruby-macros >= 5 -Url: http://www.chef.io -Source: http://rubygems.org/gems/%{mod_full_name}.gem +Url: https://www.chef.io +Source: https://rubygems.org/gems/%{mod_full_name}.gem Source1: gem2rpm.yml Summary: A class based configuration library License: Apache-2.0 ++++++ mixlib-config-2.2.4.gem -> mixlib-config-2.2.5.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Gemfile new/Gemfile --- old/Gemfile 2016-09-03 00:20:31.000000000 +0200 +++ new/Gemfile 2018-02-09 19:25:02.000000000 +0100 @@ -1,5 +1,3 @@ source "https://rubygems.org" gemspec - -gem "github_changelog_generator", group: :changelog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/README.md new/README.md --- old/README.md 2016-09-03 00:20:31.000000000 +0200 +++ new/README.md 2018-02-09 19:25:02.000000000 +0100 @@ -43,6 +43,32 @@ MyConfig[:first_value] = 'foobar' # sets first_value to 'foobar' ``` +If you prefer to allow your users to pass in configuration via YAML or JSON files, `mixlib-config` supports that too! + +```ruby + MyConfig.from_file('~/.myconfig.yml') + MyConfig.from_file('~/.myconfig.json') +``` + +This way, a user could write a YAML config file that looked like this: + +```yaml +--- +first_value: 'hi' +second_value: 'goodbye' +``` + +or a JSON file that looks like this: + +```json +{ + "first_value": "hi", + "second_value": "goodbye" +} +``` + +Please note: There is an inherent limitation in the logic you can do with YAML and JSON file. At this time, `mixlib-config` does not support ERB or other logic in YAML or JSON config (read "static content only"). + ## Nested Configuration Often you want to be able to group configuration options to provide a common context. Mixlib::Config supports this thus: @@ -61,14 +87,17 @@ The user can write their config file in one of three formats: -#### Method Style +### Method Style + ```ruby logging.base_filename 'superlog' logging.max_log_files 2 ``` -#### Block Style +### Block Style + Using this format the block is executed in the context, so all configurables on that context is directly accessible + ```ruby logging do base_filename 'superlog' @@ -76,8 +105,10 @@ end ``` -#### Block with Argument Style +### Block with Argument Style + Using this format the context is given to the block as an argument + ```ruby logging do |l| l.base_filename = 'superlog' @@ -92,6 +123,100 @@ MyConfig[:logging][:max_log_files] ``` +### Lists of Contexts +For use cases where you need to be able to specify a list of things with identical configuration +you can define a `context_config_list` like so: + +```ruby + require 'mixlib/config' + + module MyConfig + extend Mixlib::Config + + # The first argument is the plural word for your item, the second is the singular + config_context_list :apples, :apple do + default :species + default :color, 'red' + default :crispness, 10 + end + end +``` + +With this definition everytime the `apple` is called within the config file it +will create a new item that can be configured with a block like so: + +```ruby +apple do + species 'Royal Gala' +end +apple do + species 'Granny Smith' + color 'green' +end +``` + +You can then iterate over the defined values in code: + +```ruby +MyConfig.apples.each do |apple| + puts "#{apple.species} are #{apple.color}" +end + +# => Royal Gala are red +# => Granny Smith are green +``` + +_**Note**: When using the config context lists they must use the [block style](#block-style) or [block with argument style](#block-with-argument-style)_ + +### Hashes of Contexts +For use cases where you need to be able to specify a list of things with identical configuration +that are keyed to a specific value, you can define a `context_config_hash` like so: + +```ruby + require 'mixlib/config' + + module MyConfig + extend Mixlib::Config + + # The first argument is the plural word for your item, the second is the singular + config_context_hash :apples, :apple do + default :species + default :color, 'red' + default :crispness, 10 + end + end +``` + +This can then be used in the config file like so: + +```ruby +apple 'Royal Gala' do + species 'Royal Gala' +end +apple 'Granny Smith' do + species 'Granny Smith' + color 'green' +end + +# You can also reopen a context to edit a value +apple 'Royal Gala' do + crispness 3 +end +``` + +You can then iterate over the defined values in code: + +```ruby +MyConfig.apples.each do |key, apple| + puts "#{key} => #{apple.species} are #{apple.color}" +end + +# => Royal Gala => Royal Gala are red +# => Granny Smith => Granny Smith are green +``` + +_**Note**: When using the config context hashes they must use the [block style](#block-style) or [block with argument style](#block-with-argument-style)_ + ## Default Values Mixlib::Config has a powerful default value facility. In addition to being able to specify explicit default values, you can even specify Ruby code blocks that will run if the config value is not set. This can allow you to build options whose values are based on other options. @@ -147,3 +272,26 @@ NOTE: if you have arrays of arrays, or other deep nesting, we suggest you use code blocks to set up your default values (`default(:option) { [ [ 1, 2 ], [ 3, 4 ] ] }`). Deep children will not always be reset to their default values. Enjoy! + +## Contributing + +For information on contributing to this project see <https://github.com/chef/chef/blob/master/CONTRIBUTING.md> + +## License + +- Copyright:: Copyright (c) 2009-2016 Chef Software, Inc. +- License:: Apache License, Version 2.0 + +```text +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Rakefile new/Rakefile --- old/Rakefile 2016-09-03 00:20:31.000000000 +0200 +++ new/Rakefile 2018-02-09 19:25:02.000000000 +0100 @@ -7,7 +7,7 @@ Bundler::GemHelper.install_tasks -task :default => :spec +task default: [:style, :spec] desc "Run specs" RSpec::Core::RakeTask.new(:spec) do |spec| @@ -16,23 +16,19 @@ gem_spec = eval(File.read("mixlib-config.gemspec")) +begin + require "chefstyle" + require "rubocop/rake_task" + RuboCop::RakeTask.new(:style) do |task| + task.options += ["--display-cop-names", "--no-color"] + end +rescue LoadError + puts "chefstyle/rubocop is not available. gem install chefstyle to do style checking." +end + RDoc::Task.new do |rdoc| rdoc.rdoc_dir = "rdoc" rdoc.title = "mixlib-config #{gem_spec.version}" rdoc.rdoc_files.include("README*") rdoc.rdoc_files.include("lib/**/*.rb") end - -begin - require "github_changelog_generator/task" - - GitHubChangelogGenerator::RakeTask.new :changelog do |config| - config.issues = false - config.future_release = Mixlib::Config::VERSION - config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature".split(",") - config.bug_labels = "bug,Bug,Improvement,Upstream Bug".split(",") - config.exclude_labels = "duplicate,question,invalid,wontfix,no_changelog,Exclude From Changelog,Question,Discussion".split(",") - end -rescue LoadError - puts "github_changelog_generator is not available. gem install github_changelog_generator to generate changelogs" -end Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/mixlib/config/version.rb new/lib/mixlib/config/version.rb --- old/lib/mixlib/config/version.rb 2016-09-03 00:20:31.000000000 +0200 +++ new/lib/mixlib/config/version.rb 2018-02-09 19:25:02.000000000 +0100 @@ -19,7 +19,7 @@ module Mixlib module Config - VERSION = "2.2.4" + VERSION = "2.2.5" end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/mixlib/config.rb new/lib/mixlib/config.rb --- old/lib/mixlib/config.rb 2016-09-03 00:20:31.000000000 +0200 +++ new/lib/mixlib/config.rb 2018-02-09 19:25:02.000000000 +0100 @@ -30,10 +30,14 @@ class << base; attr_accessor :configuration; end class << base; attr_accessor :configurables; end class << base; attr_accessor :config_contexts; end + class << base; attr_accessor :config_context_lists; end + class << base; attr_accessor :config_context_hashes; end class << base; attr_accessor :config_parent; end base.configuration = Hash.new base.configurables = Hash.new base.config_contexts = Hash.new + base.config_context_lists = Hash.new + base.config_context_hashes = Hash.new base.initialize_mixlib_config end @@ -49,7 +53,51 @@ # === Parameters # filename<String>:: A filename to read from def from_file(filename) - self.instance_eval(IO.read(filename), filename, 1) + if %w{ .yml .yaml }.include?(File.extname(filename)) + from_yaml(filename) + elsif File.extname(filename) == ".json" + from_json(filename) + else + instance_eval(IO.read(filename), filename, 1) + end + end + + # Parses valid YAML structure into Ruby so it can be ingested into the Class + # + # === Parameters + # filename<String>:: A filename to read from + def from_yaml(filename) + require "yaml" + from_hash(YAML.load(IO.read(filename)), filename) + end + + # Parses valid JSON structure into Ruby + # + # === Parameters + # filename<String>:: A filename to read from + def from_json(filename) + require "json" + from_hash(JSON.parse(IO.read(filename)), filename) + end + + # Transforms a Hash into method-style configuration syntax to be processed + # + # === Parameters + # hash<Hash>:: A Hash containing configuration + def from_hash(hash, filename = "in_memory") + ruby_translation = [] + + to_dotted_hash(hash).each do |k, v| + if v.is_a? Array + ruby_translation << "#{k} #{v}" + elsif v.is_a? String + ruby_translation << "#{k} \"#{v}\"" + else + ruby_translation << "#{k} #{v}" + end + end + + instance_eval(ruby_translation.join("\n"), filename, 1) end # Pass Mixlib::Config.configure() a block, and it will yield itself @@ -57,7 +105,7 @@ # === Parameters # block<Block>:: A block that is called with self.configuration as the argument. def configure(&block) - yield(self.configuration) + yield(configuration) end # Get the value of a config option @@ -98,7 +146,7 @@ # <True>:: If the config option exists # <False>:: If the config option does not exist def has_key?(key) - self.configuration.has_key?(key.to_sym) + configuration.has_key?(key.to_sym) end # Resets a config option to its default. @@ -106,13 +154,13 @@ # === Parameters # symbol<Symbol>:: Name of the config option def delete(symbol) - self.configuration.delete(symbol) + configuration.delete(symbol) end # Resets all config options to their defaults. def reset self.configuration = Hash.new - self.config_contexts.values.each { |config_context| config_context.reset } + config_contexts.values.each { |config_context| config_context.reset } end # Makes a copy of any non-default values. @@ -159,19 +207,31 @@ # } # def save(include_defaults = false) - result = self.configuration.dup + result = configuration.dup if include_defaults - (self.configurables.keys - result.keys).each do |missing_default| + (configurables.keys - result.keys).each do |missing_default| # Ask any configurables to save themselves into the result array - if self.configurables[missing_default].has_default - result[missing_default] = self.configurables[missing_default].default + if configurables[missing_default].has_default + result[missing_default] = configurables[missing_default].default end end end - self.config_contexts.each_pair do |key, context| + config_contexts.each_pair do |key, context| context_result = context.save(include_defaults) result[key] = context_result if context_result.size != 0 || include_defaults end + config_context_lists.each_pair do |key, meta| + meta[:values].each do |context| + context_result = context.save(include_defaults) + result[key] = (result[key] || []) << context_result if context_result.size != 0 || include_defaults + end + end + config_context_hashes.each_pair do |key, meta| + meta[:values].each_pair do |context_key, context| + context_result = context.save(include_defaults) + (result[key] ||= {})[context_key] = context_result if context_result.size != 0 || include_defaults + end + end result end alias :to_hash :save @@ -180,7 +240,7 @@ # # === Parameters # hash<Hash>: a hash in the same format as output by save. - # + # # === Returns # self def restore(hash) @@ -192,6 +252,26 @@ config_context.reset end end + config_context_lists.each do |key, meta| + meta[:values] = [] + if hash.has_key?(key) + hash[key].each do |val| + context = define_context(meta[:definition_blocks]) + context.restore(val) + meta[:values] << context + end + end + end + config_context_hashes.each do |key, meta| + meta[:values] = {} + if hash.has_key?(key) + hash[key].each do |vkey, val| + context = define_context(meta[:definition_blocks]) + context.restore(val) + meta[:values][vkey] = context + end + end + end end # Merge an incoming hash with our config options @@ -203,11 +283,11 @@ # self def merge!(hash) hash.each do |key, value| - if self.config_contexts.has_key?(key) + if config_contexts.has_key?(key) # Grab the config context and let internal_get cache it if so desired - self.config_contexts[key].restore(value) + config_contexts[key].restore(value) else - self.configuration[key] = value + configuration[key] = value end end self @@ -221,7 +301,7 @@ # === Returns # result of Hash#keys def keys - self.configuration.keys + configuration.keys end # Creates a shallow copy of the internal hash @@ -332,6 +412,70 @@ context end + # Allows you to create a new list of config contexts where you can define new + # options with default values. + # + # This method allows you to open up the configurable more than once. + # + # For example: + # + # config_context_list :listeners, :listener do + # configurable(:url).defaults_to("http://localhost") + # end + # + # === Parameters + # symbol<Symbol>: the plural name for contexts in the list + # symbol<Symbol>: the singular name for contexts in the list + # block<Block>: a block that will be run in the context of this new config + # class. + def config_context_list(plural_symbol, singular_symbol, &block) + if configurables.has_key?(symbol) + raise ReopenedConfigurableWithConfigContextError, "Cannot redefine config value #{plural_symbol} with a config context" + end + + unless config_context_lists.has_key?(plural_symbol) + config_context_lists[plural_symbol] = { + definition_blocks: [], + values: [], + } + define_list_attr_accessor_methods(plural_symbol, singular_symbol) + end + + config_context_lists[plural_symbol][:definition_blocks] << block if block_given? + end + + # Allows you to create a new hash of config contexts where you can define new + # options with default values. + # + # This method allows you to open up the configurable more than once. + # + # For example: + # + # config_context_hash :listeners, :listener do + # configurable(:url).defaults_to("http://localhost") + # end + # + # === Parameters + # symbol<Symbol>: the plural name for contexts in the list + # symbol<Symbol>: the singular name for contexts in the list + # block<Block>: a block that will be run in the context of this new config + # class. + def config_context_hash(plural_symbol, singular_symbol, &block) + if configurables.has_key?(symbol) + raise ReopenedConfigurableWithConfigContextError, "Cannot redefine config value #{plural_symbol} with a config context" + end + + unless config_context_hashes.has_key?(plural_symbol) + config_context_hashes[plural_symbol] = { + definition_blocks: [], + values: {}, + } + define_hash_attr_accessor_methods(plural_symbol, singular_symbol) + end + + config_context_hashes[plural_symbol][:definition_blocks] << block if block_given? + end + NOT_PASSED = Object.new # Gets or sets strict mode. When strict mode is on, only values which @@ -407,6 +551,27 @@ private + # Given a (nested) Hash, turn it into a single top-level hash using dots as + # nesting notation. This allows for direction translation into method-style + # setting of Config. + # + # === Parameters + # hash<Hash>:: The hash to "de-nestify" + # recursive_key<String>:: The existing key to prepend going forward + # + # === Returns + # value:: A single-depth Hash using dot notation to indicate nesting + def to_dotted_hash(hash, recursive_key = "") + hash.each_with_object({}) do |(k , v), ret| + key = recursive_key + k.to_s + if v.is_a? Hash + ret.merge!(to_dotted_hash(v, key + ".")) + else + ret[key] = v + end + end + end + # Internal dispatch setter for config values. # # === Parameters @@ -415,7 +580,7 @@ # def internal_set(symbol, value) if configurables.has_key?(symbol) - configurables[symbol].set(self.configuration, value) + configurables[symbol].set(configuration, value) elsif config_contexts.has_key?(symbol) config_contexts[symbol].restore(value.to_hash) else @@ -430,9 +595,13 @@ def internal_get(symbol) if configurables.has_key?(symbol) - configurables[symbol].get(self.configuration) + configurables[symbol].get(configuration) elsif config_contexts.has_key?(symbol) config_contexts[symbol] + elsif config_context_lists.has_key?(symbol) + config_context_lists[symbol] + elsif config_context_hashes.has_key?(symbol) + config_context_hashes[symbol] else if config_strict_mode == :warn Chef::Log.warn("Reading unsupported config value #{symbol}.") @@ -476,5 +645,62 @@ end end end + + def define_list_attr_accessor_methods(plural_symbol, singular_symbol) + # When Ruby 1.8.7 is no longer supported, this stuff can be done with define_singleton_method! + meta = class << self; self; end + # Getter for list + meta.send :define_method, plural_symbol do + internal_get(plural_symbol)[:values] + end + # Adds a single new context to the list + meta.send :define_method, singular_symbol do |&block| + context_list_details = internal_get(plural_symbol) + new_context = define_context(context_list_details[:definition_blocks]) + context_list_details[:values] << new_context + # If the block expects no arguments, then instance_eval + if block.arity == 0 + new_context.instance_eval(&block) + else # yield to the block + block.yield(new_context) + end + end + end + + def define_hash_attr_accessor_methods(plural_symbol, singular_symbol) + # When Ruby 1.8.7 is no longer supported, this stuff can be done with define_singleton_method! + meta = class << self; self; end + # Getter for list + meta.send :define_method, plural_symbol do + internal_get(plural_symbol)[:values] + end + # Adds a single new context to the list + meta.send :define_method, singular_symbol do |key, &block| + context_hash_details = internal_get(plural_symbol) + context = if context_hash_details[:values].has_key? key + context_hash_details[:values][key] + else + new_context = define_context(context_hash_details[:definition_blocks]) + context_hash_details[:values][key] = new_context + new_context + end + # If the block expects no arguments, then instance_eval + if block.arity == 0 + context.instance_eval(&block) + else # yield to the block + block.yield(context) + end + end + end + + def define_context(definition_blocks) + context = Class.new + context.extend(::Mixlib::Config) + context.config_parent = self + definition_blocks.each do |block| + context.instance_eval(&block) + end + context + end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2016-09-03 00:20:31.000000000 +0200 +++ new/metadata 2018-02-09 19:25:02.000000000 +0100 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: mixlib-config version: !ruby/object:Gem::Version - version: 2.2.4 + version: 2.2.5 platform: ruby authors: - Chef Software, Inc. autorequire: bindir: bin cert_chain: [] -date: 2016-09-02 00:00:00.000000000 Z +date: 2018-02-09 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake @@ -39,6 +39,20 @@ - !ruby/object:Gem::Version version: '3.0' - !ruby/object:Gem::Dependency + name: chefstyle + requirement: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' + type: :development + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' +- !ruby/object:Gem::Dependency name: rdoc requirement: !ruby/object:Gem::Requirement requirements: @@ -74,7 +88,7 @@ - mixlib-config.gemspec - spec/mixlib/config_spec.rb - spec/spec_helper.rb -homepage: http://www.chef.io +homepage: https://www.chef.io licenses: - Apache-2.0 metadata: {} @@ -86,7 +100,7 @@ requirements: - - ">=" - !ruby/object:Gem::Version - version: '0' + version: '2.2' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" @@ -94,7 +108,7 @@ version: '0' requirements: [] rubyforge_project: -rubygems_version: 2.6.6 +rubygems_version: 2.7.4 signing_key: specification_version: 4 summary: A class based configuration library diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mixlib-config.gemspec new/mixlib-config.gemspec --- old/mixlib-config.gemspec 2016-09-03 00:20:31.000000000 +0200 +++ new/mixlib-config.gemspec 2018-02-09 19:25:02.000000000 +0100 @@ -13,16 +13,18 @@ "LICENSE", "README.md", ] - s.files = [ "LICENSE", "NOTICE", "README.md", "Gemfile", "Rakefile" ] + Dir.glob("*.gemspec") + + s.files = ["LICENSE", "NOTICE", "README.md", "Gemfile", "Rakefile"] + Dir.glob("*.gemspec") + Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } - s.homepage = "http://www.chef.io" + s.homepage = "https://www.chef.io" s.require_paths = ["lib"] s.rubygems_version = "1.8.23" + s.required_ruby_version = ">= 2.2" s.summary = "A class based configuration library" s.description = s.summary s.license = "Apache-2.0" s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3.0" + s.add_development_dependency "chefstyle" s.add_development_dependency "rdoc" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/mixlib/config_spec.rb new/spec/mixlib/config_spec.rb --- old/spec/mixlib/config_spec.rb 2016-09-03 00:20:31.000000000 +0200 +++ new/spec/mixlib/config_spec.rb 2018-02-09 19:25:02.000000000 +0100 @@ -30,28 +30,28 @@ allow(File).to receive(:exists?).and_return(true) allow(File).to receive(:readable?).and_return(true) allow(IO).to receive(:read).with("config.rb").and_return("alpha = 'omega'\nfoo = 'bar'") - expect(lambda { + expect(lambda do ConfigIt.from_file("config.rb") - }).to_not raise_error + end).to_not raise_error end it "doesn't raise an ArgumentError with an explanation if you try and set a non-existent variable" do - expect(lambda { + expect(lambda do ConfigIt[:foobar] = "blah" - }).to_not raise_error + end).to_not raise_error end it "raises an Errno::ENOENT if it can't find the file" do - expect(lambda { + expect(lambda do ConfigIt.from_file("/tmp/timmytimmytimmy") - }).to raise_error(Errno::ENOENT) + end).to raise_error(Errno::ENOENT) end it "allows the error to bubble up when it's anything other than IOError" do allow(IO).to receive(:read).with("config.rb").and_return("@#asdf") - expect(lambda { + expect(lambda do ConfigIt.from_file("config.rb") - }).to raise_error(SyntaxError) + end).to raise_error(SyntaxError) end it "allows you to reference a value by index" do @@ -796,6 +796,20 @@ expect(@klass.blah.x).to eql(10) expect(@klass.save).to eql({ :blah => { :x => 10 } }) end + + # this tests existing (somewhat bizzare) behavior of mixlib-config where testing to + # see if a key exists is equivalent to testing if the key has been set -- we can still + # retrieve the default value if it was set. the code in chef/chef which merges + # knife config values into cli values will be sensitive to this behavior. + it "defaults values do not show up when querying with #has_key?" do + expect(@klass.blah.has_key?(:x)).to be false + expect(@klass.blah.x).to be 5 + end + + it "if we assign the values, they show up when querying with #has_key?" do + @klass.blah.x = 5 + expect(@klass.blah.has_key?(:x)).to be true + end end describe "When a configurable exists with a nested context" do @@ -1003,4 +1017,201 @@ end).to raise_error(Mixlib::Config::ReopenedConfigContextWithConfigurableError) end + describe "config context lists" do + let(:klass) do + klass = Class.new + klass.extend ::Mixlib::Config + klass.instance_eval do + config_context_list(:tests, :test) do + default :y, 20 + end + end + klass + end + it "defines list methods when declaring a config_context_list" do + expect(klass.methods).to include :test + expect(klass.methods).to include :tests + end + + it "creates a new item each time the singular list is called" do + klass.test do + y 40 + end + klass.test do + y 50 + end + expect(klass.tests.length).to be 2 + expect(klass.tests.first.y).to be 40 + expect(klass.tests.last.y).to be 50 + end + + it "can save the config list" do + klass.test do + y 40 + end + klass.test do + y 50 + end + expect(klass.save).to eq({ + tests: [ + { y: 40 }, + { y: 50 }, + ], + }) + end + + it "can restore the config list from a hash" do + hash = { + tests: [ + { y: 40 }, + { y: 50 }, + ], + } + klass.restore(hash) + expect(klass.tests.length).to be 2 + expect(klass.tests.first.y).to be 40 + expect(klass.tests.last.y).to be 50 + end + end + + describe "config context hashes" do + let(:klass) do + klass = Class.new + klass.extend ::Mixlib::Config + klass.instance_eval do + config_context_hash(:tests, :test) do + default :y, 20 + end + end + klass + end + + it "defines list methods when declaring a config_context_hash" do + expect(klass.methods).to include :test + expect(klass.methods).to include :tests + end + + context "when called with a new key each time" do + it "creates a new item each time" do + klass.test :one do + y 40 + end + klass.test :two do + y 50 + end + expect(klass.tests.length).to be 2 + expect(klass.tests[:one].y).to be 40 + expect(klass.tests[:two].y).to be 50 + end + end + context "when called with the same key" do + it "modifies the existing value" do + klass.test :only do + y 40 + end + klass.test :only do + y 50 + end + expect(klass.tests.length).to be 1 + expect(klass.tests[:only].y).to be 50 + end + end + + it "can save the config hash" do + klass.test :one do + y 40 + end + klass.test :two do + y 50 + end + expect(klass.save).to eq({ + tests: { + one: { y: 40 }, + two: { y: 50 }, + }, + }) + end + + it "can restore the config hash from a hash" do + hash = { + tests: { + one: { y: 40 }, + two: { y: 50 }, + }, + } + klass.restore(hash) + expect(klass.tests.length).to be 2 + expect(klass.tests[:one].y).to be 40 + expect(klass.tests[:two].y).to be 50 + end + end + + describe ".from_yaml" do + let(:yaml) do + <<-EOH +--- +foo: + - bar + - baz + - matazz +alpha: beta + EOH + end + + it "turns YAML into method-style setting" do + allow(File).to receive(:exists?).and_return(true) + allow(File).to receive(:readable?).and_return(true) + allow(IO).to receive(:read).with("config.yml").and_return(yaml) + + expect(lambda do + ConfigIt.from_file("config.yml") + end).to_not raise_error + + expect(ConfigIt.foo).to eql(%w{ bar baz matazz }) + expect(ConfigIt.alpha).to eql("beta") + end + end + + describe ".from_json" do + let(:json) do + <<-EOH +{ + "foo": [ + "bar", + "baz", + "matazz" + ], + "alpha": "beta" +} + EOH + end + + it "turns YAML into method-style setting" do + allow(File).to receive(:exists?).and_return(true) + allow(File).to receive(:readable?).and_return(true) + allow(IO).to receive(:read).with("config.json").and_return(json) + + expect(lambda do + ConfigIt.from_file("config.json") + end).to_not raise_error + + expect(ConfigIt.foo).to eql(%w{ bar baz matazz }) + expect(ConfigIt.alpha).to eql("beta") + end + end + + describe ".from_hash" do + let(:hash) do + { + "alpha" => "beta", + "foo" => %w{ bar baz matazz}, + } + end + + it "translates the Hash into method-style" do + ConfigIt.from_hash(hash) + expect(ConfigIt.foo).to eql(%w{ bar baz matazz }) + expect(ConfigIt.alpha).to eql("beta") + end + end end