configatron 4.4.1 → 4.5.0

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.
@@ -1,3 +1,7 @@
1
+ === 4.5.0
2
+
3
+ * [backwards-incompatible] Switch from DeepClone to copy-on-write for temp. As part of the implementation, *values* are no longer cloned.
4
+
1
5
  === 4.4.1
2
6
 
3
7
  * Make lock!/unlock! support blocks
@@ -1,6 +1,5 @@
1
1
  require 'configatron/version'
2
2
 
3
- require 'configatron/deep_clone'
4
3
  require 'configatron/errors'
5
4
  require 'configatron/integrations'
6
5
  require 'configatron/root_store'
@@ -15,11 +15,41 @@ class Configatron::RootStore < BasicObject
15
15
  public :new
16
16
  end
17
17
 
18
+ @@cow = 0
19
+
18
20
  def initialize
19
21
  @locked = false
22
+ @cow = nil
20
23
  reset!
21
24
  end
22
25
 
26
+ def __cow
27
+ @cow
28
+ end
29
+
30
+ def __cow_path(path)
31
+ start = @store.__cow_clone
32
+
33
+ node = start
34
+ branch = path.map do |key|
35
+ node = node[key]
36
+ node.__cow_clone
37
+ end
38
+ nodes = [start] + branch
39
+
40
+ # [node1, node2, node3] with
41
+ # [node2, node3, node4] and
42
+ # ['key1', 'key2, 'key3']
43
+ nodes[0...-1].zip(nodes[1..-1], path) do |parent, child, key|
44
+ # These are all cow_clones, so won't trigger a further cow
45
+ # modification.
46
+ parent[key] = child
47
+ end
48
+
49
+ @store = nodes.first
50
+ nodes.last
51
+ end
52
+
23
53
  def method_missing(name, *args, &block)
24
54
  store.__send__(name, *args, &block)
25
55
  end
@@ -39,13 +69,19 @@ class Configatron::RootStore < BasicObject
39
69
  end
40
70
 
41
71
  def temp_start
42
- @temp = ::Configatron::DeepClone.deep_clone(@store)
43
72
  @temp_locked = @locked
73
+ @temp_cow = @cow
74
+
75
+ # Just need to have a unique Copy-on-Write generation ID
76
+ @cow = @@cow += 1
77
+ @temp = @store
44
78
  end
45
79
 
46
80
  def temp_end
47
- @store = @temp
48
81
  @locked = @temp_locked
82
+ @cow = @temp_cow
83
+
84
+ @store = @temp
49
85
  end
50
86
 
51
87
  def locked?
@@ -4,10 +4,38 @@ class Configatron
4
4
  class Store < BasicObject
5
5
  extend ::Forwardable
6
6
 
7
- def initialize(root_store, name='configatron', attributes={})
7
+ def initialize(root_store, name='configatron', attributes={}, path=[])
8
8
  @root_store = root_store
9
9
  @name = name
10
10
  @attributes = attributes
11
+
12
+ # We could derive @name from @path, though this would break
13
+ # backwards-compatibility.
14
+ @path = path
15
+
16
+ @cow = root_store.__cow
17
+ end
18
+
19
+ def clone
20
+ Store.new(
21
+ @root_store,
22
+ @name,
23
+ @attributes.clone,
24
+ @path
25
+ )
26
+ end
27
+
28
+ def __cow
29
+ @cow
30
+ end
31
+
32
+ def __cow_clone
33
+ # A temp has started since the last time this was written
34
+ if @root_store.__cow != @cow
35
+ self.clone
36
+ else
37
+ self
38
+ end
11
39
  end
12
40
 
13
41
  def [](key)
@@ -15,7 +43,7 @@ class Configatron
15
43
  if @root_store.locked?
16
44
  ::Kernel.raise ::Configatron::UndefinedKeyError.new("Key not found: #{key} (for locked #{self})")
17
45
  end
18
- ::Configatron::Store.new(@root_store, "#{@name}.#{key}")
46
+ ::Configatron::Store.new(@root_store, "#{@name}.#{key}", {}, @path + [key])
19
47
  end
20
48
  return val
21
49
  end
@@ -24,7 +52,16 @@ class Configatron
24
52
  if @root_store.locked?
25
53
  ::Kernel.raise ::Configatron::LockedError.new("Cannot set key #{key} for locked #{self}")
26
54
  end
27
- @attributes[key.to_sym] = value
55
+
56
+ key = key.to_sym
57
+ if @root_store.__cow != @cow
58
+ copy = @root_store.__cow_path(@path)
59
+ # Cow should now match, so this won't recurse. (Note this is
60
+ # not particularly thread-safe.)
61
+ copy.store(key, value)
62
+ else
63
+ @attributes[key] = value
64
+ end
28
65
  end
29
66
 
30
67
  def fetch(key, default_value = nil, &block)
@@ -89,15 +126,6 @@ class Configatron
89
126
  do_lookup(name, *args, &block)
90
127
  end
91
128
 
92
- # Needed for deep_clone to actually clone this object
93
- def clone(cloned={})
94
- root_store = DeepClone.deep_clone(@root_store, cloned)
95
- name = DeepClone.deep_clone(@name, cloned)
96
- attributes = DeepClone.deep_clone(@attributes, cloned)
97
-
98
- Store.new(root_store, name, attributes)
99
- end
100
-
101
129
  def to_h
102
130
  @attributes.each_with_object({}) do |(k, v), h|
103
131
  v = v.call if ::Configatron::Proc === v
@@ -1,3 +1,3 @@
1
1
  class Configatron
2
- VERSION = "4.4.1"
2
+ VERSION = "4.5.0"
3
3
  end
