garcun 0.0.2

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 (139) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +17 -0
  3. data/.gitignore +197 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +201 -0
  7. data/README.md +521 -0
  8. data/Rakefile +47 -0
  9. data/garcun.gemspec +83 -0
  10. data/lib/garcon.rb +290 -0
  11. data/lib/garcon/chef/chef_helpers.rb +343 -0
  12. data/lib/garcon/chef/coerce/coercer.rb +134 -0
  13. data/lib/garcon/chef/coerce/coercions/boolean_definitions.rb +34 -0
  14. data/lib/garcon/chef/coerce/coercions/date_definitions.rb +32 -0
  15. data/lib/garcon/chef/coerce/coercions/date_time_definitions.rb +32 -0
  16. data/lib/garcon/chef/coerce/coercions/fixnum_definitions.rb +34 -0
  17. data/lib/garcon/chef/coerce/coercions/float_definitions.rb +32 -0
  18. data/lib/garcon/chef/coerce/coercions/hash_definitions.rb +29 -0
  19. data/lib/garcon/chef/coerce/coercions/integer_definitions.rb +31 -0
  20. data/lib/garcon/chef/coerce/coercions/string_definitions.rb +45 -0
  21. data/lib/garcon/chef/coerce/coercions/time_definitions.rb +32 -0
  22. data/lib/garcon/chef/handler/devreporter.rb +127 -0
  23. data/lib/garcon/chef/log.rb +64 -0
  24. data/lib/garcon/chef/node.rb +100 -0
  25. data/lib/garcon/chef/provider/civilize.rb +209 -0
  26. data/lib/garcon/chef/provider/development.rb +159 -0
  27. data/lib/garcon/chef/provider/download.rb +420 -0
  28. data/lib/garcon/chef/provider/house_keeping.rb +265 -0
  29. data/lib/garcon/chef/provider/node_cache.rb +31 -0
  30. data/lib/garcon/chef/provider/partial.rb +183 -0
  31. data/lib/garcon/chef/provider/recovery.rb +80 -0
  32. data/lib/garcon/chef/provider/zip_file.rb +271 -0
  33. data/lib/garcon/chef/resource/attribute.rb +52 -0
  34. data/lib/garcon/chef/resource/base_dsl.rb +174 -0
  35. data/lib/garcon/chef/resource/blender.rb +140 -0
  36. data/lib/garcon/chef/resource/lazy_eval.rb +66 -0
  37. data/lib/garcon/chef/resource/resource_name.rb +109 -0
  38. data/lib/garcon/chef/secret_bag.rb +204 -0
  39. data/lib/garcon/chef/validations.rb +76 -0
  40. data/lib/garcon/chef_inclusions.rb +151 -0
  41. data/lib/garcon/configuration.rb +138 -0
  42. data/lib/garcon/core_ext.rb +39 -0
  43. data/lib/garcon/core_ext/array.rb +27 -0
  44. data/lib/garcon/core_ext/binding.rb +64 -0
  45. data/lib/garcon/core_ext/boolean.rb +66 -0
  46. data/lib/garcon/core_ext/duration.rb +271 -0
  47. data/lib/garcon/core_ext/enumerable.rb +34 -0
  48. data/lib/garcon/core_ext/file.rb +127 -0
  49. data/lib/garcon/core_ext/filetest.rb +62 -0
  50. data/lib/garcon/core_ext/hash.rb +279 -0
  51. data/lib/garcon/core_ext/kernel.rb +159 -0
  52. data/lib/garcon/core_ext/lazy.rb +222 -0
  53. data/lib/garcon/core_ext/method_access.rb +243 -0
  54. data/lib/garcon/core_ext/module.rb +92 -0
  55. data/lib/garcon/core_ext/nil.rb +53 -0
  56. data/lib/garcon/core_ext/numeric.rb +44 -0
  57. data/lib/garcon/core_ext/object.rb +342 -0
  58. data/lib/garcon/core_ext/pathname.rb +152 -0
  59. data/lib/garcon/core_ext/process.rb +41 -0
  60. data/lib/garcon/core_ext/random.rb +497 -0
  61. data/lib/garcon/core_ext/string.rb +312 -0
  62. data/lib/garcon/core_ext/struct.rb +49 -0
  63. data/lib/garcon/core_ext/symbol.rb +170 -0
  64. data/lib/garcon/core_ext/time.rb +234 -0
  65. data/lib/garcon/exceptions.rb +101 -0
  66. data/lib/garcon/inflections.rb +237 -0
  67. data/lib/garcon/inflections/defaults.rb +79 -0
  68. data/lib/garcon/inflections/inflections.rb +182 -0
  69. data/lib/garcon/inflections/rules_collection.rb +37 -0
  70. data/lib/garcon/secret.rb +271 -0
  71. data/lib/garcon/stash/format.rb +114 -0
  72. data/lib/garcon/stash/journal.rb +226 -0
  73. data/lib/garcon/stash/queue.rb +83 -0
  74. data/lib/garcon/stash/serializer.rb +86 -0
  75. data/lib/garcon/stash/store.rb +435 -0
  76. data/lib/garcon/task.rb +31 -0
  77. data/lib/garcon/task/atomic.rb +151 -0
  78. data/lib/garcon/task/atomic_boolean.rb +127 -0
  79. data/lib/garcon/task/condition.rb +99 -0
  80. data/lib/garcon/task/copy_on_notify_observer_set.rb +154 -0
  81. data/lib/garcon/task/copy_on_write_observer_set.rb +153 -0
  82. data/lib/garcon/task/count_down_latch.rb +92 -0
  83. data/lib/garcon/task/delay.rb +196 -0
  84. data/lib/garcon/task/dereferenceable.rb +144 -0
  85. data/lib/garcon/task/event.rb +119 -0
  86. data/lib/garcon/task/executor.rb +275 -0
  87. data/lib/garcon/task/executor_options.rb +59 -0
  88. data/lib/garcon/task/future.rb +107 -0
  89. data/lib/garcon/task/immediate_executor.rb +84 -0
  90. data/lib/garcon/task/ivar.rb +171 -0
  91. data/lib/garcon/task/lazy_reference.rb +74 -0
  92. data/lib/garcon/task/monotonic_time.rb +69 -0
  93. data/lib/garcon/task/obligation.rb +256 -0
  94. data/lib/garcon/task/observable.rb +101 -0
  95. data/lib/garcon/task/priority_queue.rb +234 -0
  96. data/lib/garcon/task/processor_count.rb +128 -0
  97. data/lib/garcon/task/read_write_lock.rb +304 -0
  98. data/lib/garcon/task/safe_task_executor.rb +58 -0
  99. data/lib/garcon/task/single_thread_executor.rb +97 -0
  100. data/lib/garcon/task/thread_pool/cached.rb +71 -0
  101. data/lib/garcon/task/thread_pool/executor.rb +294 -0
  102. data/lib/garcon/task/thread_pool/fixed.rb +61 -0
  103. data/lib/garcon/task/thread_pool/worker.rb +90 -0
  104. data/lib/garcon/task/timer.rb +44 -0
  105. data/lib/garcon/task/timer_set.rb +194 -0
  106. data/lib/garcon/task/timer_task.rb +377 -0
  107. data/lib/garcon/task/waitable_list.rb +58 -0
  108. data/lib/garcon/utility/ansi.rb +199 -0
  109. data/lib/garcon/utility/at_random.rb +77 -0
  110. data/lib/garcon/utility/crypto.rb +292 -0
  111. data/lib/garcon/utility/equalizer.rb +146 -0
  112. data/lib/garcon/utility/faker/extensions/array.rb +22 -0
  113. data/lib/garcon/utility/faker/extensions/symbol.rb +9 -0
  114. data/lib/garcon/utility/faker/faker.rb +164 -0
  115. data/lib/garcon/utility/faker/faker/company.rb +17 -0
  116. data/lib/garcon/utility/faker/faker/hacker.rb +30 -0
  117. data/lib/garcon/utility/faker/faker/version.rb +3 -0
  118. data/lib/garcon/utility/faker/locales/en-US.yml +83 -0
  119. data/lib/garcon/utility/faker/locales/en.yml +21 -0
  120. data/lib/garcon/utility/file_helper.rb +170 -0
  121. data/lib/garcon/utility/hookers.rb +178 -0
  122. data/lib/garcon/utility/interpolation.rb +90 -0
  123. data/lib/garcon/utility/memstash.rb +364 -0
  124. data/lib/garcon/utility/misc.rb +54 -0
  125. data/lib/garcon/utility/msg_from_god.rb +62 -0
  126. data/lib/garcon/utility/retry.rb +238 -0
  127. data/lib/garcon/utility/timeout.rb +58 -0
  128. data/lib/garcon/utility/uber/builder.rb +91 -0
  129. data/lib/garcon/utility/uber/callable.rb +7 -0
  130. data/lib/garcon/utility/uber/delegates.rb +13 -0
  131. data/lib/garcon/utility/uber/inheritable_attr.rb +37 -0
  132. data/lib/garcon/utility/uber/options.rb +101 -0
  133. data/lib/garcon/utility/uber/uber_version.rb +3 -0
  134. data/lib/garcon/utility/uber/version.rb +33 -0
  135. data/lib/garcon/utility/url_helper.rb +100 -0
  136. data/lib/garcon/utils.rb +29 -0
  137. data/lib/garcon/version.rb +62 -0
  138. data/lib/garcun.rb +24 -0
  139. metadata +680 -0
