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