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,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
|