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,52 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'arrow/exceptions'
|
|
4
|
+
require 'arrow/session/id'
|
|
5
|
+
|
|
6
|
+
# The Arrow::Session::UsertrackId class, a derivative of Arrow::Session::Id.
|
|
7
|
+
# This class creates session id objects which uses Apache's builtin
|
|
8
|
+
# mod_usertrack for the session key.
|
|
9
|
+
#
|
|
10
|
+
# == Authors
|
|
11
|
+
#
|
|
12
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
13
|
+
#
|
|
14
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
15
|
+
#
|
|
16
|
+
class Arrow::Session::UserTrackId < Arrow::Session::Id
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
#############################################################
|
|
21
|
+
### C L A S S M E T H O D S
|
|
22
|
+
#############################################################
|
|
23
|
+
|
|
24
|
+
### Returns an untainted copy of the specified +idstring+ if it is in
|
|
25
|
+
### the expected form for this type of id.
|
|
26
|
+
def self::validate( uri, idstring )
|
|
27
|
+
return nil if idstring.nil?
|
|
28
|
+
rval = idstring[/^[\w.]+\.\d+$/] or return nil?
|
|
29
|
+
rval.untaint
|
|
30
|
+
return rval
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Generate a new id string for the given request
|
|
35
|
+
def self::generate( uri, request )
|
|
36
|
+
if uri.path
|
|
37
|
+
cookieName = uri.path.sub( %r{^/}, '' )
|
|
38
|
+
else
|
|
39
|
+
cookieName = 'Apache'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
unless request.cookies.key?( cookieName )
|
|
43
|
+
raise SessionError, "No cookie named '%s' was found. Make sure "\
|
|
44
|
+
"mod_usertrack is enabled and configured correctly" %
|
|
45
|
+
cookieName
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return validate( uri, request.cookies[cookieName].value )
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end # class Arrow::Session::UsertrackId
|
|
52
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
require 'apache/fakerequest'
|
|
6
|
+
|
|
7
|
+
require 'arrow'
|
|
8
|
+
require 'arrow/applet'
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# A collection of helper methods and classes for RSpec applet specifications
|
|
12
|
+
#
|
|
13
|
+
# == Synopsis
|
|
14
|
+
#
|
|
15
|
+
# require 'arrow/spechelpers'
|
|
16
|
+
#
|
|
17
|
+
# describe "SomeApplet" do
|
|
18
|
+
# include Arrow::AppletFixtures
|
|
19
|
+
#
|
|
20
|
+
# before( :all ) do
|
|
21
|
+
# @appletclass = load_appletclass( "someapplet" )
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# before( :each ) do
|
|
25
|
+
# @applet = @appletclass.new( nil, nil, nil )
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# == Authors
|
|
30
|
+
#
|
|
31
|
+
# * Michael Granger <mgranger@laika.com>
|
|
32
|
+
#
|
|
33
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
34
|
+
#
|
|
35
|
+
module Arrow::SpecHelpers
|
|
36
|
+
|
|
37
|
+
TEST_HEADERS = {
|
|
38
|
+
'Host' => 'www.example.com:80',
|
|
39
|
+
'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4',
|
|
40
|
+
'Accept' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
|
41
|
+
'Accept-Language' => 'en-us,en;q=0.5',
|
|
42
|
+
'Accept-Encoding' => 'gzip,deflate',
|
|
43
|
+
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
|
44
|
+
'Keep-Alive' => '300',
|
|
45
|
+
'Connection' => 'keep-alive',
|
|
46
|
+
'Referer' => 'https://www.example.com/',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Find directories that applets live in (current just searches the CWD for subdirectories
|
|
51
|
+
### called 'applets')
|
|
52
|
+
def find_applet_directories
|
|
53
|
+
basedir = Pathname.pwd
|
|
54
|
+
return Pathname.glob( basedir + '**' + 'applets' ).
|
|
55
|
+
find_all {|path| path.directory? && path.readable? }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Load an appletclass for the specified +name+ and return it.
|
|
60
|
+
def load_appletclass( name )
|
|
61
|
+
dirs = self.find_applet_directories
|
|
62
|
+
appletfiles = dirs.collect {|dir| Pathname.glob(dir + "**/#{name}.rb") }.flatten
|
|
63
|
+
|
|
64
|
+
if appletfiles.empty?
|
|
65
|
+
raise "Couldn't find an applet named '#{name}' in applet path %s" %
|
|
66
|
+
[ dirs.collect {|dir| dir.to_s}.join(':') ]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return Arrow::Applet.load( appletfiles.first ).first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end # Arrow::SpecHelpers
|
|
73
|
+
|
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
require 'arrow/mixins'
|
|
6
|
+
require 'arrow/exceptions'
|
|
7
|
+
require 'arrow/object'
|
|
8
|
+
require 'arrow/path'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# The Arrow::Template class, instances of which are used to
|
|
12
|
+
# generate output for Arrow applications.
|
|
13
|
+
#
|
|
14
|
+
# == Synopsis
|
|
15
|
+
#
|
|
16
|
+
# :TODO: Write some useful Arrow::Template examples
|
|
17
|
+
#
|
|
18
|
+
# == Authors
|
|
19
|
+
#
|
|
20
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
|
21
|
+
#
|
|
22
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
|
23
|
+
#
|
|
24
|
+
class Arrow::Template < Arrow::Object
|
|
25
|
+
extend Forwardable
|
|
26
|
+
include Arrow::HashUtilities
|
|
27
|
+
|
|
28
|
+
require 'arrow/template/parser'
|
|
29
|
+
require 'arrow/template/nodes'
|
|
30
|
+
require 'arrow/template/iterator'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Configuration defaults. Valid members are the same as those listed for
|
|
34
|
+
# the +config+ item of the #new method.
|
|
35
|
+
DEFAULTS = {
|
|
36
|
+
:parserClass => Arrow::Template::Parser,
|
|
37
|
+
:elideDirectiveLines => true,
|
|
38
|
+
:debuggingComments => false,
|
|
39
|
+
:commentStart => '<!-- ',
|
|
40
|
+
:commentEnd => ' -->',
|
|
41
|
+
:strictAttributes => false,
|
|
42
|
+
}
|
|
43
|
+
DEFAULTS.freeze
|
|
44
|
+
|
|
45
|
+
# A Hash which specifies the default renderers for different classes of
|
|
46
|
+
# objects.
|
|
47
|
+
DEFAULT_RENDERERS = {
|
|
48
|
+
Arrow::Template => lambda {|subtempl,templ|
|
|
49
|
+
subtempl.render( nil, nil, templ )
|
|
50
|
+
},
|
|
51
|
+
::Object => :to_s,
|
|
52
|
+
::Array => lambda {|ary,tmpl|
|
|
53
|
+
tmpl.render_objects(*ary)
|
|
54
|
+
},
|
|
55
|
+
::Hash => lambda {|hsh,tmpl|
|
|
56
|
+
hsh.collect do |k,v| tmpl.render_objects(k, ": ", v) end
|
|
57
|
+
},
|
|
58
|
+
::Method => lambda {|meth,tmpl|
|
|
59
|
+
tmpl.render_objects( meth.call )
|
|
60
|
+
},
|
|
61
|
+
::Exception => lambda {|err,tmpl|
|
|
62
|
+
tmpl.render_comment "%s: %s: %s" % [
|
|
63
|
+
err.class.name,
|
|
64
|
+
err.message,
|
|
65
|
+
err.backtrace ? err.backtrace[0] : "Stupid exception with no backtrace.",
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### A class for objects which contain the execution space of all the
|
|
72
|
+
### code in a single rendering of a template.
|
|
73
|
+
class RenderingScope < Arrow::Object
|
|
74
|
+
|
|
75
|
+
### Create a new RenderingScope object with the specified
|
|
76
|
+
### definitions +defs+. Each key => value pair in +defs+ will become
|
|
77
|
+
### singleton accessors on the resulting object.
|
|
78
|
+
def initialize( defs={} )
|
|
79
|
+
@definitions = []
|
|
80
|
+
self.add_definition_set( defs )
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
######
|
|
85
|
+
public
|
|
86
|
+
######
|
|
87
|
+
|
|
88
|
+
# The stack of definition contexts being represented by the scope.
|
|
89
|
+
#attr_reader :definitions
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
### Fetch the Binding object for the RenderingScope.
|
|
93
|
+
def get_binding; binding; end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Add the specified definitions +defs+ to the object.
|
|
97
|
+
def add_definition_set( defs )
|
|
98
|
+
# self.log.debug "adding definition set: %p" % [ defs ]
|
|
99
|
+
@definitions.push( defs )
|
|
100
|
+
|
|
101
|
+
defs.each do |name,val|
|
|
102
|
+
raise Arrow::ScopeError, "Cannot add a definition with a blank key" if
|
|
103
|
+
name.to_s.empty?
|
|
104
|
+
raise Arrow::ScopeError, "Cannot override @definitions" if
|
|
105
|
+
name == 'definitions'
|
|
106
|
+
@definitions.last[ name ] = val
|
|
107
|
+
|
|
108
|
+
# Add accessor and ivar for the definition if it doesn't
|
|
109
|
+
# already have one.
|
|
110
|
+
unless self.respond_to?( name.to_s.to_sym )
|
|
111
|
+
#self.log.debug "Adding accessor for %s" % name
|
|
112
|
+
(class << self; self; end).instance_eval {
|
|
113
|
+
attr_accessor name.to_s.to_sym
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
#self.log.debug "Already have an accessor for '#{name}'"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
self.instance_variable_set( "@#{name}", defs[name] )
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
### Remove the specified definitions +defs+ from the object. Using
|
|
125
|
+
### a definition so removed after this point will raise an error.
|
|
126
|
+
def remove_definition_set
|
|
127
|
+
#self.log.debug "Removing definition set from stack of %d frames" %
|
|
128
|
+
# @definitions.nitems
|
|
129
|
+
defs = @definitions.pop
|
|
130
|
+
#self.log.debug "Removing defs: %p, %d frames left" %
|
|
131
|
+
# [ defs, @definitions.nitems ]
|
|
132
|
+
|
|
133
|
+
defs.keys.each do |name|
|
|
134
|
+
next if name == 'definitions'
|
|
135
|
+
|
|
136
|
+
# If there was already a definition in effect with the same
|
|
137
|
+
# name in a previous scope, fetch it so we can play with it.
|
|
138
|
+
previousSet = @definitions.reverse.find {|set| set.key?(name)}
|
|
139
|
+
#self.log.debug "Found previousSet %p for %s in scope stack of %d frames" %
|
|
140
|
+
# [ previousSet, name, @definitions.nitems ]
|
|
141
|
+
|
|
142
|
+
# If none of the previous definition sets had a definition
|
|
143
|
+
# with the same name, remove the accessor and the ivar
|
|
144
|
+
unless previousSet
|
|
145
|
+
#self.log.debug "Removing definition '%s' entirely" % name
|
|
146
|
+
(class << self; self; end).module_eval {
|
|
147
|
+
remove_method name.to_s.to_sym
|
|
148
|
+
}
|
|
149
|
+
remove_instance_variable( "@#{name}" )
|
|
150
|
+
|
|
151
|
+
# Otherwise just reset the ivar to the previous value
|
|
152
|
+
else
|
|
153
|
+
#self.log.debug "Restoring previous def for '%s'" % name
|
|
154
|
+
self.instance_variable_set( "@#{name}", previousSet[name] )
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### Override the given definitions +defs+ for the duration of the
|
|
162
|
+
### given block. After the block exits, the original definitions
|
|
163
|
+
### will be restored.
|
|
164
|
+
def override( defs ) # :yields: receiver
|
|
165
|
+
begin
|
|
166
|
+
#self.log.debug "Before adding definitions: %d scope frame/s. Last: %p" %
|
|
167
|
+
# [ @definitions.nitems, @definitions.last.keys ]
|
|
168
|
+
self.add_definition_set( defs )
|
|
169
|
+
#self.log.debug "After adding definitions: %d scope frame/s. Last: %p" %
|
|
170
|
+
# [ @definitions.nitems, @definitions.last.keys ]
|
|
171
|
+
yield( self )
|
|
172
|
+
ensure
|
|
173
|
+
self.remove_definition_set
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end # class RenderingScope
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
#############################################################
|
|
182
|
+
### C L A S S M E T H O D S
|
|
183
|
+
#############################################################
|
|
184
|
+
|
|
185
|
+
# The Array of directories the template class searches for template
|
|
186
|
+
# names given to #load.
|
|
187
|
+
@load_path = %w{.}
|
|
188
|
+
class << self
|
|
189
|
+
attr_accessor :load_path
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
### Load a template from a file.
|
|
194
|
+
def self::load( name, path=[] )
|
|
195
|
+
|
|
196
|
+
# Find the file on either the specified or default path
|
|
197
|
+
path = self.load_path if path.empty?
|
|
198
|
+
Arrow::Logger[self].debug "Searching for template '%s' in %d directories" %
|
|
199
|
+
[ name, path.size ]
|
|
200
|
+
filename = self.find_file( name, path )
|
|
201
|
+
Arrow::Logger[self].debug "Found '%s'" % [ filename ]
|
|
202
|
+
|
|
203
|
+
# Read the template source
|
|
204
|
+
source = File.read( filename )
|
|
205
|
+
source.untaint
|
|
206
|
+
|
|
207
|
+
# Create a new template object, set its path and filename, then tell it
|
|
208
|
+
# to parse the loaded source to define its behaviour. Parse is called
|
|
209
|
+
# after the file and path are set so directives in the template can
|
|
210
|
+
# use them.
|
|
211
|
+
obj = new()
|
|
212
|
+
obj._file = filename
|
|
213
|
+
obj._load_path.replace( path )
|
|
214
|
+
obj.parse( source )
|
|
215
|
+
|
|
216
|
+
return obj
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
### Find the specified +file+ in the given +path+ (or the Template
|
|
221
|
+
### class's #load_path if not specified).
|
|
222
|
+
def self::find_file( file, path=[] )
|
|
223
|
+
raise TemplateError, "Filename #{file} is tainted." if
|
|
224
|
+
file.tainted?
|
|
225
|
+
|
|
226
|
+
filename = nil
|
|
227
|
+
path.collect {|dir| File.expand_path(file, dir).untaint }.each do |fn|
|
|
228
|
+
Arrow::Logger[self].debug "Checking path %p" % [ fn ]
|
|
229
|
+
if File.file?( fn )
|
|
230
|
+
Arrow::Logger[self].debug " found the template file at %p" % [ fn ]
|
|
231
|
+
filename = fn
|
|
232
|
+
break
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
Arrow::Logger[self].debug " %p does not exist or is not a plain file." % [ fn ]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
raise Arrow::TemplateError,
|
|
239
|
+
"Template '%s' not found. Search path was %p" %
|
|
240
|
+
[ file, path ] unless filename
|
|
241
|
+
|
|
242
|
+
return filename
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
### Create an attr_reader method for the specified +sym+, but one which
|
|
247
|
+
### will look for instance variables with any leading underbars removed.
|
|
248
|
+
def self::attr_underbarred_reader( sym )
|
|
249
|
+
ivarname = '@' + sym.to_s.gsub( /^_+/, '' )
|
|
250
|
+
define_method( sym ) {
|
|
251
|
+
self.instance_variable_get( ivarname )
|
|
252
|
+
}
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
### Create an attr_accessor method for the specified +sym+, but one which
|
|
257
|
+
### will look for instance variables with any leading underbars removed.
|
|
258
|
+
def self::attr_underbarred_accessor( sym )
|
|
259
|
+
ivarname = '@' + sym.to_s.gsub( /^_+/, '' )
|
|
260
|
+
define_method( sym ) {
|
|
261
|
+
self.instance_variable_get( ivarname )
|
|
262
|
+
}
|
|
263
|
+
define_method( "#{sym}=" ) {|arg|
|
|
264
|
+
self.instance_variable_set( ivarname, arg )
|
|
265
|
+
}
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
#############################################################
|
|
270
|
+
### I N S T A N C E M E T H O D S
|
|
271
|
+
#############################################################
|
|
272
|
+
|
|
273
|
+
### Create a new template object with the specified +content+ (a String)
|
|
274
|
+
### and +config+ hash. The +config+ can contain one or more of the
|
|
275
|
+
### following keys:
|
|
276
|
+
###
|
|
277
|
+
### [<b>:parserClass</b>]
|
|
278
|
+
### The class object that will be instantiated to parse the template
|
|
279
|
+
### text into nodes. Defaults to Arrow::Template::Parser.
|
|
280
|
+
### [<b>:elideDirectiveLines</b>]
|
|
281
|
+
### If set to a +true+ value, lines of the template which contain only
|
|
282
|
+
### whitespace and one or more non-rendering directives will be
|
|
283
|
+
### discarded from the rendered output.
|
|
284
|
+
### [<b>:debuggingComments</b>]
|
|
285
|
+
### If set to a +true+ value, nodes which are set up to do so will
|
|
286
|
+
### insert a comment with debugging information immediately before
|
|
287
|
+
### their rendered output.
|
|
288
|
+
### [<b>:commentStart</b>]
|
|
289
|
+
### The String which will be prepended to all comments rendered in the
|
|
290
|
+
### output. See #render_comment.
|
|
291
|
+
### [<b>:commentEnd</b>]
|
|
292
|
+
### The String which will be appended to all comments rendered in the
|
|
293
|
+
### output. See #render_comment.
|
|
294
|
+
### [<b>:strictAttributes</b>]
|
|
295
|
+
### If set to a +true+ value, method calls which don't match
|
|
296
|
+
### already-extant attributes will result in NameErrors. This is
|
|
297
|
+
### +false+ by default, which causes method calls to generate
|
|
298
|
+
### attributes with the same name.
|
|
299
|
+
def initialize( content=nil, config={} )
|
|
300
|
+
@config = DEFAULTS.merge( config, &HashMergeFunction )
|
|
301
|
+
@renderers = DEFAULT_RENDERERS.dup
|
|
302
|
+
@attributes = {}
|
|
303
|
+
@syntax_tree = []
|
|
304
|
+
@source = content
|
|
305
|
+
@file = nil
|
|
306
|
+
@creation_time = Time.now
|
|
307
|
+
@load_path = self.class.load_path.dup
|
|
308
|
+
@prerender_done = false
|
|
309
|
+
@postrender_done = false
|
|
310
|
+
|
|
311
|
+
@enclosing_templates = []
|
|
312
|
+
|
|
313
|
+
case content
|
|
314
|
+
when String
|
|
315
|
+
self.parse( content )
|
|
316
|
+
when Array
|
|
317
|
+
self.install_syntax_tree( content )
|
|
318
|
+
when NilClass
|
|
319
|
+
# No-op
|
|
320
|
+
else
|
|
321
|
+
raise TemplateError,
|
|
322
|
+
"Can't handle a %s as template content" % content.class.name
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
### Initialize a copy of the +original+ template object.
|
|
328
|
+
def initialize_copy( original )
|
|
329
|
+
super
|
|
330
|
+
|
|
331
|
+
@attributes = {}
|
|
332
|
+
tree = original._syntax_tree.collect {|node| node.clone}
|
|
333
|
+
self.install_syntax_tree( tree )
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
######
|
|
338
|
+
public
|
|
339
|
+
######
|
|
340
|
+
|
|
341
|
+
# Square-bracket methods access template attributes
|
|
342
|
+
def_delegators :@attributes, :[], :[]=
|
|
343
|
+
|
|
344
|
+
# The Hash of "attributes" for the template -- data fields which hold
|
|
345
|
+
# values which can be accessed by the template's nodes for rendering.
|
|
346
|
+
attr_underbarred_reader :_attributes
|
|
347
|
+
|
|
348
|
+
# The Array of first-level nodes which make up the AST of the template.
|
|
349
|
+
attr_underbarred_reader :_syntax_tree
|
|
350
|
+
|
|
351
|
+
# The template's configuration
|
|
352
|
+
attr_underbarred_reader :_config
|
|
353
|
+
|
|
354
|
+
# The Hash of rendering Procs, Methods, or Symbols (which specify a
|
|
355
|
+
# method on the rendered object) which are used to render objects in the
|
|
356
|
+
# template's node contents.
|
|
357
|
+
attr_underbarred_reader :_renderers
|
|
358
|
+
|
|
359
|
+
# The source file for the template, if any
|
|
360
|
+
attr_underbarred_accessor :_file
|
|
361
|
+
|
|
362
|
+
# The template source
|
|
363
|
+
attr_underbarred_accessor :_source
|
|
364
|
+
|
|
365
|
+
# The load path used when the template was loaded. This is the path that
|
|
366
|
+
# will be used to load any subordinate resources (eg., includes).
|
|
367
|
+
attr_underbarred_accessor :_load_path
|
|
368
|
+
|
|
369
|
+
# The Time that the template object was created
|
|
370
|
+
attr_underbarred_accessor :_creation_time
|
|
371
|
+
|
|
372
|
+
# The template which contains this one (if any) during a render.
|
|
373
|
+
attr_underbarred_accessor :_enclosing_templates
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
### Return the template that is enclosing the receiver in the current context,
|
|
378
|
+
### if any.
|
|
379
|
+
def _enclosing_template
|
|
380
|
+
self._enclosing_templates.last
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
### Return a human-readable representation of the template object.
|
|
385
|
+
def inspect
|
|
386
|
+
"#<%s:0x%0x %s (%d nodes)>" % [
|
|
387
|
+
self.class.name,
|
|
388
|
+
self.object_id * 2,
|
|
389
|
+
@file ? @file : '(anonymous)',
|
|
390
|
+
@syntax_tree.length,
|
|
391
|
+
]
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
### Return the approximate size of the template, in bytes. Used by
|
|
396
|
+
### Arrow::Cache for size thresholds.
|
|
397
|
+
def memsize
|
|
398
|
+
@source ? @source.length : 0
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
### Parse the given template source (a String) and put the resulting
|
|
403
|
+
### nodes into the template's syntax_tree.
|
|
404
|
+
def parse( source )
|
|
405
|
+
@source = source
|
|
406
|
+
parserClass = @config[:parserClass]
|
|
407
|
+
tree = parserClass.new( @config ).parse( source, self )
|
|
408
|
+
|
|
409
|
+
#self.log.debug "Parse complete: syntax tree is: %p" % tree
|
|
410
|
+
return self.install_syntax_tree( tree )
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
### Install a new syntax tree in the template object, replacing the old one,
|
|
415
|
+
### if any.
|
|
416
|
+
def install_syntax_tree( tree )
|
|
417
|
+
@syntax_tree = tree
|
|
418
|
+
@syntax_tree.each do |node| node.add_to_template(self) end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
### Install the given +node+ into the template object.
|
|
423
|
+
def install_node( node )
|
|
424
|
+
#self.log.debug "Installing a %s %p" % [node.type, node]
|
|
425
|
+
|
|
426
|
+
if node.respond_to?( :name ) && node.name
|
|
427
|
+
unless @attributes.key?( node.name )
|
|
428
|
+
#self.log.debug "Installing an attribute for a node named %p" % node.name
|
|
429
|
+
@attributes[ node.name ] = nil
|
|
430
|
+
self.add_attribute_accessor( node.name.to_sym )
|
|
431
|
+
self.add_attribute_mutator( node.name.to_sym )
|
|
432
|
+
else
|
|
433
|
+
#self.log.debug "Already have a attribute named %p" % node.name
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
### Returns +true+ if the source file from which the template was read
|
|
440
|
+
### has been modified since the receiver was instantiated. Always
|
|
441
|
+
### returns +false+ if the template wasn't loaded from a file.
|
|
442
|
+
def changed?
|
|
443
|
+
return false unless @file
|
|
444
|
+
|
|
445
|
+
if File.exists?( @file )
|
|
446
|
+
self.log.debug "Comparing creation time '%s' with file mtime '%s'" %
|
|
447
|
+
[ @creation_time, File.mtime(@file) ]
|
|
448
|
+
rval = File.mtime( @file ) > @creation_time
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
self.log.debug "Template file '%s' has %s" %
|
|
452
|
+
[ @file, rval ? "changed" : "not changed" ]
|
|
453
|
+
return rval
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
### Returns +true+ if this template has already been through a pre-render.
|
|
458
|
+
def prerender_done?
|
|
459
|
+
return @prerender_done
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
### Prep the template for rendering, calling each of its nodes'
|
|
464
|
+
### #before_rendering hook.
|
|
465
|
+
def prerender( enclosing_template=nil )
|
|
466
|
+
@enclosing_templates << enclosing_template
|
|
467
|
+
|
|
468
|
+
@syntax_tree.each do |node|
|
|
469
|
+
# self.log.debug " pre-rendering %p" % [node]
|
|
470
|
+
node.before_rendering( self ) if
|
|
471
|
+
node.respond_to?( :before_rendering )
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
alias_method :before_rendering, :prerender
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
### Render the template to text and return it as a String. If called with an
|
|
478
|
+
### Array of +nodes+, the template will render them instead of its own
|
|
479
|
+
### syntax_tree. If given a scope (a Module object), a Binding of its
|
|
480
|
+
### internal state it will be used as the context of evaluation for the
|
|
481
|
+
### render. If not specified, a new anonymous Module instance is created for
|
|
482
|
+
### the render. If a +enclosing_template+ is given, make it available during
|
|
483
|
+
### rendering for variable-sharing, etc. Returns the results of each nodes'
|
|
484
|
+
### render joined together with the default string separator (+$,+).
|
|
485
|
+
def render( nodes=nil, scope=nil, enclosing_template=nil )
|
|
486
|
+
rval = []
|
|
487
|
+
nodes ||= self.get_prepped_nodes
|
|
488
|
+
scope ||= self.make_rendering_scope
|
|
489
|
+
|
|
490
|
+
self.prerender( enclosing_template )
|
|
491
|
+
|
|
492
|
+
# Render each node
|
|
493
|
+
nodes.each do |node|
|
|
494
|
+
# self.log.debug " rendering %p" % [ node ]
|
|
495
|
+
begin
|
|
496
|
+
rval << node.render( self, scope )
|
|
497
|
+
rescue ::Exception => err
|
|
498
|
+
rval << err
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
return self.render_objects( *rval )
|
|
503
|
+
ensure
|
|
504
|
+
self.postrender
|
|
505
|
+
end
|
|
506
|
+
alias_method :to_s, :render
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
### Returns +true+ if this template has already been through a post-render.
|
|
510
|
+
def postrender_done?
|
|
511
|
+
return @postrender_done
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
### Clean up after template rendering, calling each of its nodes'
|
|
516
|
+
### #after_rendering hook.
|
|
517
|
+
def postrender( enclosing_template=nil )
|
|
518
|
+
@syntax_tree.each do |node|
|
|
519
|
+
# self.log.debug " post-rendering %p" % [node]
|
|
520
|
+
node.after_rendering( self ) if
|
|
521
|
+
node.respond_to?( :after_rendering )
|
|
522
|
+
end
|
|
523
|
+
@enclosing_templates.pop
|
|
524
|
+
end
|
|
525
|
+
alias_method :after_rendering, :postrender
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
### Create an anonymous module to act as a scope for any evals that take
|
|
529
|
+
### place during a single render.
|
|
530
|
+
def make_rendering_scope
|
|
531
|
+
# self.log.debug "Making rendering scope with attributes: %p" % [@attributes]
|
|
532
|
+
scope = RenderingScope.new( @attributes )
|
|
533
|
+
return scope
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
### Render the specified objects into text.
|
|
538
|
+
def render_objects( *objs )
|
|
539
|
+
objs.collect do |obj|
|
|
540
|
+
rval = nil
|
|
541
|
+
key = (@renderers.keys & obj.class.ancestors).sort {|a,b| a <=> b}.first
|
|
542
|
+
|
|
543
|
+
begin
|
|
544
|
+
if key
|
|
545
|
+
case @renderers[ key ]
|
|
546
|
+
when Proc, Method
|
|
547
|
+
rval = @renderers[ key ].call( obj, self )
|
|
548
|
+
when Symbol
|
|
549
|
+
methodname = @renderers[ key ]
|
|
550
|
+
rval = obj.send( methodname )
|
|
551
|
+
else
|
|
552
|
+
raise TypeError, "Unknown renderer type '%s' for %p" %
|
|
553
|
+
[ @renderers[key], obj ]
|
|
554
|
+
end
|
|
555
|
+
else
|
|
556
|
+
rval = obj.to_s
|
|
557
|
+
end
|
|
558
|
+
rescue => err
|
|
559
|
+
self.log.error "rendering error while rendering %p (a %s): %s" %
|
|
560
|
+
[obj, obj.class.name, err.message]
|
|
561
|
+
@renderers[ ::Exception ].call( err, self )
|
|
562
|
+
end
|
|
563
|
+
end.join
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
### Render the given +message+ as a comment as specified by the template
|
|
568
|
+
### configuration.
|
|
569
|
+
def render_comment( message )
|
|
570
|
+
comment = "%s%s%s\n" % [
|
|
571
|
+
@config[:commentStart],
|
|
572
|
+
message,
|
|
573
|
+
@config[:commentEnd],
|
|
574
|
+
]
|
|
575
|
+
#self.log.debug "Rendered comment: %s" % comment
|
|
576
|
+
return comment
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
### Call the given +block+, overriding the contents of the template's attributes
|
|
581
|
+
### and the definitions in the specified +scope+ with those from the pairs in
|
|
582
|
+
### the given +hash+.
|
|
583
|
+
def with_overridden_attributes( scope, hash )
|
|
584
|
+
oldvals = {}
|
|
585
|
+
begin
|
|
586
|
+
hash.each do |name, value|
|
|
587
|
+
#self.log.debug "Overriding attribute %s with value: %p" %
|
|
588
|
+
# [ name, value ]
|
|
589
|
+
oldvals[name] = @attributes.key?( name ) ? @attributes[ name ] : nil
|
|
590
|
+
@attributes[ name ] = value
|
|
591
|
+
end
|
|
592
|
+
scope.override( hash ) do
|
|
593
|
+
yield( self )
|
|
594
|
+
end
|
|
595
|
+
ensure
|
|
596
|
+
oldvals.each do |name, value|
|
|
597
|
+
#self.log.debug "Restoring old value: %s for attribute %p" %
|
|
598
|
+
# [ name, value ]
|
|
599
|
+
@attributes.delete( name )
|
|
600
|
+
@attributes[ name ] = oldvals[name] if oldvals[name]
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
#########
|
|
608
|
+
protected
|
|
609
|
+
#########
|
|
610
|
+
|
|
611
|
+
### Returns the syntax tree with its nodes prerendered in accordance with
|
|
612
|
+
### the template's configuration.
|
|
613
|
+
def get_prepped_nodes
|
|
614
|
+
tree = @syntax_tree.dup
|
|
615
|
+
|
|
616
|
+
self.strip_directive_whitespace( tree ) if
|
|
617
|
+
@config[:elideDirectiveLines]
|
|
618
|
+
|
|
619
|
+
return tree
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
### Strip whitespace from the tails of textnodes before and the head
|
|
624
|
+
### of textnodes after lines consisting only of non-rendering directives
|
|
625
|
+
### in the given template syntax +tree+.
|
|
626
|
+
def strip_directive_whitespace( tree )
|
|
627
|
+
# Make a flat list of all nodes
|
|
628
|
+
nodes = tree.collect {|node| node.to_a}.flatten
|
|
629
|
+
|
|
630
|
+
# Elide non-rendering directive lines. Match node lists like:
|
|
631
|
+
# <TextNode> =~ /\n\s*$/
|
|
632
|
+
# <NonRenderingNode>*
|
|
633
|
+
# <TextNode> =~ /^\n/
|
|
634
|
+
# removing one "\n" from the tail of the leading textnode and the
|
|
635
|
+
# head of the trailing textnode. Trailing textnode can also be a
|
|
636
|
+
# leading textnode for another series.
|
|
637
|
+
nodes.each_with_index do |node,i|
|
|
638
|
+
leadingNode = nodes[i-1]
|
|
639
|
+
|
|
640
|
+
# If both the leading node and the current one match the
|
|
641
|
+
# criteria, look for a trailing node.
|
|
642
|
+
if i.nonzero? && leadingNode.is_a?( TextNode ) &&
|
|
643
|
+
leadingNode =~ /\n\s*$/s
|
|
644
|
+
|
|
645
|
+
# Find the trailing node. Abandon the search on any
|
|
646
|
+
# rendering directive or text node that includes a blank line.
|
|
647
|
+
trailingNode = nodes[i..-1].find do |node|
|
|
648
|
+
break nil if node.rendering?
|
|
649
|
+
node.is_a?( TextNode ) && node =~ /^\n/
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
leadingNode.body.sub!( /\n\s*$/, '' ) if trailingNode
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
### Autoload accessor/mutator methods for attributes.
|
|
659
|
+
def method_missing( sym, *args, &block )
|
|
660
|
+
name = sym.to_s.gsub( /=$/, '' )
|
|
661
|
+
super unless @attributes.key?( name ) || !@config[:strictAttributes]
|
|
662
|
+
|
|
663
|
+
#self.log.debug "Autoloading for #{sym}"
|
|
664
|
+
|
|
665
|
+
# Mutator
|
|
666
|
+
if /=$/ =~ sym.to_s
|
|
667
|
+
#self.log.debug "Autoloading mutator %p" % sym
|
|
668
|
+
self.add_attribute_mutator( sym )
|
|
669
|
+
# Accessor
|
|
670
|
+
else
|
|
671
|
+
#self.log.debug "Autoloading accessor %p" % sym
|
|
672
|
+
self.add_attribute_accessor( sym )
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Don't use #send to avoid infinite recursion in case method
|
|
676
|
+
# definition has failed for some reason.
|
|
677
|
+
self.method( sym ).call( *args )
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
### Add a singleton accessor (getter) method for accessing the attribute
|
|
682
|
+
### specified by +sym+ to the receiver.
|
|
683
|
+
def add_attribute_accessor( sym )
|
|
684
|
+
name = sym.to_s.sub( /=$/, '' )
|
|
685
|
+
|
|
686
|
+
code = %Q{
|
|
687
|
+
def self::#{name}
|
|
688
|
+
@attributes[#{name.inspect}]
|
|
689
|
+
end
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
# $stderr.puts "Auto-defining accessor for #{name}: #{code}"
|
|
693
|
+
eval( code, nil, "#{name} [Auto-defined]", __LINE__ )
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
### Add a singleton mutator (setter) method for accessing the attribute
|
|
698
|
+
### specified by +sym+ to the receiver.
|
|
699
|
+
def add_attribute_mutator( sym )
|
|
700
|
+
name = sym.to_s.sub( /=$/, '' )
|
|
701
|
+
|
|
702
|
+
code = %Q{
|
|
703
|
+
def self::#{name}=( arg )
|
|
704
|
+
@attributes[ #{name.inspect} ] = arg
|
|
705
|
+
end
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# $stderr.puts "Auto-defining mutator for #{name}: #{code}"
|
|
709
|
+
eval( code, nil, "#{name}= [Auto-defined]", __LINE__ )
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
end # class Arrow::Template
|
|
713
|
+
|