nugrant 2.0.0.pre1 → 2.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f14bf2a70810293d967c9e9d24b60408880e5b0
4
- data.tar.gz: 1d24d5cfa15fde530b4daab7743adb9b29bf74a4
3
+ metadata.gz: 969a898ed0ac4d45b6adc22f43304d80a61d3eda
4
+ data.tar.gz: a2f9140bab38bd74f9297cda110572d49c0f8425
5
5
  SHA512:
6
- metadata.gz: 4ade947b4d55e761d3ec22065b34b680cac998f9544fa98e30a309ce9c65d4f2a76bde76233633e9eb630725c0fc622ea6c8b163332af8401347af8d0dd409d9
7
- data.tar.gz: 20d4f38d519f1e15f487d0bdecd6d74246a8b748d4933f98285504587360c24cd5936c111fb85dfb578079abb9a28a47e7f8dccf7b22655510aa77a87a91725b
6
+ metadata.gz: ba35c594059cb9c04fef4495e55ca55fddbdafe95041335cd6ddec5e4234d6c65150f6418d407fc3d9dad28cff383a7402c369d0c429f3541eed7c00c7ed7dfe
7
+ data.tar.gz: b5684cd172236eb57add8c9492fc8818b23707f813adbc2c9dc21ce951f5b8c4f949646e208a2acdb510f1ef53ea68ddb4491872f4af96fd530cacf2c38b03e1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # 2.0.0 (In progress)
2
2
 
3
+ * Added possibility to change array merge strategy. This can
4
+ be used in Vagrant by doing `config.user.array_merge_strategy = <strategy>`
5
+ where valid strategies are:
6
+
7
+ * :replace => Replace current values by new ones
8
+ * :extend => Merge current values with new ones
9
+ * :concat => Append new values to current ones
10
+
11
+ * Better handling in Vagrant of cases where the vagrant user
12
+ file cannot be parsed correctly. This is now reported
13
+ as an error in Vagrant an nicely displayed with the path
14
+ of the offending file and the parser error message.
15
+
16
+ * Better handling of how global Nugrant options are passed and
17
+ handled. Everything is now located in the `Nugrant::Config`
18
+ object and used by everything that need some configuration
19
+ parameters.
20
+
3
21
  * It is now possible to customize key error handling by passing
4
22
  an options hash with key `:key_error` and a `Proc` value.
5
23
 
data/README.md CHANGED
@@ -126,6 +126,10 @@ In text, this means that current parameters overrides user
126
126
  parameters, user parameters overrides system parameters and
127
127
  finally system parameters overrides defaults parameters.
128
128
 
129
+ When two keys that are merged together points to Array values,
130
+ the default operation is to replace current Array by
131
+ overriding one. The default merge strategy can be customized.
132
+
129
133
  ### Vagrant
130
134
 
131
135
  All examples shown here are for Vagrant 1.1+. They have
@@ -323,6 +327,36 @@ If the user decides to change it, he just has to set it in his
323
327
  own `.vagrantuser` and it will override the default value defined
324
328
  in the Vagrantfile.
325
329
 
330
+ #### Array merge strategies
331
+
332
+ As stated previously, when two arrays are merged together,
333
+ the default strategy is to replace current array with new one.
334
+
335
+ However, in some certain circumstances, you may need another
336
+ behavior. That is why we also provide two other strategies
337
+ that can be used.
338
+
339
+ * `:concat`
340
+ With this strategy, new array is appended to the end
341
+ of current array when merged. The `Array` `+` operator
342
+ is used to concatenante the two arrays.
343
+
344
+ * `:extend`
345
+ With this strategy, current array is extended with values
346
+ from new one. The `Array` `|` (union) operator
347
+ is used to extend the array.
348
+
349
+ You can use the following snippet directly within your Vagrantfile
350
+ to change the array merge strategy:
351
+
352
+ Vagrant.configure("2") do |config|
353
+ config.user.array_merge_strategy = :extend
354
+
355
+ config.ssh.port config.user.vm.ssh_port
356
+ end
357
+
358
+ If you specify an unsupported strategy, nothing will happen.
359
+
326
360
  #### Commands
327
361
 
