arrow 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. metadata +339 -0
@@ -0,0 +1,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
+