collapsium 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/collapsium/environment_override.rb +64 -41
- data/lib/collapsium/pathed_access.rb +7 -7
- data/lib/collapsium/support/methods.rb +80 -16
- data/lib/collapsium/version.rb +1 -1
- data/lib/collapsium/viral_capabilities.rb +9 -9
- data/spec/environment_override_spec.rb +49 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b85cef5b1becbc08cec24005e6e7c31b4e8156a
|
4
|
+
data.tar.gz: 1a25c135dcb2d7784dedcb04cdee7e6633e841eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d54cd48ceef5cd706e662b40451f349595e88ecc7987ee54544110adff1a8493a11345e83d2ef659f825741029630d8d911b4be8325111ea28d7558fb2a7ad3
|
7
|
+
data.tar.gz: 24dc1841b56c2b5304d13a8977e1123aabfe196ec87f912bc4251c575ffdb9d7cfd17703a9b10d933b03a335111350ff4aa6d70099c93eeebc893d7cbb4631f2
|
data/Gemfile.lock
CHANGED
@@ -10,6 +10,7 @@
|
|
10
10
|
require 'collapsium/support/hash_methods'
|
11
11
|
require 'collapsium/support/methods'
|
12
12
|
|
13
|
+
require 'collapsium/recursive_dup'
|
13
14
|
require 'collapsium/viral_capabilities'
|
14
15
|
|
15
16
|
module Collapsium
|
@@ -27,8 +28,12 @@ module Collapsium
|
|
27
28
|
# For example, the key "some!@email.org" will become "SOME_EMAIL_ORG".
|
28
29
|
#
|
29
30
|
# If PathedAccess is also used, the :path_prefix of nested hashes will
|
30
|
-
# be consulted after converting it in the same manner.
|
31
|
+
# be consulted after converting it in the same manner. NOTE:
|
32
|
+
# include EnvironmentOverride *after* PathedAccess for both to work well
|
33
|
+
# together.
|
31
34
|
module EnvironmentOverride
|
35
|
+
include ::Collapsium::RecursiveDup
|
36
|
+
|
32
37
|
##
|
33
38
|
# If the module is included, extended or prepended in a class, it'll
|
34
39
|
# wrap accessor methods.
|
@@ -38,33 +43,54 @@ module Collapsium
|
|
38
43
|
|
39
44
|
##
|
40
45
|
# Returns a proc read access.
|
41
|
-
ENV_ACCESS_READER = proc do |
|
46
|
+
ENV_ACCESS_READER = proc do |wrapped_method, *args, &block|
|
42
47
|
# If there are no arguments, there's nothing to do with paths. Just
|
43
48
|
# delegate to the hash.
|
44
49
|
if args.empty?
|
45
|
-
next
|
50
|
+
next wrapped_method.call(*args, &block)
|
46
51
|
end
|
47
52
|
|
48
|
-
# The method's receiver is encapsulated in the
|
53
|
+
# The method's receiver is encapsulated in the wrapped_method; we'll
|
49
54
|
# use it a few times so let's reduce typing. This is essentially the
|
50
55
|
# equivalent of `self`.
|
51
|
-
receiver =
|
56
|
+
receiver = wrapped_method.receiver
|
52
57
|
|
53
58
|
# All KEYED_READ_METHODS have a key as the first argument.
|
54
59
|
key = args[0]
|
55
60
|
|
56
|
-
# Grab matching environment variable names.
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
# Grab matching environment variable names. If PathedAccess is
|
62
|
+
# supported, we'll try environment variables of the path, starting
|
63
|
+
# with the full qualified path and ending with just the last key
|
64
|
+
# component.
|
65
|
+
env_keys = []
|
66
|
+
if receiver.respond_to?(:path_prefix)
|
67
|
+
|
68
|
+
# If we have a prefix, use it.
|
69
|
+
components = []
|
70
|
+
if not receiver.path_prefix.nil?
|
71
|
+
components += receiver.path_components(receiver.path_prefix)
|
72
|
+
end
|
73
|
+
|
74
|
+
# The key has its own components. If the key components and prefix
|
75
|
+
# components overlap (it happens), ignore the duplication.
|
76
|
+
key_comps = receiver.path_components(key.to_s)
|
77
|
+
if key_comps.first == components.last
|
78
|
+
components.pop
|
79
|
+
end
|
80
|
+
components += key_comps
|
81
|
+
|
82
|
+
# Start with most qualified, shifting off the first component
|
83
|
+
# until we reach just the last component.
|
84
|
+
loop do
|
85
|
+
path = receiver.normalize_path(components)
|
86
|
+
env_keys << path
|
87
|
+
components.shift
|
88
|
+
if components.empty?
|
89
|
+
break
|
90
|
+
end
|
67
91
|
end
|
92
|
+
else
|
93
|
+
env_keys = [key.to_s]
|
68
94
|
end
|
69
95
|
env_keys.map! { |k| receiver.key_to_env(k) }
|
70
96
|
env_keys.select! { |k| not k.empty? }
|
@@ -84,47 +110,44 @@ module Collapsium
|
|
84
110
|
# the parsed result. Otherwise use it as a string.
|
85
111
|
require 'json'
|
86
112
|
# rubocop:disable Lint/HandleExceptions
|
113
|
+
parsed = env_value
|
87
114
|
begin
|
88
|
-
|
115
|
+
parsed = JSON.parse(env_value)
|
89
116
|
rescue JSON::ParserError
|
90
117
|
# Do nothing. We just use the env_value verbatim.
|
91
118
|
end
|
92
119
|
# rubocop:enable Lint/HandleExceptions
|
93
120
|
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
old_value = v
|
101
|
-
break
|
102
|
-
end
|
103
|
-
end
|
121
|
+
# Excellent, we've got an environment variable. Only use it if it
|
122
|
+
# changes something, though. We'll temporarily unset the environment
|
123
|
+
# variable while fetching the old value.
|
124
|
+
ENV.delete(env_key)
|
125
|
+
old_value = receiver[key]
|
126
|
+
ENV[env_key] = env_value # not the parsed value!
|
104
127
|
|
105
|
-
|
106
|
-
|
107
|
-
# changing it always will lead to an infinite recursion.
|
108
|
-
# The double's super_method will never be called, but rather
|
109
|
-
# always this wrapper.
|
110
|
-
if env_value != old_value
|
111
|
-
value = env_value
|
128
|
+
if parsed != old_value
|
129
|
+
value = parsed
|
112
130
|
end
|
113
131
|
break
|
114
132
|
end
|
133
|
+
|
115
134
|
if not value.nil?
|
116
135
|
# We can't just return the value, because that doesn't respect the
|
117
|
-
# method being called. We can
|
118
|
-
#
|
119
|
-
#
|
120
|
-
|
136
|
+
# method being called. We also can't store the value, because that
|
137
|
+
# would not reset the Hash after the environment variable was
|
138
|
+
# cleared.
|
139
|
+
#
|
140
|
+
# We can deal with this by duplicating the receiver, writing the value
|
141
|
+
# we found into the appropriate key, and then sending the
|
142
|
+
# wrapped_method to the duplicate.
|
143
|
+
double = receiver.recursive_dup
|
121
144
|
double[key] = value
|
122
|
-
meth = double.method(
|
145
|
+
meth = double.method(wrapped_method.name)
|
123
146
|
next meth.call(*args, &block)
|
124
147
|
end
|
125
148
|
|
126
149
|
# Otherwise, fall back on the super method.
|
127
|
-
next
|
150
|
+
next wrapped_method.call(*args, &block)
|
128
151
|
end.freeze # proc
|
129
152
|
|
130
153
|
def included(base)
|
@@ -140,7 +163,7 @@ module Collapsium
|
|
140
163
|
end
|
141
164
|
|
142
165
|
def enhance(base)
|
143
|
-
# Make the capabilities of classes using
|
166
|
+
# Make the capabilities of classes using EnvironmentOverride viral.
|
144
167
|
base.extend(ViralCapabilities)
|
145
168
|
|
146
169
|
# Wrap read accessor functions to deal with paths
|
@@ -66,17 +66,17 @@ module Collapsium
|
|
66
66
|
# access will create intermediary hashes when e.g. setting a value for
|
67
67
|
# `foo.bar.baz`, and the `bar` Hash doesn't exist yet.
|
68
68
|
def create_proc(write_access)
|
69
|
-
return proc do |
|
69
|
+
return proc do |wrapped_method, *args, &block|
|
70
70
|
# If there are no arguments, there's nothing to do with paths. Just
|
71
71
|
# delegate to the hash.
|
72
72
|
if args.empty?
|
73
|
-
next
|
73
|
+
next wrapped_method.call(*args, &block)
|
74
74
|
end
|
75
75
|
|
76
|
-
# The method's receiver is encapsulated in the
|
76
|
+
# The method's receiver is encapsulated in the wrapped_method; we'll
|
77
77
|
# use it a few times so let's reduce typing. This is essentially the
|
78
78
|
# equivalent of `self`.
|
79
|
-
receiver =
|
79
|
+
receiver = wrapped_method.receiver
|
80
80
|
|
81
81
|
# With any of the dispatch methods, we know that the first argument has
|
82
82
|
# to be a key. We'll try to split it by the path separator.
|
@@ -95,12 +95,12 @@ module Collapsium
|
|
95
95
|
if receiver.object_id == leaf.object_id
|
96
96
|
# a) if the leaf and the receiver are identical, then the receiver
|
97
97
|
# itself was requested, and we really just need to delegate to its
|
98
|
-
#
|
99
|
-
meth =
|
98
|
+
# wrapped_method.
|
99
|
+
meth = wrapped_method
|
100
100
|
else
|
101
101
|
# b) if the leaf is different from the receiver, we want to delegate
|
102
102
|
# to the leaf.
|
103
|
-
meth = leaf.method(
|
103
|
+
meth = leaf.method(wrapped_method.name)
|
104
104
|
end
|
105
105
|
|
106
106
|
# If the first argument was a symbol key, we want to use it verbatim.
|
@@ -16,10 +16,9 @@ module Collapsium
|
|
16
16
|
##
|
17
17
|
# Functionality for extending the behaviour of Hash methods
|
18
18
|
module Methods
|
19
|
-
|
20
19
|
##
|
21
20
|
# Given the base module, wraps the given method name in the given block.
|
22
|
-
# The block must accept the
|
21
|
+
# The block must accept the wrapped_method as the first parameter, followed
|
23
22
|
# by any arguments and blocks the super method might accept.
|
24
23
|
#
|
25
24
|
# The canonical usage example is of a module that when prepended wraps
|
@@ -31,9 +30,9 @@ module Collapsium
|
|
31
30
|
# include ::Collapsium::Support::Methods
|
32
31
|
#
|
33
32
|
# def prepended(base)
|
34
|
-
# wrap_method(base, :
|
33
|
+
# wrap_method(base, :method_name) do |wrapped_method, *args, &block|
|
35
34
|
# # modify args, if desired
|
36
|
-
# result =
|
35
|
+
# result = wrapped_method.call(*args, &block)
|
37
36
|
# # do something with the result, if desired
|
38
37
|
# next result
|
39
38
|
# end
|
@@ -41,7 +40,59 @@ module Collapsium
|
|
41
40
|
# end
|
42
41
|
# end
|
43
42
|
# ```
|
44
|
-
def wrap_method(base, method_name,
|
43
|
+
def wrap_method(base, method_name, options = {}, &wrapper_block)
|
44
|
+
# Option defaults (need to check for nil if we default to true)
|
45
|
+
if options[:raise_on_missing].nil?
|
46
|
+
options[:raise_on_missing] = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Grab helper methods
|
50
|
+
base_method, def_method = resolve_helpers(base, method_name,
|
51
|
+
options[:raise_on_missing])
|
52
|
+
if base_method.nil?
|
53
|
+
# Indicates that we're not done building a Module yet
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
# Hack for calling the private method "define_method"
|
58
|
+
def_method.call(method_name) do |*args, &method_block|
|
59
|
+
# We're trying to prevent loops by maintaining a stack of wrapped
|
60
|
+
# method invocations.
|
61
|
+
@__collapsium_methods_callstack ||= []
|
62
|
+
|
63
|
+
# Our current binding is based on the wrapper block and our own class,
|
64
|
+
# as well as the arguments (CRC32).
|
65
|
+
require 'zlib'
|
66
|
+
signature = Zlib::crc32(JSON::dump(args))
|
67
|
+
the_binding = [wrapper_block.object_id, self.class.object_id, signature]
|
68
|
+
|
69
|
+
# We'll either pass the wrapped method to the wrapper block, or invoke
|
70
|
+
# it ourselves.
|
71
|
+
wrapped_method = base_method.bind(self)
|
72
|
+
|
73
|
+
# If we do find a loop with the current binding involved, we'll just
|
74
|
+
# call the wrapped method.
|
75
|
+
if Methods.loop_detected?(the_binding, @__collapsium_methods_callstack)
|
76
|
+
next wrapped_method.call(*args, &method_block)
|
77
|
+
end
|
78
|
+
|
79
|
+
# If there is no loop, call the wrapper block and pass along the
|
80
|
+
# wrapped method as the first argument.
|
81
|
+
args.unshift(wrapped_method)
|
82
|
+
|
83
|
+
# Then yield to the given wrapper block. The wrapper should decide
|
84
|
+
# whether to call the old method or not. But by modifying our stack
|
85
|
+
# before/after the invocation, we allow the loop detection above to
|
86
|
+
# work.
|
87
|
+
@__collapsium_methods_callstack << the_binding
|
88
|
+
result = wrapper_block.call(*args, &method_block)
|
89
|
+
@__collapsium_methods_callstack.pop
|
90
|
+
|
91
|
+
next result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def resolve_helpers(base, method_name, raise_on_missing)
|
45
96
|
# The base class must define an instance method of method_name, otherwise
|
46
97
|
# this will NameError. That's also a good check that sensible things are
|
47
98
|
# being done.
|
@@ -56,7 +107,7 @@ module Collapsium
|
|
56
107
|
if raise_on_missing
|
57
108
|
raise
|
58
109
|
end
|
59
|
-
return
|
110
|
+
return base_method, def_method
|
60
111
|
end
|
61
112
|
def_method = base.method(:define_method)
|
62
113
|
else
|
@@ -68,18 +119,31 @@ module Collapsium
|
|
68
119
|
def_method = base.method(:define_singleton_method)
|
69
120
|
end
|
70
121
|
|
71
|
-
|
72
|
-
|
73
|
-
# Prepend the old method to the argument list; but bind it to the current
|
74
|
-
# instance.
|
75
|
-
super_method = base_method.bind(self)
|
76
|
-
args.unshift(super_method)
|
122
|
+
return base_method, def_method
|
123
|
+
end
|
77
124
|
|
78
|
-
|
79
|
-
|
80
|
-
|
125
|
+
class << self
|
126
|
+
# Given an input array, return repeated sequences from the array. It's
|
127
|
+
# used in loop detection.
|
128
|
+
def repeated(array)
|
129
|
+
counts = Hash.new(0)
|
130
|
+
array.each { |val| counts[val] += 1 }
|
131
|
+
return counts.reject { |_, count| count == 1 }.keys
|
81
132
|
end
|
82
|
-
|
133
|
+
|
134
|
+
# Given a call stack and a binding, returns true if there seems to be a
|
135
|
+
# loop in the call stack with the binding causing it, false otherwise.
|
136
|
+
def loop_detected?(the_binding, stack)
|
137
|
+
# Make a temporary stack with the binding pushed
|
138
|
+
tmp_stack = stack.dup
|
139
|
+
tmp_stack << the_binding
|
140
|
+
loops = Methods.repeated(tmp_stack)
|
141
|
+
|
142
|
+
# If we do find a loop with the current binding involved, we'll just
|
143
|
+
# call the wrapped method.
|
144
|
+
return loops.include?(the_binding)
|
145
|
+
end
|
146
|
+
end # class << self
|
83
147
|
|
84
148
|
end # module Methods
|
85
149
|
|
data/lib/collapsium/version.rb
CHANGED
@@ -67,25 +67,25 @@ module Collapsium
|
|
67
67
|
# a wrapper that uses enhance_hash_value to, well, enhance Hash results.
|
68
68
|
def enhance(base)
|
69
69
|
# rubocop:disable Style/ClassVars
|
70
|
-
@@write_block ||= proc do |
|
70
|
+
@@write_block ||= proc do |wrapped_method, *args, &block|
|
71
71
|
arg_copy = args.map do |arg|
|
72
|
-
enhance_hash_value(
|
72
|
+
enhance_hash_value(wrapped_method.receiver, arg)
|
73
73
|
end
|
74
|
-
result =
|
75
|
-
next enhance_hash_value(
|
74
|
+
result = wrapped_method.call(*arg_copy, &block)
|
75
|
+
next enhance_hash_value(wrapped_method.receiver, result)
|
76
76
|
end
|
77
|
-
@@read_block ||= proc do |
|
78
|
-
result =
|
79
|
-
next enhance_hash_value(
|
77
|
+
@@read_block ||= proc do |wrapped_method, *args, &block|
|
78
|
+
result = wrapped_method.call(*args, &block)
|
79
|
+
next enhance_hash_value(wrapped_method.receiver, result)
|
80
80
|
end
|
81
81
|
# rubocop:enable Style/ClassVars
|
82
82
|
|
83
83
|
READ_METHODS.each do |method_name|
|
84
|
-
wrap_method(base, method_name, false, &@@read_block)
|
84
|
+
wrap_method(base, method_name, raise_on_missing: false, &@@read_block)
|
85
85
|
end
|
86
86
|
|
87
87
|
WRITE_METHODS.each do |method_name|
|
88
|
-
wrap_method(base, method_name, false, &@@write_block)
|
88
|
+
wrap_method(base, method_name, raise_on_missing: false, &@@write_block)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
@@ -37,7 +37,7 @@ describe ::Collapsium::EnvironmentOverride do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
context "without
|
40
|
+
context "without PathedAccess" do
|
41
41
|
it "overrides first-order keys" do
|
42
42
|
expect(@tester["foo"].is_a?(Hash)).to be_truthy
|
43
43
|
ENV["FOO"] = "test"
|
@@ -56,16 +56,30 @@ describe ::Collapsium::EnvironmentOverride do
|
|
56
56
|
@tester.store("foo", 42)
|
57
57
|
expect(@tester["foo"]).to eql 42
|
58
58
|
end
|
59
|
+
|
60
|
+
it "changes with every env change" do
|
61
|
+
ENV["BAR"] = "test1"
|
62
|
+
expect(@tester["foo"]["bar"]).to eql "test1"
|
63
|
+
ENV["BAR"] = "test2"
|
64
|
+
expect(@tester["foo"]["bar"]).to eql "test2"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "resets when the env resets" do
|
68
|
+
ENV["BAR"] = "test"
|
69
|
+
expect(@tester["foo"]["bar"]).to eql "test"
|
70
|
+
ENV.delete("BAR")
|
71
|
+
expect(@tester["foo"]["bar"]).to eql 42
|
72
|
+
end
|
59
73
|
end
|
60
74
|
|
61
|
-
context "with
|
75
|
+
context "with PathedAccess" do
|
62
76
|
before :each do
|
63
77
|
@tester = { "foo" => { "bar" => 42 } }
|
64
|
-
@tester.extend(::Collapsium::EnvironmentOverride)
|
65
78
|
@tester.extend(::Collapsium::PathedAccess)
|
66
|
-
|
67
|
-
ENV
|
68
|
-
ENV
|
79
|
+
@tester.extend(::Collapsium::EnvironmentOverride)
|
80
|
+
ENV.delete("FOO")
|
81
|
+
ENV.delete("BAR")
|
82
|
+
ENV.delete("FOO_BAR")
|
69
83
|
end
|
70
84
|
|
71
85
|
it "overrides first-order keys" do
|
@@ -76,10 +90,10 @@ describe ::Collapsium::EnvironmentOverride do
|
|
76
90
|
end
|
77
91
|
|
78
92
|
it "inherits environment override" do
|
79
|
-
expect(@tester["foo
|
93
|
+
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_truthy
|
80
94
|
ENV["BAR"] = "test"
|
81
|
-
expect(@tester["foo
|
82
|
-
expect(@tester["foo
|
95
|
+
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_falsy
|
96
|
+
expect(@tester["foo.bar"]).to eql "test"
|
83
97
|
end
|
84
98
|
|
85
99
|
it "write still works" do
|
@@ -87,6 +101,20 @@ describe ::Collapsium::EnvironmentOverride do
|
|
87
101
|
expect(@tester["foo"]).to eql 42
|
88
102
|
end
|
89
103
|
|
104
|
+
it "changes with every env change" do
|
105
|
+
ENV["BAR"] = "test1"
|
106
|
+
expect(@tester["foo.bar"]).to eql "test1"
|
107
|
+
ENV["BAR"] = "test2"
|
108
|
+
expect(@tester["foo.bar"]).to eql "test2"
|
109
|
+
end
|
110
|
+
|
111
|
+
it "resets when the env resets" do
|
112
|
+
ENV["BAR"] = "test"
|
113
|
+
expect(@tester["foo.bar"]).to eql "test"
|
114
|
+
ENV.delete("BAR")
|
115
|
+
expect(@tester["foo.bar"]).to eql 42
|
116
|
+
end
|
117
|
+
|
90
118
|
it "overrides from pathed key" do
|
91
119
|
expect(@tester["foo.bar"].is_a?(Fixnum)).to be_truthy
|
92
120
|
ENV["FOO_BAR"] = "test"
|
@@ -144,11 +172,22 @@ describe ::Collapsium::EnvironmentOverride do
|
|
144
172
|
expect(@tester["foo"].is_a?(Hash)).to be_truthy
|
145
173
|
expect(@tester["foo"]["json_key"]).to eql "json_value"
|
146
174
|
end
|
175
|
+
|
176
|
+
it "respects PathedAccess" do
|
177
|
+
@tester = { "foo" => { "bar" => 42 } }
|
178
|
+
@tester.extend(::Collapsium::PathedAccess)
|
179
|
+
@tester.extend(::Collapsium::EnvironmentOverride)
|
180
|
+
|
181
|
+
ENV["FOO_BAR"] = '{ "json_key": "json_value" }'
|
182
|
+
expect(@tester["foo.bar"].is_a?(Hash)).to be_truthy
|
183
|
+
expect(@tester["foo.bar"]["json_key"]).to eql "json_value"
|
184
|
+
expect(@tester["foo"]["bar.json_key"]).to eql "json_value"
|
185
|
+
end
|
147
186
|
end
|
148
187
|
|
149
188
|
context "coverage" do
|
150
189
|
it "raises when not passing arguments" do
|
151
|
-
expect { @tester.fetch }.to raise_error
|
190
|
+
expect { @tester.fetch }.to raise_error(ArgumentError)
|
152
191
|
end
|
153
192
|
end
|
154
193
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: collapsium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Finkhaeuser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|