328
362
  In this section, we describe the various vagrant commands defined
data/lib/nugrant/bag.rb CHANGED
@@ -7,49 +7,41 @@ module Nugrant
7
7
  # differences with a normal Hash are indifferent access
8
8
  # (symbol or string) and method access (via method call).
9
9
  #
10
+ # Hash objects in the map are converted to Bag. This ensure
11
+ # proper nesting of functionality.
12
+ #
10
13
  # =| Arguments
11
14
  # * `elements`
12
15
  # The initial elements the bag should be built with it.'
13
16
  # Must be an object responding to `each` and accepting
14
- # a block with two arguments: `key, value`.]. Defaults to
17
+ # a block with two arguments: `key, value`. Defaults to
15
18
  # the empty hash.
16
19
  #
17
- # * `options`
18
- # An options hash where some customization option can be passed.
19
- # Defaults to an empty hash, see options for specific option default
20
- # values.
21
- #
22
- # =| Options
23
- # * `:key_error`
24
- # A callable object receiving a single parameter `key` that is
25
- # called when a key cannot be found in the Bag. The received key
26
- # is already converted to a symbol. If the callable does not
27
- # raise an exception, the result of it's execution is returned.
28
- # The default value is a callable that throws a KeyError exception.
20
+ # * `config`
21
+ # A Nugrant::Config object or hash passed to Nugrant::Config
22
+ # constructor. Used for `key_error` handler.
29
23
  #
30
- def initialize(elements = {}, options = {})
24
+ def initialize(elements = {}, config = {})
31
25
  super()
32
26
 
33
- @__key_error = options[:key_error] || Proc.new do |key|
34
- raise KeyError, "Undefined parameter '#{key}'" if not key?(key)
35
- end
27
+ @__config = Config::convert(config)
36
28
 
37
29
  (elements || {}).each do |key, value|
38
- self[key] = value.kind_of?(Hash) ? Bag.new(value, options) : value
30
+ self[key] = value.kind_of?(Hash) ? Bag.new(value, config) : value
39
31
  end
40
32
  end
41
33
 
42
34
  def method_missing(method, *args, &block)
43
- return self[method]
35
+ self[method]
44
36
  end
45
37
 
46
38
  ##
47
- ### Hash Overriden Methods (for string & symbol indifferent access)
39
+ ### Hash Overridden Methods (for string & symbol indifferent access)
48
40
  ##
49
41
 
50
42
  def [](input)
51
43
  key = __convert_key(input)
52
- return @__key_error.call(key) if not key?(key)
44
+ return @__config.key_error.call(key) if not key?(key)
53
45
 
54
46
  super(key)
55
47
  end
@@ -63,24 +55,10 @@ module Nugrant
63
55
  end
64
56
 
65
57
  ##
66
- # This method first start by converting the `input` parameter
67
- # into a bag. It will then *deep* merge current values with
68
- # the new ones coming from the `input`.
58
+ # This method deeply merge two instance together
69
59
  #
70
- # The array merge strategy is by default to replace current
71
- # values with new ones. You can use option `:array_strategy`
72
- # to change this default behavior.
73
60
  #
74
- # +Options+
75
- # * :array_strategy
76
- # * :replace (Default) => Replace current values by new ones
77
- # * :extend => Merge current values with new ones
78
- # * :concat => Append new values to current ones
79
- #
80
- def merge!(input, options = {})
81
- options = {:array_strategy => :replace}.merge(options)
82
-
83
- array_strategy = options[:array_strategy]
61
+ def merge!(input)
84
62
  input.each do |key, value|
85
63
  current = __get(key)
86
64
  case
@@ -88,10 +66,10 @@ module Nugrant
88
66
  self[key] = value
89
67
 
90
68
  when current.kind_of?(Hash) && value.kind_of?(Hash)
91
- current.merge!(value, options)
69
+ current.merge!(value)
92
70
 
93
71
  when current.kind_of?(Array) && value.kind_of?(Array)
94
- self[key] = send("__#{array_strategy}_array_merge", current, value)
72
+ self[key] = send("__#{@__config.array_merge_strategy}_array_merge", current, value)
95
73
 
96
74
  when value != nil
