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,343 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'strscan'
4
+ require 'arrow/object'
5
+ require 'arrow/mixins'
6
+
7
+
8
+ module Arrow
9
+
10
+ # The Arrow::HTMLTokenizer class -- a simple HTML parser that can be used to break HTML
11
+ # down into tokens.
12
+ #
13
+ # Some of the code and design were stolen from the excellent HTMLTokenizer
14
+ # library by Ben Giddings <bg@infofiend.com>.
15
+ #
16
+ # == VCS Id
17
+ #
18
+ # $Id$
19
+ #
20
+ # == Authors
21
+ #
22
+ # * Michael Granger <ged@FaerieMUD.org>
23
+ #
24
+ # :include: LICENSE
25
+ #
26
+ #--
27
+ #
28
+ # Please see the file LICENSE in the top-level directory for licensing details.
29
+ #
30
+ class HTMLTokenizer < Arrow::Object
31
+ include Enumerable
32
+
33
+ # SVN Revision
34
+ SVNRev = %q$Rev$
35
+
36
+ # SVN Id
37
+ SVNId = %q$Id$
38
+
39
+
40
+ ### Create a new Arrow::HtmlTokenizer object.
41
+ def initialize( source )
42
+ @source = source
43
+ @scanner = StringScanner.new( source )
44
+ end
45
+
46
+
47
+ ######
48
+ public
49
+ ######
50
+
51
+ # The HTML source being tokenized
52
+ attr_reader :source
53
+
54
+ # The StringScanner doing the tokenizing
55
+ attr_reader :scanner
56
+
57
+
58
+ ### Enumerable interface: Iterates over parsed tokens, calling the
59
+ ### supplied block with each one.
60
+ def each
61
+ @scanner.reset
62
+
63
+ until @scanner.empty?
64
+ if @scanner.peek(1) == '<'
65
+ tag = @scanner.scan_until( />/ )
66
+
67
+ case tag
68
+ when /^<!--/
69
+ token = HTMLComment.new( tag )
70
+ when /^<!/
71
+ token = DocType.new( tag )
72
+ when /^<\?/
73
+ token = ProcessingInstruction.new( tag )
74
+ else
75
+ token = HTMLTag.new( tag )
76
+ end
77
+ else
78
+ text = @scanner.scan( /[^<]+/ )
79
+ token = HTMLText.new( text )
80
+ end
81
+
82
+ yield( token )
83
+ end
84
+ end
85
+
86
+
87
+
88
+ #########
89
+ protected
90
+ #########
91
+
92
+
93
+ end # class HTMLTokenizer
94
+
95
+
96
+ ### Base class for HTML tokens output by Arrow::HTMLTokenizer.
97
+ class HTMLToken < Arrow::Object # :nodoc:
98
+
99
+ ### Initialize a token with the +raw+ source of it.
100
+ def initialize( raw ) # :notnew:
101
+ super()
102
+ @raw = raw
103
+ end
104
+
105
+ # The raw source of the token
106
+ attr_accessor :raw
107
+ alias_method :to_s, :raw
108
+
109
+ ### Return an HTML fragment that can be used to represent the token
110
+ ### symbolically in a web-based introspection interface.
111
+ def to_html
112
+ content = nil
113
+
114
+ if block_given?
115
+ content = yield
116
+ # self.log.debug "content = %p" % content
117
+ else
118
+ content = self.escape_html( @raw )
119
+ end
120
+
121
+ tokenclass = self.css_class
122
+
123
+ %q{<span class="token %s">%s</span>} % [
124
+ tokenclass,
125
+ content,
126
+ ]
127
+ end
128
+
129
+
130
+ ### Return the HTML element class attribute that corresponds to this node.
131
+ def css_class
132
+ tokenclass = self.class.name.
133
+ sub( /Arrow::(HTML)?/i, '').
134
+ gsub( /::/, '-' ).
135
+ gsub( /([a-z])([A-Z])/, "\\1-\\2" ).
136
+ gsub( /[^-\w]+/, '' ).
137
+ downcase
138
+ tokenclass << "-token" unless /-token$/.match( tokenclass )
139
+
140
+ return tokenclass
141
+ end
142
+
143
+
144
+ ### Escape special characters in the given +string+ for display in an
145
+ ### HTML inspection interface. This escapes common invisible characters
146
+ ### like tabs and carriage-returns in additional to the regular HTML
147
+ ### escapes.
148
+ def escape_html( string )
149
+ return "nil" if string.nil?
150
+ string = string.inspect unless string.is_a?( String )
151
+ string.
152
+ gsub(/&/, '&amp;').
153
+ gsub(/</, '&lt;').
154
+ gsub(/>/, '&gt;').
155
+ gsub(/\r?\n/, %Q{<br />\n}).
156
+ gsub(/\t/, '&nbsp;&nbsp;&nbsp;&nbsp;')
157
+ end
158
+ end
159
+
160
+
161
+ ### Class for tokens output by Arrow::HTMLTokenizer for the text bits of an
162
+ ### HTML document.
163
+ class HTMLText < HTMLToken # :nodoc:
164
+
165
+ ### Return an HTML fragment that can be used to represent the token
166
+ ### symbolically in a web-based introspection interface.
167
+ def to_html
168
+ marked = self.escape_html( @raw )
169
+ marked.gsub( /(&amp;[^;]+;)/ ) {|ent|
170
+ %Q{<span class="entity">#{ent}</span>}
171
+ }
172
+ super { marked }
173
+ end
174
+
175
+ end
176
+
177
+
178
+ ### Class for tokens output by Arrow::HTMLTokenizer for HTML comments.
179
+ class HTMLComment < HTMLToken # :nodoc:
180
+ CommentPattern = /^<!--((?:[^-]|-(?!-))*)-->$/
181
+
182
+ def initialize( raw )
183
+ super
184
+
185
+ unless (( match = CommentPattern.match(raw) ))
186
+ raise ArgumentError,
187
+ "Malformed comment %p" % raw
188
+ end
189
+
190
+ @contents = match[1]
191
+ end
192
+
193
+ attr_accessor :contents
194
+ end
195
+
196
+
197
+ ### Class for tokens output by Arrow::HTMLTokenizer for the tags in an HTML
198
+ ### document.
199
+ class HTMLTag < HTMLToken # :nodoc:
200
+
201
+ # The pattern for matching tag attribute key-value pairs
202
+ AttributePattern = %r{
203
+ \s*([-A-Za-z:]+)
204
+ (?:\s*=\s*(
205
+ "(?:[^"]|\\.)*" | # Match strings quoted with "
206
+ '(?:[^']|\\.)*' | # Match strings quoted with '
207
+ \S+ # Match non-whitespace
208
+ ))?
209
+ }mx
210
+
211
+ #############################################################
212
+ ### I N S T A N C E M E T H O D S
213
+ #############################################################
214
+
215
+ ### Create a new HTMLTag from the specified raw source.
216
+ def initialize( raw )
217
+ unless (( match = /<\s*(\/)?(\w+)\s*([^>]*)>/.match(raw) ))
218
+ raise ArgumentError,
219
+ "Malformed HTMLTag: %p" % raw
220
+ end
221
+
222
+ @endtag = !match[1].nil?
223
+ @tagname = match[2]
224
+ @rawattrs = match[3] || ''
225
+ @attrs = nil
226
+
227
+ super
228
+ end
229
+
230
+
231
+ ######
232
+ public
233
+ ######
234
+
235
+ # The name of the tag
236
+ attr_reader :tagname
237
+
238
+ ### Returns +true+ if this tag is an closing tag
239
+ def endtag?; @endtag; end
240
+
241
+
242
+ ### Return the Hash of tag attributes belonging to this token.
243
+ def attrs
244
+ unless @attrs
245
+ @attrs = {}
246
+ @rawattrs.scan( AttributePattern ) {|name,value|
247
+ ns = nil
248
+ if /:/ =~ name
249
+ ns, name = name.split(/:/, 2)
250
+ if ns == 'html' then ns = nil end
251
+ end
252
+ cname = name.gsub(/-/, '_').downcase
253
+ cval = value.nil? ? true : value.gsub(/^["']|['"]$/, '')
254
+
255
+ if ns.nil?
256
+ @attrs[ cname.to_sym ] = cval
257
+ else
258
+ @attrs[ ns.to_sym ] ||= {}
259
+ @attrs[ ns.to_sym ][ name.to_sym ] = cval
260
+ end
261
+ }
262
+ end
263
+
264
+ return @attrs
265
+ end
266
+
267
+
268
+ ### Return the tag attribute with the specified name (if it exists).
269
+ def []( name )
270
+ self.attrs[ name.gsub(/-/, '_').downcase.to_sym ]
271
+ end
272
+
273
+
274
+ ### Return an HTML fragment that can be used to represent the token
275
+ ### symbolically in a web-based introspection interface.
276
+ def to_html
277
+ tagopen, tagbody = @raw.split( /\s+/, 2 )
278
+ # self.log.debug "tagopen = %p, tagbody = %p" % [ tagopen, tagbody ]
279
+
280
+ tagopen = self.escape_html( tagopen ).sub( %r{^&lt;(/)?(\w+)} ) {|match|
281
+ %Q{&lt;#$1<span class="tag-token-name">#$2</span>}
282
+ }
283
+
284
+ unless tagbody.nil?
285
+ tagbody.sub!( />$/, '' )
286
+ tagbody = self.escape_html( tagbody ).gsub( AttributePattern ) {|match|
287
+ name, mid, val = match.split(/(\s*=\s*)/, 2)
288
+
289
+ val.gsub!( /(\[\?(?:[^\?]|\?(?!\]))+\?\])/s ) {|m|
290
+ %q{<span class="%s">%s</span>} %
291
+ [ 'tag-attr-directive', m ]
292
+ }
293
+
294
+ %q{<span class="%s">%s</span>%s<span class="%s">%s</span>} % [
295
+ 'tag-token-attr-name',
296
+ name,
297
+ mid,
298
+ 'tag-token-attr-value',
299
+ val,
300
+ ]
301
+ }
302
+ tagbody << '&gt;'
303
+ end
304
+
305
+ #self.log.debug "tagopen = %p; tagbody = %p" %
306
+ # [ tagopen, tagbody ]
307
+ super { [tagopen, tagbody].compact.join(" ") }
308
+ end
309
+
310
+ ### Escape special characters in the given +string+ for display in an
311
+ ### HTML inspection interface.
312
+ def escape_html( string )
313
+ return "nil" if string.nil?
314
+ string = string.inspect unless string.is_a?( String )
315
+ string.
316
+ gsub(/&/, '&amp;').
317
+ gsub(/</, '&lt;').
318
+ gsub(/>/, '&gt;')
319
+ end
320
+ end
321
+
322
+
323
+ ### Class for tokens output by Arrow::HTMLTokenizer for the processing
324
+ ### instructions contained in an HTML document.
325
+ class ProcessingInstruction < HTMLToken # :nodoc:
326
+ def initialize( raw )
327
+ @instruction, @body = raw.gsub(/^\?|\?$/, '').split( /\s+/, 2 )
328
+ super
329
+ end
330
+
331
+ attr_accessor :instruction, :body
332
+ end
333
+
334
+
335
+ ### Class for tokens output by Arrow::HTMLTokenizer for the doctype
336
+ ### declaration of an HTML document.
337
+ class DocType < HTMLToken # :nodoc:
338
+ end
339
+
340
+
341
+ end # module Arrow
342
+
343
+
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'configurability'
4
+ require 'arrow/mixins'
5
+
6
+ # A hierarchical logging class for the Arrow framework. It provides a
7
+ # generalized means of logging from inside Arrow classes, and then selectively
8
+ # outputting/formatting log messages from points within the hierarchy.
9
+ #
10
+ # A lot of concepts in this class were stolen from Log4r, though it's all
11
+ # original code, and works a bit differently.
12
+ #
13
+ # == Synopsis
14
+ #
15
+ # require 'arrow/object'
16
+ # require 'arrow/logger'
17
+ #
18
+ # logger = Arrow::Logger.global
19
+ # logfile = File.open( "global.log", "a" )
20
+ # logger.outputters << Arrow::Logger::Outputter.new(logfile)
21
+ # logger.level = :debug
22
+ #
23
+ # class MyClass < Arrow::Object
24
+ #
25
+ # def self::fooMethod
26
+ # Arrow::Logger.debug( "In server start routine" )
27
+ # Arrow::Logger.info( "Server is not yet configured." )
28
+ # Arrow::Logger.notice( "Server is starting up." )
29
+ # end
30
+ #
31
+ # def initialize
32
+ # self.log.info( "Initializing another MyClass object." )
33
+ # end
34
+ # end
35
+ #
36
+ # == VCS Id
37
+ #
38
+ # $Id: logger.rb,v ba725438313d 2010/08/09 02:08:40 ged $
39
+ #
40
+ # == Authors
41
+ #
42
+ # * Michael Granger <ged@FaerieMUD.org>
43
+ #
44
+ # Please see the file LICENSE in the top-level directory for licensing details.
45
+ #
46
+ class Arrow::Logger
47
+ extend Configurability
48
+
49
+ config_key :logging
50
+
51
+ require 'arrow/logger/outputter'
52
+
53
+
54
+ # Construct a log levels Hash on the fly
55
+ LEVELS = [
56
+ :debug,
57
+ :info,
58
+ :notice,
59
+ :warning,
60
+ :error,
61
+ :crit,
62
+ :alert,
63
+ :emerg,
64
+ ].inject({}) {|hsh, sym| hsh[ sym ] = hsh.length; hsh}
65
+ LEVEL_NAMES = LEVELS.invert
66
+
67
+ ### Module for adding internals debugging to the Logger class
68
+ module DebugLogger # :nodoc:
69
+ def debug_msg( *parts ) # :nodoc:
70
+ # $deferr.puts parts.join('') if $DEBUG
71
+ end
72
+ end
73
+
74
+ include DebugLogger
75
+ extend DebugLogger
76
+
77
+
78
+
79
+ #############################################################
80
+ ### C L A S S M E T H O D S
81
+ #############################################################
82
+
83
+ # Loggers for Modules, keyed by Module
84
+ @logger_map = Hash.new do |h,mod|
85
+ h[ mod ] = self.new( mod )
86
+ end
87
+ class << self; attr_reader :logger_map ; end
88
+
89
+
90
+ ### Configure logging from the 'logging' section of the config.
91
+ def self::configure( config )
92
+
93
+ self.reset
94
+ apacheoutputter = Arrow::Logger::Outputter.create( 'apache' )
95
+
96
+ config.each do |klass, setting|
97
+ level, uri = self.parse_log_setting( setting )
98
+
99
+ # Use the Apache log as the outputter if none is configured
100
+ if uri.nil?
101
+ outputter = apacheoutputter
102
+ else
103
+ outputter = Arrow::Logger::Outputter.create( uri )
104
+ end
105
+
106
+ # The 'global' entry configures the global logger
107
+ if klass == :global
108
+ self.global.level = level
109
+ self.global.outputters << outputter
110
+ next
111
+ end
112
+
113
+ # If the class bit is something like 'applet', then transform
114
+ # it into 'Arrow::Applet'
115
+ if klass.to_s.match( /^[a-z][a-zA-Z]+$/ )
116
+ realclass = "Arrow::%s" % klass.to_s.sub(/^([a-z])/){ $1.upcase }
117
+ else
118
+ realclass = klass.to_s
119
+ end
120
+
121
+ Arrow::Logger[ realclass ].level = level
122
+ Arrow::Logger[ realclass ].outputters << outputter
123
+ end
124
+
125
+ end
126
+
127
+
128
+ ### Parse the configuration for a given class's logger. The configuration
129
+ ### is in the form:
130
+ ### <level> [<outputter_uri>]
131
+ ### where +level+ is one of the logging levels defined by this class (see
132
+ ### the LEVELS constant), and the optional +outputter_uri+ indicates which
133
+ ### outputter to use, and how it should be configured. See
134
+ ### Arrow::Logger::Outputter for more info.
135
+ ###
136
+ ### Examples:
137
+ ### notice
138
+ ### debug file:///tmp/broker-debug.log
139
+ ### error dbi://www:password@localhost/www.errorlog?driver=postgresql
140
+ ###
141
+ def self::parse_log_setting( setting )
142
+ level, rawuri = setting.split( ' ', 2 )
143
+ uri = rawuri.nil? ? nil : URI.parse( rawuri )
144
+
145
+ return level.to_sym, uri
146
+ end
147
+
148
+
149
+ ### Return the Arrow::Logger for the given module +mod+, which can be a
150
+ ### Module object, a Symbol, or a String.
151
+ def self::[]( mod=nil )
152
+ return self.global if mod.nil?
153
+
154
+ case mod
155
+ when Module
156
+ return self.logger_map[ mod ]
157
+
158
+ # If it's a String, try to map it to a class name, falling back on the global
159
+ # logger if that fails
160
+ when String
161
+ mod = mod.split('::').
162
+ inject( Object ) {|k, modname| k.const_get(modname) } rescue Object
163
+ return self.logger_map[ mod ]
164
+ else
165
+
166
+ return self.logger_map[ mod.class ]
167
+ end
168
+
169
+ end
170
+
171
+
172
+ ### Return the global Arrow logger, setting it up if it hasn't been
173
+ ### already.
174
+ def self::global
175
+ self.logger_map[ Object ]
176
+ end
177
+
178
+
179
+ ### Reset the logging subsystem. Clears out any registered loggers and
180
+ ### their associated outputters.
181
+ def self::reset
182
+ self.logger_map.clear
183
+ end
184
+
185
+
186
+ ### Autoload global logging methods for the log levels
187
+ def self::method_missing( sym, *args )
188
+ return super unless LEVELS.key?( sym )
189
+
190
+ self.global.debug( "Autoloading class log method '#{sym}'." )
191
+ (class << self; self; end).class_eval do
192
+ define_method( sym ) do |*args|
193
+ self.global.send( sym, *args )
194
+ end
195
+ end
196
+
197
+ self.global.send( sym, *args )
198
+ end
199
+
200
+
201
+ #############################################################
202
+ ### I N S T A N C E M E T H O D S
203
+ #############################################################
204
+
205
+ ### Create and return a new Arrow::Logger object for the given +mod+ (a Module object). If
206
+ ### It will be configured at the given +level+. Any +outputters+ that are specified will be
207
+ ### added.
208
+ def initialize( mod, level=:info, *outputters )
209
+ @module = mod
210
+ @outputters = outputters
211
+ @trace = false
212
+ @level = nil
213
+
214
+ # Cached Array of modules and classes between
215
+ # this logger's module and Object
216
+ @supermods = nil
217
+
218
+ # Level to force messages written to this logger to
219
+ @forced_level = nil
220
+
221
+ self.level = level
222
+ end
223
+
224
+
225
+ ######
226
+ public
227
+ ######
228
+
229
+ # The module this logger is associated with
230
+ attr_reader :module
231
+
232
+ # The outputters attached to this branch of the logger tree.
233
+ attr_accessor :outputters
234
+
235
+ # Set to a true value to turn tracing on
236
+ attr_accessor :trace
237
+
238
+ # The integer level of the logger.
239
+ attr_reader :level
240
+
241
+ # The level to force messages written to this logger to
242
+ attr_accessor :forced_level
243
+
244
+
245
+ ### Return a human-readable string representation of the object.
246
+ def inspect
247
+ "#<%s:0x%0x %s [level: %s, outputters: %d, trace: %s]>" % [
248
+ self.class.name,
249
+ self.object_id * 2,
250
+ self.readable_name,
251
+ self.readable_level,
252
+ self.outputters.length,
253
+ self.trace ? "on" : "off",
254
+ ]
255
+ end
256
+
257
+
258
+ ### Return a (more-detailed) human-readable string representation of the object.
259
+ def inspect_details( level=0 )
260
+ indent = ' ' * (level + 1)
261
+
262
+ prelude = "<< %s [level: %s, trace: %s] >>" % [
263
+ self.readable_name,
264
+ self.readable_level,
265
+ self.trace ? "on" : "off",
266
+ ]
267
+
268
+ details = []
269
+ unless self.outputters.empty?
270
+ details << "Outputters:" << self.outputters.map {|op| op.inspect }
271
+ end
272
+ details = details.flatten.compact.map {|line| indent + line }
273
+
274
+ if level.zero?
275
+ return [ prelude, *details ].join( "\n" )
276
+ else
277
+ return [ prelude, *details ]
278
+ end
279
+ end
280
+
281
+
282
+ ### Return the name of the logger formatted to be suitable for reading.
283
+ def readable_name
284
+ return '(global)' if self.module == Object
285
+ return self.module.inspect if self.module.name == ''
286
+ return self.module.name
287
+ end
288
+
289
+
290
+ ### Return the logger's level as a Symbol.
291
+ def readable_level
292
+ return LEVEL_NAMES[ @level ]
293
+ end
294
+
295
+
296
+ ### Set the level of this logger to +level+. The +level+ can be a
297
+ ### String, a Symbol, or an Integer.
298
+ def level=( level )
299
+ # debug_msg ">>> Setting log level for %s to %p" %
300
+ # [ self.name.empty? ? "[Global]" : self.name, level ]
301
+
302
+ case level
303
+ when String
304
+ @level = LEVELS[ level.to_sym ]
305
+ when Symbol
306
+ @level = LEVELS[ level ]
307
+ when Integer
308
+ @level = level
309
+ else
310
+ @level = nil
311
+ end
312
+
313
+ # If the level wasn't set correctly, raise an error after setting
314
+ # the level to something reasonable.
315
+ if @level.nil?
316
+ @level = LEVELS[ :notice ]
317
+ raise ArgumentError, "Illegal log level specification: %p for %s" %
318
+ [ level, self.name ]
319
+ end
320
+ end
321
+
322
+
323
+ ### Return the Arrow::Logger for this instance's module's parent class if it's a Class,
324
+ ### and the global logger otherwise.
325
+ def superlogger
326
+ if @module == Object
327
+ return nil
328
+ elsif @module.respond_to?( :superclass )
329
+ Arrow::Logger[ @module.superclass ]
330
+ else
331
+ Arrow::Logger.global
332
+ end
333
+ end
334
+
335
+
336
+ ### Return the Array of modules and classes the receiver's module includes
337
+ ### or inherits, inclusive of the receiver's module itself.
338
+ def supermods
339
+ unless @supermods
340
+ objflag = false
341
+ @supermods = self.module.ancestors.partition {|mod| objflag ||= (mod == Object) }.last
342
+ @supermods << Object
343
+ end
344
+
345
+ return @supermods
346
+ end
347
+
348
+
349
+
350
+ ### Return a uniquified Array of the loggers which are more-generally related
351
+ ### hierarchically to the receiver, inclusive, and whose level is +level+ or
352
+ ### lower.
353
+ def hierloggers( level=:emerg )
354
+ level = LEVELS[ level ] if level.is_a?( Symbol )
355
+
356
+ loggers = []
357
+ self.supermods.each do |mod|
358
+ logger = self.class.logger_map[ mod ]
359
+ next unless logger.level <= level
360
+
361
+ loggers << logger
362
+ yield( logger ) if block_given?
363
+ end
364
+
365
+ return loggers
366
+ end
367
+
368
+
369
+ ### Return a uniquified Array of all outputters for this logger and all of the
370
+ ### loggers above it in the logging hierarchy that are set to +level+ or lower.
371
+ ### If called with a block, it will be called once for each outputter and the first
372
+ ### logger to which it is attached.
373
+ def hieroutputters( level=LEVELS[:emerg] )
374
+ outputters = []
375
+
376
+ # Look for loggers which are higher in the hierarchy
377
+ self.hierloggers( level ) do |logger|
378
+ outpary = logger.outputters || []
379
+ newoutpary = outpary - (outpary & outputters)
380
+
381
+ # If there are any outputters which haven't already been seen,
382
+ # output to them.
383
+ unless newoutpary.empty?
384
+ # debug_msg "hieroutputters: adding: %s" %
385
+ # newoutpary.collect {|outp| outp.description}.join(", ")
386
+ if block_given?
387
+ newoutpary.each {|outputter| yield(outputter, logger)}
388
+ end
389
+ outputters += newoutpary
390
+ end
391
+ end
392
+
393
+ return outputters
394
+ end
395
+
396
+
397
+ ### Write the given +args+ to any connected outputters if +level+ is
398
+ ### less than or equal to this logger's level.
399
+ def write( level, *args )
400
+ # debug_msg "Writing message at %p from %s: %p" % [ level, caller(2).first, args ]
401
+
402
+ msg, frame = nil, nil
403
+ time = Time.now
404
+
405
+ # If tracing is turned on, pick the first frame in the stack that
406
+ # isn't in this file, or the last one if that fails to yield one.
407
+ if @trace
408
+ frame = caller(1).find {|fr| fr !~ %r{arrow/logger\.rb} } ||
409
+ caller(1).last
410
+ end
411
+
412
+ level = @forced_level if @forced_level
413
+
414
+ # Find the outputters that need to be written to, then write to them.
415
+ self.hieroutputters( level ) do |outp, logger|
416
+ # debug_msg "Got outputter %p" % outp
417
+ msg ||= args.collect {|obj| self.stringify_object(obj) }.join
418
+ outp.write( time, level, self.readable_name, frame, msg )
419
+ end
420
+ end
421
+
422
+
423
+ ### Append the given +obj+ to the logger at +:debug+ level. This is for
424
+ ### compatibility with objects that append to $stderr for their logging
425
+ ### (e.g., net/protocols-based libraries).
426
+ def <<( obj )
427
+ self.write( :debug, obj )
428
+ return self
429
+ end
430
+
431
+
432
+ #########
433
+ protected
434
+ #########
435
+
436
+ ### Dump the given object for output in the log.
437
+ def stringify_object( obj )
438
+ return case obj
439
+ when Exception
440
+ "%s:\n %s" % [ obj.message, obj.backtrace.join("\n ") ]
441
+ when String
442
+ obj
443
+ else
444
+ obj.inspect
445
+ end
446
+ end
447
+
448
+
449
+ ### Auto-install logging methods (ie., methods whose names match one of
450
+ ### Arrow::Logger::LEVELS.
451
+ def method_missing( sym, *args )
452
+ name = sym.to_s
453
+ level = name[/\w+/].to_sym
454
+ return super unless Arrow::Logger::LEVELS.member?( level )
455
+ code = nil
456
+
457
+ case name
458
+ when /^\w+\?/
459
+ code = self.make_level_predicate_method( level )
460
+
461
+ when /^\w+$/
462
+ code = self.make_writer_method( level )
463
+
464
+ else
465
+ return super
466
+ end
467
+
468
+ self.class.send( :define_method, sym, &code )
469
+ return self.method( sym ).call( *args )
470
+ end
471
+
472
+
473
+ ### Return a Proc suitable for installing as a predicate method for the given
474
+ ### logging level.
475
+ def make_level_predicate_method( level )
476
+ numeric_level = LEVELS[level]
477
+ Proc.new { self.level < numeric_level }
478
+ end
479
+
480
+
481
+ ### Return a Proc suitable for installing as a log-writing method for the given
482
+ ### logging level.
483
+ def make_writer_method( level )
484
+ Proc.new {|*args| self.write(level, *args)}
485
+ end
486
+
487
+ end # class Arrow::Logger
488
+