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,222 @@
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
+ # Lazy ass Ruby.
21
+ #
22
+ module Lazy
23
+ # Raised when a demanded computation diverges (e.g. if it tries to directly
24
+ # use its own result)
25
+ #
26
+ class DivergenceError < Exception
27
+ def initialize(message = 'Computation diverges')
28
+ super(message)
29
+ end
30
+ end
31
+
32
+ # Wraps an exception raised by a lazy computation.
33
+ #
34
+ # The reason we wrap such exceptions in LazyException is that they need to
35
+ # be distinguishable from similar exceptions which might normally be raised
36
+ # by whatever strict code we happen to be in at the time.
37
+ #
38
+ class LazyException < DivergenceError
39
+ attr_reader :reason
40
+
41
+ def initialize(reason)
42
+ @reason = reason
43
+ super "Exception in lazy computation: #{reason} (#{reason.class})"
44
+ set_backtrace(reason.backtrace.dup) if reason
45
+ end
46
+ end
47
+
48
+ # A handle for a promised computation. They are transparent, so that in
49
+ # most cases, a promise can be used as a proxy for the computation's result
50
+ # object. The one exception is truth testing -- a promise will always look
51
+ # true to Ruby, even if the actual result object is nil or false.
52
+ #
53
+ # If you want to test the result for truth, get the unwrapped result object
54
+ # via Kernel.demand.
55
+ #
56
+ class Promise
57
+ alias __class__ class
58
+ instance_methods.each do |method|
59
+ undef_method method unless method =~ /^(__|object_|instance_)/
60
+ end
61
+
62
+ def initialize(&computation)
63
+ @computation = computation
64
+ end
65
+
66
+ def __synchronize__
67
+ yield
68
+ end
69
+
70
+ # Create this once here, rather than creating a proc object for every
71
+ # evaluation.
72
+ DIVERGES = lambda { raise DivergenceError.new }
73
+
74
+ # Differentiate inspection of DIVERGES lambda.
75
+ def DIVERGES.inspect
76
+ 'DIVERGES'
77
+ end
78
+
79
+ def __result__
80
+ __synchronize__ do
81
+ if @computation
82
+ raise LazyException.new(@exception) if @exception
83
+
84
+ computation = @computation
85
+ @computation = DIVERGES
86
+
87
+ begin
88
+ @result = demand(computation.call(self))
89
+ @computation = nil
90
+ rescue DivergenceError
91
+ raise
92
+ rescue Exception => e
93
+ @exception = e
94
+ raise LazyException.new(@exception)
95
+ end
96
+ end
97
+
98
+ @result
99
+ end
100
+ end
101
+
102
+ def inspect
103
+ __synchronize__ do
104
+ if @computation
105
+ "#<#{__class__} computation=#{@computation.inspect}>"
106
+ else
107
+ @result.inspect
108
+ end
109
+ end
110
+ end
111
+
112
+ def respond_to?(message)
113
+ message = message.to_sym
114
+ message == :__result__ or
115
+ message == :inspect or
116
+ __result__.respond_to? message
117
+ end
118
+
119
+ def method_missing(*args, &block)
120
+ __result__.__send__(*args, &block)
121
+ end
122
+ end
123
+
124
+ # Thread safe version of Promise class.
125
+ #
126
+ class PromiseSafe < Promise
127
+ def __synchronize__
128
+ current = Thread.current
129
+
130
+ Thread.critical = true
131
+ unless @computation
132
+ Thread.critical = false
133
+ yield
134
+ else
135
+ if @owner == current
136
+ Thread.critical = false
137
+ raise DivergenceError.new
138
+ end
139
+ while @owner
140
+ Thread.critical = false
141
+ Thread.pass
142
+ Thread.critical = true
143
+ end
144
+ @owner = current
145
+ Thread.critical = false
146
+
147
+ begin
148
+ yield
149
+ ensure
150
+ @owner = nil
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Future class subclasses PromiseSafe.
157
+ #
158
+ class Future < PromiseSafe
159
+ def initialize(&computation)
160
+ result = nil
161
+ exception = nil
162
+ thread = Thread.new do
163
+ begin
164
+ result = computation.call(self)
165
+ rescue Exception => exception
166
+ end
167
+ end
168
+
169
+ super do
170
+ raise DivergenceError.new if Thread.current == thread
171
+ thread.join
172
+ raise exception if exception
173
+ result
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ module Kernel
180
+ # The promise() function is used together with demand() to implement
181
+ # lazy evaluation. It returns a promise to evaluate the provided
182
+ # block at a future time. Evaluation can be demanded and the block's
183
+ # result obtained via the demand() function.
184
+ #
185
+ # Implicit evaluation is also supported: the first message sent to it will
186
+ # demand evaluation, after which that message and any subsequent messages
187
+ # will be forwarded to the result object.
188
+ #
189
+ # As an aid to circular programming, the block will be passed a promise
190
+ # for its own result when it is evaluated. Be careful not to force
191
+ # that promise during the computation, lest the computation diverge.
192
+ #
193
+ def promise(&computation)
194
+ Lazy::Promise.new(&computation)
195
+ end
196
+
197
+ # Forces the result of a promise to be computed (if necessary) and returns
198
+ # the bare result object. Once evaluated, the result of the promise will
199
+ # be cached. Nested promises will be evaluated together, until the first
200
+ # non-promise result.
201
+ #
202
+ # If called on a value that is not a promise, it will simply return it.
203
+ #
204
+ def demand(promise)
205
+ if promise.respond_to? :__result__
206
+ promise.__result__
207
+ else
208
+ promise
209
+ end
210
+ end
211
+
212
+ # Schedules a computation to be run asynchronously in a background thread
213
+ # and returns a promise for its result. An attempt to demand the result of
214
+ # the promise will block until the computation finishes.
215
+ #
216
+ # As with Kernel.promise, this passes the block a promise for its own result.
217
+ # Use wisely.
218
+ #
219
+ def future(&computation)
220
+ Lazy::Future.new(&computation)
221
+ end
222
+ end
@@ -0,0 +1,243 @@
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
+ module Garcon
21
+ module Extensions
22
+ # MethodReader allows you to access keys of the hash via method calls. This
23
+ # gives you an OStruct like way to access your hash's keys. It will
24
+ # recognize keys either as strings or symbols.
25
+ #
26
+ # @note
27
+ # That while nil keys will be returned as nil, undefined keys will raise
28
+ # NoMethodErrors. Also note that #respond_to? has been patched to
29
+ # appropriately recognize key methods.
30
+ #
31
+ # @example
32
+ # class StashCache < Hash
33
+ # include Garcon::Extensions::MethodReader
34
+ # end
35
+ #
36
+ # cash = StashCache.new
37
+ # cash['box'] = 'full'
38
+ # cash.box # => 'full'
39
+ #
40
+ module MethodReader
41
+ def respond_to?(name, include_private = false)
42
+ return true if key?(name.to_s) || key?(name.to_sym)
43
+ super
44
+ end
45
+
46
+ def method_missing(name, *args)
47
+ if key?(name)
48
+ self[name]
49
+ else
50
+ sname = name.to_s
51
+ if key?(sname)
52
+ self[sname]
53
+ elsif sname[-1] == '?'
54
+ kname = sname[0..-2]
55
+ key?(kname) || key?(kname.to_sym)
56
+ else
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ # MethodWriter gives you #key_name= shortcuts for writing to your hash.
64
+ # Keys are written as strings, override #convert_key if you would like to
65
+ # have symbols or something else.
66
+ #
67
+ # @note
68
+ # That MethodWriter also overrides #respond_to such that any
69
+ # #method_name= will respond appropriately as true.
70
+ #
71
+ # @example
72
+ # class MyHash < Hash
73
+ # include Garcon::Extensions::MethodWriter
74
+ # end
75
+ #
76
+ # h = MyHash.new
77
+ # h.awesome = 'sauce'
78
+ # h['awesome'] # => 'sauce'
79
+ #
80
+ module MethodWriter
81
+ def respond_to?(name, include_private = false)
82
+ return true if name.to_s =~ /=$/
83
+ super
84
+ end
85
+
86
+ def method_missing(name, *args)
87
+ if args.size == 1 && name.to_s =~ /(.*)=$/
88
+ return self[convert_key(Regexp.last_match[1])] = args.first
89
+ end
90
+
91
+ super
92
+ end
93
+
94
+ def convert_key(key)
95
+ key.to_s
96
+ end
97
+ end
98
+
99
+ # MethodQuery gives you the ability to check for the truthiness of a key
100
+ # via method calls. Note that it will return false if the key is set to a
101
+ # non-truthful value, not if the key isn't set at all. Use #key? for
102
+ # checking if a key has been set.
103
+ #
104
+ # MethodQuery will check against both string and symbol names of the method
105
+ # for existing keys. It also patches #respond_to to appropriately detect
106
+ # the query methods.
107
+ #
108
+ # @example
109
+ # class MyHash < Hash
110
+ # include Garcon::Extensions::MethodQuery
111
+ # end
112
+ #
113
+ # h = MyHash.new
114
+ # h['abc'] = 123
115
+ # h.abc? # => true
116
+ # h['def'] = nil
117
+ # h.def? # => false
118
+ # h.hji? # => NoMethodError
119
+ #
120
+ module MethodQuery
121
+ def respond_to?(name, include_private = false)
122
+ if name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) ||
123
+ key?(Regexp.last_match[1].to_sym))
124
+ return true
125
+ end
126
+ super
127
+ end
128
+
129
+ def method_missing(name, *args)
130
+ if args.empty? && name.to_s =~ /(.*)\?$/ &&
131
+ (key?(Regexp.last_match[1]) ||
132
+ key?(Regexp.last_match[1].to_sym))
133
+ return self[Regexp.last_match[1]] ||
134
+ self[Regexp.last_match[1].to_sym]
135
+ end
136
+ super
137
+ end
138
+ end
139
+
140
+ # A macro module that will automatically include MethodReader,
141
+ # MethodWriter, and MethodQuery, giving you the ability to read, write,
142
+ # and query keys in a hash using method call shortcuts.
143
+ #
144
+ module MethodAccess
145
+ def self.included(base)
146
+ [MethodReader, MethodWriter, MethodQuery].each do |mod|
147
+ base.send :include, mod
148
+ end
149
+ end
150
+ end
151
+
152
+ # MethodOverridingWriter gives you #key_name= shortcuts for writing to your
153
+ # hash. It allows methods to be overridden by #key_name= shortcuts and
154
+ # aliases those methods with two leading underscores.
155
+ #
156
+ # Keys are written as strings. Override #convert_key if you would like to
157
+ # have symbols or something else.
158
+ #
159
+ # Note that MethodOverridingWriter also overrides #respond_to_missing? such
160
+ # that any #method_name= will respond appropriately as true.
161
+ #
162
+ # @example
163
+ # class MyHash < Hash
164
+ # include Garcon::Extensions::MethodOverridingWriter
165
+ # end
166
+ #
167
+ # h = MyHash.new
168
+ # h.awesome = 'sauce'
169
+ # h['awesome'] # => 'sauce'
170
+ # h.zip = 'a-dee-doo-dah'
171
+ # h.zip # => 'a-dee-doo-dah'
172
+ # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
173
+ #
174
+ module MethodOverridingWriter
175
+ def convert_key(key)
176
+ key.to_s
177
+ end
178
+
179
+ def method_missing(name, *args)
180
+ if args.size == 1 && name.to_s =~ /(.*)=$/
181
+ key = Regexp.last_match[1]
182
+ redefine_method(key) if method?(key) && !already_overridden?(key)
183
+ return self[convert_key(key)] = args.first
184
+ end
185
+
186
+ super
187
+ end
188
+
189
+ def respond_to_missing?(name, include_private = false)
190
+ return true if name.to_s.end_with?('=')
191
+ super
192
+ end
193
+
194
+ protected
195
+
196
+ def already_overridden?(name)
197
+ method?("__#{name}")
198
+ end
199
+
200
+ def method?(name)
201
+ methods.map(&:to_s).include?(name)
202
+ end
203
+
204
+ def redefine_method(method_name)
205
+ eigenclass = class << self; self; end
206
+ eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
207
+ eigenclass.__send__(:define_method, method_name, -> {self[method_name]})
208
+ end
209
+ end
210
+
211
+ # A macro module that will automatically include MethodReader,
212
+ # MethodOverridingWriter, and MethodQuery, giving you the ability to read,
213
+ # write, and query keys in a hash using method call shortcuts that can
214
+ # override object methods. Any overridden object method is automatically
215
+ # aliased with two leading underscores.
216
+ #
217
+ module MethodAccessWithOverride
218
+ def self.included(base)
219
+ [MethodReader, MethodOverridingWriter, MethodQuery].each do |mod|
220
+ base.send :include, mod
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ module Extensions
227
+ module PrettyInspect
228
+ def self.included(base)
229
+ base.send :alias_method, :hash_inspect, :inspect
230
+ base.send :alias_method, :inspect, :stash_inspect
231
+ end
232
+
233
+ def stash_inspect
234
+ ret = "#<#{self.class}"
235
+ keys.sort_by(&:to_s).each do |key|
236
+ ret << " #{key}=#{self[key].inspect}"
237
+ end
238
+ ret << '>'
239
+ ret
240
+ end
241
+ end
242
+ end
243
+ end