arrow 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +1590 -0
- data/LICENSE +28 -0
- data/README +75 -0
- data/Rakefile +366 -0
- data/Rakefile.local +63 -0
- data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
- data/data/arrow/applets/args.rb +50 -0
- data/data/arrow/applets/config.rb +55 -0
- data/data/arrow/applets/error.rb +63 -0
- data/data/arrow/applets/files.rb +46 -0
- data/data/arrow/applets/inspect.rb +46 -0
- data/data/arrow/applets/nosuchapplet.rb +31 -0
- data/data/arrow/applets/status.rb +92 -0
- data/data/arrow/applets/test.rb +133 -0
- data/data/arrow/applets/tutorial/counter.rb +96 -0
- data/data/arrow/applets/tutorial/dingus.rb +67 -0
- data/data/arrow/applets/tutorial/hello.rb +34 -0
- data/data/arrow/applets/tutorial/hello2.rb +73 -0
- data/data/arrow/applets/tutorial/imgtext.rb +90 -0
- data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
- data/data/arrow/applets/tutorial/index.rb +36 -0
- data/data/arrow/applets/tutorial/logo.rb +98 -0
- data/data/arrow/applets/tutorial/memcache.rb +61 -0
- data/data/arrow/applets/tutorial/missing.rb +37 -0
- data/data/arrow/applets/tutorial/protected.rb +100 -0
- data/data/arrow/applets/tutorial/redirector.rb +52 -0
- data/data/arrow/applets/tutorial/rndimages.rb +159 -0
- data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
- data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
- data/data/arrow/applets/tutorial/superhello.rb +72 -0
- data/data/arrow/applets/tutorial/timeclock.rb +78 -0
- data/data/arrow/applets/view-applet.rb +123 -0
- data/data/arrow/applets/view-template.rb +85 -0
- data/data/arrow/applets/wiki.rb +274 -0
- data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
- data/data/arrow/templates/applet-status.tmpl +153 -0
- data/data/arrow/templates/args-display.tmpl +120 -0
- data/data/arrow/templates/config/display-table.tmpl +36 -0
- data/data/arrow/templates/config/display.tmpl +36 -0
- data/data/arrow/templates/counter-deleted.tmpl +33 -0
- data/data/arrow/templates/counter.tmpl +59 -0
- data/data/arrow/templates/dingus.tmpl +55 -0
- data/data/arrow/templates/enumtable.tmpl +8 -0
- data/data/arrow/templates/error-display.tmpl +92 -0
- data/data/arrow/templates/filemap.tmpl +89 -0
- data/data/arrow/templates/hello-world-src.tmpl +34 -0
- data/data/arrow/templates/hello-world.tmpl +60 -0
- data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
- data/data/arrow/templates/imgtext/form.tmpl +70 -0
- data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
- data/data/arrow/templates/imgtext/reload.tmpl +55 -0
- data/data/arrow/templates/inspect/display.tmpl +80 -0
- data/data/arrow/templates/loginform.tmpl +64 -0
- data/data/arrow/templates/logout.tmpl +32 -0
- data/data/arrow/templates/memcache/display.tmpl +41 -0
- data/data/arrow/templates/navbar.incl +27 -0
- data/data/arrow/templates/nosuchapplet.tmpl +32 -0
- data/data/arrow/templates/printsource.tmpl +35 -0
- data/data/arrow/templates/protected.tmpl +36 -0
- data/data/arrow/templates/rndimages.tmpl +38 -0
- data/data/arrow/templates/service-response.tmpl +13 -0
- data/data/arrow/templates/sharenotes/display.tmpl +38 -0
- data/data/arrow/templates/status.tmpl +120 -0
- data/data/arrow/templates/templateviewer.tmpl +43 -0
- data/data/arrow/templates/test/harness.tmpl +57 -0
- data/data/arrow/templates/test/list.tmpl +48 -0
- data/data/arrow/templates/test/problem.tmpl +42 -0
- data/data/arrow/templates/tutorial/index.tmpl +37 -0
- data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
- data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
- data/data/arrow/templates/view-applet.tmpl +40 -0
- data/data/arrow/templates/view-template.tmpl +83 -0
- data/data/arrow/templates/wiki/formerror.tmpl +47 -0
- data/data/arrow/templates/wiki/markup_help.incl +6 -0
- data/data/arrow/templates/wiki/new.tmpl +56 -0
- data/data/arrow/templates/wiki/new_system.tmpl +122 -0
- data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
- data/data/arrow/templates/wiki/show.tmpl +34 -0
- data/docs/manual/layouts/default.page +43 -0
- data/docs/manual/lib/api-filter.rb +81 -0
- data/docs/manual/lib/editorial-filter.rb +64 -0
- data/docs/manual/lib/examples-filter.rb +244 -0
- data/docs/manual/lib/links-filter.rb +117 -0
- data/lib/apache/fakerequest.rb +448 -0
- data/lib/apache/logger.rb +33 -0
- data/lib/arrow.rb +51 -0
- data/lib/arrow/acceptparam.rb +207 -0
- data/lib/arrow/applet.rb +725 -0
- data/lib/arrow/appletmixins.rb +218 -0
- data/lib/arrow/appletregistry.rb +590 -0
- data/lib/arrow/applettestcase.rb +503 -0
- data/lib/arrow/broker.rb +255 -0
- data/lib/arrow/cache.rb +176 -0
- data/lib/arrow/config-loaders/yaml.rb +75 -0
- data/lib/arrow/config.rb +615 -0
- data/lib/arrow/constants.rb +24 -0
- data/lib/arrow/cookie.rb +359 -0
- data/lib/arrow/cookieset.rb +108 -0
- data/lib/arrow/dispatcher.rb +368 -0
- data/lib/arrow/dispatcherloader.rb +50 -0
- data/lib/arrow/exceptions.rb +61 -0
- data/lib/arrow/fallbackhandler.rb +48 -0
- data/lib/arrow/formvalidator.rb +631 -0
- data/lib/arrow/htmltokenizer.rb +343 -0
- data/lib/arrow/logger.rb +488 -0
- data/lib/arrow/logger/apacheoutputter.rb +69 -0
- data/lib/arrow/logger/arrayoutputter.rb +63 -0
- data/lib/arrow/logger/coloroutputter.rb +111 -0
- data/lib/arrow/logger/fileoutputter.rb +96 -0
- data/lib/arrow/logger/htmloutputter.rb +54 -0
- data/lib/arrow/logger/outputter.rb +123 -0
- data/lib/arrow/mixins.rb +425 -0
- data/lib/arrow/monkeypatches.rb +94 -0
- data/lib/arrow/object.rb +117 -0
- data/lib/arrow/path.rb +196 -0
- data/lib/arrow/service.rb +447 -0
- data/lib/arrow/session.rb +289 -0
- data/lib/arrow/session/dbstore.rb +100 -0
- data/lib/arrow/session/filelock.rb +160 -0
- data/lib/arrow/session/filestore.rb +132 -0
- data/lib/arrow/session/id.rb +98 -0
- data/lib/arrow/session/lock.rb +253 -0
- data/lib/arrow/session/md5id.rb +42 -0
- data/lib/arrow/session/nulllock.rb +42 -0
- data/lib/arrow/session/posixlock.rb +166 -0
- data/lib/arrow/session/sha1id.rb +54 -0
- data/lib/arrow/session/store.rb +366 -0
- data/lib/arrow/session/usertrackid.rb +52 -0
- data/lib/arrow/spechelpers.rb +73 -0
- data/lib/arrow/template.rb +713 -0
- data/lib/arrow/template/attr.rb +31 -0
- data/lib/arrow/template/call.rb +31 -0
- data/lib/arrow/template/comment.rb +33 -0
- data/lib/arrow/template/container.rb +118 -0
- data/lib/arrow/template/else.rb +41 -0
- data/lib/arrow/template/elsif.rb +44 -0
- data/lib/arrow/template/escape.rb +53 -0
- data/lib/arrow/template/export.rb +87 -0
- data/lib/arrow/template/for.rb +145 -0
- data/lib/arrow/template/if.rb +78 -0
- data/lib/arrow/template/import.rb +119 -0
- data/lib/arrow/template/include.rb +206 -0
- data/lib/arrow/template/iterator.rb +208 -0
- data/lib/arrow/template/nodes.rb +734 -0
- data/lib/arrow/template/parser.rb +571 -0
- data/lib/arrow/template/prettyprint.rb +53 -0
- data/lib/arrow/template/render.rb +191 -0
- data/lib/arrow/template/selectlist.rb +94 -0
- data/lib/arrow/template/set.rb +87 -0
- data/lib/arrow/template/timedelta.rb +81 -0
- data/lib/arrow/template/unless.rb +78 -0
- data/lib/arrow/template/urlencode.rb +51 -0
- data/lib/arrow/template/yield.rb +139 -0
- data/lib/arrow/templatefactory.rb +125 -0
- data/lib/arrow/testcase.rb +567 -0
- data/lib/arrow/transaction.rb +608 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/documentation.rb +114 -0
- data/rake/helpers.rb +502 -0
- data/rake/hg.rb +282 -0
- data/rake/manual.rb +787 -0
- data/rake/packaging.rb +129 -0
- data/rake/publishing.rb +278 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/spec/arrow/acceptparam_spec.rb +157 -0
- data/spec/arrow/applet_spec.rb +575 -0
- data/spec/arrow/appletmixins_spec.rb +409 -0
- data/spec/arrow/appletregistry_spec.rb +294 -0
- data/spec/arrow/broker_spec.rb +153 -0
- data/spec/arrow/config_spec.rb +224 -0
- data/spec/arrow/cookieset_spec.rb +164 -0
- data/spec/arrow/dispatcher_spec.rb +137 -0
- data/spec/arrow/dispatcherloader_spec.rb +65 -0
- data/spec/arrow/formvalidator_spec.rb +781 -0
- data/spec/arrow/logger_spec.rb +346 -0
- data/spec/arrow/mixins_spec.rb +120 -0
- data/spec/arrow/service_spec.rb +645 -0
- data/spec/arrow/session_spec.rb +121 -0
- data/spec/arrow/template/iterator_spec.rb +222 -0
- data/spec/arrow/templatefactory_spec.rb +185 -0
- data/spec/arrow/transaction_spec.rb +319 -0
- data/spec/arrow_spec.rb +37 -0
- data/spec/lib/appletmatchers.rb +281 -0
- data/spec/lib/constants.rb +77 -0
- data/spec/lib/helpers.rb +41 -0
- data/spec/lib/matchers.rb +44 -0
- data/tests/cookie.tests.rb +310 -0
- data/tests/path.tests.rb +157 -0
- data/tests/session.tests.rb +111 -0
- data/tests/session_id.tests.rb +82 -0
- data/tests/session_lock.tests.rb +191 -0
- data/tests/session_store.tests.rb +53 -0
- data/tests/template.tests.rb +1360 -0
- 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
|
+
|
data/lib/arrow.rb
ADDED
@@ -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:
|
data/lib/arrow/applet.rb
ADDED
@@ -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
|
+
|