arrow 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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,571 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'pluginfactory'
|
6
|
+
|
7
|
+
require 'arrow/object'
|
8
|
+
require 'arrow/mixins'
|
9
|
+
require 'arrow/exceptions'
|
10
|
+
require 'arrow/template'
|
11
|
+
require 'arrow/path'
|
12
|
+
|
13
|
+
# The Arrow::Template::Parser class, a derivative of
|
14
|
+
# Arrow::Object. This is the default parser class for the default Arrow
|
15
|
+
# templating system.
|
16
|
+
#
|
17
|
+
# == Authors
|
18
|
+
#
|
19
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
20
|
+
#
|
21
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
22
|
+
#
|
23
|
+
class Arrow::Template::Parser < Arrow::Object
|
24
|
+
include Arrow::HashUtilities
|
25
|
+
|
26
|
+
### Regexp constants for parsing
|
27
|
+
module Patterns
|
28
|
+
|
29
|
+
# The Regexp that is used to match directive tag openings. Must be at
|
30
|
+
# least 2 characters wide.
|
31
|
+
TAGOPEN = %r{([<|\[])\?}
|
32
|
+
|
33
|
+
# The Hash that maps tag-closings to tag-middle patterns.
|
34
|
+
TAGMIDDLE = Hash.new {|hsh, key|
|
35
|
+
# Extract the first and second characters from the tag-closing
|
36
|
+
# pattern to build the tag-middle pattern.
|
37
|
+
src = key.is_a?( Regexp ) ? key.source : key.to_s
|
38
|
+
mobj = /(\\.|.)(\\.|.)/.match( src ) or
|
39
|
+
raise Arrow::ParseError, "couldn't extract first and second "\
|
40
|
+
"char from closing tag '%s'" % key
|
41
|
+
char1, char2 = mobj[1,2]
|
42
|
+
hsh[key] = Regexp.new( /((?:[^#{char1}]|#{char1}(?!#{char2}))+)/ )
|
43
|
+
}
|
44
|
+
|
45
|
+
# The Hash that maps tag openings to matching tag closings with flipped
|
46
|
+
# braces.
|
47
|
+
TAGCLOSE = Hash.new {|hsh, key|
|
48
|
+
compliment = key.reverse.tr('<{([', '>})]')
|
49
|
+
hsh[key] = Regexp.new( Regexp.quote(compliment) )
|
50
|
+
}
|
51
|
+
|
52
|
+
# Paren-group map
|
53
|
+
CAPTURE = Hash.new {|hsh, key|
|
54
|
+
Regexp.new( '(' + key.to_s + ')' )
|
55
|
+
}
|
56
|
+
ALTERNATION = Hash.new {|hsh, *keys|
|
57
|
+
Regexp.new( '(?:' + keys.join('|') + ')' )
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
# Constant patterns for parsing
|
62
|
+
DOT = /\./
|
63
|
+
COMMA = /,/
|
64
|
+
WHITESPACE = /\s*/
|
65
|
+
EQUALS = /=/
|
66
|
+
|
67
|
+
INFIX = /\.|::|(?>\[)/
|
68
|
+
IDENTIFIER = /[a-z]\w*/i
|
69
|
+
LBRACKET = /[\[(]/
|
70
|
+
RBRACKET = Hash.new {|hsh, key|
|
71
|
+
compliment = key.tr( '<{([', '>})]' )
|
72
|
+
hsh[key] = Regexp.new( Regexp.quote(compliment) )
|
73
|
+
}
|
74
|
+
|
75
|
+
NUMBER = /[-+]?\d+(?:\.\d+)?(?:e-?\d+)?/
|
76
|
+
|
77
|
+
# :FIXME: I would do this with something like
|
78
|
+
# /(["'/])((?:[^\1]|\\\.)*)\1/, but Ruby apparently doesn't grok
|
79
|
+
# backreferences inside character classes:
|
80
|
+
# (ruby 1.8.1 (2003-10-25) [i686-linux])
|
81
|
+
# irb(main):001:0> /(["'])((?:[^\1]|\\\.)*)\1/.match( \
|
82
|
+
# %{foo "and \"bar\" and baz" and some "other stuff"} )[0]
|
83
|
+
# ==> "\"and \"bar\" and baz\" and some \"other stuff\""
|
84
|
+
TICKQSTRING = /'((?:[^']|\\')*)'/
|
85
|
+
DBLQSTRING = /"((?:[^"]|\\")*)"/
|
86
|
+
SLASHQSTRING = %r{/((?:[^/]|\\/)*)/}
|
87
|
+
QUOTEDSTRING = ALTERNATION[[ TICKQSTRING, DBLQSTRING, SLASHQSTRING ]]
|
88
|
+
|
89
|
+
SYMBOL = /:[@$]?[a-z]\w+/ | /:/ +
|
90
|
+
ALTERNATION[[ DBLQSTRING, TICKQSTRING ]]
|
91
|
+
VARIABLE = /(?:\$|@@?)?_?/ + IDENTIFIER
|
92
|
+
|
93
|
+
REGEXP = %r{/((?:[^/]|\\.)+)/}
|
94
|
+
REBINDOP = /\s*(?:=~|matches)(?=\s)/
|
95
|
+
|
96
|
+
PATHNAME = %r{((?:[-\w.,:+@#$\%\(\)/]|\\ |\?(?!>))+)}
|
97
|
+
|
98
|
+
ARGUMENT = /[*&]?/ + IDENTIFIER
|
99
|
+
ARGDEFAULT = EQUALS + WHITESPACE +
|
100
|
+
ALTERNATION[[ IDENTIFIER, NUMBER, QUOTEDSTRING, SYMBOL, VARIABLE ]]
|
101
|
+
|
102
|
+
end
|
103
|
+
include Patterns
|
104
|
+
|
105
|
+
|
106
|
+
### Parse state object class. Instance of this class represent a
|
107
|
+
### parser's progress through a given template body.
|
108
|
+
class State < Arrow::Object
|
109
|
+
include Patterns
|
110
|
+
extend Forwardable
|
111
|
+
|
112
|
+
### Create and return a new parse state for the specified
|
113
|
+
### +text+. The +initialData+ is used to propagate directive
|
114
|
+
### bookkeeping state through recursive parses.
|
115
|
+
def initialize( text, template, initialData={} )
|
116
|
+
@scanner = StringScanner.new( text )
|
117
|
+
@template = template
|
118
|
+
@tag_open = nil
|
119
|
+
@tag_middle = nil
|
120
|
+
@tag_close = nil
|
121
|
+
@current_branch = []
|
122
|
+
@current_branch_node = nil
|
123
|
+
@current_node = nil
|
124
|
+
@data = initialData
|
125
|
+
|
126
|
+
#self.log.debug "From %s: Created a parse state for %p (%p). Data is: %p" %
|
127
|
+
# [ caller(1).first, text, template, initialData ]
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
######
|
132
|
+
public
|
133
|
+
######
|
134
|
+
|
135
|
+
# Delegate the index operators to the @data hash
|
136
|
+
def_delegators :@data, :[], :[]=
|
137
|
+
|
138
|
+
# The template that corresponds to the parse state
|
139
|
+
attr_reader :template
|
140
|
+
|
141
|
+
# The StringScanner object which is scanning the text being parsed.
|
142
|
+
attr_reader :scanner
|
143
|
+
|
144
|
+
# The current branch of the parse state. Branches are added and
|
145
|
+
# removed for re-entrances into the parse loop.
|
146
|
+
attr_reader :current_branch
|
147
|
+
|
148
|
+
# The pointer into the syntax tree for the node which is the base of
|
149
|
+
# the current branch.
|
150
|
+
attr_reader :current_branch_node
|
151
|
+
|
152
|
+
# The pointer into the syntax tree for the current node
|
153
|
+
attr_reader :current_node
|
154
|
+
|
155
|
+
# The string that contains the opening string of the current tag, if
|
156
|
+
# any.
|
157
|
+
attr_reader :tag_open
|
158
|
+
|
159
|
+
# The pattern that will match the middle of the current tag during
|
160
|
+
# the parse of a directive.
|
161
|
+
attr_accessor :tag_middle
|
162
|
+
|
163
|
+
# The pattern that will match the current tag-closing characters during
|
164
|
+
# the parse of a directive.
|
165
|
+
attr_accessor :tag_close
|
166
|
+
|
167
|
+
# A miscellaneous data hash to allow directives to keep their own
|
168
|
+
# state for a parse.
|
169
|
+
attr_reader :data
|
170
|
+
|
171
|
+
|
172
|
+
### Return the line number of the line on which the parse's pointer
|
173
|
+
### current rests.
|
174
|
+
def line
|
175
|
+
return @scanner.string[ 0, @scanner.pos ].count( $/ ) + 1
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
### Set the middle and closing tag patterns from the given matched
|
180
|
+
### opening string.
|
181
|
+
def set_tag_patterns( opening )
|
182
|
+
@tag_open = opening
|
183
|
+
@tag_close = TAGCLOSE[ opening ]
|
184
|
+
@tag_middle = TAGMIDDLE[ @tag_close ]
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
### Add the given +nodes+ to the state's syntax tree.
|
189
|
+
def add_nodes( *nodes )
|
190
|
+
@current_branch.push( *nodes )
|
191
|
+
@current_node = @current_branch.last
|
192
|
+
return self
|
193
|
+
end
|
194
|
+
alias_method :<<, :add_nodes
|
195
|
+
|
196
|
+
|
197
|
+
### Add a branch belonging to the specified +node+ to the parse
|
198
|
+
### state for the duration of the supplied block, removing and
|
199
|
+
### returning it when the block returns.
|
200
|
+
def branch( node )
|
201
|
+
raise LocalJumpError, "no block given" unless
|
202
|
+
block_given?
|
203
|
+
|
204
|
+
# Save and set the current branch node
|
205
|
+
entryNode = @current_branch_node
|
206
|
+
@current_branch_node = node
|
207
|
+
|
208
|
+
# Push a new branch and make the current branch point to it
|
209
|
+
# while saving the one we entered with so it can be restored
|
210
|
+
# later.
|
211
|
+
entryBranch = @current_branch
|
212
|
+
entryBranch.push( @current_branch = [] )
|
213
|
+
|
214
|
+
yield( self )
|
215
|
+
|
216
|
+
# Restore the current branch and branch node to what they were
|
217
|
+
# before
|
218
|
+
@current_branch = entryBranch
|
219
|
+
@current_branch_node = entryNode
|
220
|
+
|
221
|
+
return @current_branch.pop
|
222
|
+
end
|
223
|
+
end # class State
|
224
|
+
|
225
|
+
|
226
|
+
# Default configuration hash
|
227
|
+
Defaults = {
|
228
|
+
:strict_end_tags => false,
|
229
|
+
:ignore_unknown_PIs => true,
|
230
|
+
}
|
231
|
+
|
232
|
+
|
233
|
+
#############################################################
|
234
|
+
### I N S T A N C E M E T H O D S
|
235
|
+
#############################################################
|
236
|
+
|
237
|
+
### Create a new parser using the specified +config+. The +config+ can
|
238
|
+
### contain one or more of the following keys:
|
239
|
+
###
|
240
|
+
### [<b>:strict_end_tags</b>]
|
241
|
+
### Raise an error if the optional name associated with an <?end?> tag
|
242
|
+
### doesn't match the directive it closes. Defaults to +false+.
|
243
|
+
### [<b>:ignore_unknown_PIs</b>]
|
244
|
+
### When this is set to +true+, any processing instructions found in a
|
245
|
+
### template that don't parse will be kept as-is in the output. If
|
246
|
+
### this is +false+, unrecognized PIs will raise an error at parse
|
247
|
+
### time. Defaults to +true+.
|
248
|
+
def initialize( config={} )
|
249
|
+
@config = Defaults.merge( config, &HashMergeFunction )
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
### Initialize a duplicate of the +original+ parser.
|
254
|
+
def initialize_copy( original )
|
255
|
+
super
|
256
|
+
@config = original.config.dup
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
######
|
261
|
+
public
|
262
|
+
######
|
263
|
+
|
264
|
+
# The configuration object which contains the parser's config.
|
265
|
+
attr_reader :config
|
266
|
+
|
267
|
+
|
268
|
+
### Parse and return a template syntax tree from the given +string+.
|
269
|
+
def parse( string, template, initialData={} )
|
270
|
+
|
271
|
+
# Create a new parse state and build the parse tree with it.
|
272
|
+
begin
|
273
|
+
state = State.new( string, template, initialData )
|
274
|
+
syntax_tree = self.scan_for_nodes( state )
|
275
|
+
|
276
|
+
rescue Arrow::TemplateError => err
|
277
|
+
Kernel.raise( err ) unless defined? state
|
278
|
+
state.scanner.unscan if state.scanner.matched? #<- segfaults
|
279
|
+
|
280
|
+
# Get the linecount and chunk of erroring content
|
281
|
+
errorContent = get_parse_context( state.scanner )
|
282
|
+
|
283
|
+
msg = err.message.split( /:/ ).uniq.join( ':' ) +
|
284
|
+
%{ at line %d of %s: %s...} %
|
285
|
+
[ state.line, template._file, errorContent ]
|
286
|
+
Kernel.raise( err.class, msg )
|
287
|
+
end
|
288
|
+
|
289
|
+
return syntax_tree
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
### Use the specified +state+ (a StringScanner object) to scan for
|
294
|
+
### directive and plain-text nodes. The +context+ argument, if set,
|
295
|
+
### indicates a recursive call for the directive named. The +node+ will
|
296
|
+
### be used as the branch node in the parse state.
|
297
|
+
def scan_for_nodes( state, context=nil, node=nil )
|
298
|
+
return state.branch( node ) do
|
299
|
+
scanner = state.scanner
|
300
|
+
|
301
|
+
# Scan until the scanner reaches the end of its string. Early exits
|
302
|
+
# 'break' of this loop.
|
303
|
+
catch( :endscan ) {
|
304
|
+
until scanner.eos?
|
305
|
+
startpos = scanner.pos
|
306
|
+
#self.log.debug %{Scanning from %d:%p} %
|
307
|
+
# [ scanner.pos, scanner.rest[0,20] + '..' ]
|
308
|
+
|
309
|
+
# Scan for the next directive. When the scanner reaches
|
310
|
+
# the end of the parsed string, just append any plain
|
311
|
+
# text that's left and stop scanning.
|
312
|
+
if scanner.skip_until( TAGOPEN )
|
313
|
+
|
314
|
+
# Add the literal String node leading up to the tag
|
315
|
+
# as a text node :FIXME: Have to do it this way
|
316
|
+
# because StringScanner#pre_match does weird crap if
|
317
|
+
# skip_until skips only one or no character/s.
|
318
|
+
if ( scanner.pos - startpos > scanner.matched.length )
|
319
|
+
offset = scanner.pos - scanner.matched.length - 1
|
320
|
+
state << Arrow::Template::TextNode.
|
321
|
+
new( scanner.string[startpos..offset] )
|
322
|
+
#self.log.debug "Added text node %p" %
|
323
|
+
# scanner.string[startpos..offset]
|
324
|
+
end
|
325
|
+
|
326
|
+
# Now scan the directive that was found
|
327
|
+
state << self.scan_directive( state, context )
|
328
|
+
else
|
329
|
+
state << Arrow::Template::TextNode.new( scanner.rest )
|
330
|
+
scanner.terminate
|
331
|
+
end
|
332
|
+
end
|
333
|
+
}
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
### Given the specified parse +state+ which is pointing past the opening
|
339
|
+
### of a directive tag, parse the directive.
|
340
|
+
def scan_directive( state, context=nil )
|
341
|
+
scanner = state.scanner
|
342
|
+
|
343
|
+
# Set the patterns in the parse state to compliment the
|
344
|
+
# opening tag.
|
345
|
+
state.set_tag_patterns( scanner.matched )
|
346
|
+
tag_begin = state.line
|
347
|
+
|
348
|
+
# Scan for the directive name; if no valid name can be
|
349
|
+
# found, handle an unknown PI/directive.
|
350
|
+
scanner.skip( WHITESPACE )
|
351
|
+
unless (( tag = scanner.scan( IDENTIFIER ) ))
|
352
|
+
#self.log.debug "No identifier at '%s...'" % scanner.rest[0,20]
|
353
|
+
|
354
|
+
# If the tag_open is <?, then this is a PI that we don't
|
355
|
+
# grok. The reaction to this is configurable, so decide what to
|
356
|
+
# do.
|
357
|
+
if state.tag_open == '<?'
|
358
|
+
return handle_unknown_pi( state )
|
359
|
+
|
360
|
+
# ...otherwise, it's just a malformed non-PI tag, which
|
361
|
+
# is always an error.
|
362
|
+
else
|
363
|
+
raise Arrow::ParseError, "malformed directive name"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# If it's anything but an 'end' tag, create a directive object.
|
368
|
+
unless tag == 'end'
|
369
|
+
begin
|
370
|
+
node = Arrow::Template::Directive.create( tag, self, state )
|
371
|
+
rescue ::FactoryError => err
|
372
|
+
return self.handle_unknown_pi( state, tag )
|
373
|
+
end
|
374
|
+
|
375
|
+
# If it's an 'end',
|
376
|
+
else
|
377
|
+
#self.log.debug "Found end tag."
|
378
|
+
|
379
|
+
# If this scan is occuring in a recursive parse, make sure the
|
380
|
+
# 'end' is closing the correct thing and break out of the node
|
381
|
+
# search. Note that the trailing '?>' is left in the scanner to
|
382
|
+
# match at the end of the loop that opened this recursion.
|
383
|
+
if context
|
384
|
+
scanner.skip( WHITESPACE )
|
385
|
+
closed_tag = scanner.scan( IDENTIFIER )
|
386
|
+
#self.log.debug "End found for #{closed_tag}"
|
387
|
+
|
388
|
+
# If strict end tags is turned on, check to be sure we
|
389
|
+
# got the correct 'end'.
|
390
|
+
if @config[:strict_end_tags]
|
391
|
+
raise Arrow::ParseError,
|
392
|
+
"missing or malformed closing tag name" if
|
393
|
+
closed_tag.nil?
|
394
|
+
raise Arrow::ParseError,
|
395
|
+
"mismatched closing tag name '#{closed_tag}'" unless
|
396
|
+
closed_tag.downcase == context.downcase
|
397
|
+
end
|
398
|
+
|
399
|
+
# Jump out of the loop in #scan_for_nodes...
|
400
|
+
throw :endscan
|
401
|
+
else
|
402
|
+
raise Arrow::ParseError, "dangling end"
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# Skip to the end of the tag
|
407
|
+
self.scan_for_tag_ending( state ) or
|
408
|
+
raise Arrow::ParseError,
|
409
|
+
"malformed tag starting at line %d: no closing tag "\
|
410
|
+
"delimiters %p found" % [ tag_begin, state.tag_close ]
|
411
|
+
|
412
|
+
return node
|
413
|
+
end
|
414
|
+
|
415
|
+
|
416
|
+
### Given the specified +state+ (an Arrow::Template::Parser::State
|
417
|
+
### object), scan for and return an indentifier. If +skip_whitespace+ is
|
418
|
+
### +true+, any leading whitespace characters will be skipped. Returns
|
419
|
+
### +nil+ if no identifier is found.
|
420
|
+
def scan_for_identifier( state, skip_whitespace=true )
|
421
|
+
#self.log.debug "Scanning for identifier at %p" %
|
422
|
+
# state.scanner.rest[0,20]
|
423
|
+
|
424
|
+
state.scanner.skip( WHITESPACE ) if skip_whitespace
|
425
|
+
rval = state.scanner.scan( IDENTIFIER ) or return nil
|
426
|
+
|
427
|
+
#self.log.debug "Found identifier %p" % rval
|
428
|
+
return rval
|
429
|
+
end
|
430
|
+
|
431
|
+
|
432
|
+
### Given the specified +state+ (an Arrow::Template::Parser::State
|
433
|
+
### object), scan for and return a quoted string, including the
|
434
|
+
### quotes. If +skip_whitespace+ is +true+, any leading whitespace
|
435
|
+
### characters will be skipped. Returns +nil+ if no quoted string is
|
436
|
+
### found.
|
437
|
+
def scan_for_quoted_string( state, skip_whitespace=true )
|
438
|
+
#self.log.debug "Scanning for quoted string at %p" %
|
439
|
+
# state.scanner.rest[0,20]
|
440
|
+
|
441
|
+
state.scanner.skip( WHITESPACE ) if skip_whitespace
|
442
|
+
|
443
|
+
rval = state.scanner.scan( QUOTEDSTRING ) or return nil
|
444
|
+
|
445
|
+
#self.log.debug "Found quoted string %p" % rval
|
446
|
+
return rval
|
447
|
+
end
|
448
|
+
|
449
|
+
|
450
|
+
### Given the specified +state+ (an Arrow::Template::Parser::State
|
451
|
+
### object), scan for and return a methodchain. Returns +nil+ if no
|
452
|
+
### methodchain is found.
|
453
|
+
def scan_for_methodchain( state )
|
454
|
+
scanner = state.scanner
|
455
|
+
#self.log.debug "Scanning for methodchain at %p" %
|
456
|
+
# scanner.rest[0,20]
|
457
|
+
|
458
|
+
rval = scanner.scan( INFIX ) || ''
|
459
|
+
rval << (scanner.scan( WHITESPACE ) || '')
|
460
|
+
rval << (scanner.scan( state.tag_middle ) || '')
|
461
|
+
|
462
|
+
#self.log.debug "Found methodchain %p" % rval
|
463
|
+
return rval
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
### Given the specified +state+ (an Arrow::Template::Parser::State
|
468
|
+
### object), scan for and return the current tag ending. Returns +nil+
|
469
|
+
### if no tag ending is found.
|
470
|
+
def scan_for_tag_ending( state, skip_whitespace=true )
|
471
|
+
scanner = state.scanner
|
472
|
+
#self.log.debug "Scanning for tag ending at %p" %
|
473
|
+
# scanner.rest[0,20]
|
474
|
+
|
475
|
+
scanner.skip( WHITESPACE ) if skip_whitespace
|
476
|
+
rval = scanner.scan( state.tag_close ) or
|
477
|
+
return nil
|
478
|
+
|
479
|
+
#self.log.debug "Found tag ending %p" % rval
|
480
|
+
return rval
|
481
|
+
end
|
482
|
+
|
483
|
+
|
484
|
+
### Given the specified +state+ (an Arrow::Template::Parser::State
|
485
|
+
### object), scan for and return two Arrays of identifiers. The first is
|
486
|
+
### the list of parsed arguments as they appeared in the source, and the
|
487
|
+
### second is the same list with all non-word characters removed. Given
|
488
|
+
### an arglist like:
|
489
|
+
### foo, bar=baz, *bim, &boozle
|
490
|
+
### the returned arrays will contain:
|
491
|
+
### ["foo", "bar=baz", "*bim", "&boozle"]
|
492
|
+
### and
|
493
|
+
### ["foo", "bar", "bim", "boozle"]
|
494
|
+
### respectively.
|
495
|
+
def scan_for_arglist( state, skip_whitespace=true )
|
496
|
+
scanner = state.scanner
|
497
|
+
#self.log.debug "Scanning for arglist at %p" %
|
498
|
+
# scanner.rest[0,20]
|
499
|
+
|
500
|
+
args = []
|
501
|
+
pureargs = []
|
502
|
+
scanner.skip( WHITESPACE ) if skip_whitespace
|
503
|
+
while (( rval = scanner.scan(ARGUMENT) ))
|
504
|
+
args << rval
|
505
|
+
pureargs << rval.gsub( /\W+/, '' )
|
506
|
+
scanner.skip( WHITESPACE )
|
507
|
+
if (( rval = scanner.scan(ARGDEFAULT) ))
|
508
|
+
args.last << rval
|
509
|
+
end
|
510
|
+
break unless scanner.skip( WHITESPACE + COMMA + WHITESPACE )
|
511
|
+
end
|
512
|
+
|
513
|
+
return nil if args.empty?
|
514
|
+
|
515
|
+
#self.log.debug "Found args: %p, pureargs: %p" %
|
516
|
+
# [ args, pureargs ]
|
517
|
+
return args, pureargs
|
518
|
+
end
|
519
|
+
|
520
|
+
|
521
|
+
### Given the specified +state+ (an Arrow::Template::Parser::State
|
522
|
+
### object), scan for and return a valid-looking file pathname.
|
523
|
+
def scan_for_pathname( state, skip_whitespace=true )
|
524
|
+
scanner = state.scanner
|
525
|
+
#self.log.debug "Scanning for file path at %p" %
|
526
|
+
# scanner.rest[0,20]
|
527
|
+
|
528
|
+
scanner.skip( WHITESPACE ) if skip_whitespace
|
529
|
+
rval = scanner.scan( PATHNAME ) or
|
530
|
+
return nil
|
531
|
+
|
532
|
+
#self.log.debug "Found path: %p" % rval
|
533
|
+
return rval
|
534
|
+
end
|
535
|
+
|
536
|
+
|
537
|
+
|
538
|
+
#########
|
539
|
+
protected
|
540
|
+
#########
|
541
|
+
|
542
|
+
### Handle an unknown ProcessingInstruction.
|
543
|
+
def handle_unknown_pi( state, tag="" )
|
544
|
+
|
545
|
+
# If the configuration doesn't say to ignore unknown PIs or it's an
|
546
|
+
# [?alternate-synax?] directive, raise an error.
|
547
|
+
if state.tag_open == '[?' || !@config[:ignore_unknown_PIs]
|
548
|
+
raise Arrow::ParseError, "unknown directive"
|
549
|
+
end
|
550
|
+
|
551
|
+
remainder = state.scanner.scan( %r{(?:[^?]|\?(?!>))*\?>} ) or
|
552
|
+
raise Arrow::ParseError, "failed to skip unknown PI"
|
553
|
+
|
554
|
+
pi = state.tag_open + tag + remainder
|
555
|
+
self.log.info( "Ignoring unknown PI (to = #{state.tag_open.inspect}) '#{pi}'" )
|
556
|
+
return Arrow::Template::TextNode.new( pi )
|
557
|
+
end
|
558
|
+
|
559
|
+
|
560
|
+
### Return a string showing the given +scanner+'s context in the string
|
561
|
+
### being parsed.
|
562
|
+
def get_parse_context( scanner )
|
563
|
+
str = scanner.string
|
564
|
+
|
565
|
+
pre = str[ scanner.pos < 40 ? 0 : scanner.pos - 40, 39 ]
|
566
|
+
post = scanner.rest[ 0, 40 ]
|
567
|
+
|
568
|
+
return "#{pre}[*** ERROR ***]#{post}"
|
569
|
+
end
|
570
|
+
|
571
|
+
end # class Arrow::Template::Parser
|