ruwiki 0.9.0
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/Readme.rubygems +86 -0
- data/Readme.tarfile +65 -0
- data/bin/ruwiki +58 -0
- data/bin/ruwiki.cgi +87 -0
- data/bin/ruwiki_convert +56 -0
- data/bin/ruwiki_service.rb +82 -0
- data/bin/ruwiki_servlet +53 -0
- data/contrib/enscript-token.rb +55 -0
- data/contrib/rublog_integrator.rb +68 -0
- data/data/Default/ProjectIndex.ruwiki +49 -0
- data/data/Ruwiki/Antispam.ruwiki +65 -0
- data/data/Ruwiki/BugTracking.ruwiki +33 -0
- data/data/Ruwiki/ChangeLog.ruwiki +102 -0
- data/data/Ruwiki/Configuring_Ruwiki.ruwiki +151 -0
- data/data/Ruwiki/Extending_Ruwiki.ruwiki +317 -0
- data/data/Ruwiki/LicenseAndAuthorInfo.ruwiki +30 -0
- data/data/Ruwiki/ProjectIndex.ruwiki +84 -0
- data/data/Ruwiki/Roadmap.ruwiki +225 -0
- data/data/Ruwiki/RuwikiTemplatingLibrary.ruwiki +156 -0
- data/data/Ruwiki/RuwikiUtilities.ruwiki +157 -0
- data/data/Ruwiki/SandBox.ruwiki +9 -0
- data/data/Ruwiki/To_Do.ruwiki +51 -0
- data/data/Ruwiki/TroubleShooting.ruwiki +33 -0
- data/data/Ruwiki/WikiFeatures.ruwiki +17 -0
- data/data/Ruwiki/WikiMarkup.ruwiki +261 -0
- data/data/Tutorial/AddingPages.ruwiki +16 -0
- data/data/Tutorial/AddingProjects.ruwiki +16 -0
- data/data/Tutorial/ProjectIndex.ruwiki +11 -0
- data/data/Tutorial/SandBox.ruwiki +9 -0
- data/data/agents.banned +60 -0
- data/data/agents.readonly +321 -0
- data/data/hostip.banned +30 -0
- data/data/hostip.readonly +28 -0
- data/lib/ruwiki.rb +622 -0
- data/lib/ruwiki/auth.rb +56 -0
- data/lib/ruwiki/auth/gforge.rb +73 -0
- data/lib/ruwiki/backend.rb +318 -0
- data/lib/ruwiki/backend/flatfiles.rb +217 -0
- data/lib/ruwiki/config.rb +244 -0
- data/lib/ruwiki/exportable.rb +192 -0
- data/lib/ruwiki/handler.rb +342 -0
- data/lib/ruwiki/lang/de.rb +339 -0
- data/lib/ruwiki/lang/en.rb +334 -0
- data/lib/ruwiki/lang/es.rb +339 -0
- data/lib/ruwiki/page.rb +262 -0
- data/lib/ruwiki/servlet.rb +38 -0
- data/lib/ruwiki/template.rb +553 -0
- data/lib/ruwiki/utils.rb +24 -0
- data/lib/ruwiki/utils/command.rb +102 -0
- data/lib/ruwiki/utils/converter.rb +297 -0
- data/lib/ruwiki/utils/manager.rb +639 -0
- data/lib/ruwiki/utils/servletrunner.rb +295 -0
- data/lib/ruwiki/wiki.rb +147 -0
- data/lib/ruwiki/wiki/tokens.rb +136 -0
- data/lib/ruwiki/wiki/tokens/00default.rb +211 -0
- data/lib/ruwiki/wiki/tokens/01wikilinks.rb +166 -0
- data/lib/ruwiki/wiki/tokens/02actions.rb +63 -0
- data/lib/ruwiki/wiki/tokens/abbreviations.rb +40 -0
- data/lib/ruwiki/wiki/tokens/calendar.rb +147 -0
- data/lib/ruwiki/wiki/tokens/headings.rb +43 -0
- data/lib/ruwiki/wiki/tokens/lists.rb +112 -0
- data/lib/ruwiki/wiki/tokens/rubylists.rb +48 -0
- data/ruwiki.conf +22 -0
- data/ruwiki.pkg +0 -0
- data/templates/default/body.tmpl +19 -0
- data/templates/default/content.tmpl +7 -0
- data/templates/default/controls.tmpl +23 -0
- data/templates/default/edit.tmpl +27 -0
- data/templates/default/error.tmpl +14 -0
- data/templates/default/footer.tmpl +23 -0
- data/templates/default/ruwiki.css +297 -0
- data/templates/default/save.tmpl +8 -0
- data/templates/sidebar/body.tmpl +19 -0
- data/templates/sidebar/content.tmpl +8 -0
- data/templates/sidebar/controls.tmpl +8 -0
- data/templates/sidebar/edit.tmpl +27 -0
- data/templates/sidebar/error.tmpl +13 -0
- data/templates/sidebar/footer.tmpl +22 -0
- data/templates/sidebar/ruwiki.css +347 -0
- data/templates/sidebar/save.tmpl +10 -0
- data/templates/simple/body.tmpl +13 -0
- data/templates/simple/content.tmpl +7 -0
- data/templates/simple/controls.tmpl +8 -0
- data/templates/simple/edit.tmpl +25 -0
- data/templates/simple/error.tmpl +10 -0
- data/templates/simple/footer.tmpl +10 -0
- data/templates/simple/ruwiki.css +192 -0
- data/templates/simple/save.tmpl +8 -0
- data/tests/harness.rb +52 -0
- data/tests/tc_backend_flatfile.rb +103 -0
- data/tests/tc_bugs.rb +74 -0
- data/tests/tc_exportable.rb +64 -0
- data/tests/tc_template.rb +145 -0
- data/tests/tc_tokens.rb +335 -0
- data/tests/testall.rb +20 -0
- metadata +182 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Ruwiki
|
|
3
|
+
# Copyright � 2002 - 2004, Digikata and HaloStatue
|
|
4
|
+
# Alan Chen (alan@digikata.com)
|
|
5
|
+
# Austin Ziegler (ruwiki@halostatue.ca)
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the same terms as Ruby.
|
|
8
|
+
#
|
|
9
|
+
# $Id: servlet.rb,v 1.7 2004/09/29 05:36:30 austin Exp $
|
|
10
|
+
#++
|
|
11
|
+
require 'webrick'
|
|
12
|
+
|
|
13
|
+
class Ruwiki::Servlet < WEBrick::HTTPServlet::AbstractServlet
|
|
14
|
+
class << self
|
|
15
|
+
attr_accessor :config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(config)
|
|
19
|
+
@config = config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Converts a POST into a GET.
|
|
23
|
+
def do_POST(req, res)
|
|
24
|
+
do_GET(req, res)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def do_GET(req, res)
|
|
28
|
+
# Generate the reponse handlers for Ruwiki from the request and response
|
|
29
|
+
# objects provided.
|
|
30
|
+
wiki = Ruwiki.new(Ruwiki::Handler.from_webrick(req, res))
|
|
31
|
+
|
|
32
|
+
# Configuration defaults to certain values. This overrides the defaults.
|
|
33
|
+
wiki.config = Ruwiki::Servlet.config unless Ruwiki::Servlet.config.nil?
|
|
34
|
+
wiki.config!
|
|
35
|
+
wiki.config.logger = @config.logger
|
|
36
|
+
wiki.run
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Ruwiki
|
|
3
|
+
# Copyright � 2002 - 2004, Digikata and HaloStatue
|
|
4
|
+
# Alan Chen (alan@digikata.com)
|
|
5
|
+
# Austin Ziegler (ruwiki@halostatue.ca)
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the same terms as Ruby.
|
|
8
|
+
#
|
|
9
|
+
# This file is originally from rdoc by Dave Thomas (dave@pragprog.com).
|
|
10
|
+
#
|
|
11
|
+
# $Id: template.rb,v 1.11 2004/11/26 12:18:46 austin Exp $
|
|
12
|
+
#++
|
|
13
|
+
require 'cgi'
|
|
14
|
+
|
|
15
|
+
# Ruwiki templating, based originally on RDoc's "cheap-n-cheerful" HTML
|
|
16
|
+
# page template system, which is a line-oriented, text-based templating
|
|
17
|
+
# system.
|
|
18
|
+
#
|
|
19
|
+
# Templates can contain:
|
|
20
|
+
#
|
|
21
|
+
# * The directive !INCLUDE!, which will include the next template from the
|
|
22
|
+
# provided list. This is processed before any template substitution, so
|
|
23
|
+
# repeating and optional blocks work on the values within the template
|
|
24
|
+
# substitution.
|
|
25
|
+
# * Substitutable variable values between percent signs (<tt>%key%</tt>).
|
|
26
|
+
# Optional variable values can be preceded by a question mark
|
|
27
|
+
# (<tt>%?key%</tt>).
|
|
28
|
+
# * Label values between hash marks (<tt>#key#</tt>). Optional label
|
|
29
|
+
# values can be preceded by a question mark (<tt>#?key#</tt>).
|
|
30
|
+
# * Links (<tt>HREF:ref:name:</tt>).
|
|
31
|
+
# * Repeating substitution values (<tt>[:key| stuff :]</tt>). The value of
|
|
32
|
+
# +key+ may be an integer value or a range (in which case key will be
|
|
33
|
+
# used as an iterator, providing the current value of key on successive
|
|
34
|
+
# values), an array of scalar values (substituting each value), or an
|
|
35
|
+
# array of hashes (in which case it works like repeating blocks, see
|
|
36
|
+
# below). These must NOT be nested. Note that integer value counting is
|
|
37
|
+
# one-based.
|
|
38
|
+
# * Optional substitution values (<tt>[?key| stuff ?]</tt> or <tt>[!key|
|
|
39
|
+
# stuff ?]</tt>. These must NOT be nested.
|
|
40
|
+
# * Repeating blocks:
|
|
41
|
+
# START:key
|
|
42
|
+
# ... stuff
|
|
43
|
+
# END:key
|
|
44
|
+
# * Optional blocks:
|
|
45
|
+
# IF:key
|
|
46
|
+
# ... stuff
|
|
47
|
+
# ENDIF:key
|
|
48
|
+
# or:
|
|
49
|
+
# IFNOT:key
|
|
50
|
+
# ... stuff
|
|
51
|
+
# ENDIF:key
|
|
52
|
+
#
|
|
53
|
+
# When generating the output, a hash of values is provided and an optional
|
|
54
|
+
# hash of labels is provided. Simple variables are resolved directly from
|
|
55
|
+
# the hash; labels are resolved as Symbols from the label hash or are
|
|
56
|
+
# otherwise treated as variables. Labels are always resolved from a single
|
|
57
|
+
# message hash.
|
|
58
|
+
#
|
|
59
|
+
# The +key+ for repeating blocks (one-line or multi-line) must be an array
|
|
60
|
+
# of hashes. The repeating block will be generated once for each entry.
|
|
61
|
+
# Blocks can be nested arbitrarily deeply.
|
|
62
|
+
#
|
|
63
|
+
# Optional blocks will only be generated if the test is true. IF blocks
|
|
64
|
+
# test for the presence of +key+ or that +key+ is non-+nil+; IFNOT blocks
|
|
65
|
+
# look for the absence or +nil+ value of +key+. IFBLANK blocks test for
|
|
66
|
+
# the absence, +nil+ value, or emptiness of +key+; IFNOTBLANK blocks test
|
|
67
|
+
# for the presence of +key+ and that it is neither +nil+ nor empty.
|
|
68
|
+
#
|
|
69
|
+
# Usage: Given a set of templates <tt>T1</tt>, <tt>T2</tt>, etc.
|
|
70
|
+
#
|
|
71
|
+
# values = { "name" => "Dave", "state" => "TX" }
|
|
72
|
+
# fr = { :name => "Nom", :state => "Etat" }
|
|
73
|
+
# en = { :name => "Name", :state => "State" }
|
|
74
|
+
# tt = TemplatePage.new(T1, T2, T3)
|
|
75
|
+
#
|
|
76
|
+
# res = ""
|
|
77
|
+
# tt.process(res, values, fr)
|
|
78
|
+
# tt.process(res, values, en)
|
|
79
|
+
#
|
|
80
|
+
class Ruwiki::TemplatePage
|
|
81
|
+
BLOCK_RE = %r{^\s*(IF|IFNOT|IFBLANK|IFNOTBLANK|ENDIF|START|END):(\w+)?}
|
|
82
|
+
HREF_RE = %r{HREF:(\w+?):(\w+?):}
|
|
83
|
+
LABEL_RE = %r{#(\??)(-?)(\w+?)#}
|
|
84
|
+
VARIABLE_RE = %r{%(\??)(-?)(\w+?)%}
|
|
85
|
+
IFLINE_RE = %r{\[([?!])(\w+?)\|(.*?)\?\]}
|
|
86
|
+
BLOCKLINE_RE = %r{\[:(\w+?)\|(.*?):\]}
|
|
87
|
+
INCLUDE_RE = %r{!INCLUDE!}
|
|
88
|
+
|
|
89
|
+
DDLB_RES = [
|
|
90
|
+
[ :check, %r{%check:(\w+?)%} ],
|
|
91
|
+
[ :date, %r{%date:(\w+?)%} ],
|
|
92
|
+
[ :popup, %r{%popup:(\w+?):(\w+?)%} ],
|
|
93
|
+
[ :ddlb, %r{%ddlb:(\w+?):(\w+?)%} ],
|
|
94
|
+
[ :vsortddlb, %r{%vsortddlb:(\w+?):(\w+?)%} ],
|
|
95
|
+
[ :radio, %r{%radio:(\w+?):(\w+?)%} ],
|
|
96
|
+
[ :radioone, %r{%radioone:(\w+?):(\w+?)%} ],
|
|
97
|
+
[ :input, %r{%input:(\w+?):(\d+?):(\d+?)%} ],
|
|
98
|
+
[ :text, %r{%text:(\w+?):(\d+?):(\d+?)%} ],
|
|
99
|
+
[ :pwinput, %r{%pwinput:(\w+?):(\d+?):(\d+?)%} ],
|
|
100
|
+
[ :pair, %r{%pair(\d)?:([^:]+)(\w+?)%} ]
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
# Nasty hack to allow folks to insert tags if they really, really want to
|
|
104
|
+
OPEN_TAG = "\001"
|
|
105
|
+
CLOSE_TAG = "\002"
|
|
106
|
+
BR = "#{OPEN_TAG}br#{CLOSE_TAG}"
|
|
107
|
+
|
|
108
|
+
# A Context holds a stack of key/value pairs (like a symbol table). When
|
|
109
|
+
# asked to resolve a key, it first searches the top of the stack, then the
|
|
110
|
+
# next level, and so on until it finds a match (or runs out of entries).
|
|
111
|
+
class Context
|
|
112
|
+
def initialize
|
|
113
|
+
@stack = []
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def push(hash)
|
|
117
|
+
@stack.push(hash)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def pop
|
|
121
|
+
@stack.pop
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Find a scalar value, throwing an exception if not found. This method is
|
|
125
|
+
# used when substituting the %xxx% constructs
|
|
126
|
+
def find_scalar_raw(key)
|
|
127
|
+
@stack.reverse_each do |level|
|
|
128
|
+
if level.has_key?(key)
|
|
129
|
+
val = level[key]
|
|
130
|
+
return val unless val.kind_of?(Array)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
raise "Template error: can't find variable '#{key}'."
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def find_scalar(key)
|
|
137
|
+
find_scalar_raw(key) || ''
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Lookup any key in the stack of hashes
|
|
141
|
+
def lookup(key)
|
|
142
|
+
@stack.reverse_each do |level|
|
|
143
|
+
return level[key] if level.has_key?(key)
|
|
144
|
+
end
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Simple class to read lines out of a string
|
|
150
|
+
class LineReader
|
|
151
|
+
attr_reader :lines
|
|
152
|
+
def initialize(lines)
|
|
153
|
+
@lines = lines
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# read the next line
|
|
157
|
+
def read
|
|
158
|
+
@lines.shift
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Return a list of lines up to the line that matches a pattern. That last
|
|
162
|
+
# line is discarded.
|
|
163
|
+
def read_up_to(pattern)
|
|
164
|
+
res = []
|
|
165
|
+
while line = read
|
|
166
|
+
if pattern.match(line)
|
|
167
|
+
return LineReader.new(res)
|
|
168
|
+
else
|
|
169
|
+
res << line
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
raise "Missing end tag in template: #{pattern.source}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Return a copy of ourselves that can be modified without affecting us
|
|
176
|
+
def dup
|
|
177
|
+
LineReader.new(@lines.dup)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# +templates+ is an array of strings containing the templates. We start at
|
|
182
|
+
# the first, and substitute in subsequent ones where the string
|
|
183
|
+
# <tt>!INCLUDE!</tt> occurs. For example, we could have the overall page
|
|
184
|
+
# template containing
|
|
185
|
+
#
|
|
186
|
+
# <html><body>
|
|
187
|
+
# <h1>Master</h1>
|
|
188
|
+
# !INCLUDE!
|
|
189
|
+
# </body></html>
|
|
190
|
+
#
|
|
191
|
+
# and substitute subpages in to it by passing [master, sub_page]. This
|
|
192
|
+
# gives us a cheap way of framing pages
|
|
193
|
+
def initialize(*templates)
|
|
194
|
+
result = templates.shift.dup
|
|
195
|
+
templates.each { |content| result.sub!(INCLUDE_RE, content) }
|
|
196
|
+
@lines = LineReader.new(result.split(/\r?\n/))
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
attr_reader :lines
|
|
200
|
+
|
|
201
|
+
def set_options(opts = {})
|
|
202
|
+
@message = opts[:messages] || {}
|
|
203
|
+
@output = opts[:output] || $stdout
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Render templates as HTML. Compatibility method for Rublog and
|
|
207
|
+
# Rdoc.
|
|
208
|
+
def write_html_on(op, value_hash, message_hash = {})
|
|
209
|
+
to_html(value_hash, { :output => op, :messages => message_hash })
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Render templates as HTML
|
|
213
|
+
def to_html(value_hash, options = {})
|
|
214
|
+
set_options(options)
|
|
215
|
+
esc = proc { |str| CGI.escapeHTML(str) }
|
|
216
|
+
@output << process(value_hash, esc)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Render templates as TeX. Compatibility method for Rublog and
|
|
220
|
+
# Rdoc.
|
|
221
|
+
def write_tex_on(op, value_hash, message_hash = {})
|
|
222
|
+
to_tex(value_hash, { :output => op, :messages => message_hash })
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Render templates as TeX
|
|
226
|
+
def to_tex(value_hash, options = {})
|
|
227
|
+
set_options(options)
|
|
228
|
+
|
|
229
|
+
esc = proc do |str|
|
|
230
|
+
str.
|
|
231
|
+
gsub(/</, '<').
|
|
232
|
+
gsub(/>/, '>').
|
|
233
|
+
gsub(/&/) { '\\&' }.
|
|
234
|
+
gsub(/([$&%\#{}_])/) { "\\#$1" }.
|
|
235
|
+
gsub(/>/, '$>$').
|
|
236
|
+
gsub(/</, '$<$')
|
|
237
|
+
end
|
|
238
|
+
str = ""
|
|
239
|
+
|
|
240
|
+
str << process(value_hash, esc)
|
|
241
|
+
@output << str
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Render templates as plain text. Compatibility method for Rublog and
|
|
245
|
+
# Rdoc.
|
|
246
|
+
def write_plain_on(op, value_hash, message_hash = {})
|
|
247
|
+
to_plain(value_hash, { :output => op, :messages => message_hash })
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Render templates as plain text.
|
|
251
|
+
def to_plain(value_hash, options = {})
|
|
252
|
+
set_options(options)
|
|
253
|
+
esc = proc {|str| str}
|
|
254
|
+
@output << process(value_hash, esc)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Render the templates. The The <tt>value_hash</tt> contains key/value
|
|
258
|
+
# pairs used to drive the substitution (as described above). The
|
|
259
|
+
# +escaper+ is a proc which will be used to sanitise the contents of the
|
|
260
|
+
# template.
|
|
261
|
+
def process(value_hash, escaper)
|
|
262
|
+
@context = Context.new
|
|
263
|
+
sub(@lines.dup, value_hash, escaper).
|
|
264
|
+
tr("\000", '\\').
|
|
265
|
+
tr(OPEN_TAG, '<').
|
|
266
|
+
tr(CLOSE_TAG, '>')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Substitute a set of key/value pairs into the given template. Keys with
|
|
270
|
+
# scalar values have them substituted directly into the page. Those with
|
|
271
|
+
# array values invoke <tt>substitute_array</tt> (below), which examples a
|
|
272
|
+
# block of the template once for each row in the array.
|
|
273
|
+
#
|
|
274
|
+
# This routine also copes with the <tt>IF:</tt>_key_ directive, removing
|
|
275
|
+
# chunks of the template if the corresponding key does not appear in the
|
|
276
|
+
# hash, and the START: directive, which loops its contents for each value
|
|
277
|
+
# in an array
|
|
278
|
+
def sub(lines, values, escaper)
|
|
279
|
+
@context.push(values)
|
|
280
|
+
skip_to = nil
|
|
281
|
+
result = []
|
|
282
|
+
|
|
283
|
+
while line = lines.read
|
|
284
|
+
mv = line.match(BLOCK_RE)
|
|
285
|
+
|
|
286
|
+
if mv.nil?
|
|
287
|
+
result << expand(line.dup, escaper)
|
|
288
|
+
next
|
|
289
|
+
else
|
|
290
|
+
cmd = mv.captures[0]
|
|
291
|
+
tag = mv.captures[1]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
case cmd
|
|
295
|
+
when "IF", "IFNOT", "IFNOTBLANK", "IFBLANK"
|
|
296
|
+
raise "#{cmd}: must have a key to test." if tag.nil?
|
|
297
|
+
|
|
298
|
+
val = @context.lookup(tag)
|
|
299
|
+
case cmd # Skip lines if the value is...
|
|
300
|
+
when "IF" # false or +nil+ (not false => true)
|
|
301
|
+
test = (not val)
|
|
302
|
+
when "IFBLANK" # +nil+ or empty
|
|
303
|
+
test = (not (val.nil? or val.empty?))
|
|
304
|
+
when "IFNOT"
|
|
305
|
+
test = val
|
|
306
|
+
when "IFNOTBLANK" #
|
|
307
|
+
test = (val.nil? or val.empty?)
|
|
308
|
+
end
|
|
309
|
+
lines.read_up_to(/^\s*ENDIF:#{tag}/) if test
|
|
310
|
+
when "ENDIF"
|
|
311
|
+
nil
|
|
312
|
+
when "START"
|
|
313
|
+
raise "#{cmd}: must have a key." if tag.nil?
|
|
314
|
+
|
|
315
|
+
body = lines.read_up_to(/^\s*END:#{tag}/)
|
|
316
|
+
inner = @context.lookup(tag)
|
|
317
|
+
raise "unknown tag: #{tag}" unless inner
|
|
318
|
+
raise "not array: #{tag}" unless inner.kind_of?(Array)
|
|
319
|
+
inner.each { |vals| result << sub(body.dup, vals, escaper) }
|
|
320
|
+
result << "" # Append the missing \n
|
|
321
|
+
else
|
|
322
|
+
result << expand(line.dup, escaper)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
@context.pop
|
|
327
|
+
|
|
328
|
+
result.join("\n")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Given an individual line, we look for %xxx%, %?xxx%, #xxx#, #?xxx#,
|
|
332
|
+
# [:key| xxx :], [?key| stuff ?], [!key| stuff ?] and HREF:ref:name:
|
|
333
|
+
# constructs, substituting as appropriate.
|
|
334
|
+
def expand(line, escaper)
|
|
335
|
+
# Generate a cross-reference if a reference is given. Otherwise, just
|
|
336
|
+
# fill in the name part.
|
|
337
|
+
line = line.gsub(HREF_RE) do
|
|
338
|
+
ref = @context.lookup($1)
|
|
339
|
+
name = @context.find_scalar($2)
|
|
340
|
+
|
|
341
|
+
if ref and not ref.kind_of?(Array)
|
|
342
|
+
%Q(<a href="#{ref}">#{name}</a>)
|
|
343
|
+
else
|
|
344
|
+
name
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Look for optional content.
|
|
349
|
+
line = line.gsub(IFLINE_RE) do
|
|
350
|
+
type = $1
|
|
351
|
+
name = $2
|
|
352
|
+
stuff = $3
|
|
353
|
+
|
|
354
|
+
case type
|
|
355
|
+
when '?'
|
|
356
|
+
test = @context.lookup(name)
|
|
357
|
+
when '!'
|
|
358
|
+
test = (not @context.lookup(name))
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
if test
|
|
362
|
+
stuff
|
|
363
|
+
else
|
|
364
|
+
""
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Look for repeating content.
|
|
369
|
+
line = line.gsub(BLOCKLINE_RE) do |match|
|
|
370
|
+
name = $1
|
|
371
|
+
stuff = $2
|
|
372
|
+
|
|
373
|
+
val = @context.lookup(name)
|
|
374
|
+
ss = ""
|
|
375
|
+
case val
|
|
376
|
+
when nil
|
|
377
|
+
nil
|
|
378
|
+
when Fixnum
|
|
379
|
+
val.times { |ii| ss << stuff.sub(/%#{name}%/, "#{ii + 1}") }
|
|
380
|
+
when Range
|
|
381
|
+
val.each { |ii| ss << stuff.sub(/%#{name}%/, "#{ii}") }
|
|
382
|
+
when Array
|
|
383
|
+
if not val.empty? and val[0].kind_of?(Hash)
|
|
384
|
+
val.each do |vv|
|
|
385
|
+
@context.push(vv)
|
|
386
|
+
ss << expand(stuff, escaper)
|
|
387
|
+
@context.pop
|
|
388
|
+
end
|
|
389
|
+
else
|
|
390
|
+
val.each { |ee| ss << stuff.sub(/%#{name}%/, "#{ee}") }
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
ss
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Substitute in values for #xxx# constructs.
|
|
397
|
+
line = line.gsub(LABEL_RE) do
|
|
398
|
+
mandatory = $1.nil?
|
|
399
|
+
escaped = $2.nil?
|
|
400
|
+
key = $3.intern
|
|
401
|
+
val = @message[key]
|
|
402
|
+
|
|
403
|
+
if val.nil?
|
|
404
|
+
raise "Template error: can't find label '#{key}'." if mandatory
|
|
405
|
+
""
|
|
406
|
+
else
|
|
407
|
+
val = val.to_s
|
|
408
|
+
val = escaper.call(val) if escaped
|
|
409
|
+
val.tr('\\', "\000")
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Substitute in values for %xxx% constructs. This is made complex
|
|
414
|
+
# because the replacement string may contain characters that are
|
|
415
|
+
# meaningful to the regexp (like \1)
|
|
416
|
+
line = line.gsub(VARIABLE_RE) do
|
|
417
|
+
mandatory = $1.nil?
|
|
418
|
+
escaped = $2.nil?
|
|
419
|
+
key = $3
|
|
420
|
+
val = @context.lookup(key)
|
|
421
|
+
|
|
422
|
+
if val.nil?
|
|
423
|
+
raise "Template error: can't find variable '#{key}'." if mandatory
|
|
424
|
+
""
|
|
425
|
+
else
|
|
426
|
+
val = val.to_s
|
|
427
|
+
val = escaper.call(val) if escaped
|
|
428
|
+
val.tr('\\', "\000")
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Substitute DDLB controls:
|
|
433
|
+
DDLB_RES.each do |ddlb|
|
|
434
|
+
line = line.gsub(ddlb[1]) do
|
|
435
|
+
self.send(ddlb[0], Regexp.last_match.captures)
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
line
|
|
440
|
+
rescue Exception => ex
|
|
441
|
+
raise "Error in template: #{ex}\nOriginal line: #{line}\n#{ex.backtrace[0]}"
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def check(*args)
|
|
445
|
+
value = @context.find_scalar_raw(args[0])
|
|
446
|
+
checked = value ? " checked" : ""
|
|
447
|
+
"<input type=\"checkbox\" name=\"#{name}\"#{checked}>"
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def vsortddlb(*args)
|
|
451
|
+
ddlb(*(args.dup << true))
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def ddlb(*args)
|
|
455
|
+
value = @context.find_scalar(args[0]).to_s
|
|
456
|
+
options = @context.lookup(args[1])
|
|
457
|
+
sort_on = args[2] || 0
|
|
458
|
+
|
|
459
|
+
unless options and options.kind_of?(Hash)
|
|
460
|
+
raise "Missing options #{args[1]} for ddlb #{args[0]}."
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
res = %Q(<select name="#{args[0]}">)
|
|
464
|
+
|
|
465
|
+
sorted = options.to_a.sort do |aa, bb|
|
|
466
|
+
if aa[0] == -1
|
|
467
|
+
-1
|
|
468
|
+
elsif bb[0] == -1
|
|
469
|
+
1
|
|
470
|
+
else
|
|
471
|
+
aa[sort_on] <=> bb[sort_on]
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
sorted.each do |key, val|
|
|
476
|
+
selected = (key.to_s == value) ? " selected" : ""
|
|
477
|
+
res << %Q(<option value="#{key}"#{selected}>#{val}</option>)
|
|
478
|
+
end
|
|
479
|
+
res << "</select>"
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def date(*args)
|
|
483
|
+
yy = "#{argv[0]}_y"
|
|
484
|
+
mm = "#{argv[0]}_m"
|
|
485
|
+
dd = "#{argv[0]}_d"
|
|
486
|
+
%Q<#{input(yy, 4, 4)} . #{input(mm, 2, 2)} . #{input(dd, 2, 2)}>
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def radioone(*args)
|
|
490
|
+
radio(*(args.dup << ""))
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def radio(*args)
|
|
494
|
+
value = @context.find_scalar(argv[0]).to_s
|
|
495
|
+
options = @context.lookup(argv[1])
|
|
496
|
+
br = argv[2] || "<br />"
|
|
497
|
+
|
|
498
|
+
unless options and options.kind_of?(Hash)
|
|
499
|
+
raise "Missing options #{args[1]} for radio #{args[0]}."
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
res = ""
|
|
503
|
+
options.keys.sort.each do |key|
|
|
504
|
+
val = options[key]
|
|
505
|
+
checked = (key.to_s == value) ? " checked" : ""
|
|
506
|
+
res << %Q(<label>
|
|
507
|
+
<input type="radio" name="#{args[0]}"
|
|
508
|
+
value="#{key}"#{checked}">#{val}</label>#{br})
|
|
509
|
+
end
|
|
510
|
+
res
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def text(*args)
|
|
514
|
+
value = @context.find_scalar(args[0]).to_s
|
|
515
|
+
%Q(<textarea name="#{args[0]}" cols="#{args[1]}" rows="#{args[2]}">
|
|
516
|
+
#{CGI.escapeHTML(value)}
|
|
517
|
+
</textarea>)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def pwinput(*args)
|
|
521
|
+
input(*(args.dup << "password"))
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def input(*args)
|
|
525
|
+
name = args[0]
|
|
526
|
+
value = @context.find_scalar(name).to_s
|
|
527
|
+
width = args[1]
|
|
528
|
+
max = args[2]
|
|
529
|
+
iptype = args[3] || "text"
|
|
530
|
+
%Q(<input type="#{iptype}" name="#{name}" value="#{value}" size="#{width}" maxsize="#{max}">)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def popup(*args)
|
|
534
|
+
url = CGI.escapeHTML(@context.find_scalar(args[0]).to_s)
|
|
535
|
+
text = @context.find_scalar(args[1]).to_s
|
|
536
|
+
%Q|<a href="#{url}" target="Popup" class="methodtitle" onClick="popup('#{url}'); return false;">#{text}</a>|
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def pair(*args)
|
|
540
|
+
label = args[0]
|
|
541
|
+
name = args[1]
|
|
542
|
+
colsp = args[2]
|
|
543
|
+
|
|
544
|
+
value = @context.find_scalar(name).to_s
|
|
545
|
+
value = case value
|
|
546
|
+
when "true" then "Yes"
|
|
547
|
+
when "false" then "No"
|
|
548
|
+
else value
|
|
549
|
+
end
|
|
550
|
+
td = (colsp.nil? or colsp.empty?) ? "<td>" : %Q{<td colspan="#{colsp}">}
|
|
551
|
+
"#{Html.tag(label)}#{td}#{value}</td>"
|
|
552
|
+
end
|
|
553
|
+
end
|