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,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+
5
+ module Apache
6
+
7
+ # An adapter object that can be used as a log device for a Logger instance that
8
+ # sends log messages to Apache's logging subsystem at 'debug' level.
9
+ class LogDevice < Logger::LogDevice
10
+
11
+ def initialize( *args ); end
12
+
13
+ ### Write a logging message to Apache's debug log.
14
+ def write( message )
15
+ Apache.request.server.log_debug( message )
16
+ end
17
+
18
+ ### No-op -- this is here just so Logger doesn't complain
19
+ def close; end
20
+
21
+ end # class LogDevice
22
+
23
+ # A formatter for log messages that will be forwarded into Apache's log system.
24
+ class LogFormatter < Logger::Formatter
25
+
26
+ def call( severity, time, progname, msg )
27
+ return "[%s] %s: %s" % [ severity, progname, msg ]
28
+ end
29
+
30
+ end
31
+
32
+ end # module Apache
33
+
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'tmpdir'
5
+
6
+ require 'pathname'
7
+
8
+
9
+ #
10
+ # The Arrow module, a namespace container for classes in the
11
+ # Arrow web application framework.
12
+ #
13
+ # == Authors
14
+ #
15
+ # * Martin Chase <mchase@rubycrafters.com>
16
+ # * Michael Granger <mgranger@rubycrafters.com>
17
+ # * David McCorkhill <dmccorkhill@rubycrafters.com>
18
+ #
19
+ # Please see the file LICENSE in the top-level directory for licensing details.
20
+ #
21
+ module Arrow
22
+
23
+ # Library version
24
+ VERSION = '1.0.7'
25
+
26
+ # VCS revision
27
+ REVISION = %q$Revision: 1b8226c06192 $
28
+
29
+
30
+ require 'arrow/constants'
31
+ require 'arrow/monkeypatches'
32
+ require 'arrow/exceptions'
33
+ require 'arrow/mixins'
34
+ require 'arrow/logger'
35
+
36
+
37
+ # Hook up PluginFactory logging to Arrow logging
38
+ PluginFactory.logger_callback = lambda do |lvl, msg|
39
+ Arrow::Logger[PluginFactory].debug( msg )
40
+ end
41
+ PluginFactory.log.debug( "Hooked up PluginFactory logging through Arrow's logger." )
42
+
43
+
44
+ ### Return the library's version string
45
+ def self::version_string( include_buildnum=false )
46
+ vstring = "%s %s" % [ self.name, VERSION ]
47
+ vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum
48
+ return vstring
49
+ end
50
+
51
+ end # module Arrow
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'arrow'
4
+ require 'arrow/exceptions'
5
+ require 'arrow/mixins'
6
+
7
+
8
+ # Arrow::AcceptParam -- a parser for Accept headers, allowing for
9
+ # weighted and wildcard comparisions.
10
+ #
11
+ # == Synopsis
12
+ #
13
+ # require 'arrow/acceptparam'
14
+ # ap = Arrow::AcceptParam.parse( "text/html;q=0.9;level=2" )
15
+ #
16
+ # ap.type #=> 'text'
17
+ # ap.subtype #=> 'html'
18
+ # ap.qvalue #=> 0.9
19
+ # ap =~ 'text/*' #=> true
20
+ #
21
+ # == Authors
22
+ #
23
+ # This class was originally written as part of the ThingFish project.
24
+ #
25
+ # * Michael Granger <ged@FaerieMUD.org>
26
+ # * Mahlon E. Smith <mahlon@martini.nu>
27
+ #
28
+ # Please see the file LICENSE in the top-level directory for licensing details.
29
+ #
30
+ class Arrow::AcceptParam
31
+ include Comparable,
32
+ Arrow::Loggable
33
+
34
+ # The default quality value (weight) if none is specified
35
+ Q_DEFAULT = 1.0
36
+
37
+ # The maximum quality value
38
+ Q_MAX = Q_DEFAULT
39
+
40
+
41
+ ### Parse the given +accept_param+ and return an AcceptParam object.
42
+ def self::parse( accept_param )
43
+ raise Arrow::RequestError, "Bad Accept param: no media-range" unless
44
+ accept_param =~ %r{/}
45
+ media_range, *stuff = accept_param.split( /\s*;\s*/ )
46
+ type, subtype = media_range.downcase.split( '/', 2 )
47
+ qval, opts = stuff.partition {|par| par =~ /^q\s*=/ }
48
+
49
+ return new( type, subtype, qval.first, *opts )
50
+ end
51
+
52
+
53
+ ### Create a new Arrow::AcceptParam with the given media +range+, quality value
54
+ ### (+qval+), and extensions
55
+ def initialize( type, subtype, qval=Q_DEFAULT, *extensions )
56
+ type = nil if type == '*'
57
+ subtype = nil if subtype == '*'
58
+
59
+ @type = type
60
+ @subtype = subtype
61
+ @qvalue = normalize_qvalue( qval )
62
+ @extensions = extensions.flatten
63
+ end
64
+
65
+
66
+ ######
67
+ public
68
+ ######
69
+
70
+ # The 'type' part of the media range
71
+ attr_reader :type
72
+
73
+ # The 'subtype' part of the media range
74
+ attr_reader :subtype
75
+
76
+ # The weight of the param
77
+ attr_reader :qvalue
78
+
79
+ # An array of any accept-extensions specified with the parameter
80
+ attr_reader :extensions
81
+
82
+
83
+ ### Match operator -- returns true if +other+ (an AcceptParan or something
84
+ ### that can to_s to a mime type) is a mime type which matches the receiving
85
+ ### AcceptParam.
86
+ def =~( other )
87
+ unless other.is_a?( Arrow::AcceptParam )
88
+ other = self.class.parse( other.to_s ) rescue nil
89
+ return false unless other
90
+ end
91
+
92
+ # */* returns true in either side of the comparison.
93
+ # ASSUMPTION: There will never be a case when a type is wildcarded
94
+ # and the subtype is specific. (e.g., */xml)
95
+ # We gave up trying to read RFC 2045.
96
+ return true if other.type.nil? || self.type.nil?
97
+
98
+ # text/html =~ text/html
99
+ # text/* =~ text/html
100
+ # text/html =~ text/*
101
+ if other.type == self.type
102
+ return true if other.subtype.nil? || self.subtype.nil?
103
+ return true if other.subtype == self.subtype
104
+ end
105
+
106
+ return false
107
+ end
108
+
109
+
110
+ ### Return a human-readable version of the object
111
+ def inspect
112
+ return "#<%s:0x%07x '%s/%s' q=%0.3f %p>" % [
113
+ self.class.name,
114
+ self.object_id * 2,
115
+ self.type || '*',
116
+ self.subtype || '*',
117
+ self.qvalue,
118
+ self.extensions,
119
+ ]
120
+ end
121
+
122
+
123
+ ### Return the parameter as a String suitable for inclusion in an Accept
124
+ ### HTTP header
125
+ def to_s
126
+ return [
127
+ self.mediatype,
128
+ self.qvaluestring,
129
+ self.extension_strings
130
+ ].compact.join(';')
131
+ end
132
+
133
+
134
+ ### The mediatype of the parameter, consisting of the type and subtype
135
+ ### separated by '/'.
136
+ def mediatype
137
+ return "%s/%s" % [ self.type || '*', self.subtype || '*' ]
138
+ end
139
+ alias_method :mimetype, :mediatype
140
+ alias_method :content_type, :mediatype
141
+
142
+
143
+ ### The weighting or "qvalue" of the parameter in the form "q=<value>"
144
+ def qvaluestring
145
+ # 3 digit precision, trim excess zeros
146
+ return sprintf( "q=%0.3f", self.qvalue ).gsub(/0{1,2}$/, '')
147
+ end
148
+
149
+
150
+ ### Return a String containing any extensions for this parameter, joined
151
+ ### with ';'
152
+ def extension_strings
153
+ return nil if @extensions.empty?
154
+ return @extensions.compact.join('; ')
155
+ end
156
+
157
+
158
+ ### Comparable interface. Sort parameters by weight: Returns -1 if +other+
159
+ ### is less specific than the receiver, 0 if +other+ is as specific as
160
+ ### the receiver, and +1 if +other+ is more specific than the receiver.
161
+ def <=>( other )
162
+
163
+ if rval = (other.qvalue <=> @qvalue).nonzero?
164
+ return rval
165
+ end
166
+
167
+ if @type.nil?
168
+ return 1 if ! other.type.nil?
169
+ elsif other.type.nil?
170
+ return -1
171
+ end
172
+
173
+ if @subtype.nil?
174
+ return 1 if ! other.subtype.nil?
175
+ elsif other.subtype.nil?
176
+ return -1
177
+ end
178
+
179
+ if rval = (other.extensions.length <=> @extensions.length).nonzero?
180
+ return rval
181
+ end
182
+
183
+ return self.mediatype <=> other.mediatype
184
+ end
185
+
186
+
187
+ #######
188
+ private
189
+ #######
190
+
191
+ ### Given an input +qvalue+, return the Float equivalent.
192
+ def normalize_qvalue( qvalue )
193
+ return Q_DEFAULT unless qvalue
194
+ qvalue = Float( qvalue.to_s.sub(/q=/, '') ) unless qvalue.is_a?( Float )
195
+
196
+ if qvalue > Q_MAX
197
+ self.log.notice "Squishing invalid qvalue %p to %0.1f" %
198
+ [ qvalue, Q_DEFAULT ]
199
+ return Q_DEFAULT
200
+ end
201
+
202
+ return qvalue
203
+ end
204
+
205
+ end # Arrow::AcceptParam
206
+
207
+ # vim: set nosta noet ts=4 sw=4:
@@ -0,0 +1,725 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'arrow/mixins'
4
+ require 'arrow/exceptions'
5
+ require 'arrow/object'
6
+ require 'arrow/formvalidator'
7
+ require 'arrow/transaction'
8
+
9
+ # An abstract base class for Arrow applets. Provides execution logic,
10
+ # argument-parsing/untainting/validation, and templating through an injected
11
+ # factory.
12
+ #
13
+ # == Synopsis
14
+ #
15
+ # require 'arrow/applet'
16
+ #
17
+ # class MyApplet < Arrow::Applet
18
+ # applet_name "My Applet"
19
+ # applet_description 'Displays a block of whatever character is ' +
20
+ # 'passed as argument'
21
+ # applet_maintainer 'Michael Granger <mgranger@rubycrafters.com>'
22
+ # applet_version '1.01'
23
+ # default_action :form
24
+ #
25
+ # # Define the 'display' action
26
+ # def_action :display do |txn|
27
+ # char = txn.vargs[:char] || 'x'
28
+ # char_page = self.make_character_page( char )
29
+ # templ = self.load_template( :main )
30
+ # templ.char_page = char_page
31
+ #
32
+ # return templ
33
+ # end
34
+ # template :main, "main.tmpl"
35
+ #
36
+ # # Define the 'form' action -- display a form that can be used to set
37
+ # # the character the block is composed of. Save the returned proxy so
38
+ # # the related signature values can be set.
39
+ # formaction = def_action :form do |txn|
40
+ # templ = self.load_template( :form )
41
+ # templ.txn = txn
42
+ # return templ
43
+ # end
44
+ # formaction.template = "form.tmpl"
45
+ #
46
+ # # Make a page full of character +char+.
47
+ # def make_character_page( char )
48
+ # page = ''
49
+ # 40.times do
50
+ # page << (char * 80) << "\n"
51
+ # end
52
+ # end
53
+ #
54
+ # end
55
+ #
56
+ # == Subversion Id
57
+ #
58
+ # $Id$
59
+ #
60
+ # == Authors
61
+ #
62
+ # * Michael Granger <ged@FaerieMUD.org>
63
+ #
64
+ # :include: LICENSE
65
+ #
66
+ #--
67
+ #
68
+ # Please see the file LICENSE in the BASE directory for licensing details.
69
+ #
70
+ class Arrow::Applet < Arrow::Object
71
+
72
+
73
+ ### Applet signature struct. The fields are as follows:
74
+ ### [<b>name</b>]
75
+ ### The name of the applet; used for introspection and reports.
76
+ ### [<b>description</b>]
77
+ ### The description of the applet; used for introspection.
78
+ ### [<b>maintainer</b>]
79
+ ### The name of the maintainer for reports and introspection.
80
+ ### [<b>version</b>]
81
+ ### The version or revision number of the applet, which can be
82
+ ### any object that has a #to_s method.
83
+ ### [<b>default_action</b>]
84
+ ### The action that will be run if no action is specified.
85
+ ### [<b>templates</b>]
86
+ ### A hash of templates used by the applet. The keys are Symbol
87
+ ### identifiers which will be used for lookup, and the values are the
88
+ ### paths to template files.
89
+ ### [<b>validator_profiles</b>]
90
+ ### A hash containing profiles for the built in form validator, one
91
+ ### per action. See the documentation for FormValidator for the format
92
+ ### of each profile hash.
93
+ SignatureStruct = Struct.new( :name, :description, :maintainer,
94
+ :version, :config, :default_action, :templates, :validator_profiles )
95
+
96
+ # Default-generators for Signatures which are missing one or more of the
97
+ # optional pairs.
98
+ SignatureStructDefaults = {
99
+ :name => proc {|rawsig, klass| klass.name},
100
+ :description => "(none)",
101
+ :maintainer => "", # Workaround for RDoc
102
+ :version => nil, # Workaround for RDoc
103
+ :default_action => '_default',
104
+ :config => {},
105
+ :templates => {},
106
+ :validator_profiles => {
107
+ :__default__ => {
108
+ :optional => [:action],
109
+ :constraints => {
110
+ :action => /^\w+$/,
111
+ },
112
+ },
113
+ }
114
+ }
115
+
116
+ SignatureStructDefaults[:version] = proc {|rawsig, klass|
117
+ if klass.const_defined?( :SVNRev )
118
+ return klass.const_get( :SVNRev ).gsub(/Rev: /, 'r')
119
+ elsif klass.const_defined?( :Version )
120
+ return klass.const_get( :Version )
121
+ elsif klass.const_defined?( :VERSION )
122
+ return klass.const_get( :VERSION )
123
+ elsif klass.const_defined?( :REVISION )
124
+ return klass.const_get( :REVISION )
125
+ elsif klass.const_defined?( :Revision )
126
+ return klass.const_get( :Revision )
127
+ elsif klass.const_defined?( :Rcsid )
128
+ return klass.const_get( :Rcsid )
129
+ else
130
+ begin
131
+ File.stat( klass.sourcefile ).mtime.strftime('%Y%m%d-%M:%H')
132
+ rescue
133
+ end
134
+ end
135
+ }
136
+
137
+
138
+ ### Proxy into the Applet's signature for a given action.
139
+ class SigProxy
140
+
141
+ ### Create a new proxy into the given +klass+'s Signature for the
142
+ ### specified +action_name+.
143
+ def initialize( action_name, klass )
144
+ @action_name = action_name.to_s.to_sym
145
+ @signature = klass.signature
146
+ @signature[:templates] ||= {}
147
+ @signature[:validator_profiles] ||= {}
148
+ end
149
+
150
+
151
+ ### Get the template associated with the same name as the proxied
152
+ ### action.
153
+ def template
154
+ @signature[:templates][@action_name]
155
+ end
156
+
157
+
158
+ ### Set the template associated with the same name as the proxied
159
+ ### action to +tmpl+.
160
+ def template=( tmpl )
161
+ @signature[:templates][@action_name] = tmpl
162
+ end
163
+
164
+
165
+ ### Get the validator profile associated with the same name as the
166
+ ### proxied action.
167
+ def validator_profile
168
+ @signature[:validator_profiles][@action_name]
169
+ end
170
+
171
+
172
+ ### Set the validator profile associated with the same name as the
173
+ ### proxied action to +hash+.
174
+ def validator_profile=( hash )
175
+ @signature[:validator_profiles][@action_name] = hash
176
+ end
177
+
178
+ end # class SigProxy
179
+
180
+
181
+ # The array of loaded applet classes (derivatives) and an array of
182
+ # newly-loaded ones.
183
+ @derivatives = []
184
+ @newly_loaded = []
185
+
186
+
187
+ #############################################################
188
+ ### C L A S S M E T H O D S
189
+ #############################################################
190
+
191
+ class << self
192
+ # The Array of loaded applet classes (derivatives)
193
+ attr_reader :derivatives
194
+
195
+ # The Array of applet classes that were loaded by the most recent call
196
+ # to .load.
197
+ attr_reader :newly_loaded
198
+
199
+ # The file containing the applet's class definition
200
+ attr_accessor :filename
201
+ end
202
+
203
+
204
+ ### Set the path for the template specified by +sym+ to +path+.
205
+ def self::template( sym, path=nil )
206
+ case sym
207
+ when Symbol, String
208
+ self.signature.templates[ sym ] = path
209
+
210
+ when Hash
211
+ self.signature.templates.merge!( sym )
212
+
213
+ else
214
+ raise ArgumentError, "cannot convert %s to Symbol" % [ sym ]
215
+ end
216
+ end
217
+ class << self
218
+ # Allow either 'template' or 'templates'
219
+ alias_method :templates, :template
220
+ end
221
+
222
+
223
+ ### Set the name of the applet to +name+.
224
+ def self::applet_name( name )
225
+ self.signature.name = name
226
+ end
227
+
228
+
229
+ ### Set the description of the applet to +desc+.
230
+ def self::applet_description( desc )
231
+ self.signature.description = desc
232
+ end
233
+
234
+
235
+ ### Set the contact information for the maintainer of the applet to +info+.
236
+ def self::applet_maintainer( info )
237
+ self.signature.maintainer = info
238
+ end
239
+
240
+
241
+ ### Set the contact information for the maintainer of the applet to +info+.
242
+ def self::applet_version( ver )
243
+ self.signature.version = ver
244
+ end
245
+
246
+
247
+ ### Set the default action for the applet to +action+.
248
+ def self::default_action( action )
249
+ self.signature.default_action = action.to_s
250
+ end
251
+ deprecate_class_method :applet_default_action, :default_action
252
+
253
+
254
+ ### Set the validator +rules+ for the specified +action+.
255
+ def self::validator( action, rules={} )
256
+ if action.is_a?( Hash ) && rules.empty?
257
+ Arrow::Logger[ self ].debug "Assuming hash syntax for validation definition: %p" % [ action ]
258
+ action, rules = *action.to_a.first
259
+ end
260
+ Arrow::Logger[ self ].debug "Defining validator for action %p with rules %p" % [ action, rules ]
261
+ self.signature.validator_profiles[ action ] = rules
262
+ end
263
+
264
+
265
+ ### Inheritance callback: register any derivative classes so they can be
266
+ ### looked up later.
267
+ def self::inherited( klass )
268
+ @inherited_from = true
269
+ if defined?( @newly_loaded )
270
+ @newly_loaded.push( klass )
271
+ super
272
+ else
273
+ Arrow::Applet.inherited( klass )
274
+ end
275
+ end
276
+
277
+
278
+ ### Have any subclasses of this class been created?
279
+ def self::inherited_from?
280
+ @inherited_from
281
+ end
282
+
283
+
284
+ ### Method definition callback: Check newly-defined action methods for
285
+ ### appropriate arity.
286
+ def self::method_added( sym )
287
+ if /^(\w+)_action$/.match( sym.to_s ) &&
288
+ self.instance_method( sym ).arity.zero?
289
+ raise ScriptError, "Inappropriate arity for #{sym}", caller(1)
290
+ end
291
+ end
292
+
293
+
294
+ ### Load any applet classes in the given file and return them. Ignores
295
+ ### any class which has a subclass in the file unless +include_base_classes+
296
+ ### is set false
297
+ def self::load( filename, include_base_classes=false )
298
+ self.newly_loaded.clear
299
+
300
+ # Load the applet file in an anonymous module. Any applet classes get
301
+ # collected via the ::inherited hook into @newly_loaded
302
+ Kernel.load( filename, true )
303
+
304
+ newderivatives = @newly_loaded.dup
305
+ @derivatives -= @newly_loaded
306
+ @derivatives.push( *@newly_loaded )
307
+
308
+ newderivatives.each do |applet|
309
+ applet.filename = filename
310
+ end
311
+
312
+ unless include_base_classes
313
+ newderivatives.delete_if do |applet|
314
+ applet.inherited_from?
315
+ end
316
+ end
317
+
318
+ return newderivatives
319
+ end
320
+
321
+
322
+ ### Return the name of the applet class after stripping off any
323
+ ### namespace-safe prefixes.
324
+ def self::normalized_name
325
+ self.name.sub( /#<Module:0x\w+>::/, '' )
326
+ end
327
+
328
+
329
+ ### Get the applet's signature (an
330
+ ### Arrow::Applet::SignatureStruct object).
331
+ def self::signature
332
+ @signature ||= make_signature()
333
+ end
334
+
335
+
336
+ ### Returns +true+ if the applet class has a signature.
337
+ def self::signature?
338
+ !self.signature.nil?
339
+ end
340
+
341
+
342
+ ### Signature lookup: look for either a constant or an instance
343
+ ### variable of the class that contains the raw signature hash, and
344
+ ### convert it to an Arrow::Applet::SignatureStruct object.
345
+ def self::make_signature
346
+ rawsig = nil
347
+ if self.instance_variables.include?( "@signature" )
348
+ rawsig = self.instance_variable_get( :@signature )
349
+ elsif self.constants.include?( "Signature" )
350
+ rawsig = self.const_get( :Signature )
351
+ elsif self.constants.include?( "SIGNATURE" )
352
+ rawsig = self.const_get( :SIGNATURE )
353
+ else
354
+ rawsig = {}
355
+ end
356
+
357
+ # Backward-compatibility: Rewrite the 'vargs' member as
358
+ # 'validator_profiles' if 'vargs' exists and 'validator_profiles'
359
+ # doesn't. 'vargs' member will be deleted regardless.
360
+ rawsig[ :validator_profiles ] ||= rawsig.delete( :vargs ) if
361
+ rawsig.key?( :vargs )
362
+
363
+ # If the superclass has a signature, inherit values from it for
364
+ # pairs that are missing.
365
+ if self.superclass < Arrow::Applet && self.superclass.signature?
366
+ self.superclass.signature.each_pair do |member,value|
367
+ next if [:name, :description, :version].include?( member )
368
+ if rawsig[member].nil?
369
+ rawsig[ member ] = value.dup rescue value
370
+ end
371
+ end
372
+ end
373
+
374
+ # Apply sensible defaults for members that aren't defined
375
+ SignatureStructDefaults.each do |key,val|
376
+ next if rawsig[ key ]
377
+ case val
378
+ when Proc, Method
379
+ rawsig[ key ] = val.call( rawsig, self )
380
+ when Numeric, NilClass, FalseClass, TrueClass
381
+ rawsig[ key ] = val
382
+ else
383
+ rawsig[ key ] = val.dup
384
+ end
385
+ end
386
+
387
+ # Signature = Struct.new( :name, :description, :maintainer,
388
+ # :version, :config, :default_action, :templates, :validatorArgs,
389
+ # :monitors )
390
+ members = SignatureStruct.members.collect {|m| m.to_sym}
391
+ return SignatureStruct.new( *rawsig.values_at(*members) )
392
+ end
393
+
394
+
395
+ ### Define an action for the applet. Transactions which include the
396
+ ### specified +name+ as the first directory of the uri after the one the
397
+ ### applet is assigned to will be passed to the given +block+. The
398
+ ### return value from this method is an Arrow::Applet::SigProxy which
399
+ ### can be used to set associated values in the applet's Signature; see
400
+ ### the Synopsis in lib/arrow/applet.rb for examples of how to use this.
401
+ def self::def_action( name, &block )
402
+ name = '_default' if name.to_s.empty?
403
+
404
+ # Action must accept at least a transaction argument
405
+ unless block.arity.nonzero?
406
+ raise ScriptError,
407
+ "Malformed action #{name}: must accept at least one argument"
408
+ end
409
+
410
+ methodName = "#{name}_action"
411
+ define_method( methodName, &block )
412
+ SigProxy.new( name, self )
413
+ end
414
+
415
+
416
+ deprecate_class_method :action, :def_action
417
+
418
+
419
+
420
+ #############################################################
421
+ ### I N S T A N C E M E T H O D S
422
+ #############################################################
423
+
424
+ ### Create a new Arrow::Applet object with the specified +config+ (an
425
+ ### Arrow::Config object), +template_factory+ (an Arrow::TemplateFactory
426
+ ### object), and the +uri+ the applet will live under in the appserver (a
427
+ ### String).
428
+ def initialize( config, template_factory, uri )
429
+ @config = config
430
+ @template_factory = template_factory
431
+ @uri = uri
432
+
433
+ @signature = self.class.signature.dup
434
+ @run_count = 0
435
+ @total_utime = 0
436
+ @total_stime = 0
437
+
438
+ # Make a regexp out of all public <something>_action methods
439
+ @actions = self.public_methods( true ).
440
+ select {|meth| /^(\w+)_action$/ =~ meth }.
441
+ collect {|meth| meth.gsub(/_action/, '') }
442
+ @actions_regexp = Regexp.new( "^(" + actions.join( '|' ) + ")$" )
443
+ end
444
+
445
+
446
+ ######
447
+ public
448
+ ######
449
+
450
+ # The Arrow::Config object which contains the system's configuration.
451
+ attr_accessor :config
452
+
453
+ # The URI the applet answers to
454
+ attr_reader :uri
455
+
456
+ # The Struct that contains the configuration values for this applet
457
+ attr_reader :signature
458
+
459
+ # The number of times this particular applet object has been run
460
+ attr_reader :run_count
461
+
462
+ # The number of user seconds spent in this applet's #run method.
463
+ attr_reader :total_utime
464
+
465
+ # The number of system seconds spent in this applet's #run method.
466
+ attr_reader :total_stime
467
+
468
+ # The Arrow::TemplateFactory object used to load templates for the applet.
469
+ attr_reader :template_factory
470
+
471
+ # The list of all valid actions on the applet
472
+ attr_reader :actions
473
+
474
+
475
+ ### Run the specified +action+ for the given +txn+ and the specified
476
+ ### +args+.
477
+ def run( txn, *args )
478
+ self.log.debug "Running %s" % [ self.signature.name ]
479
+
480
+ return self.time_request do
481
+ name, *newargs = self.get_action_name( txn, *args )
482
+ txn.vargs = self.make_validator( name, txn )
483
+ action = self.find_action_method( txn, name, *newargs )
484
+
485
+ # Decline the request if the action isn't a callable object
486
+ unless action.respond_to?( :arity )
487
+ self.log.info "action method (%p) doesn't do #arity, returning it as-is." %
488
+ [ action ]
489
+ return action
490
+ end
491
+
492
+ self.log.debug "calling action method %p with args (%p)" % [ action, newargs ]
493
+ self.call_action_method( txn, action, *newargs )
494
+ end
495
+ end
496
+
497
+
498
+ ### Wrapper method for a delegation (chained) request.
499
+ def delegate( txn, chain, *args )
500
+ yield( chain )
501
+ end
502
+
503
+
504
+ ### Returns +true+ if the receiver has a #delegate method that is inherited
505
+ ### from somewhere other than the base Arrow::Applet class.
506
+ def delegable?
507
+ return self.method(:delegate).to_s !~ /\(Arrow::Applet\)/
508
+ end
509
+ alias_method :chainable?, :delegable?
510
+
511
+
512
+ ### The action invoked if the specified action is not explicitly
513
+ ### defined. The default implementation will look for a template with the
514
+ ### same key as the action, and if found, will load that and return it.
515
+ def action_missing_action( txn, raction, *args )
516
+ self.log.debug "In action_missing_action with: raction = %p, args = %p" %
517
+ [ raction, args ]
518
+
519
+ if raction && raction.to_s =~ /^([a-z]\w+)$/
520
+ tmplkey = $1.untaint
521
+ self.log.debug "tmpl is: %p (%stainted)" %
522
+ [ tmplkey, tmplkey.tainted? ? "" : "not " ]
523
+
524
+ if @signature.templates.key?( tmplkey.to_sym )
525
+ self.log.debug "Using template sender default action for %s" % raction
526
+ txn.vargs = self.make_validator( tmplkey, txn )
527
+
528
+ tmpl = self.load_template( raction.to_sym )
529
+ tmpl.txn = txn
530
+ tmpl.applet = self
531
+
532
+ return tmpl
533
+ end
534
+ end
535
+
536
+ raise Arrow::AppletError, "No such action '%s' in %s" %
537
+ [ raction, self.signature.name ]
538
+ end
539
+
540
+
541
+ ### Return a human-readable String representing the applet.
542
+ def inspect
543
+ "<%s:0x%08x: %s [%s/%s]>" % [
544
+ self.class.name,
545
+ self.object_id * 2,
546
+ @signature.name,
547
+ @signature.version,
548
+ @signature.maintainer
549
+ ]
550
+ end
551
+
552
+
553
+ ### Returns the average number of seconds (user + system) per run.
554
+ def average_usage
555
+ return 0.0 if @run_count.zero?
556
+ (@total_utime + @total_stime) / @run_count.to_f
557
+ end
558
+
559
+
560
+ #########
561
+ protected
562
+ #########
563
+
564
+ ### Time the block, logging the result
565
+ def time_request
566
+ starttimes = Process.times
567
+ yield
568
+ ensure
569
+ runtimes = Process.times
570
+ @run_count += 1
571
+ @total_utime += utime = (runtimes.utime - starttimes.utime)
572
+ @total_stime += stime = (runtimes.stime - starttimes.stime)
573
+ self.log.info \
574
+ "[PID %d] Runcount: %d, User: %0.2f/%0.2f, System: %0.2f/%0.2f" %
575
+ [ Process.pid, @run_count, utime, @total_utime, stime, @total_stime ]
576
+ end
577
+
578
+
579
+ ### Get the expected action name from the specified +txn+ and the +args+ extracted from the
580
+ ### URI path; return the action as a Symbol and the remaining arguments as a splatted Array.
581
+ def get_action_name( txn, *args )
582
+ self.log.debug "Fetching the intended action name from txn = %p, args = %p" % [ txn, args ]
583
+
584
+ name = args.shift
585
+ name = nil if name.to_s.empty?
586
+ name ||= @signature.default_action or
587
+ raise Arrow::AppletError, "Missing default handler"
588
+
589
+ if (( action = self.map_to_valid_action(name) ))
590
+ self.log.debug " found what looks like a valid action (%p)" % [ action ]
591
+ return action.to_sym, *args
592
+ else
593
+ self.log.debug " didn't find a valid action; returning :action_missing"
594
+ return :action_missing, name, *args
595
+ end
596
+ end
597
+
598
+
599
+ ### Map the given +action+ name to an action that's been declared, or else map it to a
600
+ ### fallback action ('action_missing' by default).
601
+ def map_to_valid_action( action )
602
+ self.log.debug "trying to map %p to a valid action method" % [ action ]
603
+
604
+ if (( match = @actions_regexp.match(action.to_s) ))
605
+ action = match.captures[0]
606
+ action.untaint
607
+ self.log.debug "Matched action = #{action}"
608
+ return action
609
+ else
610
+ self.log.debug " no matching action method in %p" % [ @actions_regexp ]
611
+ return nil
612
+ end
613
+ end
614
+
615
+
616
+ ### Given an +action+ name and any other URI path +args+ from the request, return
617
+ ### a Method object that will handle the request, and the remaining arguments
618
+ ### as a splatted Array.
619
+ def find_action_method( txn, action, *args )
620
+ self.log.debug "Mapping %s( %p ) to an action" % [ action, args ]
621
+ return self.method( "#{action}_action" )
622
+ end
623
+
624
+
625
+ ### Invoke the specified +action+ (an object that responds to #arity and #call) with the
626
+ ### given +txn+ and the +args+ which it can accept based on its arity.
627
+ def call_action_method( txn, action, *args )
628
+ self.log.debug "Applet action arity: %d; args = %p" %
629
+ [ action.arity, args ]
630
+
631
+ # Invoke the action with the right number of arguments.
632
+ if action.arity < 0
633
+ return action.call( txn, *args )
634
+ elsif action.arity >= 1
635
+ args.unshift( txn )
636
+ until args.length >= action.arity do args << nil end
637
+ return action.call( *(args[0, action.arity]) )
638
+ else
639
+ raise Arrow::AppletError,
640
+ "Malformed action: Must accept at least a transaction argument"
641
+ end
642
+ end
643
+
644
+
645
+ ### Run an action with a duped transaction (e.g., from another action)
646
+ def subrun( action, txn, *args )
647
+ action, txn = txn, action if action.is_a?( Arrow::Transaction )
648
+
649
+ txn.vargs ||= self.make_validator( action, txn )
650
+ action = self.method( "#{action}_action" ) unless action.respond_to?( :arity )
651
+ self.log.debug "Running subordinate action '%s' from '%s'" %
652
+ [ action, caller[0] ]
653
+
654
+ return self.call_action_method( txn, action, *args )
655
+ end
656
+
657
+
658
+ ### Load and return the template associated with the given +key+ according
659
+ ### to the applet's signature. Returns +nil+ if no such template exists.
660
+ def load_template( key )
661
+ tname = @signature.templates[key] or
662
+ raise Arrow::AppletError,
663
+ "No such template %p defined in the signature for %s (%s)" %
664
+ [ key, self.signature.name, self.class.filename ]
665
+
666
+ tname.untaint
667
+
668
+ return @template_factory.get_template( tname )
669
+ end
670
+ alias_method :template, :load_template
671
+
672
+
673
+ ### Create a FormValidator object for the specified +action+ which has
674
+ ### been given the arguments from the given +txn+.
675
+ def make_validator( action, txn )
676
+ profile = self.get_validator_profile_for_action( action, txn ) or
677
+ return nil
678
+
679
+ # Create a new validator object, map the request args into a regular
680
+ # hash, and then send them to the validaator with the applicable profile
681
+ self.log.debug "Creating form validator for profile: %p" % [ profile ]
682
+
683
+ params = {}
684
+
685
+ # Only try to parse form parameters if there's a form
686
+ if txn.form_request?
687
+ txn.request.paramtable.each do |key,val|
688
+ # Multi-valued vs. single params
689
+ params[key] = val.to_a.length > 1 ? val.to_a : val.to_s
690
+ end
691
+ end
692
+ validator = Arrow::FormValidator.new( profile, params )
693
+
694
+ self.log.debug "Validator: %p" % validator
695
+ return validator
696
+ end
697
+
698
+
699
+ ### Return the validator profile that corresponds to the +action+ which
700
+ ### will be executed by the specified +txn+. Returns the __default__
701
+ ### profile if no more-specific one is available.
702
+ def get_validator_profile_for_action( action, txn )
703
+ if action.to_s =~ /^(\w+)$/
704
+ action = $1
705
+ action.untaint
706
+ else
707
+ self.log.warning "Invalid action '#{action.inspect}'"
708
+ action = :__default__
709
+ end
710
+
711
+ # Look up the profile for the applet or the default one
712
+ profile = @signature.validator_profiles[ action.to_sym ] ||
713
+ @signature.validator_profiles[ :__default__ ]
714
+
715
+ if profile.nil?
716
+ self.log.warning "No validator for #{action}, and no __default__. "\
717
+ "Returning nil validator."
718
+ return nil
719
+ end
720
+
721
+ return profile
722
+ end
723
+
724
+ end # class Arrow::Applet
725
+