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,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
+