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,78 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/template'
|
|
4
|
+
require 'arrow/template/nodes'
|
|
5
|
+
|
|
6
|
+
# The Arrow::Template::UnlessDirective class, a derivative of
|
|
7
|
+
# Arrow::Template::BracketingDirective. Instances of this class represent a
|
|
8
|
+
# section of a template that is rendered conditionally.
|
|
9
|
+
#
|
|
10
|
+
# The formats the directive supports are:
|
|
11
|
+
#
|
|
12
|
+
# <?unless <name>?>...<?end unless?>
|
|
13
|
+
# <?unless <name>.<methodchain>?>...<?end unless?>
|
|
14
|
+
# <?unless <name> (matches|=~) <regex>?>...<?end unless?>
|
|
15
|
+
# <?unless <name>.<methodchain> (matches|=~) <regex>?>...<?end unless?>
|
|
16
|
+
#
|
|
17
|
+
# Note that this directive does not support all possible Ruby expressions in the
|
|
18
|
+
# conditional, and must have a valid associated identifier (the <em>name</em>
|
|
19
|
+
# bit).
|
|
20
|
+
#
|
|
21
|
+
# == Authors
|
|
22
|
+
#
|
|
23
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
24
|
+
#
|
|
25
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
26
|
+
#
|
|
27
|
+
class Arrow::Template::UnlessDirective < Arrow::Template::BracketingDirective # :nodoc:
|
|
28
|
+
include Arrow::Template::ConditionalDirective
|
|
29
|
+
|
|
30
|
+
require 'arrow/template/else'
|
|
31
|
+
require 'arrow/template/elsif'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#############################################################
|
|
35
|
+
### I N S T A N C E M E T H O D S
|
|
36
|
+
#############################################################
|
|
37
|
+
|
|
38
|
+
#########
|
|
39
|
+
protected
|
|
40
|
+
#########
|
|
41
|
+
|
|
42
|
+
### Render the contents of the conditional if it evaluates to +false+, or
|
|
43
|
+
### the nodes after 'elsif' or 'else' subnodes if their conditions are
|
|
44
|
+
### met.
|
|
45
|
+
def render_contents( template, scope )
|
|
46
|
+
cond = has_been_true = !self.evaluate( template, scope )
|
|
47
|
+
|
|
48
|
+
nodes = []
|
|
49
|
+
|
|
50
|
+
# Now splice out the chunk of nodes that should be rendered based on
|
|
51
|
+
# the conditional.
|
|
52
|
+
@subnodes.each do |node|
|
|
53
|
+
case node
|
|
54
|
+
when Arrow::Template::ElsifDirective
|
|
55
|
+
if !has_been_true
|
|
56
|
+
cond = has_been_true = node.evaluate( template, scope )
|
|
57
|
+
else
|
|
58
|
+
cond = false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
when Arrow::Template::ElseDirective
|
|
62
|
+
if !has_been_true
|
|
63
|
+
cond = has_been_true = true
|
|
64
|
+
else
|
|
65
|
+
cond = false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
else
|
|
69
|
+
nodes.push( node ) if cond
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
return template.render( nodes, scope )
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
end # class Arrow::Template::UnlessDirective
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/exceptions'
|
|
4
|
+
require 'arrow/path'
|
|
5
|
+
require 'arrow/template/nodes'
|
|
6
|
+
require 'arrow/template/call'
|
|
7
|
+
|
|
8
|
+
# The Arrow::Template::URLEncodeDirective class, a derivative of
|
|
9
|
+
# Arrow::Template::CallDirective. This is the class which defines the
|
|
10
|
+
# behaviour of the 'urlencode' template directive.
|
|
11
|
+
#
|
|
12
|
+
# == VCS Id
|
|
13
|
+
#
|
|
14
|
+
# $Id$
|
|
15
|
+
#
|
|
16
|
+
# == Authors
|
|
17
|
+
#
|
|
18
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
19
|
+
#
|
|
20
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
21
|
+
#
|
|
22
|
+
class Arrow::Template::URLEncodeDirective < Arrow::Template::CallDirective # :nodoc:
|
|
23
|
+
|
|
24
|
+
# Non-URIC Characters (RFC 2396)
|
|
25
|
+
NonUricRegexp = /[^A-Za-z0-9\-_.!~*'()]/
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
######
|
|
29
|
+
public
|
|
30
|
+
######
|
|
31
|
+
|
|
32
|
+
### Render the content and return it as URL-escaped text.
|
|
33
|
+
def render( template, scope )
|
|
34
|
+
rawary = super
|
|
35
|
+
rary = []
|
|
36
|
+
|
|
37
|
+
# Try our best to skip debugging comments
|
|
38
|
+
if template._config[:debuggingComments]
|
|
39
|
+
rary.push( rawary.shift ) if /^<!--.*-->$/ =~ rawary.first
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
rawary.each do |line|
|
|
43
|
+
rary << line.to_s.gsub( NonUricRegexp ) do |match|
|
|
44
|
+
"%%%02x" % [ match[0] ]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return rary
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end # class Arrow::Template::URLEncodeDirective
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/exceptions'
|
|
4
|
+
require 'arrow/path'
|
|
5
|
+
require 'arrow/template/nodes'
|
|
6
|
+
|
|
7
|
+
# The Arrow::Template::YieldDirective class, a derivative
|
|
8
|
+
# of Arrow::Template::BracketingDirective. This is the class which defines the
|
|
9
|
+
# behaviour of the 'yield' template directive.
|
|
10
|
+
#
|
|
11
|
+
# == Syntax
|
|
12
|
+
#
|
|
13
|
+
# <?yield <args> from <attribute>.<block_method> ?>
|
|
14
|
+
#
|
|
15
|
+
# The <em>args</em> portion is similar to Ruby argument lists: it supports
|
|
16
|
+
# defaults, <tt>*vars</tt>, and hash arguments.
|
|
17
|
+
#
|
|
18
|
+
# == Examples
|
|
19
|
+
#
|
|
20
|
+
# <!-- Iterate over the incoming headers of an Apache::Request -->
|
|
21
|
+
# <?yield name, value from request.headers_in.each ?>
|
|
22
|
+
# <?attr name ?>: <?escape value ?>
|
|
23
|
+
# <?end yield?>
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
# == Authors
|
|
27
|
+
#
|
|
28
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
29
|
+
#
|
|
30
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
31
|
+
#
|
|
32
|
+
class Arrow::Template::YieldDirective < Arrow::Template::BracketingDirective # :nodoc:
|
|
33
|
+
include Arrow::Template::Parser::Patterns
|
|
34
|
+
|
|
35
|
+
# The regexp format of the 'yield' part of the directive tag.
|
|
36
|
+
FROM = WHITESPACE + /from/i + WHITESPACE
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
#############################################################
|
|
40
|
+
### C L A S S M E T H O D S
|
|
41
|
+
#############################################################
|
|
42
|
+
|
|
43
|
+
### Returns +false+; disallows prepended formats.
|
|
44
|
+
def self::allows_format?
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
#############################################################
|
|
50
|
+
### I N S T A N C E M E T H O D S
|
|
51
|
+
#############################################################
|
|
52
|
+
|
|
53
|
+
### Initialize a new YieldDirective object with the specified +type+,
|
|
54
|
+
### +parser+, and +state+.
|
|
55
|
+
def initialize( type, parser, state )
|
|
56
|
+
@args = []
|
|
57
|
+
@pureargs = []
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
######
|
|
63
|
+
public
|
|
64
|
+
######
|
|
65
|
+
|
|
66
|
+
# The argument list for the yield block, with sigils and defaults, if any.
|
|
67
|
+
attr_reader :args
|
|
68
|
+
|
|
69
|
+
# The argument list for the callback, with any sigils and defaults
|
|
70
|
+
# stripped away.
|
|
71
|
+
attr_reader :pureargs
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
#########
|
|
75
|
+
protected
|
|
76
|
+
#########
|
|
77
|
+
|
|
78
|
+
### Parse the contents of the directive, looking for an optional format
|
|
79
|
+
### for tags like <?directive "%-15s" % foo ?>, then a required
|
|
80
|
+
### identifier, then an optional methodchain attached to the identifier.
|
|
81
|
+
def parse_directive_contents( parser, state )
|
|
82
|
+
@args, @pureargs = parser.scan_for_arglist( state )
|
|
83
|
+
return nil unless @args
|
|
84
|
+
state.scanner.skip( FROM ) or
|
|
85
|
+
raise Arrow::ParseError, "no 'from' for yield"
|
|
86
|
+
|
|
87
|
+
super
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Build a Proc object that encapsulates the execution necessary to
|
|
92
|
+
### render the directive.
|
|
93
|
+
def build_rendering_proc( template, scope )
|
|
94
|
+
code = %q{
|
|
95
|
+
Proc.new {|__item, __callback|
|
|
96
|
+
res = []
|
|
97
|
+
__item%s {|%s| res << __callback.call(%s)}
|
|
98
|
+
res
|
|
99
|
+
}
|
|
100
|
+
} % [ self.methodchain, self.args.join(","), self.pureargs.join(",") ]
|
|
101
|
+
code.untaint
|
|
102
|
+
|
|
103
|
+
#self.log.debug "Rendering proc code is: %p" % code
|
|
104
|
+
desc = "[%s (%s): %s]" %
|
|
105
|
+
[ self.class.name, __FILE__, self.methodchain ]
|
|
106
|
+
|
|
107
|
+
return eval( code, scope.get_binding, desc, __LINE__ )
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
### Pass a callback to our inner node-rendering method as the block for
|
|
112
|
+
### the specified. Builds a callback which is yielded to from within the
|
|
113
|
+
### block passed to whatever this directive is calling, which in turn
|
|
114
|
+
### renders each of its subnodes with the arguments specified by the
|
|
115
|
+
### yield.
|
|
116
|
+
def render_contents( template, scope )
|
|
117
|
+
#self.log.debug "Bulding callback for rendering subnodes..."
|
|
118
|
+
callback = Proc.new {|*blockArgs|
|
|
119
|
+
res = []
|
|
120
|
+
attributes = {}
|
|
121
|
+
blockArgs.zip( self.pureargs ) do |pair|
|
|
122
|
+
attributes[ pair[1] ] = pair[0]
|
|
123
|
+
end
|
|
124
|
+
#self.log.debug " override attributes are: %p" % [ attributes ]
|
|
125
|
+
template.with_overridden_attributes( scope, attributes ) do |template|
|
|
126
|
+
res << template.render( @subnodes, scope )
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
res
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#self.log.debug "calling method chain; callback: %p" % callback
|
|
133
|
+
self.call_methodchain( template, scope, callback )
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
end # class Arrow::Template::YieldDirective
|
|
138
|
+
|
|
139
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/mixins'
|
|
4
|
+
require 'arrow/object'
|
|
5
|
+
require 'arrow/exceptions'
|
|
6
|
+
require 'arrow/cache'
|
|
7
|
+
|
|
8
|
+
# The TemplateFactory class, which is responsible for
|
|
9
|
+
# interpreting the 'templates' section of the configuration, and providing
|
|
10
|
+
# template-loading and -caching according to that configuration,
|
|
11
|
+
#
|
|
12
|
+
# == Authors
|
|
13
|
+
#
|
|
14
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
15
|
+
#
|
|
16
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
17
|
+
#
|
|
18
|
+
class Arrow::TemplateFactory < Arrow::Object
|
|
19
|
+
require 'arrow/template'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#############################################################
|
|
23
|
+
### C L A S S M E T H O D S
|
|
24
|
+
#############################################################
|
|
25
|
+
|
|
26
|
+
### Given an Arrow::Config object (+config+), attempt to load and
|
|
27
|
+
### instantiate the configured template loader object.
|
|
28
|
+
def self::build_template_loader( config )
|
|
29
|
+
|
|
30
|
+
# Resolve the loader name into the Class object by traversing
|
|
31
|
+
# constants.
|
|
32
|
+
klass = config.templates.loader.
|
|
33
|
+
split( /::/ ).
|
|
34
|
+
inject( Object ) {|mod, name|
|
|
35
|
+
mod.const_get( name ) or raise ConfigError,
|
|
36
|
+
"No such template loader class #{name} for #{mod.name}"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if klass.respond_to?( :load, false )
|
|
40
|
+
Arrow::Logger[ self ].debug "Loader (%s) class responds to ::load; using it directly: %p" %
|
|
41
|
+
[ klass.name, klass.method(:load) ]
|
|
42
|
+
return klass
|
|
43
|
+
else
|
|
44
|
+
Arrow::Logger[ self ].debug "Loader (%s) expects instantiation." % [ klass.name ]
|
|
45
|
+
return klass.new( config )
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
#############################################################
|
|
51
|
+
### I N S T A N C E M E T H O D S
|
|
52
|
+
#############################################################
|
|
53
|
+
|
|
54
|
+
### Create a new TemplateFactory from the given configuration object,
|
|
55
|
+
### which should specify a loader class for templates.
|
|
56
|
+
def initialize( config )
|
|
57
|
+
@config = config
|
|
58
|
+
@cache = nil
|
|
59
|
+
|
|
60
|
+
if config.templates.cache
|
|
61
|
+
@cache = Arrow::Cache.new(
|
|
62
|
+
"Template Factory",
|
|
63
|
+
config.templates.cacheConfig,
|
|
64
|
+
&method(:template_expiration_hook) )
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@loader = self.class.build_template_loader( config )
|
|
68
|
+
@path = config.templates.path
|
|
69
|
+
|
|
70
|
+
super()
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
######
|
|
75
|
+
public
|
|
76
|
+
######
|
|
77
|
+
|
|
78
|
+
# The Arrow::Cache object used to cache template objects.
|
|
79
|
+
attr_accessor :cache
|
|
80
|
+
|
|
81
|
+
# The loader object that the factory uses to load templates
|
|
82
|
+
attr_accessor :loader
|
|
83
|
+
|
|
84
|
+
# The path to search for templates
|
|
85
|
+
attr_accessor :path
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Load a template object with the specified name.
|
|
89
|
+
def get_template( name )
|
|
90
|
+
self.log.debug "Fetching template '#{name}'"
|
|
91
|
+
|
|
92
|
+
if @cache
|
|
93
|
+
self.log.debug "Doing cached fetch."
|
|
94
|
+
tmpl = @cache.fetch( name, &method(:load_from_file) )
|
|
95
|
+
|
|
96
|
+
if tmpl.changed?
|
|
97
|
+
self.log.debug "Template has changed on disk: reloading"
|
|
98
|
+
@cache.invalidate( name )
|
|
99
|
+
tmpl = @cache.fetch( name, &method(:load_from_file) )
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
return tmpl.dup
|
|
103
|
+
else
|
|
104
|
+
self.log.debug "Caching disabled. Loading from file."
|
|
105
|
+
return self.load_from_file( name )
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
### Load a template from its source file (ie., if caching is turned off
|
|
111
|
+
### or if the cached version is either expired or not yet seen)
|
|
112
|
+
def load_from_file( name )
|
|
113
|
+
self.log.debug "Loading template #{name} from the filesystem"
|
|
114
|
+
return @loader.load( name, @path )
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
### Called when a template is expired from the cache
|
|
119
|
+
def template_expiration_hook( key, template )
|
|
120
|
+
self.log.debug "Template %s is expiring." % key
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end # class Arrow::TemplateFactory
|
|
124
|
+
|
|
125
|
+
|
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
# This is an abstract test case class for building Test::Unit unit tests for the
|
|
4
|
+
# Arrow web application framework. It consolidates most of the maintenance work
|
|
5
|
+
# that must be done to build a test file by adjusting the $LOAD_PATH to include
|
|
6
|
+
# the lib/ and ext/ directories, as well as adding some other useful methods
|
|
7
|
+
# that make building and maintaining the tests much easier (IMHO). See the docs
|
|
8
|
+
# for Test::Unit for more info on the particulars of unit testing.
|
|
9
|
+
#
|
|
10
|
+
# == Synopsis
|
|
11
|
+
#
|
|
12
|
+
# $LOAD_PATH.unshift "tests/lib" unless $LOAD_PATH.include?("tests/lib")
|
|
13
|
+
# require 'arrow/testcase'
|
|
14
|
+
#
|
|
15
|
+
# class MySomethingTest < Arrow::TestCase
|
|
16
|
+
# def set_up
|
|
17
|
+
# super()
|
|
18
|
+
# @foo = 'bar'
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def test_00_something
|
|
22
|
+
# obj = nil
|
|
23
|
+
# assert_nothing_raised { obj = MySomething.new }
|
|
24
|
+
# assert_instance_of MySomething, obj
|
|
25
|
+
# assert_respond_to :myMethod, obj
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# == VCS Id
|
|
30
|
+
#
|
|
31
|
+
# $Id$
|
|
32
|
+
#
|
|
33
|
+
# == Authors
|
|
34
|
+
#
|
|
35
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
36
|
+
#
|
|
37
|
+
# Copyright (c) 2003, 2004, 2006 RubyCrafters, LLC.
|
|
38
|
+
#
|
|
39
|
+
# This work is licensed under the Creative Commons Attribution License. To view
|
|
40
|
+
# a copy of this license, visit http://creativecommons.org/licenses/by/1.0 or
|
|
41
|
+
# send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
|
|
42
|
+
# 94305, USA.
|
|
43
|
+
#
|
|
44
|
+
#
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
require "readline"
|
|
48
|
+
include Readline
|
|
49
|
+
rescue LoadError
|
|
50
|
+
def readline( prompt )
|
|
51
|
+
$defout.print( prompt )
|
|
52
|
+
$defout.flush
|
|
53
|
+
return $stdin.gets
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
require 'rubygems'
|
|
59
|
+
rescue LoadError
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
require 'test/unit'
|
|
63
|
+
require 'test/unit/assertions'
|
|
64
|
+
require 'test/unit/util/backtracefilter'
|
|
65
|
+
|
|
66
|
+
require 'flexmock'
|
|
67
|
+
require 'apache/fakerequest'
|
|
68
|
+
|
|
69
|
+
require 'arrow'
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Test case class
|
|
73
|
+
class Arrow::TestCase < Test::Unit::TestCase
|
|
74
|
+
|
|
75
|
+
@@methodCounter = 0
|
|
76
|
+
|
|
77
|
+
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
|
|
78
|
+
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin
|
|
79
|
+
# <zenin@best.com>
|
|
80
|
+
AnsiAttributes = {
|
|
81
|
+
'clear' => 0,
|
|
82
|
+
'reset' => 0,
|
|
83
|
+
'bold' => 1,
|
|
84
|
+
'dark' => 2,
|
|
85
|
+
'underline' => 4,
|
|
86
|
+
'underscore' => 4,
|
|
87
|
+
'blink' => 5,
|
|
88
|
+
'reverse' => 7,
|
|
89
|
+
'concealed' => 8,
|
|
90
|
+
|
|
91
|
+
'black' => 30, 'on_black' => 40,
|
|
92
|
+
'red' => 31, 'on_red' => 41,
|
|
93
|
+
'green' => 32, 'on_green' => 42,
|
|
94
|
+
'yellow' => 33, 'on_yellow' => 43,
|
|
95
|
+
'blue' => 34, 'on_blue' => 44,
|
|
96
|
+
'magenta' => 35, 'on_magenta' => 45,
|
|
97
|
+
'cyan' => 36, 'on_cyan' => 46,
|
|
98
|
+
'white' => 37, 'on_white' => 47
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# The name of the file containing marshalled configuration values
|
|
102
|
+
ConfigSaveFile = "test.cfg"
|
|
103
|
+
|
|
104
|
+
### Inheritance callback -- adds @setupMethods and @teardownMethods ivars
|
|
105
|
+
### and accessors to the inheriting class.
|
|
106
|
+
def self::inherited( klass )
|
|
107
|
+
klass.module_eval {
|
|
108
|
+
@setupMethods = []
|
|
109
|
+
@teardownMethods = []
|
|
110
|
+
|
|
111
|
+
class << self
|
|
112
|
+
attr_accessor :setupMethods
|
|
113
|
+
attr_accessor :teardownMethods
|
|
114
|
+
end
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### Returns a String containing the specified ANSI escapes suitable for
|
|
120
|
+
### inclusion in another string. The <tt>attributes</tt> should be one
|
|
121
|
+
### or more of the keys of AnsiAttributes.
|
|
122
|
+
def self::ansiCode( *attributes )
|
|
123
|
+
return '' unless /(?:xterm(?:-color)?|eterm|linux)/i =~ ENV['TERM']
|
|
124
|
+
|
|
125
|
+
attr = attributes.collect {|a|
|
|
126
|
+
AnsiAttributes[a] ? AnsiAttributes[a] : nil
|
|
127
|
+
}.compact.join(';')
|
|
128
|
+
if attr.empty?
|
|
129
|
+
return ''
|
|
130
|
+
else
|
|
131
|
+
return "\e[%sm" % attr
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
### Output the specified <tt>msgs</tt> joined together to
|
|
137
|
+
### <tt>STDERR</tt> if <tt>$DEBUG</tt> is set.
|
|
138
|
+
def self::debugMsg( *msgs )
|
|
139
|
+
return unless $DEBUG
|
|
140
|
+
self.message "%sDEBUG>>> %s %s" %
|
|
141
|
+
[ ansiCode('dark', 'white'), msgs.join(''), ansiCode('reset') ]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
### Output the specified <tt>msgs</tt> joined together to
|
|
146
|
+
### <tt>STDOUT</tt>.
|
|
147
|
+
def self::message( *msgs )
|
|
148
|
+
$stderr.puts msgs.join('')
|
|
149
|
+
$stderr.flush
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
### Append a setup block for the current testcase
|
|
153
|
+
def self::addSetupBlock( &block )
|
|
154
|
+
@@methodCounter += 1
|
|
155
|
+
newMethodName = "setup_#{@@methodCounter}".to_sym
|
|
156
|
+
define_method( newMethodName, &block )
|
|
157
|
+
self.setupMethods.push newMethodName
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### Prepend a teardown block for the current testcase
|
|
162
|
+
def self::addTeardownBlock( &block )
|
|
163
|
+
@@methodCounter += 1
|
|
164
|
+
newMethodName = "teardown_#{@@methodCounter}".to_sym
|
|
165
|
+
define_method( newMethodName, &block )
|
|
166
|
+
self.teardownMethods.unshift newMethodName
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
#############################################################
|
|
171
|
+
### I N S T A N C E M E T H O D S
|
|
172
|
+
#############################################################
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
######
|
|
176
|
+
public
|
|
177
|
+
######
|
|
178
|
+
|
|
179
|
+
### Run dynamically-added setup methods
|
|
180
|
+
def setup( *args )
|
|
181
|
+
if self.class < Arrow::TestCase
|
|
182
|
+
self.class.setupMethods.each {|sblock|
|
|
183
|
+
self.send( sblock )
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
alias_method :set_up, :setup
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
### Run dynamically-added teardown methods
|
|
191
|
+
def teardown( *args )
|
|
192
|
+
if self.class < Arrow::TestCase
|
|
193
|
+
self.class.teardownMethods.each {|tblock|
|
|
194
|
+
self.send( tblock )
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
alias_method :tear_down, :teardown
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
### Skip the current step (called from #setup) with the +reason+ given.
|
|
202
|
+
def skip( reason=nil )
|
|
203
|
+
if reason
|
|
204
|
+
msg = "Skipping %s: %s" % [ @method_name, reason ]
|
|
205
|
+
else
|
|
206
|
+
msg = "Skipping %s: No reason given." % @method_name
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
$stderr.puts( msg ) if $VERBOSE
|
|
210
|
+
@method_name = :skipped_test
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def skipped_test # :nodoc:
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
### Add the specified +block+ to the code that gets executed by #setup.
|
|
219
|
+
def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
### Add the specified +block+ to the code that gets executed by #teardown.
|
|
223
|
+
def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
### Turn off the stupid 'No tests were specified'
|
|
227
|
+
def default_test; end
|
|
228
|
+
|
|
229
|
+
#################################################################
|
|
230
|
+
### A S S E R T I O N S
|
|
231
|
+
#################################################################
|
|
232
|
+
|
|
233
|
+
### Test Hashes for equivalent content
|
|
234
|
+
def assert_hash_equal( expected, actual, msg="" )
|
|
235
|
+
errmsg = "Expected hash <%p> to be equal to <%p>" % [expected, actual]
|
|
236
|
+
errmsg += ": #{msg}" unless msg.empty?
|
|
237
|
+
|
|
238
|
+
assert_block( errmsg ) {
|
|
239
|
+
diffs = compare_hashes( expected, actual )
|
|
240
|
+
unless diffs.empty?
|
|
241
|
+
errmsg += ": " + diffs.join("; ")
|
|
242
|
+
return false
|
|
243
|
+
else
|
|
244
|
+
return true
|
|
245
|
+
end
|
|
246
|
+
}
|
|
247
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
248
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
249
|
+
/assert_hash_equal/ =~ frame
|
|
250
|
+
}
|
|
251
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
252
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
### Compare two hashes for content, returning a list of their differences as
|
|
257
|
+
### descriptions. An empty Array return-value means they were the same.
|
|
258
|
+
def compare_hashes( hash1, hash2, subkeys=nil )
|
|
259
|
+
diffs = []
|
|
260
|
+
seenKeys = []
|
|
261
|
+
|
|
262
|
+
hash1.each {|k,v|
|
|
263
|
+
if !hash2.key?( k )
|
|
264
|
+
diffs << "missing %p pair" % k
|
|
265
|
+
elsif hash1[k].is_a?( Hash ) && hash2[k].is_a?( Hash )
|
|
266
|
+
diffs.push( compare_hashes(hash1[k], hash2[k]) )
|
|
267
|
+
elsif hash2[k] != hash1[k]
|
|
268
|
+
diffs << "value for %p expected to be %p, but was %p" %
|
|
269
|
+
[ k, hash1[k], hash2[k] ]
|
|
270
|
+
else
|
|
271
|
+
seenKeys << k
|
|
272
|
+
end
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
extraKeys = (hash2.keys - hash1.keys)
|
|
276
|
+
diffs << "extra key/s: #{extraKeys.join(', ')}" unless extraKeys.empty?
|
|
277
|
+
|
|
278
|
+
return diffs.flatten
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
### Test Hashes (or any other objects with a #keys method) for key set
|
|
283
|
+
### equality
|
|
284
|
+
def assert_same_keys( expected, actual, msg="" )
|
|
285
|
+
errmsg = "Expected keys of <%p> to be equal to those of <%p>" %
|
|
286
|
+
[ actual, expected ]
|
|
287
|
+
errmsg += ": #{msg}" unless msg.empty?
|
|
288
|
+
|
|
289
|
+
ekeys = expected.keys; akeys = actual.keys
|
|
290
|
+
assert_block( errmsg ) {
|
|
291
|
+
diffs = []
|
|
292
|
+
|
|
293
|
+
# XOR the arrays and make a diff for each one
|
|
294
|
+
((ekeys + akeys) - (ekeys & akeys)).each do |key|
|
|
295
|
+
if ekeys.include?( key )
|
|
296
|
+
diffs << "missing key %p" % [key]
|
|
297
|
+
else
|
|
298
|
+
diffs << "extra key %p" % [key]
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
unless diffs.empty?
|
|
303
|
+
errmsg += "\n" + diffs.join("; ")
|
|
304
|
+
return false
|
|
305
|
+
else
|
|
306
|
+
return true
|
|
307
|
+
end
|
|
308
|
+
}
|
|
309
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
310
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
311
|
+
/assert_hash_equal/ =~ frame
|
|
312
|
+
}
|
|
313
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
314
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
### Override the stupid deprecated #assert_not_nil so when it
|
|
319
|
+
### disappears, code doesn't break.
|
|
320
|
+
def assert_not_nil( obj, msg=nil )
|
|
321
|
+
msg ||= "<%p> expected to not be nil." % obj
|
|
322
|
+
assert_block( msg ) { !obj.nil? }
|
|
323
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
324
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
325
|
+
/assert_not_nil/ =~ frame
|
|
326
|
+
}
|
|
327
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
328
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
### Succeeds if +obj+ include? +item+.
|
|
333
|
+
def assert_include( item, obj, msg=nil )
|
|
334
|
+
msg ||= "<%p> expected to include <%p>." % [ obj, item ]
|
|
335
|
+
assert_block( msg ) { obj.respond_to?(:include?) && obj.include?(item) }
|
|
336
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
337
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
338
|
+
/assert_include/ =~ frame
|
|
339
|
+
}
|
|
340
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
341
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
### Negative of assert_respond_to
|
|
346
|
+
def assert_not_tainted( obj, msg=nil )
|
|
347
|
+
msg ||= "<%p> expected to NOT be tainted" % [ obj ]
|
|
348
|
+
assert_block( msg ) {
|
|
349
|
+
!obj.tainted?
|
|
350
|
+
}
|
|
351
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
352
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
353
|
+
/assert_not_tainted/ =~ frame
|
|
354
|
+
}
|
|
355
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
356
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
### Negative of assert_respond_to
|
|
361
|
+
def assert_not_respond_to( obj, meth )
|
|
362
|
+
msg = "%s expected NOT to respond to '%s'" %
|
|
363
|
+
[ obj.inspect, meth ]
|
|
364
|
+
assert_block( msg ) {
|
|
365
|
+
!obj.respond_to?( meth )
|
|
366
|
+
}
|
|
367
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
368
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
369
|
+
/assert_not_respond_to/ =~ frame
|
|
370
|
+
}
|
|
371
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
372
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
### Assert that the instance variable specified by +sym+ of an +object+
|
|
377
|
+
### is equal to the specified +value+. The '@' at the beginning of the
|
|
378
|
+
### +sym+ will be prepended if not present.
|
|
379
|
+
def assert_ivar_equal( value, object, sym )
|
|
380
|
+
sym = "@#{sym}".to_sym unless /^@/ =~ sym.to_s
|
|
381
|
+
msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" %
|
|
382
|
+
[ sym, object.inspect, value.inspect ]
|
|
383
|
+
msg += "\tbut was: <%p>" % [ object.instance_variable_get(sym) ]
|
|
384
|
+
assert_block( msg ) {
|
|
385
|
+
value == object.instance_variable_get(sym)
|
|
386
|
+
}
|
|
387
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
388
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
389
|
+
/assert_ivar_equal/ =~ frame
|
|
390
|
+
}
|
|
391
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
392
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
### Assert that the specified +object+ has an instance variable which
|
|
397
|
+
### matches the specified +sym+. The '@' at the beginning of the +sym+
|
|
398
|
+
### will be prepended if not present.
|
|
399
|
+
def assert_has_ivar( sym, object )
|
|
400
|
+
sym = "@#{sym}" unless /^@/ =~ sym.to_s
|
|
401
|
+
msg = "Object <%s> expected to have an instance variable <%s>" %
|
|
402
|
+
[ object.inspect, sym ]
|
|
403
|
+
assert_block( msg ) {
|
|
404
|
+
object.instance_variables.include?( sym.to_s )
|
|
405
|
+
}
|
|
406
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
407
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
408
|
+
/assert_has_ivar/ =~ frame
|
|
409
|
+
}
|
|
410
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
411
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
### Assert that the specified +str+ does *not* match the given regular
|
|
416
|
+
### expression +re+.
|
|
417
|
+
def assert_not_match( re, str )
|
|
418
|
+
msg = "<%s> expected not to match %p" %
|
|
419
|
+
[ str, re ]
|
|
420
|
+
assert_block( msg ) {
|
|
421
|
+
!re.match( str )
|
|
422
|
+
}
|
|
423
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
424
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
425
|
+
/assert_not_match/ =~ frame
|
|
426
|
+
}
|
|
427
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
428
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
### Assert that the specified +klass+ defines the specified instance
|
|
433
|
+
### method +meth+.
|
|
434
|
+
def assert_has_instance_method( klass, meth )
|
|
435
|
+
msg = "<%s> expected to define instance method #%s" %
|
|
436
|
+
[ klass, meth ]
|
|
437
|
+
assert_block( msg ) {
|
|
438
|
+
klass.instance_methods.include?( meth.to_s )
|
|
439
|
+
}
|
|
440
|
+
rescue Test::Unit::AssertionFailedError => err
|
|
441
|
+
cutframe = err.backtrace.reverse.find {|frame|
|
|
442
|
+
/assert_has_instance_method/ =~ frame
|
|
443
|
+
}
|
|
444
|
+
firstIdx = (err.backtrace.rindex( cutframe )||0) + 1
|
|
445
|
+
Kernel.raise( err, err.message, err.backtrace[firstIdx..-1] )
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
#################################################################
|
|
451
|
+
### M E S S A G E M E T H O D S
|
|
452
|
+
#################################################################
|
|
453
|
+
|
|
454
|
+
### Instance alias for the like-named class method.
|
|
455
|
+
def message( *msgs )
|
|
456
|
+
self.class.message( *msgs )
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
### Instance-alias for the like-named class method
|
|
461
|
+
def ansiCode( *attributes )
|
|
462
|
+
self.class.ansiCode( *attributes )
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
### Instance alias for the like-named class method
|
|
467
|
+
def debugMsg( *msgs )
|
|
468
|
+
self.class.debugMsg( *msgs )
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
### Return a separator line made up of <tt>length</tt> of the specified
|
|
473
|
+
### <tt>char</tt>.
|
|
474
|
+
def hrule( length=75, char="-" )
|
|
475
|
+
return (char * length ) + "\n"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
### Return a section delimited by hrules with the specified +caption+ and
|
|
479
|
+
### +content+.
|
|
480
|
+
def hruleSection( content, caption='' )
|
|
481
|
+
caption << ' ' unless caption.empty?
|
|
482
|
+
return caption +
|
|
483
|
+
hrule( 75 - caption.length ) +
|
|
484
|
+
content.chomp + "\n" +
|
|
485
|
+
hrule()
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
### Output a header for delimiting tests
|
|
490
|
+
def printTestHeader( desc )
|
|
491
|
+
return unless $VERBOSE || $DEBUG
|
|
492
|
+
message "%s>>> %s <<<%s" %
|
|
493
|
+
[ ansiCode('bold','yellow','on_blue'), desc, ansiCode('reset') ]
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
#################################################################
|
|
498
|
+
### T E S T I N G U T I L I T I E S
|
|
499
|
+
#################################################################
|
|
500
|
+
|
|
501
|
+
### Try to force garbage collection to start.
|
|
502
|
+
def collectGarbage
|
|
503
|
+
a = []
|
|
504
|
+
1000.times { a << {} }
|
|
505
|
+
a = nil
|
|
506
|
+
GC.start
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
### Touch a file and truncate it to 0 bytes
|
|
511
|
+
def zerofile( filename )
|
|
512
|
+
File.open( filename, File::WRONLY|File::CREAT ) {}
|
|
513
|
+
File.truncate( filename, 0 )
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
### Output the name of the test as it's running if in verbose mode.
|
|
518
|
+
def run( result )
|
|
519
|
+
$stderr.puts self.name if $VERBOSE || $DEBUG
|
|
520
|
+
|
|
521
|
+
# Support debugging for individual tests
|
|
522
|
+
olddb = nil
|
|
523
|
+
if $DebugPattern && $DebugPattern =~ @method_name
|
|
524
|
+
Arrow::Logger.global.outputters <<
|
|
525
|
+
Arrow::Logger::Outputter.create( 'file:stderr' )
|
|
526
|
+
Arrow::Logger.global.level = :debug
|
|
527
|
+
|
|
528
|
+
olddb = $DEBUG
|
|
529
|
+
$DEBUG = true
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
super
|
|
533
|
+
|
|
534
|
+
unless olddb.nil?
|
|
535
|
+
$DEBUG = olddb
|
|
536
|
+
Arrow::Logger.global.outputters.clear
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
#################################################################
|
|
542
|
+
### P R O M P T I N G M E T H O D S
|
|
543
|
+
#################################################################
|
|
544
|
+
|
|
545
|
+
### Output the specified <tt>promptString</tt> as a prompt (in green) and
|
|
546
|
+
### return the user's input with leading and trailing spaces removed.
|
|
547
|
+
def prompt( promptString )
|
|
548
|
+
promptString.chomp!
|
|
549
|
+
return readline( ansiCode('bold', 'green') + "#{promptString}: " +
|
|
550
|
+
ansiCode('reset') ).strip
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
### Prompt the user with the given <tt>promptString</tt> via #prompt,
|
|
555
|
+
### substituting the given <tt>default</tt> if the user doesn't input
|
|
556
|
+
### anything.
|
|
557
|
+
def promptWithDefault( promptString, default )
|
|
558
|
+
response = prompt( "%s [%s]" % [ promptString, default ] )
|
|
559
|
+
if response.empty?
|
|
560
|
+
return default
|
|
561
|
+
else
|
|
562
|
+
return response
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
end # module Arrow
|
|
567
|
+
|