97
75
  self[key] = value
@@ -2,10 +2,27 @@ require 'rbconfig'
2
2
 
3
3
  module Nugrant
4
4
  class Config
5
+ DEFAULT_ARRAY_MERGE_STRATEGY = :replace
5
6
  DEFAULT_PARAMS_FILENAME = ".nuparams"
6
7
  DEFAULT_PARAMS_FORMAT = :yaml
7
8
 
8
- attr_reader :params_filename, :params_format, :current_path, :user_path, :system_path
9
+ SUPPORTED_ARRAY_MERGE_STRATEGIES = [:concat, :extend, :replace]
10
+ SUPPORTED_PARAMS_FORMATS = [:json, :yaml]
11
+
12
+ attr_reader :params_filename, :params_format,
13
+ :current_path, :user_path, :system_path,
14
+ :array_merge_strategy,
15
+ :key_error, :parse_error
16
+
17
+ attr_writer :array_merge_strategy
18
+
19
+ ##
20
+ # Convenience method to easily accept either a hash that will
21
+ # be converted to a Nugrant::Config object or directly a config
22
+ # object.
23
+ def self.convert(config = {})
24
+ return config.kind_of?(Nugrant::Config) ? config : Nugrant::Config.new(config)
25
+ end
9
26
 
10
27
  ##
11
28
  # Return the fully expanded path of the user parameters
@@ -27,6 +44,14 @@ module Nugrant
27
44
  "/etc"
28
45
  end
29
46
 
47
+ def self.supported_array_merge_strategy(strategy)
48
+ SUPPORTED_ARRAY_MERGE_STRATEGIES.include?(strategy)
49
+ end
50
+
51
+ def self.supported_params_format(format)
52
+ SUPPORTED_PARAMS_FORMATS.include?(format)
53
+ end
54
+
30
55
  ##
31
56
  # Return true if we are currently on a Windows platform.
32
57
  #
@@ -64,18 +89,50 @@ module Nugrant
64
89
  # parameters should resides. The parameters loaded from this
65
90
  # location have the third highest precedence.
66
91
  # Defaults => Default system path depending on OS + @params_filename
92
+ # * +:array_merge_strategy+ - This option controls how array values are merged together when merging
93
+ # two Bag instances. Possible values are:
94
+ # * :replace => Replace current values by new ones
95
+ # * :extend => Merge current values with new ones
96
+ # * :concat => Append new values to current ones
97
+ # Defaults => The strategy :replace.
98
+ # * +:key_error+ - A callback method receiving one argument, the key as a symbol, and that
99
+ # deal with the error. If the callable does not
100
+ # raise an exception, the result of it's execution is returned.
101
+ # Defaults => A callable that throws a KeyError exception.
102
+ # * +:parse_error+ - A callback method receiving two arguments, the offending filename and
103
+ # the error object, that deal with the error. If the callable does not
104
+ # raise an exception, the result of it's execution is returned.
105
+ # Defaults => A callable that returns the empty hash.
67
106
  #
68
107
  def initialize(options = {})
69
108
  @params_filename = options[:params_filename] || DEFAULT_PARAMS_FILENAME
70
109
  @params_format = options[:params_format] || DEFAULT_PARAMS_FORMAT
71
110
 
