adamantium 0.0.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.
Files changed (44) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +86 -0
  4. data/Gemfile +54 -0
  5. data/Guardfile +18 -0
  6. data/LICENSE +21 -0
  7. data/README.md +90 -0
  8. data/Rakefile +9 -0
  9. data/TODO +1 -0
  10. data/adamantium.gemspec +25 -0
  11. data/config/flay.yml +3 -0
  12. data/config/flog.yml +2 -0
  13. data/config/roodi.yml +18 -0
  14. data/config/site.reek +91 -0
  15. data/config/yardstick.yml +2 -0
  16. data/lib/adamantium.rb +249 -0
  17. data/lib/adamantium/version.rb +3 -0
  18. data/spec/rcov.opts +7 -0
  19. data/spec/shared/command_method_behavior.rb +7 -0
  20. data/spec/shared/each_method_behaviour.rb +15 -0
  21. data/spec/shared/hash_method_behavior.rb +17 -0
  22. data/spec/shared/idempotent_method_behavior.rb +7 -0
  23. data/spec/shared/invertible_method_behaviour.rb +9 -0
  24. data/spec/spec_helper.rb +11 -0
  25. data/spec/unit/adamantium/class_methods/freeze_object_spec.rb +57 -0
  26. data/spec/unit/adamantium/class_methods/new_spec.rb +14 -0
  27. data/spec/unit/adamantium/dup_spec.rb +13 -0
  28. data/spec/unit/adamantium/fixtures/classes.rb +28 -0
  29. data/spec/unit/adamantium/freeze_spec.rb +51 -0
  30. data/spec/unit/adamantium/memoize_spec.rb +57 -0
  31. data/spec/unit/adamantium/memoized_spec.rb +29 -0
  32. data/spec/unit/adamantium/module_methods/included_spec.rb +16 -0
  33. data/spec/unit/adamantium/module_methods/memoize_spec.rb +88 -0
  34. data/tasks/metrics/ci.rake +7 -0
  35. data/tasks/metrics/flay.rake +47 -0
  36. data/tasks/metrics/flog.rake +43 -0
  37. data/tasks/metrics/heckle.rake +208 -0
  38. data/tasks/metrics/metric_fu.rake +29 -0
  39. data/tasks/metrics/reek.rake +15 -0
  40. data/tasks/metrics/roodi.rake +15 -0
  41. data/tasks/metrics/yardstick.rake +23 -0
  42. data/tasks/spec.rake +45 -0
  43. data/tasks/yard.rake +9 -0
  44. metadata +174 -0