@@ -0,0 +1,238 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require_relative 'timeout'
21
+ require_relative '../exceptions'
22
+
23
+ module Garcon
24
+ # Class methods that are added when you include Garcon::Retry
25
+ #
26
+ module Retry
27
+ # Methods are also available as module-level methods as well as a mixin.
28
+ extend self
29
+
30
+ include Garcon::Exceptions
31
+
32
+ # Runs a code block, and retries it when an exception occurs. It is
33
+ # configured using four optional parameters `:tries`, `:on`, `:sleep`,
34
+ # `:match`, `:ensure` and runs the passed block. Should an exception
35
+ # occur, it'll retry for (n-1) times. Should the number of retries be
36
+ # reached without success, the last exception will be raised.
37
+ #
38
+ # @example open an URL, retry up to two times when an OpenURI::HTTPError
39
+ # occurs.
40
+ # retrier(tries: 3, on: OpenURI::HTTPError) do
41
+ # xml = open('http://example.com/test.html').read
42
+ # end
43
+ #
44
+ # @example do _something_, retry up to four times for either ArgumentErro
45
+ # or TimeoutError exceptions.
46
+ # retrier(tries: 5, on: [ArgumentError, TimeoutError]) do
47
+ # # _something_ code
48
+ # end
49
+ #
50
+ # @example ensure that block of code is executed, regardless of whether an
51
+ # exception was raised. It doesn't matter if the block exits normally,
52
+ # if it retries to execute block of code, or if it is terminated by an
53
+ # uncaught exception -- the ensure block will get run.
54
+ # f = File.open('testfile')
55
+ # ensure_cb = Proc.new do |retries|
56
+ # puts "total retry attempts: #{retries}"
57
+ # f.close
58
+ # end
59
+ # retrier(insure: ensure_cb) do
60
+ # # process file
61
+ # end
62
+ #
63
+ # @example sleeping: by default Retrier waits for one second between
64
+ # retries. You can change this and even provide your own exponential
65
+ # backoff scheme.
66
+ # retrier(sleep: 0) { } # don't pause between retries
67
+ # retrier(sleep: 10) { } # sleep 10s between retries
68
+ # retrier(sleep: ->(n) { 4**n }) { } # sleep 1, 4, 16, etc. each try
69
+ #
70
+ # @example matching error messages: you can also retry based on the
71
+ # exception message:
72
+ # retrier(matching: /IO timeout/) do |retries, exception|
73
+ # raise "yo, IO timeout!" if retries == 0
74
+ # end
75
+ #
76
+ # @example block parameters: your block is called with two optional
77
+ # parameters; the number of tries until now, and the most recent
78
+ # exception.
79
+ # retrier do |tries, exception|
80
+ # puts "try #{tries} failed with error: #{exception}" if retries > 0
81
+ # # keep trying...
82
+ # end
83
+ #
84
+ # @param opts [Hash]
85
+ #
86
+ # @option opts [Fixnum] :tries
87
+ # Number of attempts to retry before raising the last exception
88
+ #
89
+ # @option opts [Fixnum] :sleep
90
+ # Number of seconds to wait between retries, use lambda to exponentially
91
+ # increasing delay between retries.
92
+ #
93
+ # @option opts [Array(Exception)] :on
94
+ # The type of exception(s) to catch and retry on
95
+ #
96
+ # @option opts [Regex] :matching
97
+ # Match based on the exception message
98
+ #
99
+ # @option opts [Block] :ensure
100
+ # Ensure a block of code is executed, regardless of whether an exception
101
+ # is raised
102
+ #
103
+ # @yield [Proc]
104
+ # A block that will be run, and if it raises an error, re-run until
105
+ # success, or timeout is finally reached.
106
+ #
107
+ # @raise [Exception]
108
+ # Last Exception that caused the loop to retry before giving up.
109
+ #
110
+ # @return [Proc]
111
+ # The value of the passed block.
112
+ #
113
+ # @api public
114
+ def retrier(opts = {}, &_block)
115
+ tries = opts.fetch(:tries, 4)
116
+ wait = opts.fetch(:sleep, 1)
117
+ on = opts.fetch(:on, StandardError)
118
+ match = opts.fetch(:match, /.*/)
119
+ insure = opts.fetch(:ensure, Proc.new {})
120
+
121
+ retries = 0
122
+ retry_exception = nil
123
+
124
+ begin
125
+ yield retries, retry_exception
126
+ rescue *[on] => e
127
+ raise unless e.message =~ match
128
+ raise if retries + 1 >= tries
129
+
130
+ begin
131
+ sleep wait.respond_to?(:call) ? wait.call(retries) : wait
132
+ rescue *[on]
133
+ end
134
+
135
+ retries += 1
136
+ retry_exception = exception
137
+ retry
138
+ ensure
139
+ insure.call(retries)
140
+ end
141
+ end
142
+
143
+ # `#poll` is a method for knowing when something is ready. When your
144
+ # block yields true, execution continues. When your block yields false,
145
+ # poll keeps trying until it gives up and raises an error.
146
+ #
147
+ # @example wait up to 30 seconds for the TCP socket to respond.
148
+ # def wait_for_server
149
+ # poll(30) do
150
+ # begin
151
+ # TCPSocket.new(SERVER_IP, SERVER_PORT)
152
+ # true
153
+ # rescue Exception
154
+ # false
155
+ # end
156
+ # end
157
+ # end
158
+ #
159
+ # @param [Integer] wait
160
+ # The number of seconds seconds to poll.
161
+ #
162
+ # @param [Integer] delay
163
+ # Number of seconds to wait after encountering a failure, default is
164
+ # 0.1 seconds
165
+ #
166
+ # @yield [Proc]
167
+ # A block that determines whether polling should continue. Return
168
+ # `true` if the polling is complete. Return `false` if polling should
169
+ # continue.
170
+ #
171
+ # @raise [Garcon::PollingError]
172
+ # Raised after too many failed attempts.
173
+ #
174
+ # @return [Proc]
175
+ # The value of the passed block.
176
+ #
177
+ # @api public
178
+ def poll(wait = 8, delay = 0.1)
179
+ try_until = Time.now + wait
180
+
181
+ while Time.now < try_until do
182
+ result = yield
183
+ return result if result
184
+ sleep delay
185
+ end
186
+ raise TimeoutError
187
+ end
188
+
189
+ # Similar to `#poll`, `#patiently` also executes an arbitrary code block.
190
+ # If the passed block runs without raising an error, execution proceeds
191
+ # normally. If an error is raised, the block is rerun after a brief
192
+ # delay, until the block can be run without exceptions. If exceptions
193
+ # continue to raise, `#patiently` gives up after a bit (default 8
194
+ # seconds) by re-raising the most recent exception raised by the block.
195
+ #
196
+ # @example
197
+ # Returns immedialtely if no errors or as soon as error stops.
198
+ # patiently { ... }
199
+ #
200
+ # Increase patience to 10 seconds.
201
+ # patiently(10) { ... }
202
+ #
203
+ # Increase patience to 20 seconds, and delay for 3 seconds before retry.
204
+ # patiently(20, 3) { ... }
205
+ #
206
+ # @param [Integer] seconds
207
+ # number of seconds to be patient, default is 8 seconds
208
+ #
209
+ # @param [Integer] delay
210
+ # seconds to wait after encountering a failure, default is 0.1 seconds
211
+ #
212
+ # @yield [Proc]
213
+ # A block that will be run, and if it raises an error, re-run until
214
+ # success, or patience runs out.
215
+ #
216
+ # @raise [Exception] the most recent Exception that caused the loop to
217
+ # retry before giving up.
218
+ #
219
+ # @return [Proc]
220
+ # the value of the passed block.
221
+ #
222
+ # @api public
223
+ def patiently(wait = 8, delay = 0.1)
224
+ try_until = Time.now + wait
225
+ failure = nil
226
+
227
+ while Time.now < try_until do
228
+ begin
229
+ return yield
230
+ rescue Exception => e
231
+ failure = e
232
+ sleep delay
233
+ end
234
+ end
235
+ failure ? (raise failure) : (raise TimeoutError)
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'thread'
21
+ require_relative '../exceptions'
22
+
23
+ module Garcon
24
+ # Class methods that are added when you include Garcon::Timeout
25
+ #
26
+ module Timeout
27
+ # Methods are also available as module-level methods as well as a mixin.
28
+ extend self
29
+
30
+ include Garcon::Exceptions
31
+
32
+ # Wait the given number of seconds for the block operation to complete.
33
+ # Intended to be a simpler and more reliable replacement to the Ruby
34
+ # standard library `Timeout::timeout` method.
35
+ #
36
+ # @param [Integer] seconds
37
+ # Number of seconds to wait for the block to terminate. Any number may
38
+ # be used, including Floats to specify fractional seconds. A value of 0
39
+ # or nil will execute the block without any timeout.
40
+ #
41
+ # @return [Object]
42
+ # Result of the block if the block completed before the timeout,
43
+ # otherwise raises a TimeoutError exception.
44
+ #
45
+ # @raise [Garcon::TimeoutError]
46
+ # When the block operation does not complete in the alloted time.
47
+ #
48
+ # @see Ruby Timeout::timeout
49
+ #
50
+ # @api public
51
+ def timeout(seconds)
52
+ thread = Thread.new { Thread.current[:result] = yield }
53
+ thread.join(seconds) ? (return thread[:result]) : (raise TimeoutError)
54
+ ensure
55
+ Thread.kill(thread) unless thread.nil?
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,91 @@
1
+ module Uber
2
+ # When included, allows to add builder on the class level.
3
+ #
4
+ # class Operation
5
+ # include Uber::Builder
6
+ #
7
+ # builds do |params|
8
+ # SignedIn if params[:current_user]
9
+ # end
10
+ #
11
+ # class SignedIn
12
+ # end
13
+ #
14
+ # The class then has to call the builder to compute a class name using the build blocks you defined.
15
+ #
16
+ # def self.build(params)
17
+ # class_builder.call(params).
18
+ # new(params)
19
+ # end
20
+ module Builder
21
+ def self.included(base)
22
+ base.class_eval do
23
+ def self.builders
24
+ @builders ||= []
25
+ end
26
+
27
+ extend ClassMethods
28
+ end
29
+ end
30
+
31
+ class Constant
32
+ def initialize(constant) # TODO: evaluate usage of builders and implement using Uber::Options::Value.
33
+ @constant = constant
34
+ @builders = @constant.builders # only dependency, must be a Cell::Base subclass.
35
+ end
36
+
37
+ def call(*args)
38
+ build_class_for(*args)
39
+ end
40
+
41
+ private
42
+ def build_class_for(*args)
43
+ @builders.each do |blk|
44
+ klass = run_builder_block(blk, *args) and return klass
45
+ end
46
+ @constant
47
+ end
48
+
49
+ def run_builder_block(block, *args)
50
+ block.call(*args)
51
+ end
52
+ end
53
+
54
+ module ClassMethods
55
+ # Adds a builder to the cell class. Builders are used in #cell to find out the concrete
56
+ # class for rendering. This is helpful if you frequently want to render subclasses according
57
+ # to different circumstances (e.g. login situations) and you don't want to place these deciders in
58
+ # your view code.
59
+ #
60
+ # Passes the model and options from #cell into the block.
61
+ #
62
+ # Multiple build blocks are ORed, if no builder matches the building cell is used.
63
+ #
64
+ # Example:
65
+ #
66
+ # Consider two different user box cells in your app.
67
+ #
68
+ # class AuthorizedUserBox < UserInfoBox
69
+ # end
70
+ #
71
+ # class AdminUserBox < UserInfoBox
72
+ # end
73
+ #
74
+ # Now you don't want to have deciders all over your views - use a declarative builder.
75
+ #
76
+ # UserInfoBox.build do |model, options|
77
+ # AuthorizedUserBox if options[:is_signed_in]
78
+ # AdminUserBox if model.admin?
79
+ # end
80
+ #
81
+ # In your view #cell will instantiate the right cell for you now.
82
+ def builds(proc=nil, &block)
83
+ builders << (proc.kind_of?(Proc) ? proc : block)
84
+ end
85
+
86
+ def class_builder
87
+ @class_builder ||= Constant.new(self)
88
+ end
89
+ end # ClassMethods
90
+ end
91
+ end
@@ -0,0 +1,7 @@
1
+ module Uber
2
+ # Include this module into a class or extend an object to mark it as callable.
3
+ # E.g., in a dynamic option in Options::Value it will be treated like a Proc object
4
+ # and invoked with +#call+.
5
+ module Callable
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require 'forwardable'
2
+
3
+ module Uber
4
+ module Delegates
5
+ def delegates(model, *names)
6
+ mod = Module.new do
7
+ extend Forwardable
8
+ def_delegators model, *names
9
+ end
10
+ include mod
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module Uber
2
+ module InheritableAttr
3
+ def inheritable_attr(name)
4
+ instance_eval %Q{
5
+ def #{name}=(v)
6
+ @#{name} = v
7
+ end
8
+
9
+ def #{name}
10
+ return @#{name} if instance_variable_defined?(:@#{name})
11
+ @#{name} = InheritableAttribute.inherit_for(self, :#{name})
12
+ end
13
+ }
14
+ end
15
+
16
+ def self.inherit_for(klass, name)
17
+ return unless klass.superclass.respond_to?(name)
18
+
19
+ value = klass.superclass.send(name) # could be nil.
20
+ Clone.(value) # this could be dynamic, allowing other inheritance strategies.
21
+ end
22
+
23
+ class Clone
24
+ # The second argument allows injecting more types.
25
+ def self.call(value, uncloneable=uncloneable())
26
+ uncloneable.each { |klass| return value if value.kind_of?(klass) }
27
+ value.clone
28
+ end
29
+
30
+ def self.uncloneable
31
+ [Symbol, TrueClass, FalseClass, NilClass]
32
+ end
33
+ end
34
+ end
35
+
36
+ InheritableAttribute = InheritableAttr
37
+ end