@@ -99,6 +99,24 @@ class Critic::Functional::ConfigatronTest < Critic::Functional::Test
99
99
  @kernel.unknown = 'known'
100
100
  end
101
101
  end
102
+
103
+ it 'locks during the block argument' do
104
+ @kernel.unlock!
105
+
106
+ @kernel.lock! do
107
+ assert(@kernel.locked?)
108
+ end
109
+
110
+ assert(!@kernel.locked?)
111
+ end
112
+
113
+ it 'executes a block argument' do
114
+ a = 1
115
+ @kernel.lock! do
116
+ a = 2
117
+ end
118
+ assert_equal(2, a)
119
+ end
102
120
  end
103
121
 
104
122
  describe 'name' do
@@ -66,7 +66,7 @@ class Critic::Unit::StoreTest < Critic::Unit::Test
66
66
 
67
67
  it "returns true if the key is a Configatron::Store" do
68
68
  assert_equal(false, @store.key?(:bar))
69
- @store.bar = Configatron::Store.new(Configatron::RootStore)
69
+ @store.bar = Configatron::Store.new(Configatron::RootStore.new)
70
70
  assert_equal(true, @store.key?(:bar))
71
71
  end
72
72
  end
@@ -80,7 +80,7 @@ class Critic::Unit::StoreTest < Critic::Unit::Test
80
80
 
81
81
  it "returns true if the key is a Configatron::Store" do
82
82
  assert_equal(false, @store.has_key?(:bar))
83
- @store.bar = Configatron::Store.new(Configatron::RootStore)
83
+ @store.bar = Configatron::Store.new(Configatron::RootStore.new)
84
84
  assert_equal(true, @store.has_key?(:bar))
85
85
  end
86
86
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configatron
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.1
4
+ version: 4.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-04 00:00:00.000000000 Z
12
+ date: 2014-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -94,7 +94,6 @@ files:
94
94
  - configatron.gemspec
95
95
  - lib/configatron.rb
96
96
  - lib/configatron/core.rb
97
- - lib/configatron/deep_clone.rb
98
97
  - lib/configatron/delayed.rb
99
98
  - lib/configatron/dynamic.rb
100
99
  - lib/configatron/errors.rb
@@ -138,7 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
138
137
  version: '0'
139
138
  segments:
140
139
  - 0
141
- hash: 630343520399706403
140
+ hash: -3612198204560107212
142
141
  required_rubygems_version: !ruby/object:Gem::Requirement
143
142
  none: false
144
143
  requirements:
@@ -147,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
146
  version: '0'
148
147
  segments:
149
148
  - 0
150
- hash: 630343520399706403
149
+ hash: -3612198204560107212
151
150
  requirements: []
152
151
  rubyforge_project:
153
152
  rubygems_version: 1.8.25
@@ -1,81 +0,0 @@
1
- module Configatron::DeepClone
2
- # = DeepClone
3
- #
4
- # == Version
5
- # 1.2006.05.23.configatron.1 (change of the first number means Big Change)
6
- #
7
- # == Description
8
- # Adds deep_clone method to an object which produces deep copy of it. It means
9
- # if you clone a Hash, every nested items and their nested items will be cloned.
10
- # Moreover deep_clone checks if the object is already cloned to prevent endless recursion.
11
- #
12
- # == Usage
13
- #
14
- # (see examples directory under the ruby gems root directory)
15
- #
16
- # require 'rubygems'
17
- # require 'deep_clone'
18
- #
19
- # include DeepClone
20
- #
21
- # obj = []
22
- # a = [ true, false, obj ]
23
- # b = a.deep_clone
24
- # obj.push( 'foo' )
25
- # p obj # >> [ 'foo' ]
26
- # p b[2] # >> []
27
- #
28
- # == Source
29
- # http://simplypowerful.1984.cz/goodlibs/1.2006.05.23
30
- #
31
- # == Author
32
- # jan molic (/mig/at_sign/1984/dot/cz/)
33
- #
34
- # == Licence
35
- # You can redistribute it and/or modify it under the same terms of Ruby's license;
36
- # either the dual license version in 2003, or any later version.
37
- def self.deep_clone( obj=self, cloned={} )
38
- if Configatron::RootStore === obj
39
- # We never actually want to have multiple copies of our
40
- # Configatron::RootStore -- when making a temp, we just stick
41
- # the state-to-revert-to into an ivar.
42
- return obj
43
- elsif Configatron::Store === obj
44
- # Need to special-case this since it's a BasicObject, meaning it
45
- # doesn't respond to all the usual ivar magic methods
46
- cl = obj.clone(cloned)
47
- cloned[obj.__id__] = cl
48
- cloned[cl.__id__] = cl
49
- return cl
50
- elsif cloned.key?( obj.__id__ )
51
- return cloned[obj.__id__]
52
- else
53
- begin
54
- cl = obj.clone
55
- rescue Exception
56
- # unclonnable (TrueClass, Fixnum, ...)
57
- cloned[obj.__id__] = obj
58
- return obj
59
- else
60
- cloned[obj.__id__] = cl
61
- cloned[cl.__id__] = cl
62
- case
63
- when Hash === cl
64
- cl.clone.each do |k,v|
65
- cl[k] = deep_clone( v, cloned )
66
- end
67
- when Array === cl
68
- cl.collect! do |v|
69
- deep_clone( v, cloned )
70
- end
71
- end
72
- cl.instance_variables.each do |var|
73
- v = cl.instance_eval( var.to_s )
74
- v_cl = deep_clone( v, cloned )
75
- cl.instance_eval( "#{var} = v_cl" )
76
- end
77
- return cl
78
- end
79
- end
80
- end
81
- end