arrow 1.0.7

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 (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