configatron 4.4.1 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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