eggshell 0.8.1
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.
- checksums.yaml +7 -0
- data/bin/eggshell +32 -0
- data/lib/eggshell/block-handler.rb +54 -0
- data/lib/eggshell/block.rb +51 -0
- data/lib/eggshell/bundles/basics.rb +854 -0
- data/lib/eggshell/bundles/loader.rb +57 -0
- data/lib/eggshell/bundles.rb +62 -0
- data/lib/eggshell/expression-evaluator.rb +862 -0
- data/lib/eggshell/macro-handler.rb +41 -0
- data/lib/eggshell/processor-context.rb +28 -0
- data/lib/eggshell.rb +758 -0
- metadata +55 -0
@@ -0,0 +1,854 @@
|
|
1
|
+
class Eggshell::Bundles::Basics
|
2
|
+
BUNDLE_ID = 'basics'
|
3
|
+
EE = Eggshell::ExpressionEvaluator
|
4
|
+
|
5
|
+
def self.new_instance(proc, opts = nil)
|
6
|
+
BasicBlocks.new.set_processor(proc)
|
7
|
+
SectionBlocks.new.set_processor(proc)
|
8
|
+
InlineMacros.new.set_processor(proc)
|
9
|
+
BasicMacros.new.set_processor(proc)
|
10
|
+
ControlMacros.new.set_processor(proc)
|
11
|
+
|
12
|
+
proc.register_functions('', StdFunctions::FUNC_NAMES)
|
13
|
+
proc.register_functions('sprintf', Kernel)
|
14
|
+
end
|
15
|
+
|
16
|
+
# `table` block parameters:
|
17
|
+
# - `row.classes`: defaults to `['odd', 'even']`. The number of elements represents the number of cycles.
|
18
|
+
class BasicBlocks
|
19
|
+
include Eggshell::BlockHandler
|
20
|
+
|
21
|
+
CELL_ATTR_START = '!'
|
22
|
+
CELL_ATTR_END = '<!'
|
23
|
+
|
24
|
+
def set_processor(proc)
|
25
|
+
@proc = proc
|
26
|
+
@proc.register_block(self, *%w(table pre p bq div raw ol ul # - / | >))
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(name, line, buffer, indents = '', indent_level = 0)
|
30
|
+
set_block_params(name)
|
31
|
+
bp = @block_params[name]
|
32
|
+
|
33
|
+
if name == '#' || name == '-' || name == 'ol' || name == 'ul'
|
34
|
+
@type = :list
|
35
|
+
@lines = []
|
36
|
+
@lines << [line, indent_level, name == '#' ? 'ol' : 'ul'] if name == '#' || name == '-'
|
37
|
+
@indent_start = indent_level
|
38
|
+
return COLLECT
|
39
|
+
end
|
40
|
+
|
41
|
+
if name == 'table' || name == '/' || name == '|'
|
42
|
+
@type = :table
|
43
|
+
@lines = []
|
44
|
+
@lines << line if name != 'table'
|
45
|
+
return COLLECT
|
46
|
+
end
|
47
|
+
|
48
|
+
if name == '>'
|
49
|
+
@type = :dt
|
50
|
+
@lines = [line.split('::', 2)]
|
51
|
+
return COLLECT
|
52
|
+
end
|
53
|
+
|
54
|
+
# assume block text
|
55
|
+
@type = name
|
56
|
+
if line.index(name) == 0
|
57
|
+
line = line.lstrip
|
58
|
+
end
|
59
|
+
@lines = [@type == 'raw' ? @proc.expand_expr(line) : @proc.fmt_line(line)]
|
60
|
+
return @type == 'raw' ? COLLECT_RAW : COLLECT
|
61
|
+
end
|
62
|
+
|
63
|
+
def collect(line, buff, indents = '', indent_level = 0)
|
64
|
+
line = '' if !line
|
65
|
+
ret = COLLECT
|
66
|
+
if @type == :list
|
67
|
+
ret = do_list(line, buff, indents, indent_level)
|
68
|
+
elsif @type == :table
|
69
|
+
ret = do_table(line, buff, indents, indent_level)
|
70
|
+
elsif @type == :dt
|
71
|
+
ret = do_dt(line, buff, indents, indent_level)
|
72
|
+
else
|
73
|
+
ret = do_default(line, buff, indents, indent_level)
|
74
|
+
end
|
75
|
+
return ret
|
76
|
+
end
|
77
|
+
|
78
|
+
def do_list(line, buff, indents = '', indent_level = 0)
|
79
|
+
ret = COLLECT
|
80
|
+
lstrip = line.lstrip
|
81
|
+
if line && (lstrip[0] == '#' || lstrip[0] == '-')
|
82
|
+
@lines << [line[1..-1].strip, indent_level+@indent_start, lstrip[0] == '#' ? 'ol' : 'ul']
|
83
|
+
else
|
84
|
+
# if non-empty line, reprocess this line but also process buffer
|
85
|
+
ret = (line && line != '') ? RETRY : DONE
|
86
|
+
order_stack = []
|
87
|
+
otype_stack = []
|
88
|
+
last = nil
|
89
|
+
@lines.each do |tuple|
|
90
|
+
line, indent, type = tuple
|
91
|
+
# normalize indent
|
92
|
+
indent -= @indent_start
|
93
|
+
if order_stack.length == 0
|
94
|
+
order_stack << "<#{type}>"
|
95
|
+
otype_stack << type
|
96
|
+
# @todo make sure that previous item was a list
|
97
|
+
# remove closing li to enclose sublist
|
98
|
+
elsif indent > (otype_stack.length-1) && order_stack.length > 0
|
99
|
+
last = order_stack[-1]
|
100
|
+
last = last[0...last.length-5]
|
101
|
+
order_stack[-1] = last
|
102
|
+
|
103
|
+
order_stack << "#{"\t"*indent}<#{type}>"
|
104
|
+
otype_stack << type
|
105
|
+
elsif indent < (otype_stack.length-1)
|
106
|
+
count = otype_stack.length - 1 - indent
|
107
|
+
while count > 0
|
108
|
+
ltype = otype_stack.pop
|
109
|
+
order_stack << "#{"\t"*count}</#{ltype}>\n#{"\t"*(count-1)}</li>"
|
110
|
+
count -= 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
order_stack << "#{"\t"*indent}<li>#{@proc.fmt_line(line)}</li>"
|
114
|
+
end
|
115
|
+
|
116
|
+
# close nested lists
|
117
|
+
d = otype_stack.length
|
118
|
+
c = 1
|
119
|
+
otype_stack.each do |type|
|
120
|
+
ident = d - c
|
121
|
+
order_stack << "#{"\t" * ident}</#{type}>#{c == d ? '' : "</li>"}"
|
122
|
+
c += 1
|
123
|
+
end
|
124
|
+
buff << order_stack.join("\n")
|
125
|
+
end
|
126
|
+
ret
|
127
|
+
end
|
128
|
+
|
129
|
+
def do_table(line, buff, indents = '', indent_level = 0)
|
130
|
+
ret = COLLECT
|
131
|
+
if line[0] == '|' || line[0] == '/'
|
132
|
+
@lines << line
|
133
|
+
else
|
134
|
+
ret = (line[0] != '\\' && line != '') ? RETRY : DONE
|
135
|
+
map = @block_params['table'] || {}
|
136
|
+
tbl_class = map['class'] || ''
|
137
|
+
tbl_style = map['style'] || ''
|
138
|
+
tbl_attrib = ''
|
139
|
+
if map['attribs'].is_a?(String)
|
140
|
+
tbl_attrib = map['attribs']
|
141
|
+
elsif map['attribs'].is_a?(Hash)
|
142
|
+
map['attribs'].each do |key,val|
|
143
|
+
tbl_attrib = "#{tbl_attrib} #{key}='#{val}'"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
row_classes = map['row.classes']
|
147
|
+
row_classes = ['odd', 'even'] if !row_classes.is_a?(Array)
|
148
|
+
|
149
|
+
@proc.vars['t.row'] = 0
|
150
|
+
buff << "<table class='#{tbl_class}' style='#{tbl_style}'#{tbl_attrib}>"
|
151
|
+
cols = []
|
152
|
+
rows = 0
|
153
|
+
rc = 0
|
154
|
+
@lines.each do |line|
|
155
|
+
ccount = 0
|
156
|
+
if line[0] == '/' && rows == 0
|
157
|
+
cols = line[1..line.length].split('|')
|
158
|
+
buff << "<thead><tr class='#{map['head.class']}'>"
|
159
|
+
cols.each do |col|
|
160
|
+
buff << "\t#{fmt_cell(col, true, ccount)}"
|
161
|
+
ccount += 1
|
162
|
+
end
|
163
|
+
buff << '</tr></thead>'
|
164
|
+
buff << '<tbody>'
|
165
|
+
elsif line[0] == '/'
|
166
|
+
# implies footer
|
167
|
+
cols = line[1..line.length].split('|')
|
168
|
+
buff << "<tfoot><tr class='#{map['foot.class']}'>"
|
169
|
+
cols.each do |col|
|
170
|
+
buff << "\t#{fmt_cell(col, true, ccount)}"
|
171
|
+
ccount += 1
|
172
|
+
end
|
173
|
+
buff << '</tr></tfoot>'
|
174
|
+
elsif line[0] == '|' || line[0..1] == '|>'
|
175
|
+
idx = 1
|
176
|
+
sep = /(?<!\\)\|/
|
177
|
+
if line[1] == '>'
|
178
|
+
idx = 2
|
179
|
+
sep = /(?<!\\)\|\>/
|
180
|
+
end
|
181
|
+
cols = line[idx..line.length].split(sep)
|
182
|
+
@proc.vars['t.row'] = rc
|
183
|
+
rclass = row_classes[rc % row_classes.length]
|
184
|
+
buff << "<tr class='tr-row-#{rc} #{rclass}'>"
|
185
|
+
cols.each do |col|
|
186
|
+
buff << "\t#{fmt_cell(col, false, ccount)}"
|
187
|
+
ccount += 1
|
188
|
+
end
|
189
|
+
buff << '</tr>'
|
190
|
+
rc += 1
|
191
|
+
else
|
192
|
+
cols = line[1..line.length].split('|') if line[0] == '\\'
|
193
|
+
end
|
194
|
+
rows += 1
|
195
|
+
end
|
196
|
+
|
197
|
+
buff << '</tbody>'
|
198
|
+
if cols.length > 0
|
199
|
+
# @todo process footer
|
200
|
+
end
|
201
|
+
buff << "</table>"
|
202
|
+
@proc.vars['table.class'] = ''
|
203
|
+
@proc.vars['table.style'] = ''
|
204
|
+
@proc.vars['table.attribs'] = ''
|
205
|
+
end
|
206
|
+
ret
|
207
|
+
end
|
208
|
+
|
209
|
+
def do_dt(line, buff, indents = '', indent_level = 0)
|
210
|
+
ret = COLLECT
|
211
|
+
if line == '' || line[0] != '>'
|
212
|
+
ret = DONE
|
213
|
+
ret = RETRY if line[0] != '>'
|
214
|
+
|
215
|
+
buff << "<dl class='#{@proc.vars['dd.class']}'>"
|
216
|
+
@lines.each do |line|
|
217
|
+
key = line[0]
|
218
|
+
val = line[1]
|
219
|
+
buff << "<dt class='#{@proc.vars['dt.class']}'>#{key}</dt><dd class='#{@proc.vars['dd.class']}'>#{val}</dd>"
|
220
|
+
end
|
221
|
+
buff << "</dl>"
|
222
|
+
else
|
223
|
+
@lines << line[1..-1].split('::', 2)
|
224
|
+
ret = COLLECT
|
225
|
+
end
|
226
|
+
ret
|
227
|
+
end
|
228
|
+
|
229
|
+
def do_default(line, buff, indents = '', indent_level = 0)
|
230
|
+
ret = COLLECT
|
231
|
+
blank = false
|
232
|
+
|
233
|
+
raw = @type == 'raw'
|
234
|
+
pre = @type == 'pre'
|
235
|
+
nofmt = raw
|
236
|
+
|
237
|
+
# we need to group indented lines as part of block, especially if it's otherwise empty
|
238
|
+
if raw || pre || @type == 'bq'
|
239
|
+
raw = true
|
240
|
+
# strip off first indent
|
241
|
+
if indent_level > 0
|
242
|
+
#idx = indents.length / indent_level
|
243
|
+
line = indents + line
|
244
|
+
if pre
|
245
|
+
line = "\n#{line}"
|
246
|
+
elsif line == ''
|
247
|
+
line = ' '
|
248
|
+
end
|
249
|
+
else
|
250
|
+
blank = line == ''
|
251
|
+
if pre
|
252
|
+
line = "\n#{line}"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
else
|
256
|
+
line = " " if line == '\\'
|
257
|
+
blank = line == ''
|
258
|
+
end
|
259
|
+
|
260
|
+
if blank
|
261
|
+
@lines.delete('')
|
262
|
+
start_tag = ''
|
263
|
+
end_tag = ''
|
264
|
+
content = ''
|
265
|
+
if @type != 'raw'
|
266
|
+
bp = @block_params[@type]
|
267
|
+
attrs = bp['attributes'] || {}
|
268
|
+
attrs['class'] = bp['class'] || ''
|
269
|
+
attrs['style'] = bp['style'] || ''
|
270
|
+
attrs['id'] = bp['id'] || ''
|
271
|
+
@type = 'blockquote' if @type == 'bq'
|
272
|
+
# @todo get attributes from block param
|
273
|
+
start_tag = create_tag(@type, attrs)
|
274
|
+
end_tag = "</#{@type}>"
|
275
|
+
join = @type == 'pre' ? "" : "<br />\n"
|
276
|
+
|
277
|
+
content = "#{start_tag}#{@lines.join(join)}#{end_tag}"
|
278
|
+
else
|
279
|
+
content = @lines.join("\n")
|
280
|
+
end
|
281
|
+
|
282
|
+
buff << content
|
283
|
+
@lines = []
|
284
|
+
ret = DONE
|
285
|
+
else
|
286
|
+
line = !nofmt ? @proc.fmt_line(line) : @proc.expand_expr(line)
|
287
|
+
@lines << line
|
288
|
+
ret = raw ? COLLECT_RAW : COLLECT
|
289
|
+
end
|
290
|
+
ret
|
291
|
+
end
|
292
|
+
|
293
|
+
def fmt_cell(val, header = false, colnum = 0)
|
294
|
+
tag = header ? 'th' : 'td'
|
295
|
+
buff = []
|
296
|
+
attribs = ''
|
297
|
+
|
298
|
+
if val[0] == '\\'
|
299
|
+
val = val[1..val.length]
|
300
|
+
elsif val[0] == CELL_ATTR_START
|
301
|
+
rt = val.index(CELL_ATTR_END)
|
302
|
+
if rt
|
303
|
+
attribs = val[1...rt]
|
304
|
+
val = val[rt+CELL_ATTR_END.length..val.length]
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# inject column position via class
|
309
|
+
olen = attribs.length
|
310
|
+
attribs = attribs.gsub(/class=(['"])/, 'class=$1' + "td-col-#{colnum}")
|
311
|
+
if olen == attribs.length
|
312
|
+
attribs += " class='td-col-#{colnum}'"
|
313
|
+
end
|
314
|
+
|
315
|
+
buff << "<#{tag} #{attribs}>"
|
316
|
+
cclass =
|
317
|
+
if val[0] == '\\'
|
318
|
+
val = val[1..val.length]
|
319
|
+
end
|
320
|
+
|
321
|
+
buff << @proc.fmt_line(val)
|
322
|
+
buff << "</#{tag}>"
|
323
|
+
return buff.join('')
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Handles blocks that deal with sectioning of content.
|
328
|
+
<<-docblock
|
329
|
+
h1. Header 1
|
330
|
+
h2. Header 2
|
331
|
+
h3({class: 'header-3', id: 'custom-id'}). Header 3
|
332
|
+
|
333
|
+
section.
|
334
|
+
h1. Another Header 1 inside a section
|
335
|
+
end-section.
|
336
|
+
|
337
|
+
!# outputs a table of contents based on all headers accumulated
|
338
|
+
!# define the output of each header (or use 'default' as fallback)
|
339
|
+
toc.
|
340
|
+
start: <div class='toc'>
|
341
|
+
end: </div>
|
342
|
+
default: <div class='toc-$level'><a href='$id'>$title</a></div>
|
343
|
+
h4: <div class='toc-h4 special-exception'><a href='$id'>$title</a></div>
|
344
|
+
section: <div class='toc-section-wrap'>
|
345
|
+
section_end: </div>
|
346
|
+
docblock
|
347
|
+
class SectionBlocks
|
348
|
+
include Eggshell::BlockHandler
|
349
|
+
|
350
|
+
TOC_TEMPLATE = {
|
351
|
+
:default => "<div class='toc-h$level'><a href='\#$id'>$title</a></div>"
|
352
|
+
}
|
353
|
+
|
354
|
+
def set_processor(proc)
|
355
|
+
@proc = proc
|
356
|
+
@proc.register_block(self, *%w(h1 h2 h3 h4 h5 h6 hr section end-section toc))
|
357
|
+
@header_list = []
|
358
|
+
@header_idx = {}
|
359
|
+
end
|
360
|
+
|
361
|
+
def start(name, line, buffer, indents = '', indent_level = 0)
|
362
|
+
set_block_params(name)
|
363
|
+
bp = @block_params[name]
|
364
|
+
|
365
|
+
if name[0] == 'h'
|
366
|
+
if name == 'hr'
|
367
|
+
buffer << "<hr />"
|
368
|
+
else
|
369
|
+
lvl = name[1].to_i
|
370
|
+
attrs = bp['attributes'] || {}
|
371
|
+
attrs['class'] = bp['class'] || ''
|
372
|
+
attrs['style'] = bp['style'] || ''
|
373
|
+
|
374
|
+
id = bp['id'] || line.downcase.strip.gsub(/[^a-z0-9_-]+/, '-')
|
375
|
+
lid = id
|
376
|
+
i = 1
|
377
|
+
while @header_idx[lid] != nil
|
378
|
+
lid = "#{id}-#{i}"
|
379
|
+
i += 1
|
380
|
+
end
|
381
|
+
id = lid
|
382
|
+
attrs['id'] = id
|
383
|
+
title = @proc.fmt_line(line)
|
384
|
+
|
385
|
+
buffer << "#{create_tag(name, attrs)}#{title}</#{name}>"
|
386
|
+
|
387
|
+
@header_list << {:level => lvl, :id => lid, :title => title, :tag => name}
|
388
|
+
@header_idx[lid] = @header_list.length - 1
|
389
|
+
end
|
390
|
+
return DONE
|
391
|
+
elsif name == 'section'
|
392
|
+
attrs = bp['attributes'] || {}
|
393
|
+
attrs['class'] = bp['class'] || ''
|
394
|
+
attrs['style'] = bp['style'] || ''
|
395
|
+
attrs['id'] = bp['id'] || ''
|
396
|
+
buffer << create_tag('section', attrs)
|
397
|
+
@header_list << name
|
398
|
+
return DONE
|
399
|
+
elsif name == 'end-section'
|
400
|
+
buffer << '</section>'
|
401
|
+
@header_list << name
|
402
|
+
return DONE
|
403
|
+
elsif name == 'toc'
|
404
|
+
@toc_template = TOC_TEMPLATE.clone
|
405
|
+
return COLLECT
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def collect(line, buffer, indents = '', indent_level = 0)
|
410
|
+
if line == '' || !line
|
411
|
+
buffer << @proc.fmt_line(@toc_template[:start]) if @toc_template[:start]
|
412
|
+
@header_list.each do |entry|
|
413
|
+
if entry == 'section'
|
414
|
+
buffer << @proc.fmt_line(@toc_template[:section]) if @toc_template[:section]
|
415
|
+
elsif entry == 'section_end'
|
416
|
+
buffer << @proc.fmt_line(@toc_template[:section_end]) if @toc_template[:section_end]
|
417
|
+
elsif entry.is_a?(Hash)
|
418
|
+
tpl = @toc_template[entry[:tag]] || @toc_template[:default]
|
419
|
+
buffer << @proc.fmt_line(
|
420
|
+
tpl.gsub('$id', entry[:id]).gsub('$title', entry[:title]).gsub('$level', entry[:level].to_s)
|
421
|
+
)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
buffer << @proc.fmt_line(@toc_template[:end]) if @toc_template[:end]
|
425
|
+
return DONE
|
426
|
+
else
|
427
|
+
key, val = line.split(':', 2)
|
428
|
+
@toc_template[key.to_sym] = val
|
429
|
+
return COLLECT
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
class InlineMacros
|
435
|
+
include Eggshell::MacroHandler
|
436
|
+
|
437
|
+
def initialize
|
438
|
+
@capvar = nil
|
439
|
+
@collbuff = nil
|
440
|
+
@depth = 0
|
441
|
+
end
|
442
|
+
|
443
|
+
HASH_FMT_DECORATORS = {
|
444
|
+
'[*' => '<b>',
|
445
|
+
'[**' => '<strong>',
|
446
|
+
'[_' => '<i>',
|
447
|
+
'[__' => '<em>',
|
448
|
+
'*]'=> '</b>',
|
449
|
+
'**]' => '</strong>',
|
450
|
+
'_]' => '</i>',
|
451
|
+
'__]' => '</em>',
|
452
|
+
'[-_' => '<u>',
|
453
|
+
'_-]' => '</u>',
|
454
|
+
'[-' => '<strike>',
|
455
|
+
'-]' => '</strike>'
|
456
|
+
}.freeze
|
457
|
+
|
458
|
+
def set_processor(eggshell)
|
459
|
+
@proc = eggshell
|
460
|
+
@proc.register_macro(self, '[!', '[~', '[^', '[.', '[*', '[**', '[/', '[//', '[_', '[-')
|
461
|
+
end
|
462
|
+
|
463
|
+
def process(buffer, macname, args, lines, depth)
|
464
|
+
prefix = macname[0..1]
|
465
|
+
textpart = args.shift
|
466
|
+
tag = nil
|
467
|
+
|
468
|
+
case prefix
|
469
|
+
when '[^'
|
470
|
+
tag = 'sup'
|
471
|
+
when '[.'
|
472
|
+
tag = 'sub'
|
473
|
+
when '[*'
|
474
|
+
tag = macname == '[**' ? 'strong' : 'b'
|
475
|
+
when '[/'
|
476
|
+
tag = macname == '[//' ? 'em' : 'i'
|
477
|
+
when '[-'
|
478
|
+
tag = 'strike'
|
479
|
+
when '[_'
|
480
|
+
tag = 'u'
|
481
|
+
when '[~'
|
482
|
+
tag = 'a'
|
483
|
+
link = textpart ? textpart.strip : textpart
|
484
|
+
text = nil
|
485
|
+
if link == ''
|
486
|
+
text = ''
|
487
|
+
elsif link.index('; ') == nil
|
488
|
+
textpart = link
|
489
|
+
args.unshift('href:'+ link);
|
490
|
+
else
|
491
|
+
textpart, link = link.split('; ')
|
492
|
+
link = '' if !link
|
493
|
+
args.unshift('href:'+link)
|
494
|
+
end
|
495
|
+
when '[!'
|
496
|
+
tag = 'img'
|
497
|
+
args.unshift('src:'+textpart)
|
498
|
+
textpart = nil
|
499
|
+
end
|
500
|
+
|
501
|
+
buffer << restructure_html(tag, textpart ? textpart.strip : textpart, args)
|
502
|
+
end
|
503
|
+
|
504
|
+
def restructure_html(tag, text, attributes = [])
|
505
|
+
buff = "<#{tag}"
|
506
|
+
attributes.each do |attrib|
|
507
|
+
key, val = attrib.split(':', 2)
|
508
|
+
# @todo html escape?
|
509
|
+
if val
|
510
|
+
buff = "#{buff} #{key}=\"#{val.gsub('\\|', '|')}\""
|
511
|
+
else
|
512
|
+
buff = "#{buff} #{key}"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
if text == nil
|
517
|
+
buff += ' />'
|
518
|
+
else
|
519
|
+
buff = "#{buff}>#{text}</#{tag}>"
|
520
|
+
end
|
521
|
+
buff
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
# Macros:
|
526
|
+
#
|
527
|
+
# - `include`
|
528
|
+
# - `capture`
|
529
|
+
# - `var`
|
530
|
+
class BasicMacros
|
531
|
+
include Eggshell::MacroHandler
|
532
|
+
|
533
|
+
def initialize
|
534
|
+
@capvar = nil
|
535
|
+
@collbuff = nil
|
536
|
+
@depth = 0
|
537
|
+
end
|
538
|
+
|
539
|
+
def set_processor(eggshell)
|
540
|
+
@proc = eggshell
|
541
|
+
@proc.register_macro(self, *%w(! = capture var include process parse_test))
|
542
|
+
end
|
543
|
+
|
544
|
+
def process(buffer, macname, args, lines, depth)
|
545
|
+
if macname == '!'
|
546
|
+
@proc.vars[:block_params] = @proc.expr_eval(args)
|
547
|
+
elsif macname == 'process'
|
548
|
+
if args[0]
|
549
|
+
proclines = @proc.expr_eval(args[0])
|
550
|
+
proclines = proclines.split(/[\r\n]+/) if proclines.is_a?(String)
|
551
|
+
buffer << @proc.process(proclines, depth + 1) if proclines.is_a?(Array)
|
552
|
+
end
|
553
|
+
elsif macname == 'capture'
|
554
|
+
# @todo check args for fragment to parse
|
555
|
+
return if !lines
|
556
|
+
var = args[0]
|
557
|
+
@proc.vars[var] = @proc.process(lines, depth)
|
558
|
+
elsif macname == 'var' || macname == '='
|
559
|
+
# @todo support multiple vars via lines
|
560
|
+
# @todo expand value if expression
|
561
|
+
if args.length >= 2
|
562
|
+
key = args[0]
|
563
|
+
val = args[1]
|
564
|
+
|
565
|
+
if val.is_a?(Array) && val[0].is_a?(Symbol)
|
566
|
+
@proc.vars[key] = @proc.expr_eval(val)
|
567
|
+
else
|
568
|
+
@proc.vars[key] = val
|
569
|
+
end
|
570
|
+
|
571
|
+
# @todo fix this so it's possible to set things like arr[0].setter, etc.
|
572
|
+
# ptrs = EE.retrieve_var(key, @proc.vars, {}, true)
|
573
|
+
# val = val.is_a?(Array) && val[0] == :var ? EE.retrieve_var(val[1], @proc.vars, {}) : val
|
574
|
+
# if ptrs
|
575
|
+
# EE.set_var(ptrs[0], ptrs[1][0], ptrs[1][1], val)
|
576
|
+
# else
|
577
|
+
# @proc.vars[key] = val
|
578
|
+
# end
|
579
|
+
end
|
580
|
+
elsif macname == 'include'
|
581
|
+
paths = args[0]
|
582
|
+
opts = args[1] || {}
|
583
|
+
if opts['encoding']
|
584
|
+
opts[:encoding] = opts['encoding']
|
585
|
+
else
|
586
|
+
opts[:encoding] = 'utf-8'
|
587
|
+
end
|
588
|
+
if lines && lines.length > 0
|
589
|
+
paths = lines
|
590
|
+
end
|
591
|
+
do_include(paths, buffer, depth, opts)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def do_include(paths, buff, depth, opts = {})
|
596
|
+
@proc.vars[:include_stack] = [] if !@proc.vars[:include_stack]
|
597
|
+
paths = [paths] if !paths.is_a?(Array)
|
598
|
+
# @todo check all include paths?
|
599
|
+
paths.each do |inc|
|
600
|
+
inc = @proc.expand_expr(inc.strip)
|
601
|
+
checks = []
|
602
|
+
if inc[0] != '/'
|
603
|
+
@proc.vars[:include_paths].each do |root|
|
604
|
+
checks << "#{root}/#{inc}"
|
605
|
+
end
|
606
|
+
# @todo if :include_root, expand path and check that it's under the root, otherwise, sandbox
|
607
|
+
else
|
608
|
+
# sandboxed root include
|
609
|
+
if @proc.vars[:include_root]
|
610
|
+
checks << "#{@proc.vars[:include_root]}#{inc}"
|
611
|
+
else
|
612
|
+
checks << inc
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
checks.each do |inc|
|
617
|
+
if File.exists?(inc)
|
618
|
+
lines = IO.readlines(inc, $/, opts)
|
619
|
+
@proc.vars[:include_stack] << inc
|
620
|
+
@proc.context.push_line_counter
|
621
|
+
begin
|
622
|
+
buff << @proc.process(lines, depth + 1)
|
623
|
+
@proc._debug("include: 200 #{inc}")
|
624
|
+
rescue => ex
|
625
|
+
@proc._error("include: 500 #{inc}: #{ex.message}")
|
626
|
+
end
|
627
|
+
|
628
|
+
@proc.vars[:include_stack].pop
|
629
|
+
@proc.context.pop_line_counter
|
630
|
+
break
|
631
|
+
else
|
632
|
+
@proc._warn("include: 404 #{inc}")
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
class ControlMacros
|
640
|
+
include Eggshell::MacroHandler
|
641
|
+
|
642
|
+
def initialize
|
643
|
+
@stack = []
|
644
|
+
@state = []
|
645
|
+
@macstack = []
|
646
|
+
end
|
647
|
+
|
648
|
+
def set_processor(eggshell)
|
649
|
+
@proc = eggshell
|
650
|
+
@proc.register_macro(self, *%w(if elsif else loop for while break next))
|
651
|
+
end
|
652
|
+
|
653
|
+
def process(buffer, macname, args, lines, depth)
|
654
|
+
p0 = args.is_a?(Array) ? args[0] : nil
|
655
|
+
lines ? lines.delete('') : ''
|
656
|
+
|
657
|
+
macname = macname.to_sym
|
658
|
+
st = @state[depth]
|
659
|
+
if !@state[depth]
|
660
|
+
st = {:type => macname}
|
661
|
+
|
662
|
+
@state[depth] = st
|
663
|
+
# erase nested state
|
664
|
+
@state[depth+1] = nil
|
665
|
+
end
|
666
|
+
|
667
|
+
if macname == :for || macname == :loop
|
668
|
+
p0 = p0 || {}
|
669
|
+
st[:var] = p0['var']
|
670
|
+
st[:start] = p0['start']
|
671
|
+
st[:stop] = p0['stop']
|
672
|
+
st[:step] = p0['step'] || 1
|
673
|
+
st[:iter] = p0['items'] || 'items'
|
674
|
+
st[:item] = p0['item'] || 'item'
|
675
|
+
st[:counter] = p0['counter'] || 'counter'
|
676
|
+
st[:raw] = p0['raw'] # @todo inherit if not set?
|
677
|
+
st[:collect] = p0['collect']
|
678
|
+
st[:agg_block] = p0['aggregate_block']
|
679
|
+
|
680
|
+
mbuff = []
|
681
|
+
looper = nil
|
682
|
+
loop_is_map = false
|
683
|
+
|
684
|
+
# in for, construct range that can be looped over
|
685
|
+
# in loop, detect 'each' method
|
686
|
+
if macname == :for
|
687
|
+
st[:item] = st[:var]
|
688
|
+
looper = Range.new(st[:start], st[:stop]).step(st[:step]).to_a
|
689
|
+
elsif macname == :loop
|
690
|
+
begin
|
691
|
+
looper = st[:iter].is_a?(Array) && st[:iter][0].is_a?(Symbol) ? @proc.expr_eval(st[:iter]) : st[:iter]
|
692
|
+
looper = nil if !looper.respond_to?(:each)
|
693
|
+
loop_is_map = looper.is_a?(Hash)
|
694
|
+
rescue
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
collector = []
|
699
|
+
if looper
|
700
|
+
counter = 0
|
701
|
+
looper.each do |i1, i2|
|
702
|
+
# for maps, use key as the counter
|
703
|
+
val = nil
|
704
|
+
if loop_is_map
|
705
|
+
@proc.vars[st[:counter]] = i1
|
706
|
+
val = i2
|
707
|
+
else
|
708
|
+
val = i1
|
709
|
+
@proc.vars[st[:counter]] = counter
|
710
|
+
end
|
711
|
+
|
712
|
+
# inject value into :item -- if it's an expression, evaluate first
|
713
|
+
@proc.vars[st[:item]] = val.is_a?(Array) && val[0].is_a?(Symbol) ? @proc.expr_eval(val) : val
|
714
|
+
# divert lines to collector
|
715
|
+
if st[:collect]
|
716
|
+
lines.each do |_line|
|
717
|
+
if _line.is_a?(String)
|
718
|
+
collector << @proc.expand_expr(_line)
|
719
|
+
elsif _line.is_a?(Eggshell::Block)
|
720
|
+
_line.process(collector)
|
721
|
+
end
|
722
|
+
end
|
723
|
+
else
|
724
|
+
process_lines(lines, buffer, depth + 1, st[:raw])
|
725
|
+
end
|
726
|
+
break if st[:break]
|
727
|
+
|
728
|
+
counter += 1
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
# since there are lines here, process collected as an aggregated set of blocks
|
733
|
+
if collector.length > 0
|
734
|
+
if st[:collect] == 'aggregate'
|
735
|
+
if st[:agg_block]
|
736
|
+
collector.unshift(st[:agg_block])
|
737
|
+
end
|
738
|
+
process_lines(collector, buffer, depth + 1, false)
|
739
|
+
else
|
740
|
+
buffer << collector.join("\n")
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
# clear state
|
745
|
+
@state[depth] = nil
|
746
|
+
elsif macname == :while
|
747
|
+
raw = args[1]
|
748
|
+
while @proc.expr_eval(p0)
|
749
|
+
process_lines(lines, buffer, depth + 1, raw)
|
750
|
+
break if st[:break]
|
751
|
+
end
|
752
|
+
elsif macname == :if || macname == :elsif || macname == :else
|
753
|
+
cond = p0
|
754
|
+
st[:if] = true if macname == :if
|
755
|
+
if st[:cond_count] == nil || macname == :if
|
756
|
+
st[:cond_count] = 0
|
757
|
+
st[:cond_eval] = false
|
758
|
+
st[:cond_met] = false
|
759
|
+
end
|
760
|
+
|
761
|
+
last_action = st[:last_action]
|
762
|
+
st[:last_action] = macname.to_sym
|
763
|
+
|
764
|
+
# @todo more checks (e.g. no elsif after else, no multiple else, etc.)
|
765
|
+
if !st[:if] || (macname != :else && !cond)
|
766
|
+
# @todo exception?
|
767
|
+
return
|
768
|
+
end
|
769
|
+
|
770
|
+
if macname != :else
|
771
|
+
if !st[:cond_eval]
|
772
|
+
cond_struct = Eggshell::ExpressionEvaluator.struct(cond)
|
773
|
+
st[:cond_eval] = @proc.expr_eval(cond_struct)[0]
|
774
|
+
end
|
775
|
+
else
|
776
|
+
st[:cond_eval] = true
|
777
|
+
end
|
778
|
+
|
779
|
+
if st[:cond_eval] && !st[:cond_met]
|
780
|
+
st[:cond_met] = true
|
781
|
+
process_lines(lines, buffer, depth + 1)
|
782
|
+
end
|
783
|
+
elsif macname == :break
|
784
|
+
lvl = p0 || 1
|
785
|
+
i = depth - 1
|
786
|
+
|
787
|
+
# set breaks at each found loop until # of levels reached
|
788
|
+
while i >= 0
|
789
|
+
st = @state[i]
|
790
|
+
i -= 1
|
791
|
+
next if !st
|
792
|
+
if st[:type] == :for || st[:type] == :while || st[:type] == :loop
|
793
|
+
lvl -= 1
|
794
|
+
st[:break] = true
|
795
|
+
break if lvl <= 0
|
796
|
+
end
|
797
|
+
end
|
798
|
+
elsif macname == :next
|
799
|
+
lvl = p0 || 1
|
800
|
+
i = depth - 1
|
801
|
+
|
802
|
+
# set breaks at each found loop until # of levels reached
|
803
|
+
while i >= 0
|
804
|
+
st = @state[i]
|
805
|
+
i -= 1
|
806
|
+
next if !st
|
807
|
+
if st[:type] == :for || st[:type] == :while || st[:type] == :loop
|
808
|
+
lvl -= 1
|
809
|
+
st[:next] = true
|
810
|
+
break if lvl <= 0
|
811
|
+
end
|
812
|
+
end
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
def process_lines(lines, buffer, depth, raw = false)
|
817
|
+
return if !lines
|
818
|
+
ln = []
|
819
|
+
lines.each do |line|
|
820
|
+
if line.is_a?(Eggshell::Block)
|
821
|
+
if ln.length > 0
|
822
|
+
buffer << (raw ? @proc.expand_expr(ln.join("\n")) : @proc.process(ln, depth))
|
823
|
+
ln = []
|
824
|
+
end
|
825
|
+
line.process(buffer, depth)
|
826
|
+
if @state[depth-1][:next]
|
827
|
+
@state[depth-1][:next] = false
|
828
|
+
break
|
829
|
+
end
|
830
|
+
else
|
831
|
+
ln << line
|
832
|
+
end
|
833
|
+
end
|
834
|
+
if ln.length > 0
|
835
|
+
buffer << (raw ? @proc.expand_expr(ln.join("\n")) : @proc.process(ln, depth))
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
protected :process_lines
|
840
|
+
end
|
841
|
+
|
842
|
+
# Baseline functions.
|
843
|
+
# @todo catch exceptions???
|
844
|
+
module StdFunctions
|
845
|
+
# Repeats `str` by a given `amt`
|
846
|
+
def self.str_repeat(str, amt)
|
847
|
+
return str * amt
|
848
|
+
end
|
849
|
+
|
850
|
+
FUNC_NAMES = %w(str_repeat).freeze
|
851
|
+
end
|
852
|
+
|
853
|
+
include Eggshell::Bundles::Bundle
|
854
|
+
end
|