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