@@ -0,0 +1,249 @@
1
+ require 'ice_nine'
2
+
3
+ # Allows objects to be made immutable
4
+ module Adamantium
5
+
6
+ # Storage for memoized methods
7
+ Memory = Class.new(::Hash)
8
+
9
+ # Hook called when module is included
10
+ #
11
+ # @param [Module] descendant
12
+ # the module or class including Adamantium
13
+ #
14
+ # @return [self]
15
+ #
16
+ # @api private
17
+ def self.included(descendant)
18
+ super
19
+ descendant.extend ModuleMethods if descendant.kind_of?(Module)
20
+ descendant.extend ClassMethods if descendant.kind_of?(Class)
21
+ self
22
+ end
23
+
24
+ # Attempt to freeze an object
25
+ #
26
+ # @example using a value object
27
+ # Adamantium.freeze_object(12345) # => noop
28
+ #
29
+ # @example using a normal object
30
+ # Adamantium.freeze_object({}) # => duplicate & freeze object
31
+ #
32
+ # @param [Object] object
33
+ # the object to freeze
34
+ #
35
+ # @return [Object]
36
+ # if supported, the frozen object, otherwise the object directly
37
+ #
38
+ # @api public
39
+ def self.freeze_object(object)
40
+ case object
41
+ when Numeric, TrueClass, FalseClass, NilClass, Symbol
42
+ object
43
+ else
44
+ freeze_value(object)
45
+ end
46
+ end
47
+
48
+ # Returns a frozen value
49
+ #
50
+ # @param [Object] value
51
+ # a value to freeze
52
+ #
53
+ # @return [Object]
54
+ # if frozen, the value directly, otherwise a frozen copy of the value
55
+ #
56
+ # @api private
57
+ def self.freeze_value(value)
58
+ value.frozen? ? value : IceNine.deep_freeze(value.dup)
59
+ end
60
+
61
+ private_class_method :freeze_value
62
+
63
+ # Freeze the object
64
+ #
65
+ # @example
66
+ # object.freeze # object is now frozen
67
+ #
68
+ # @return [Object]
69
+ #
70
+ # @api public
71
+ def freeze
72
+ memory # initialize memory
73
+ super
74
+ end
75
+
76
+ # Get the memoized value for a method
77
+ #
78
+ # @example
79
+ # hash = object.memoized(:hash)
80
+ #
81
+ # @param [Symbol] name
82
+ # the method name
83
+ #
84
+ # @return [Object]
85
+ #
86
+ # @api public
87
+ def memoized(name)
88
+ memory[name]
89
+ end
90
+
91
+ # Sets a memoized value for a method
92
+ #
93
+ # @example
94
+ # object.memoize(:hash, 12345)
95
+ #
96
+ # @param [Symbol] name
97
+ # the method name
98
+ # @param [Object] value
99
+ # the value to memoize
100
+ #
101
+ # @return [self]
102
+ #
103
+ # @api public
104
+ def memoize(name, value)
105
+ store_memory(name, value) unless memory.key?(name)
106
+ self
107
+ end
108
+
109
+ # A noop #dup for immutable objects
110
+ #
111
+ # @example
112
+ # object.dup # => self
113
+ #
114
+ # @return [self]
115
+ #
116
+ # @api public
117
+ def dup
118
+ self
119
+ end
120
+
121
+ private
122
+
123
+ # The memoized method results
124
+ #
125
+ # @return [Hash]
126
+ #
127
+ # @api private
128
+ def memory
129
+ @__memory ||= Memory.new
130
+ end
131
+
132
+ # Store the value in memory
133
+ #
134
+ # @param [Symbol] name
135
+ # the method name
136
+ # @param [Object] value
137
+ # the value to memoize
138
+ #
139
+ # @return [self]
140
+ #
141
+ # @return [value]
142
+ #
143
+ # @api private
144
+ def store_memory(name, value)
145
+ memory[name] = Adamantium.freeze_object(value)
146
+ end
147
+
148
+ # Methods mixed in to adamantium modules
149
+ module ModuleMethods
150
+
151
+ # Hook called when module is included
152
+ #
153
+ # @param [Module] mod
154
+ # the module including ModuleMethods
155
+ #
156
+ # @return [self]
157
+ #
158
+ # @api private
159
+ def included(mod)
160
+ Adamantium.included(mod)
161
+ self
162
+ end
163
+
164
+ # Memoize a list of methods
165
+ #
166
+ # @example
167
+ # memoize :hash
168
+ #
169
+ # @param [Array<#to_s>] *methods
170
+ # a list of methods to memoize
171
+ #
172
+ # @return [self]
173
+ #
174
+ # @api public
175
+ def memoize(*methods)
176
+ methods.each { |method| memoize_method(method) }
177
+ self
178
+ end
179
+
180
+ private
181
+
182
+ # Memoize the named method
183
+ #
184
+ # @param [#to_s] method
185
+ # a method to memoize
186
+ #
187
+ # @return [undefined]
188
+ #
189
+ # @api private
190
+ def memoize_method(method)
191
+ visibility = method_visibility(method)
192
+ define_memoize_method(method)
193
+ send(visibility, method)
194
+ end
195
+
196
+ # Define a memoized method that delegates to the original method
197
+ #
198
+ # @param [Symbol] method
199
+ # the name of the method
200
+ #
201
+ # @return [undefined]
202
+ #
203
+ # @api private
204
+ def define_memoize_method(method)
205
+ original = instance_method(method)
206
+ undef_method(method)
207
+ define_method(method) do |*args|
208
+ if memory.key?(method)
209
+ memoized(method)
210
+ else
211
+ store_memory(method, original.bind(self).call(*args))
212
+ end
213
+ end
214
+ end
215
+
216
+ # Return the method visibility of a method
217
+ #
218
+ # @param [String, Symbol] method
219
+ # the name of the method
220
+ #
221
+ # @return [String]
222
+ #
223
+ # @api private
224
+ def method_visibility(method)
225
+ if private_method_defined?(method) then 'private'
226
+ elsif protected_method_defined?(method) then 'protected'
227
+ else 'public'
228
+ end
229
+ end
230
+
231
+ end # module ModuleMethods
232
+
233
+ # Methods mixed in to adamantium classes
234
+ module ClassMethods
235
+
236
+ # Instantiate a new frozen object
237
+ #
238
+ # @example
239
+ # object = AdamantiumClass.new # object is frozen
240
+ #
241
+ # @return [Object]
242
+ #
243
+ # @api public
244
+ def new(*)
245
+ IceNine.deep_freeze(super)
246
+ end
247
+
248
+ end # module ClassMethods
249
+ end # module Adamantium
@@ -0,0 +1,3 @@
1
+ module Adamantium
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,7 @@
1
+ --exclude-only "spec/,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
7
+ --failure-threshold 100
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an #each method' do
4
+ it_should_behave_like 'a command method'
5
+
6
+ context 'with no block' do
7
+ subject { object.each }
8
+
9
+ it { should be_instance_of(to_enum.class) }
10
+
11
+ it 'yields the expected values' do
12
+ subject.to_a.should eql(object.to_a)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a hash method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ specification = proc do
7
+ should be_instance_of(Fixnum)
8
+ end
9
+
10
+ it 'is a fixnum' do
11
+ instance_eval(&specification)
12
+ end
13
+
14
+ it 'memoizes the hash code' do
15
+ subject.should eql(object.memoized(:hash))
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ should equal(instance_eval(&self.class.subject))
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an invertible method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ it 'is invertible' do
7
+ subject.inverse.should equal(object)
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'adamantium'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ # require spec support files and shared behavior
8
+ Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require f }
9
+
10
+ Spec::Runner.configure do |config|
11
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Adamantium, '.freeze_object' do
6
+ subject { object.freeze_object(value) }
7
+
8
+ let(:object) { self.class.described_type }
9
+
10
+ context 'with a numeric value' do
11
+ let(:value) { 1 }
12
+
13
+ it { should equal(value) }
14
+ end
15
+
16
+ context 'with a true value' do
17
+ let(:value) { true }
18
+
19
+ it { should equal(value) }
20
+ end
21
+
22
+ context 'with a false value' do
23
+ let(:value) { false }
24
+
25
+ it { should equal(value) }
26
+ end
27
+
28
+ context 'with a nil value' do
29
+ let(:value) { nil }
30
+
31
+ it { should equal(value) }
32
+ end
33
+
34
+ context 'with a symbol value' do
35
+ let(:value) { :symbol }
36
+
37
+ it { should equal(value) }
38
+ end
39
+
40
+ context 'with a frozen value' do
41
+ let(:value) { String.new.freeze }
42
+
43
+ it { should equal(value) }
44
+ end
45
+
46
+ context 'with an unfrozen value' do
47
+ let(:value) { String.new }
48
+
49
+ it { should_not equal(value) }
50
+
51
+ it { should be_instance_of(String) }
52
+
53
+ it { should == value }
54
+
55
+ it { should be_frozen }
56
+ end
57
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../../fixtures/classes', __FILE__)
5
+
6
+ describe Adamantium::ClassMethods, '#new' do
7
+ subject { object.new }
8
+
9
+ let(:object) { AdamantiumSpecs::Object }
10
+
11
+ it { should be_instance_of(object) }
12
+
13
+ it { should be_frozen }
14
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require File.expand_path('../fixtures/classes', __FILE__)
5
+
6
+ describe Adamantium, '#dup' do
7
+ subject { object.dup }
8
+
9
+ let(:described_class) { AdamantiumSpecs::Object }
10
+ let(:object) { described_class.new }
11
+
12
+ it { should equal(object) }
13
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module AdamantiumSpecs
4
+ class Object
5
+ include Adamantium
6
+
7
+ def test
8
+ 'test'
9
+ end
10
+
11
+ def public_method
12
+ caller
13
+ end
14
+
15
+ protected
16
+
17
+ def protected_method
18
+ caller
19
+ end
20
+
21
+ private
22
+
23
+ def private_method
24
+ caller
25
+ end
26
+
27
+ end # class Object
28
+ end # module AdamantiumSpecs