arrow 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. metadata +339 -0
@@ -0,0 +1,615 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'uri'
5
+ require 'pluginfactory'
6
+ require 'forwardable'
7
+ require 'uri'
8
+
9
+ require 'arrow'
10
+ require 'arrow/constants'
11
+ require 'arrow/mixins'
12
+ require 'arrow/exceptions'
13
+ require 'arrow/object'
14
+
15
+ #
16
+ # The Arrow::Config class, instances of which use used to
17
+ # load and save configuration values for an Arrow application.
18
+ #
19
+ # == Description
20
+ # The configuration values are as follows:
21
+ #
22
+ # [<b>logging</b>]
23
+ # Arrow::Logger configuration. See arrow/logger.rb for specifics about this
24
+ # section.
25
+ # [<b>applets</b>]
26
+ # Applet configuration values:
27
+ # [<b>path</b>]
28
+ # An Arrow::Path object or colon-delimited list of directories to search for
29
+ # applet files. Defaults to: "./applets:/www/applets".
30
+ # [<b>pattern</b>]
31
+ # A glob pattern that will be used to search for applets to
32
+ # load. Default to '*.rb'.
33
+ # [<b>pollInterval</b>]
34
+ # The number of seconds between checks of the applet path for
35
+ # new/updated/deleted applet files. Defaults to 5.
36
+ # [<b>noSuchAppletHandler</b>]
37
+ # The URI of the applet which should handle requests for applets that don't
38
+ # exist. A value of '(builtin)' (the default) will cause a builtin handler to
39
+ # be invoked.
40
+ # [<b>errorHandler</b>]
41
+ # The URI of the applet which should handle untrapped exceptions raised
42
+ # from other applets. A value of '(builtin)' (the default) will cause a
43
+ # builtin handler to be invoked.
44
+ # [<b>templates</b>]
45
+ # Template configuration values:
46
+ # [<b>loader</b>]
47
+ # The name of a class or module to use to load templates for use in the
48
+ # appserver. Defaults to 'Arrow::Template'.
49
+ # [<b>path</b>]
50
+ # An Arrow::Path object or colon-delimited list of directories to search for
51
+ # templates. Defaults to "templates:/www/templates".
52
+ # [<b>cache</b>]
53
+ # Flag that determines whether or not templates are cached in an LRU cache
54
+ # in the TemplateFactory or loaded anew everytime. Default to +true+
55
+ # (templates are cached).
56
+ # [<b>cacheConfig</b>]
57
+ # Configuration for the template cache. If template caching is turned off,
58
+ # this section is ignored.
59
+ # <b>maxNum</b>:: The maximum number of templates to cache. Default to 20.
60
+ # <b>maxSize</b>:: The maximum estimated size of all cached objects. When
61
+ # the cache exceeds this size in bytes, the
62
+ # least-recently used one/s will be dropped until the
63
+ # cache's total size is less than this value.
64
+ # <b>maxObjSize</b>:: The maximum size of the cache, in bytes. If an
65
+ # object exceeeds this number of bytes in estimated
66
+ # size, it will not be cached.
67
+ # <b>expiration</b>:: The maximum lifetime of an object in the cache, in
68
+ # seconds. Objects which were cached more than this
69
+ # number of seconds before retrieval will be dropped.
70
+ # [<b>session</b>]
71
+ # Session configuration values:
72
+ # [<b>idType</b>]
73
+ # A URI which represents the id class to use and its configuration. See the
74
+ # documentation for Arrow::Session::Id and its derivatives for the form of
75
+ # the URI. 'md5:.' is the default.
76
+ # [<b>lockType</b>]
77
+ # A URI which specifies what locking class to use and its configuration. If
78
+ # this is the string +'recommended'+, the lock object will be created by
79
+ # calling the #create_recommended_lock method of the store. Defaults to
80
+ # 'recommended'. See Arrow::Session::Lock and its derivatives for the format
81
+ # of the URI.
82
+ # [<b>storeType</b>]
83
+ # A URI which specifies what backing store class to use for storing the
84
+ # session data between requests and its configuration. Default to
85
+ # 'file:/tmp'; see the documentation for Arrow::Session::Store and its
86
+ # derivatives for the form of the URI.
87
+ # [<b>idName</b>]
88
+ # The name of the session cookie and/or the session id parameter that will
89
+ # be inserted in rewritten URLs. Defaults to 'arrow-session'.
90
+ # [<b>rewriteUrls</b>]
91
+ # If set to +true+, any self-referential URLs in the appserver's output will
92
+ # be rewritten to include the session id as a parameter. Defaults to +true+.
93
+ # [<b>expires</b>]
94
+ # Set the expiration time of the session cookie. Defaults to "+48h"; see
95
+ # documentation for Apache::Cookie#expires for the format of the string.
96
+ #
97
+ #
98
+ # == Authors
99
+ #
100
+ # * Michael Granger <ged@FaerieMUD.org>
101
+ #
102
+ # Please see the file LICENSE in the top-level directory for licensing details.
103
+ #
104
+ class Arrow::Config < Arrow::Object
105
+ include Arrow::HashUtilities
106
+ extend Forwardable
107
+
108
+ require 'arrow/path'
109
+
110
+ # Define the layout and defaults for the underlying structs
111
+ DEFAULTS = {
112
+ :logging => { :global => 'notice' },
113
+
114
+ :gems => {
115
+ :require_signed => false,
116
+ :autoinstall => false,
117
+ :path => Arrow::Path.new([ "gems", *Gem.path ]),
118
+ :applets => {},
119
+ },
120
+
121
+ :applets => {
122
+ :path => Arrow::Path.new( "applets:/www/applets" ),
123
+ :pattern => '**/*.rb',
124
+ :pollInterval => 5,
125
+ :layout => {},
126
+ :config => {},
127
+ :missingApplet => '/missing',
128
+ :errorApplet => '/error',
129
+ },
130
+
131
+ :templates => {
132
+ :loader => 'Arrow::Template',
133
+ :path => Arrow::Path.new( "templates:/www/templates" ),
134
+ :cache => true,
135
+ :cacheConfig => {
136
+ :maxNum => 20,
137
+ :maxSize => (1<<17) * 20,
138
+ :maxObjSize => (1<<17),
139
+ :expiration => 36
140
+ },
141
+ },
142
+
143
+ :session => {
144
+ :idType => 'md5:.',
145
+ :lockType => 'recommended',
146
+ :storeType => 'file:/tmp',
147
+ :idName => 'arrow-session',
148
+ :rewriteUrls => true,
149
+ :expires => "+48h",
150
+ },
151
+ }
152
+ DEFAULTS.freeze
153
+
154
+
155
+
156
+ #############################################################
157
+ ### C L A S S M E T H O D S
158
+ #############################################################
159
+
160
+ ### The default config file loader to use
161
+ @default_loader = 'yaml'
162
+ @loaders = {}
163
+ class << self
164
+ attr_accessor :default_loader, :loaders
165
+ end
166
+
167
+
168
+ ### Get the loader by the given name, creating a new one if one is not
169
+ ### already instantiated.
170
+ def self::get_loader( name=nil )
171
+ name ||= self.default_loader
172
+ self.loaders[name] ||= Arrow::Config::Loader.create( name )
173
+ end
174
+
175
+
176
+ ### Read and return an Arrow::Config object from the given file or
177
+ ### configuration source using the specified +loader+.
178
+ def self::load( source, loader_obj=nil )
179
+ loader_obj = self.get_loader( loader_obj ) unless
180
+ loader_obj.is_a?( Arrow::Config::Loader )
181
+ my_source = source.dup
182
+ my_source.untaint
183
+ confighash = loader_obj.load( my_source )
184
+
185
+ obj = new( confighash )
186
+ obj.loader = loader_obj
187
+ obj.name = my_source
188
+
189
+ return obj
190
+ end
191
+
192
+
193
+ #############################################################
194
+ ### I N S T A N C E M E T H O D S
195
+ #############################################################
196
+
197
+ ### Create a new Arrow::Config object. Values passed in via the
198
+ ### +confighash+ will be used instead of the defaults.
199
+ def initialize( confighash={} )
200
+ ihash = internify_keys( untaint_values(confighash) )
201
+ mergedhash = DEFAULTS.merge( ihash, &HashMergeFunction )
202
+
203
+ @struct = ConfigStruct.new( mergedhash )
204
+ @create_time = Time.now
205
+ @name = nil
206
+ @loader = nil
207
+
208
+ super()
209
+ end
210
+
211
+
212
+ ######
213
+ public
214
+ ######
215
+
216
+ # Define delegators to the inner data structure
217
+ def_delegators :@struct, :to_hash, :to_h, :member?, :members, :merge,
218
+ :merge!, :each, :[], :[]=
219
+
220
+ # The underlying config data structure
221
+ attr_reader :struct
222
+
223
+ # The time the configuration was loaded
224
+ attr_accessor :create_time
225
+
226
+ # The name of the associated record stored on permanent storage for this
227
+ # configuration.
228
+ attr_accessor :name
229
+
230
+
231
+ ### Change the configuration object's loader. The +new_loader+ argument
232
+ ### can be either an Arrow::Config::Loader object or the name of one
233
+ ### suitable for passing to Arrow::Config::Loader.create.
234
+ def loader=( new_loader )
235
+ if new_loader.is_a?( Arrow::Config::Loader )
236
+ @loader = new_loader
237
+ else
238
+ @loader = self.class.get_loader( new_loader )
239
+ end
240
+ end
241
+
242
+
243
+ ### Fetch the loader from this config object, or create an instance
244
+ ### of the default one if none is yet associated with it.
245
+ def loader
246
+ @loader ||= self.class.get_loader
247
+ end
248
+
249
+
250
+ ### Write the configuration object using the specified name and any
251
+ ### additional +args+.
252
+ def write( name=@name, *args )
253
+ raise ArgumentError,
254
+ "No name associated with this config." unless name
255
+ lobj = self.loader
256
+ strHash = stringify_keys( @struct.to_h )
257
+ self.loader.save( strHash, name, *args )
258
+ end
259
+
260
+
261
+ ### Returns +true+ for methods which can be autoloaded
262
+ def respond_to?( sym )
263
+ return true if @struct.member?( sym.to_s.sub(/(=|\?)$/, '') )
264
+ super
265
+ end
266
+
267
+
268
+ ### Returns +true+ if the configuration has changed since it was last
269
+ ### loaded, either by setting one of its members or changing the file
270
+ ### from which it was loaded.
271
+ def changed?
272
+ return self.changed_reason ? true : false
273
+ end
274
+
275
+
276
+ ### If the configuration has changed, return the reason. If it hasn't,
277
+ ### returns nil.
278
+ def changed_reason
279
+ return "Struct was modified" if @struct.modified?
280
+
281
+ if self.name && self.loader.is_newer?( self.name, self.create_time )
282
+ return "Config source (%s) has been updated since %s" %
283
+ [ self.name, self.create_time ]
284
+ end
285
+
286
+ return nil
287
+ end
288
+
289
+
290
+ ### Reload the configuration from the original source if it has
291
+ ### changed. Returns +true+ if it was reloaded and +false+ otherwise.
292
+ def reload
293
+ return false unless @loader && @name
294
+
295
+ # Even if reloading fails, reset the creation time so we don't keep
296
+ # trying to reload a broken config
297
+ self.create_time = Time.now
298
+
299
+ confighash = @loader.load( @name )
300
+ ihash = internify_keys( untaint_values(confighash) )
301
+ mergedhash = DEFAULTS.merge( ihash, &HashMergeFunction )
302
+
303
+ @struct = ConfigStruct.new( mergedhash )
304
+
305
+ rescue => err
306
+ self.log.error "Error while trying to reload the config: %s" % err.message
307
+ err.backtrace.each {|frame| self.log.debug " " + frame }
308
+
309
+ return false
310
+ else
311
+ return true
312
+ end
313
+
314
+
315
+ #########
316
+ protected
317
+ #########
318
+
319
+ ### Hook up delegators to struct-members as they are called
320
+ def method_missing( sym, *args )
321
+ key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
322
+ return nil unless @struct.member?( key )
323
+
324
+ self.log.debug( "Autoloading #{key} accessors." )
325
+
326
+ self.class.class_eval %{
327
+ def #{key}; @struct.#{key}; end
328
+ def #{key}=(*args); @struct.#{key} = *args; end
329
+ def #{key}?; @struct.#{key}?; end
330
+ }
331
+
332
+ @struct.__send__( sym, *args )
333
+ end
334
+
335
+
336
+ #######
337
+ private
338
+ #######
339
+
340
+ ### Return a copy of the specified +hash+ with all of its values
341
+ ### untainted.
342
+ def untaint_values( hash )
343
+ newhash = {}
344
+
345
+ hash.each do |key,val|
346
+ case val
347
+ when Hash
348
+ newhash[ key ] = untaint_values( hash[key] )
349
+
350
+ when NilClass, TrueClass, FalseClass, Numeric, Symbol
351
+ newhash[ key ] = val
352
+
353
+ when Arrow::Path
354
+ # Arrow::Logger[ self ].debug "Untainting %p" % val
355
+ val.untaint
356
+ newhash[ key ] = val
357
+
358
+ when Array
359
+ # Arrow::Logger[ self ].debug "Untainting array %p" % val
360
+ newval = val.collect do |v|
361
+ case v
362
+ when NilClass, TrueClass, FalseClass, Numeric, Symbol
363
+ v
364
+ else
365
+ v.dup.untaint
366
+ end
367
+ end
368
+ newhash[ key ] = newval
369
+
370
+ else
371
+ # Arrow::Logger[ self ].debug "Untainting %p" % val
372
+ newval = val.dup
373
+ newval.untaint
374
+ newhash[ key ] = newval
375
+ end
376
+ end
377
+
378
+ return newhash
379
+ end
380
+
381
+
382
+ #############################################################
383
+ ### I N T E R I O R C L A S S E S
384
+ #############################################################
385
+
386
+ ### Hash-wrapper that allows struct-like accessor calls on nested
387
+ ### hashes.
388
+ class ConfigStruct < Arrow::Object
389
+ include Enumerable, Arrow::HashUtilities
390
+ extend Forwardable
391
+
392
+ # Mask most of Kernel's methods away so they don't collide with
393
+ # config values.
394
+ Kernel.methods(false).each {|meth|
395
+ next unless method_defined?( meth )
396
+ next if /^(?:__|dup|object_id|inspect|class|raise|method_missing)/.match( meth )
397
+ undef_method( meth )
398
+ }
399
+
400
+ # Forward some methods to the internal hash
401
+ def_delegators :@hash, :keys, :values, :value?, :[], :[]=, :length,
402
+ :empty?, :clear
403
+
404
+
405
+ ### Create a new ConfigStruct from the given +hash+.
406
+ def initialize( hash )
407
+ @hash = hash.dup
408
+ @dirty = false
409
+ end
410
+
411
+
412
+ ######
413
+ public
414
+ ######
415
+
416
+ # Modification flag. Set to +true+ to indicate the contents of the
417
+ # Struct have changed since it was created.
418
+ attr_writer :modified
419
+
420
+
421
+ ### Returns +true+ if the ConfigStruct or any of its sub-structs
422
+ ### have changed since it was created.
423
+ def modified?
424
+ @dirty || @hash.values.find do |obj|
425
+ obj.is_a?( ConfigStruct ) && obj.modified?
426
+ end
427
+ end
428
+
429
+
430
+ ### Return the receiver's values as a (possibly multi-dimensional)
431
+ ### Hash with String keys.
432
+ def to_hash
433
+ rhash = {}
434
+ @hash.each {|k,v|
435
+ case v
436
+ when ConfigStruct
437
+ rhash[k] = v.to_h
438
+ when NilClass, FalseClass, TrueClass, Numeric
439
+ # No-op (can't dup)
440
+ rhash[k] = v
441
+ when Symbol
442
+ rhash[k] = v.to_s
443
+ else
444
+ rhash[k] = v.dup
445
+ end
446
+ }
447
+ return rhash
448
+ end
449
+ alias_method :to_h, :to_hash
450
+
451
+
452
+ ### Return +true+ if the receiver responds to the given
453
+ ### method. Overridden to grok autoloaded methods.
454
+ def respond_to?( sym, priv=false )
455
+ key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
456
+ return true if self.member?( key )
457
+ super
458
+ end
459
+
460
+
461
+ ### Returns an Array of Symbols, on for each of the struct's members.
462
+ def members
463
+ @hash.keys
464
+ end
465
+
466
+
467
+ ### Returns +true+ if the given +name+ is the name of a member of
468
+ ### the receiver.
469
+ def member?( name )
470
+ return @hash.key?( name.to_sym )
471
+ end
472
+ alias_method :key?, :member?
473
+ alias_method :has_key?, :key?
474
+
475
+
476
+ ### Call into the given block for each member of the receiver.
477
+ def each( &block ) # :yield: member, value
478
+ @hash.each( &block )
479
+ end
480
+ alias_method :each_section, :each
481
+
482
+
483
+ ### Merge the specified +other+ object with this config struct. The
484
+ ### +other+ object can be either a Hash, another ConfigStruct, or an
485
+ ### Arrow::Config.
486
+ def merge!( other )
487
+ case other
488
+ when Hash
489
+ @hash = self.to_h.merge( other,
490
+ &HashMergeFunction )
491
+
492
+ when ConfigStruct
493
+ @hash = self.to_h.merge( other.to_h,
494
+ &HashMergeFunction )
495
+
496
+ when Arrow::Config
497
+ @hash = self.to_h.merge( other.struct.to_h,
498
+ &HashMergeFunction )
499
+
500
+ else
501
+ raise TypeError,
502
+ "Don't know how to merge with a %p" % other.class
503
+ end
504
+
505
+ # :TODO: Actually check to see if anything has changed?
506
+ @dirty = true
507
+
508
+ return self
509
+ end
510
+
511
+
512
+ ### Return a new ConfigStruct which is the result of merging the
513
+ ### receiver with the given +other+ object (a Hash or another
514
+ ### ConfigStruct).
515
+ def merge( other )
516
+ self.dup.merge!( other )
517
+ end
518
+
519
+
520
+ #########
521
+ protected
522
+ #########
523
+
524
+ ### Handle calls to key-methods
525
+ def method_missing( sym, *args )
526
+ key = sym.to_s.sub( /(=|\?)$/, '' ).to_sym
527
+ return nil unless @hash.key?( key )
528
+
529
+ self.class.class_eval {
530
+ define_method( key ) {
531
+ if @hash[ key ].is_a?( Hash )
532
+ @hash[ key ] = ConfigStruct.new( @hash[key] )
533
+ end
534
+
535
+ @hash[ key ]
536
+ }
537
+ define_method( "#{key}?" ) {@hash[key] ? true : false}
538
+ define_method( "#{key}=" ) {|val|
539
+ @dirty = @hash[key] != val
540
+ @hash[key] = val
541
+ }
542
+ }
543
+
544
+ self.__send__( sym, *args )
545
+ end
546
+ end # class ConfigStruct
547
+
548
+
549
+ ### Abstract base class (and Factory) for configuration loader
550
+ ### delegates. Create specific instances with the
551
+ ### Arrow::Config::Loader.create method.
552
+ class Loader < Arrow::Object
553
+ include PluginFactory
554
+
555
+ #########################################################
556
+ ### C L A S S M E T H O D S
557
+ #########################################################
558
+
559
+ ### Returns a list of directories to search for deriviatives.
560
+ def self::derivativeDirs
561
+ ["arrow/config-loaders"]
562
+ end
563
+
564
+
565
+ #########################################################
566
+ ### I N S T A N C E M E T H O D S
567
+ #########################################################
568
+
569
+ ######
570
+ public
571
+ ######
572
+
573
+ ### Load configuration values from the storage medium associated
574
+ ### with the given +name+ (e.g., filename, rowid, etc.) and return
575
+ ### them in the form of a (possibly multi-dimensional) Hash.
576
+ def load( name )
577
+ raise NotImplementedError,
578
+ "required method 'load' not implemented in '#{self.class.name}'"
579
+ end
580
+
581
+
582
+ ### Save configuration values from the given +confighash+ to the
583
+ ### storage medium associated with the given +name+ (e.g., filename,
584
+ ### rowid, etc.) and return them.
585
+ def save( confighash, name )
586
+ raise NotImplementedError,
587
+ "required method 'save' not implemented in '#{self.class.name}'"
588
+ end
589
+
590
+
591
+ ### Returns +true+ if the configuration values in the storage medium
592
+ ### associated with the given +name+ has changed since the given
593
+ ### +time+.
594
+ def is_newer?( name, time )
595
+ raise NotImplementedError,
596
+ "required method 'is_newer?' not implemented in '#{self.class.name}'"
597
+ end
598
+
599
+ end # class Loader
600
+
601
+ end # class Arrow::Config
602
+
603
+
604
+ ### If run directly, write a default config file to the current directory
605
+ if __FILE__ == $0
606
+ filename = ARGV.shift || "default.cfg"
607
+ loader = ARGV.shift || Arrow::Config.default_loader
608
+
609
+ $stderr.puts "Dumping default configuration to '%s' using the '%s' loader" %
610
+ [ filename, loader ]
611
+
612
+ conf = Arrow::Config.new
613
+ conf.loader = loader
614
+ conf.write( filename )
615
+ end