72
- raise ArgumentError,
73
- "Invalid value for :params_format. \
74
- The format [#{@params_format}] is currently not supported." if not [:json, :yaml].include?(@params_format)
75
-
76
111
  @current_path = File.expand_path(options[:current_path] || "./#{@params_filename}")
77
112
  @user_path = File.expand_path(options[:user_path] || "#{Config.default_user_path()}/#{@params_filename}")
78
113
  @system_path = File.expand_path(options[:system_path] || "#{Config.default_system_path()}/#{@params_filename}")
114
+
115
+ @array_merge_strategy = options[:array_merge_strategy] || :replace
116
+
117
+ @key_error = options[:key_error] || Proc.new do |key|
118
+ raise KeyError, "Undefined parameter '#{key}'"
119
+ end
120
+
121
+ @parse_error = options[:parse_error] || Proc.new do |filename, error|
122
+ {}
123
+ end
124
+
125
+ validate()
126
+ end
127
+
128
+ def validate()
129
+ raise ArgumentError,
130
+ "Invalid value for :params_format. \
131
+ The format [#{@params_format}] is currently not supported." if not Config.supported_params_format(@params_format)
132
+
133
+ raise ArgumentError,
134
+ "Invalid value for :array_merge_strategy. \
135
+ The array merge strategy [#{@array_merge_strategy}] is currently not supported." if not Config.supported_array_merge_strategy(@array_merge_strategy)
79
136
  end
80
137
  end
81
138
  end
@@ -6,26 +6,24 @@ require 'nugrant/bag'
6
6
  module Nugrant
7
7
  module Helper
8
8
  module Bag
9
- def self.read(filepath, filetype, options = {})
10
- data = parse_data(filepath, filetype, options)
11
-
12
- return Nugrant::Bag.new(data, options)
9
+ def self.read(filepath, filetype, config)
10
+ Nugrant::Bag.new(parse_data(filepath, filetype, config), config)
13
11
  end
14
12
 
15
13
  def self.restricted_keys()
16
14
  Nugrant::Bag.instance_methods()
17
15
  end
18
16
 
19
- def self.parse_data(filepath, filetype, options = {})
17
+ private
18
+
19
+ def self.parse_data(filepath, filetype, config)
20
20
  return if not File.exists?(filepath)
21
21
 
22
22
  File.open(filepath, "rb") do |file|
23
23
  return send("parse_#{filetype}", file)
24
24
  end
25
25
  rescue => error
26
- if options[:error_handler]
27
- options[:error_handler].handle("Could not parse the user #{filetype} parameters file '#{filepath}': #{error}")
28
- end
26
+ config.parse_error.call(filepath, error)
29
27
  end
30
28
 
31
29
  def self.parse_json(io)
@@ -29,6 +29,19 @@ module Nugrant
29
29
  @__defaults
30
30
  end
31
31
 
32
+ def array_merge_strategy
33
+ @__config.array_merge_strategy
34
+ end
35
+
36
+ def array_merge_strategy=(strategy)
37
+ return if not Nugrant::Config.supported_array_merge_strategy(strategy)
38
+
39
+ @__config.array_merge_strategy = strategy
40
+
41
+ # When array_merge_strategy change, we need to recompute parameters hierarchy
42
+ compute_all!()
43
+ end
44
+
32
45
  ##
33
46
  # Set the new default values for the
34
47
  # various parameters contain by this instance.
@@ -39,46 +52,44 @@ module Nugrant
39
52
  # * +elements+ - The new default elements
40
53
  #
41
54
  def defaults=(elements)
42
- @__defaults = Bag.new(elements, @__options)
55
+ @__defaults = Bag.new(elements, @__config)
43
56
 
44
57
  # When defaults change, we need to recompute parameters hierarchy
45
- compute_all!(@__options)
58
+ compute_all!()
46
59
  end
47
60
 
48
61
  ##
49
- # Compute all parameters bags (current, user, system, default and all).
62
+ # Setup instance variables of the mixin. It will compute all parameters bags
63
+ # (current, user, system, default and all) and stored them to these respective
64
+ # instance variables:
50
65
  #
51
- # =| Arguments
52
- # * `config`
53
- # The configuration object used to determine where to find the various
54
- # bag source data. This can be either directly a `Nugrant::Config`
55
- # object or a hash that will be pass to `Nugrant::Config` constructor.
56
- #
57
- # * `options`
58
- # An options hash where some customization option can be passed.
59
- # Defaults to an empty hash, see options for specific option default
60
- # values.
66
+ # * @__current
67
+ # * @__user
68
+ # * @__system
69
+ # * @__defaults
61
70
  #
62
- # =| Options
63
- # * `:defaults`
71
+ # =| Arguments
72
+ # * `defaults`
64
73
  # A hash that is used as the initial data for the defaults bag. Defaults
65
74
  # to an empty hash.
66
75
  #
67
- # * `:key_error`
68
- # This option is passed to Bag.new constructor in it's options hash. See
69
- # Bag.new for details on this options.
76
+ # * `config`
77
+ # A Nugrant::Config object or hash passed to Nugrant::Config
78
+ # constructor. Used to determine where to find the various
79
+ # bag data sources.
70
80
  #
71
- def compute_bags!(config, options = {})
72
- config = config.kind_of?(Nugrant::Config) ? config : Nugrant::Config.new(config)
73
-
74
- @__options = options
81
+ # Passed to nested structures that require nugrant configuration
82
+ # parameters like the Bag object and Helper::Bag module.
83
+ #
84
+ def setup!(defaults = {}, config = {})
85
+ @__config = Nugrant::Config::convert(config);
75
86
 
76
- @__current = Helper::Bag.read(config.current_path, config.params_format, options)
77
- @__user = Helper::Bag.read(config.user_path, config.params_format, options)
78
- @__system = Helper::Bag.read(config.system_path, config.params_format, options)
79
- @__defaults = Bag.new(options[:defaults] || {}, options)
87
+ @__current = Helper::Bag.read(@__config.current_path, @__config.params_format, @__config)
88
+ @__user = Helper::Bag.read(@__config.user_path, @__config.params_format, @__config)
89
+ @__system = Helper::Bag.read(@__config.system_path, @__config.params_format, @__config)
90
+ @__defaults = Bag.new(defaults, @__config)
80
91
 
81
- compute_all!(options)
92
+ compute_all!()
82
93
  end
83
94
 
84
95
  ##
@@ -86,8 +97,8 @@ module Nugrant
86
97
  # bag in the right order and return the result as a Nugrant::Bag
87
98
  # object.
88
99
  #
89
- def compute_all!(options = {})
90
- @__all = Bag.new({}, options)
100
+ def compute_all!()
101
+ @__all = Bag.new({}, @__config)
91
102
  @__all.merge!(@__defaults)
92
103
  @__all.merge!(@__system)
93
104
  @__all.merge!(@__user)
@@ -15,17 +15,11 @@ module Nugrant
15
15
  #
16
16
  # =| Arguments
17
17
  # * `config`
18
- # A hash that will be passed to Nugrant::Config.new() or
19
- # a Nugrant::Config instance directly.
20
- # See Nugrant::Config constructor for options that you can use.
18
+ # Passed to Mixin::Parameters setup! method. See method
19
+ # for further information.
21
20
  #
22
- # * `options`
23
- # An options hash that is passed to Mixin::Parameters.compute_bags! method.
24
- # See Mixin::Parameters.compute_bags! for details on the various options
25
- # available.
26
- #
27
- def initialize(config, options = {})
28
- compute_bags!(config, options)
21
+ def initialize(config)
22
+ setup!({}, config)
29
23
  end
30
24
 
31
25
  include Mixin::Parameters
@@ -7,6 +7,12 @@ module Nugrant
7
7
  module Errors
8
8
  class NugrantVagrantError < ::Vagrant::Errors::VagrantError
9
9
  error_namespace("nugrant.vagrant.errors")
10
+
11
+ def compute_context()
12
+ Helper::Stack.fetch_error_region(caller(), {
13
+ :matcher => /(.+Vagrantfile):([0-9]+)/
14
+ })
15
+ end
10
16
  end
11
17
 
12
18
  class ParameterNotFoundError < NugrantVagrantError
@@ -15,11 +21,13 @@ module Nugrant
15
21
  def initialize(options = nil, *args)
16
22
  super({:context => compute_context()}.merge(options || {}), *args)
17
23
  end
24
+ end
18
25
 
19
- def compute_context()
20
- Helper::Stack.fetch_error_region(caller(), {
21
- :matcher => /(.+Vagrantfile):([0-9]+)/
22
- })
26
+ class VagrantUserParseError < NugrantVagrantError
27
+ error_key(:parse_error)
28
+
29
+ def initialize(options = nil, *args)
30
+ super(options, *args)
23
31
  end
24
32
  end
25
33
  end
@@ -10,11 +10,15 @@ module Nugrant
10
10
  attr_reader :__current, :__user, :__system, :__defaults, :__all
11
11
 
12
12
  def initialize()
13
- compute_bags!({:params_filename => ".vagrantuser"}, options = {
13
+ setup!({},
14
+ :params_filename => ".vagrantuser",
14
15
  :key_error => Proc.new do |key|
15
16
  raise Errors::ParameterNotFoundError, :key => key.to_s
17
+ end,
18
+ :parse_error => Proc.new do |filename, error|
19
+ raise Errors::VagrantUserParseError, :filename => filename.to_s, :error => error
16
20
  end
17
- })
21
+ )
18
22
  end
19
23
 
20
24
  include Mixin::Parameters
@@ -1,3 +1,3 @@
1
1
  module Nugrant
2
- VERSION = "2.0.0.pre1"
2
+ VERSION = "2.0.0.pre2"
3
3
  end
data/locales/en.yml CHANGED
@@ -11,3 +11,14 @@ en:
11
11
 
12
12
  If you think it should be found, don't hesitate to fill an
13
13
  issue @ https://github.com/maoueh/nugrant/issues.
14
+
15
+ parse_error: |-
16
+ Nugrant: Vagrant user file could not be parsed correctly,
17
+ the file is probably in an invalid state.
18
+
19
+ File: %{filename}
20
+ Error: %{error}
21
+
22
+ If you think this is an error, don't hesitate to fill an
23
+ issue @ https://github.com/maoueh/nugrant/issues.
24
+
@@ -131,33 +131,33 @@ module Nugrant
131
131
  end
132
132
 
133
133
  def test_merge_array_extend()
134
- bag1 = create_bag({"first" => [1, 2]})
134
+ bag1 = create_bag({"first" => [1, 2]}, :array_merge_strategy => :extend)
135
135
  bag2 = create_bag({:first => [2, 3]})
136
136
 
137
- bag1.merge!(bag2, :array_strategy => :extend);
137
+ bag1.merge!(bag2);
138
138
 
139
139
  assert_equal({:first => [1, 2, 3]}, bag1.to_hash())
140
140
 
141
- bag1 = create_bag({"first" => [1, 2]})
141
+ bag1 = create_bag({"first" => [1, 2]}, :array_merge_strategy => :extend)
142
142
  bag2 = create_bag({:first => "string"})
143
143
 
144
- bag1.merge!(bag2, :array_strategy => :extend);
144
+ bag1.merge!(bag2);
145
145
 
146
146
  assert_equal({:first => "string"}, bag1.to_hash())
147
147
  end
148
148
 
149
149
  def test_merge_array_concat()
150
- bag1 = create_bag({"first" => [1, 2]})
150
+ bag1 = create_bag({"first" => [1, 2]}, :array_merge_strategy => :concat)
151
151
  bag2 = create_bag({:first => [2, 3]})
152
152
 
153
- bag1.merge!(bag2, :array_strategy => :concat);
153
+ bag1.merge!(bag2);
154
154
 
155
155
  assert_equal({:first => [1, 2, 2, 3]}, bag1.to_hash())
156
156
 
157
- bag1 = create_bag({"first" => [1, 2]})
157
+ bag1 = create_bag({"first" => [1, 2]}, :array_merge_strategy => :concat)
158
158
  bag2 = create_bag({:first => "string"})
159
159
 
160
- bag1.merge!(bag2, :array_strategy => :concat);
160
+ bag1.merge!(bag2);
161
161
 
162
162
  assert_equal({:first => "string"}, bag1.to_hash())
163
163
  end
@@ -1,7 +1,7 @@
1
1
  This readme give information on how to read resources file
2
2
  that test merge possibilities.
3
3
 
4
- Naming convetions
4
+ Naming conventions
5
5
  -----------------
6
6
 
7
7
  The filename uses a specific convention:
@@ -9,7 +9,7 @@ The filename uses a specific convention:
9
9
  params_*kind*_*level*.[yml|json]
10
10
 
11
11
  The kind is one of: [`current`|`user`|`system`] and defines which
12
- responsability they will hold. The order is `current` overrides
12
+ responsibility they will hold. The order is `current` overrides
13
13
  `user` overrides `system`.
14
14
 
15
15
  Inside file, keys have special meaning. They define how
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nugrant
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre1
4
+ version: 2.0.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu Vachon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-18 00:00:00.000000000 Z
11
+ date: 2014-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: insensitive_hash