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
data/lib/eggshell.rb
ADDED
@@ -0,0 +1,758 @@
|
|
1
|
+
# Eggshell.
|
2
|
+
module Eggshell
|
3
|
+
|
4
|
+
# Tracks line and line count. Correctness depends
|
5
|
+
class LineCounter
|
6
|
+
def initialize(file = nil)
|
7
|
+
@file = file
|
8
|
+
@l_stack = []
|
9
|
+
@l_count = []
|
10
|
+
push
|
11
|
+
end
|
12
|
+
|
13
|
+
def line
|
14
|
+
return @l_stack[-1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def line_num
|
18
|
+
c = 0
|
19
|
+
@l_count.each do |lc|
|
20
|
+
c += lc
|
21
|
+
end
|
22
|
+
return c
|
23
|
+
end
|
24
|
+
|
25
|
+
def file
|
26
|
+
@file
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the current line and offset. If offset is not nil, resets the counter to this value.
|
30
|
+
# A sample situation where this is needed:
|
31
|
+
#
|
32
|
+
# pre.
|
33
|
+
# block here
|
34
|
+
# \
|
35
|
+
# @macro {
|
36
|
+
# macro line 1
|
37
|
+
# macro line 2
|
38
|
+
# }
|
39
|
+
# \
|
40
|
+
# last block
|
41
|
+
#
|
42
|
+
# During execution, `@macro` would push a new count state. After execution is over, it's popped,
|
43
|
+
# leaving the line number at the macro start. The main process loop, however, is keeping track
|
44
|
+
# of all the lines during its own execution, so it can set the offset to the actual line again.
|
45
|
+
def new_line(line, offset = nil)
|
46
|
+
@l_stack[-1] = line
|
47
|
+
@l_count[-1] = offset != nil ? offset : @l_count[-1] + 1
|
48
|
+
end
|
49
|
+
|
50
|
+
def push
|
51
|
+
@l_stack << nil
|
52
|
+
@l_count << 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def pop
|
56
|
+
@l_stack.pop
|
57
|
+
@l_count.pop
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Processor
|
63
|
+
BLOCK_MATCH = /^([a-z0-9_-]+\.|[|\/#><*+-]+)/
|
64
|
+
BLOCK_MATCH_PARAMS = /^([a-z0-9_-]+|[|\/#><*+-]+)\(/
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@context = Eggshell::ProcessorContext.new
|
68
|
+
@vars = @context.vars
|
69
|
+
@funcs = @context.funcs
|
70
|
+
@macros = @context.macros
|
71
|
+
@blocks = @context.blocks
|
72
|
+
@block_params = @context.block_params
|
73
|
+
@expr_cache = @context.expr_cache
|
74
|
+
|
75
|
+
@noop_macro = Eggshell::MacroHandler::Defaults::NoOpHandler.new
|
76
|
+
@noop_block = Eggshell::BlockHandler::Defaults::NoOpHandler.new
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :context
|
80
|
+
|
81
|
+
def register_macro(handler, *macros)
|
82
|
+
macros.each do |mac|
|
83
|
+
@macros[mac] = handler
|
84
|
+
_trace "register_macro: #{mac}: #{handler}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def register_block(handler, *blocks)
|
89
|
+
blocks.each do |block|
|
90
|
+
@blocks[block] = handler
|
91
|
+
_trace "register_block: #{block}: #{handler}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Registers a function for embedded expressions. Functions are grouped into namespaces,
|
96
|
+
# and a handler can be assigned to handle all function calls within that namespace, or
|
97
|
+
# a specific set of functions within the namespace. The root namespace is a blank string.
|
98
|
+
#
|
99
|
+
# @param String func_key In the form `ns` or `ns:func_name`. For functions in the
|
100
|
+
# root namespace, do `:func_name`.
|
101
|
+
# @param Object handler
|
102
|
+
# @param Array func_names If `func_key` only refers to a namespace but the handler
|
103
|
+
# needs to only handle a subset of functions, supply the list of function names here.
|
104
|
+
def register_functions(func_key, handler, func_names = nil)
|
105
|
+
if !func_key.index(':') && func_names.is_a?(Array)
|
106
|
+
func_names.each do |fname|
|
107
|
+
@funcs[func_key+func_name] = handler
|
108
|
+
end
|
109
|
+
else
|
110
|
+
@funcs[func_key] = handler
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def _error(msg)
|
115
|
+
$stderr.write("[ERROR] #{msg}\n")
|
116
|
+
end
|
117
|
+
|
118
|
+
def _warn(msg)
|
119
|
+
$stderr.write("[WARN] #{msg}\n")
|
120
|
+
end
|
121
|
+
|
122
|
+
def _info(msg)
|
123
|
+
return if @vars['log.level'] < '1'
|
124
|
+
$stderr.write("[INFO] #{msg}\n")
|
125
|
+
end
|
126
|
+
|
127
|
+
def _debug(msg)
|
128
|
+
return if @vars['log.level'] < '2'
|
129
|
+
$stderr.write("[DEBUG] #{msg}\n")
|
130
|
+
end
|
131
|
+
|
132
|
+
def _trace(msg)
|
133
|
+
return if @vars['log.level'] < '3'
|
134
|
+
$stderr.write("[TRACE] #{msg}\n")
|
135
|
+
end
|
136
|
+
|
137
|
+
attr_reader :vars
|
138
|
+
|
139
|
+
def expr_eval(struct)
|
140
|
+
return Eggshell::ExpressionEvaluator.expr_eval(struct, @vars, @funcs)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Expands expressions (`\${}`) and macro calls (`\@@macro\@@`).
|
144
|
+
def expand_expr(expr)
|
145
|
+
# replace dynamic placeholders
|
146
|
+
# @todo expand to actual expressions
|
147
|
+
buff = []
|
148
|
+
esc = false
|
149
|
+
exp = false
|
150
|
+
mac = false
|
151
|
+
|
152
|
+
toks = expr.gsub(/\\[trn]/, HASH_LINE_ESCAPE).split(/(\\|\$\{|\}|@@|"|')/)
|
153
|
+
i = 0
|
154
|
+
|
155
|
+
plain_str = ''
|
156
|
+
expr_str = ''
|
157
|
+
quote = nil
|
158
|
+
expr_delim = nil
|
159
|
+
|
160
|
+
while i < toks.length
|
161
|
+
tok = toks[i]
|
162
|
+
i += 1
|
163
|
+
next if tok == ''
|
164
|
+
|
165
|
+
if esc
|
166
|
+
plain_str += tok
|
167
|
+
esc = false
|
168
|
+
next
|
169
|
+
end
|
170
|
+
|
171
|
+
if exp
|
172
|
+
if quote
|
173
|
+
expr_str += tok
|
174
|
+
if tok == quote
|
175
|
+
quote = nil
|
176
|
+
end
|
177
|
+
elsif tok == '"' || tok == "'"
|
178
|
+
expr_str += tok
|
179
|
+
quote = tok
|
180
|
+
elsif tok == expr_delim
|
181
|
+
struct = @expr_cache[expr_str]
|
182
|
+
|
183
|
+
if !struct
|
184
|
+
struct = Eggshell::ExpressionEvaluator.struct(expr_str)
|
185
|
+
@expr_cache[expr_str] = struct
|
186
|
+
end
|
187
|
+
|
188
|
+
if !mac
|
189
|
+
buff << expr_eval(struct)
|
190
|
+
else
|
191
|
+
args = struct[0]
|
192
|
+
macro = args[1]
|
193
|
+
args = args[2] || []
|
194
|
+
macro_handler = @macros[macro]
|
195
|
+
if macro_handler
|
196
|
+
macro_handler.process(buff, macro, args, nil, -1)
|
197
|
+
else
|
198
|
+
_warn("macro (inline) not found: #{macro}")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
exp = false
|
203
|
+
mac = false
|
204
|
+
expr_delim = nil
|
205
|
+
expr_str = ''
|
206
|
+
else
|
207
|
+
expr_str += tok
|
208
|
+
end
|
209
|
+
# only unescape if not in expression, since expression needs to be given as-is
|
210
|
+
elsif tok == '\\'
|
211
|
+
esc = true
|
212
|
+
next
|
213
|
+
elsif tok == '${' || tok == '@@'
|
214
|
+
if plain_str != ''
|
215
|
+
buff << plain_str
|
216
|
+
plain_str = ''
|
217
|
+
end
|
218
|
+
exp = true
|
219
|
+
expr_delim = '}'
|
220
|
+
if tok == '@@'
|
221
|
+
mac = true
|
222
|
+
expr_delim = tok
|
223
|
+
end
|
224
|
+
else
|
225
|
+
plain_str += tok
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# if exp -- throw exception?
|
230
|
+
buff << plain_str if plain_str != ''
|
231
|
+
return buff.join('')
|
232
|
+
end
|
233
|
+
|
234
|
+
TAB = "\t"
|
235
|
+
TAB_SPACE = ' '
|
236
|
+
|
237
|
+
# html tags that have end-block checks. any block starting with one of these tags will have
|
238
|
+
# its contents passed through until end of the tag
|
239
|
+
# @todo what else should be treated?
|
240
|
+
HTML_BLOCK = /^<(style|script|table|dl|select|textarea|\!--|\?)/
|
241
|
+
HTML_BLOCK_END = {
|
242
|
+
'<!--' => '-->',
|
243
|
+
'<?' => '\\?>'
|
244
|
+
}.freeze
|
245
|
+
|
246
|
+
# For lines starting with only these tags, accept as-is
|
247
|
+
HTML_PASSTHRU = /^\s*<(\/?(html|head|meta|link|title|body|br|section|div|blockquote|p|pre))/
|
248
|
+
|
249
|
+
HASH_LINE_ESCAPE = {
|
250
|
+
"\\n" => "\n",
|
251
|
+
"\\r" => "\r",
|
252
|
+
"\\t" => "\t",
|
253
|
+
"\\\\" => "\\"
|
254
|
+
}
|
255
|
+
|
256
|
+
# @param Boolean is_default If true, associates these parameters with the
|
257
|
+
# `block_type` used in `get_block_param()` or explicitly in third parameter.
|
258
|
+
# @param String block_type
|
259
|
+
def set_block_params(params, is_default = false, block_type = nil)
|
260
|
+
if block_type && is_default
|
261
|
+
@block_params[block_type] = params
|
262
|
+
else
|
263
|
+
@block_params[:pending] = params
|
264
|
+
@block_param_default = is_default
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Gets the block parameters for a block type, and merges default values if available.
|
269
|
+
def get_block_params(block_type)
|
270
|
+
bp = @block_params.delete(:pending)
|
271
|
+
if @block_params_default
|
272
|
+
if block_type && bp
|
273
|
+
@block_params[block_type] = bp if bp
|
274
|
+
end
|
275
|
+
@block_params_default = false
|
276
|
+
bp = {} if !bp
|
277
|
+
else
|
278
|
+
bp = {} if !bp
|
279
|
+
default = @block_params[block_type]
|
280
|
+
if default
|
281
|
+
default.each do |key,val|
|
282
|
+
if !bp.has_key?(key) && val
|
283
|
+
bp[key] = val.clone
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
return bp
|
289
|
+
end
|
290
|
+
|
291
|
+
# Iterates through each line of a source document and processes block-level items
|
292
|
+
# @param Fixnum call_depth For macro processing. Allows accurate tracking of nested
|
293
|
+
# block macros.
|
294
|
+
def process(lines, call_depth = 0)
|
295
|
+
buff = []
|
296
|
+
order_stack = []
|
297
|
+
otype_stack = []
|
298
|
+
in_table = false
|
299
|
+
in_html = false
|
300
|
+
end_html = nil
|
301
|
+
in_block = false
|
302
|
+
in_dl = false
|
303
|
+
|
304
|
+
macro = nil
|
305
|
+
macro_blocks = []
|
306
|
+
macro_handler = nil
|
307
|
+
macro_depth = call_depth + 1
|
308
|
+
|
309
|
+
block = nil
|
310
|
+
ext_line = nil
|
311
|
+
|
312
|
+
block_handler = nil
|
313
|
+
block_handler_raw = false
|
314
|
+
block_handler_indent = 0
|
315
|
+
|
316
|
+
i = 0
|
317
|
+
|
318
|
+
begin
|
319
|
+
while (i <= lines.length)
|
320
|
+
line = nil
|
321
|
+
indent_level = 0
|
322
|
+
indents = ''
|
323
|
+
|
324
|
+
# special condition to get a dangling line
|
325
|
+
if i == lines.length
|
326
|
+
if ext_line
|
327
|
+
line = ext_line
|
328
|
+
ext_line = nil
|
329
|
+
else
|
330
|
+
break
|
331
|
+
end
|
332
|
+
else
|
333
|
+
line = lines[i]
|
334
|
+
end
|
335
|
+
i += 1
|
336
|
+
|
337
|
+
if line.is_a?(Block)
|
338
|
+
line.process(buff)
|
339
|
+
next
|
340
|
+
end
|
341
|
+
|
342
|
+
orig = line
|
343
|
+
oline = line
|
344
|
+
|
345
|
+
# @todo configurable space tab?
|
346
|
+
offset = 0
|
347
|
+
tablen = 0
|
348
|
+
if line[0] == TAB || line[0..3] == TAB_SPACE
|
349
|
+
tab = line[0] == TAB ? TAB : TAB_SPACE
|
350
|
+
tablen = tab.length
|
351
|
+
indent_level += 1
|
352
|
+
offset = tablen
|
353
|
+
while line[offset...offset+tablen] == tab
|
354
|
+
indent_level += 1
|
355
|
+
offset += tablen
|
356
|
+
end
|
357
|
+
# if block_handler_indent > 0
|
358
|
+
# indent_level -= block_handler_indent
|
359
|
+
# offset -= (tablen * block_handler_indent)
|
360
|
+
# end
|
361
|
+
indents = line[0...offset]
|
362
|
+
line = line[offset..-1]
|
363
|
+
end
|
364
|
+
|
365
|
+
line = line.rstrip
|
366
|
+
line_end = ''
|
367
|
+
if line.length < oline.length
|
368
|
+
line_end = oline[line.length..-1]
|
369
|
+
end
|
370
|
+
|
371
|
+
# if line end in \, buffer and continue to next line;
|
372
|
+
# join buffered line once \ no longer at end
|
373
|
+
if line[-1] == '\\' && line.length > 1
|
374
|
+
if line[-2] != '\\'
|
375
|
+
# special case: if a line consists of a single \, assume line ending is wanted,
|
376
|
+
# otherwise join directly with previous line
|
377
|
+
if line == '\\'
|
378
|
+
line = line_end
|
379
|
+
else
|
380
|
+
line = line[0..-2]
|
381
|
+
end
|
382
|
+
|
383
|
+
if ext_line
|
384
|
+
ext_line += indents + line
|
385
|
+
else
|
386
|
+
ext_line = indents + line
|
387
|
+
end
|
388
|
+
next
|
389
|
+
else
|
390
|
+
line = line[0..-2]
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# join this line with last line and terminate last line
|
395
|
+
if ext_line
|
396
|
+
line = ext_line + line
|
397
|
+
ext_line = nil
|
398
|
+
end
|
399
|
+
oline = line
|
400
|
+
|
401
|
+
if line[0..1] == '!#'
|
402
|
+
next
|
403
|
+
end
|
404
|
+
|
405
|
+
# relative indenting
|
406
|
+
if block_handler_indent > 0
|
407
|
+
indents = indents[(tablen*block_handler_indent)..-1]
|
408
|
+
end
|
409
|
+
|
410
|
+
if block_handler_raw
|
411
|
+
stat = block_handler.collect(line, buff, indents, indent_level - block_handler_indent)
|
412
|
+
if stat != Eggshell::BlockHandler::COLLECT_RAW
|
413
|
+
block_handler_raw = false
|
414
|
+
if stat != Eggshell::BlockHandler::COLLECT
|
415
|
+
block_handler = nil
|
416
|
+
if stat == Eggshell::BlockHandler::RETRY
|
417
|
+
i -= 1
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
next
|
422
|
+
end
|
423
|
+
|
424
|
+
# macro processing
|
425
|
+
if line[0] == '@'
|
426
|
+
macro = nil
|
427
|
+
args = nil
|
428
|
+
delim = nil
|
429
|
+
|
430
|
+
if line.index(' ') || line.index('(') || line.index('{')
|
431
|
+
# since the macro statement is essentially a function call, parse the line as an expression
|
432
|
+
expr_struct = ExpressionEvaluator.struct(line)
|
433
|
+
fn = expr_struct.shift
|
434
|
+
if fn.is_a?(Array) && fn[0] == :fn
|
435
|
+
macro = fn[1][1..fn[1].length]
|
436
|
+
args = fn[2]
|
437
|
+
if expr_struct[-1].is_a?(Array) && expr_struct[-1][0] == :brace_op
|
438
|
+
delim = expr_struct[-1][1]
|
439
|
+
end
|
440
|
+
end
|
441
|
+
else
|
442
|
+
macro = line[1..line.length]
|
443
|
+
end
|
444
|
+
|
445
|
+
# special case: block parameter
|
446
|
+
if macro == '!'
|
447
|
+
set_block_params(args[0], args[1], args[2])
|
448
|
+
next
|
449
|
+
end
|
450
|
+
|
451
|
+
macro_handler = @macros[macro]
|
452
|
+
if macro_handler
|
453
|
+
if delim
|
454
|
+
if block
|
455
|
+
nblock = Eggshell::Block.new(macro, macro_handler, args, block.cur.depth + 1, delim)
|
456
|
+
block.push(nblock)
|
457
|
+
else
|
458
|
+
block = Eggshell::Block.new(macro, macro_handler, args, macro_depth, delim)
|
459
|
+
end
|
460
|
+
else
|
461
|
+
if block
|
462
|
+
block.collect(Eggshell::Block.new(macro, macro_handler, args, macro_depth, nil))
|
463
|
+
else
|
464
|
+
macro_handler.process(buff, macro, args, nil, macro_depth)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
else
|
468
|
+
_warn("macro not found: #{macro} | #{line}")
|
469
|
+
end
|
470
|
+
next
|
471
|
+
elsif block
|
472
|
+
if line == block.cur.delim
|
473
|
+
lb = block.pop
|
474
|
+
if !block.cur
|
475
|
+
block.process(buff)
|
476
|
+
block = nil
|
477
|
+
end
|
478
|
+
else
|
479
|
+
block.cur.collect(orig.rstrip)
|
480
|
+
end
|
481
|
+
next
|
482
|
+
end
|
483
|
+
|
484
|
+
if block_handler
|
485
|
+
stat = block_handler.collect(line, buff, indents, indent_level - block_handler_indent)
|
486
|
+
|
487
|
+
if stat == Eggshell::BlockHandler::COLLECT_RAW
|
488
|
+
block_handler_raw = true
|
489
|
+
elsif stat != Eggshell::BlockHandler::COLLECT
|
490
|
+
block_handler = nil
|
491
|
+
block_handler_raw = false
|
492
|
+
if stat == Eggshell::BlockHandler::RETRY
|
493
|
+
i -= 1
|
494
|
+
end
|
495
|
+
end
|
496
|
+
line = nil
|
497
|
+
next
|
498
|
+
end
|
499
|
+
|
500
|
+
if line.match(HTML_PASSTHRU)
|
501
|
+
if block_handler
|
502
|
+
block_handler.collect(nil, buff)
|
503
|
+
block_handler = nil
|
504
|
+
end
|
505
|
+
buff << fmt_line(line)
|
506
|
+
next
|
507
|
+
end
|
508
|
+
|
509
|
+
# html block processing
|
510
|
+
html = line.match(HTML_BLOCK)
|
511
|
+
if html
|
512
|
+
end_html = HTML_BLOCK_END["<#{html[1]}"]
|
513
|
+
end_html = "</#{html[1]}>$" if !end_html
|
514
|
+
if !line.match(end_html)
|
515
|
+
in_html = true
|
516
|
+
end
|
517
|
+
|
518
|
+
line = @vars['html.no_eval'] ? orig : expand_expr(orig)
|
519
|
+
buff << line.rstrip
|
520
|
+
|
521
|
+
next
|
522
|
+
elsif in_html
|
523
|
+
if line == ''
|
524
|
+
buff << line
|
525
|
+
else
|
526
|
+
line = @vars['html.no_eval'] ? orig : expand_expr(orig)
|
527
|
+
buff << line.rstrip
|
528
|
+
end
|
529
|
+
|
530
|
+
if line.match(end_html)
|
531
|
+
in_html = false
|
532
|
+
end_html = nil
|
533
|
+
@vars.delete('html.no_eval')
|
534
|
+
end
|
535
|
+
next
|
536
|
+
end
|
537
|
+
|
538
|
+
# @todo try to map indent to a block handler
|
539
|
+
next if line == ''
|
540
|
+
|
541
|
+
# check if the block starts off and matches against any handlers; if not, assign 'p' as default
|
542
|
+
# two checks: `block(params).`; `block.`
|
543
|
+
block_type = nil
|
544
|
+
bt = line.match(BLOCK_MATCH_PARAMS)
|
545
|
+
if bt
|
546
|
+
idx0 = bt[0].length
|
547
|
+
idx1 = line.index(').', idx0)
|
548
|
+
if idx1
|
549
|
+
block_type = line[0..idx0-2]
|
550
|
+
params = line[0...idx1+1].strip
|
551
|
+
line = line[idx1+2..line.length] || ''
|
552
|
+
if params != ''
|
553
|
+
struct = Eggshell::ExpressionEvaluator.struct(params)
|
554
|
+
arg0 = struct[0][2][0]
|
555
|
+
arg1 = struct[0][2][1]
|
556
|
+
arg0 = expr_eval(arg0) if arg0
|
557
|
+
set_block_params(arg0, arg1)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
else
|
561
|
+
block_type = line.match(BLOCK_MATCH)
|
562
|
+
if block_type && block_type[0].strip != ''
|
563
|
+
block_type = block_type[1]
|
564
|
+
len = block_type.length
|
565
|
+
block_type = block_type[0..-2] if block_type[-1] == '.'
|
566
|
+
line = line[len..line.length] || ''
|
567
|
+
else
|
568
|
+
block_type = nil
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
|
573
|
+
block_type = 'p' if !block_type
|
574
|
+
block_handler_indent = indent_level
|
575
|
+
block_handler = @blocks[block_type]
|
576
|
+
block_handler = @noop_block if !block_handler
|
577
|
+
stat = block_handler.start(block_type, line.lstrip, buff, indents, indent_level)
|
578
|
+
# block handler won't continue to next line; clear and possibly retry
|
579
|
+
if stat == Eggshell::BlockHandler::COLLECT_RAW
|
580
|
+
block_handler_raw = true
|
581
|
+
elsif stat != Eggshell::BlockHandler::COLLECT
|
582
|
+
block_handler = nil
|
583
|
+
if stat == Eggshell::BlockHandler::RETRY
|
584
|
+
i -= 1
|
585
|
+
end
|
586
|
+
else
|
587
|
+
line = nil
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
if block_handler
|
592
|
+
block_handler.collect(line, buff, indents, indent_level - block_handler_indent) if line
|
593
|
+
block_handler.collect(nil, buff)
|
594
|
+
end
|
595
|
+
rescue => ex
|
596
|
+
_error "Exception approximately on line: #{line}"
|
597
|
+
_error ex.message + "\t#{ex.backtrace.join("\n\t")}"
|
598
|
+
_error "vars = #{@vars.inspect}"
|
599
|
+
end
|
600
|
+
|
601
|
+
return buff.join("\n")
|
602
|
+
end
|
603
|
+
|
604
|
+
HASH_FMT_DECORATORS = {
|
605
|
+
'[*' => '<b>',
|
606
|
+
'[**' => '<strong>',
|
607
|
+
'[_' => '<i>',
|
608
|
+
'[__' => '<em>',
|
609
|
+
'*]'=> '</b>',
|
610
|
+
'**]' => '</strong>',
|
611
|
+
'_]' => '</i>',
|
612
|
+
'__]' => '</em>',
|
613
|
+
'[-_' => '<u>',
|
614
|
+
'_-]' => '</u>',
|
615
|
+
'[-' => '<strike>',
|
616
|
+
'-]' => '</strike>'
|
617
|
+
}.freeze
|
618
|
+
|
619
|
+
HASH_HTML_ESCAPE = {
|
620
|
+
"'" => ''',
|
621
|
+
'"' => '"',
|
622
|
+
'<' => '<',
|
623
|
+
'>' => '>',
|
624
|
+
'&' => '&'
|
625
|
+
}.freeze
|
626
|
+
|
627
|
+
# @todo more chars
|
628
|
+
def html_escape(str)
|
629
|
+
return str.gsub(/("|'|<|>|&)/, HASH_HTML_ESCAPE)
|
630
|
+
end
|
631
|
+
|
632
|
+
# Symbols in conjunction with '[' prefix and ']' suffix that define shortcut macros.
|
633
|
+
# While extensible, the standard macros are: `*`, `**`, `_`, `__`, `-`, `-_`, `^`, `.`
|
634
|
+
MACRO_OPS = "%~=!?.^\\/*_+-"
|
635
|
+
INLINE_MARKUP_REGEX_OP = Regexp.new("\\[[#{MACRO_OPS}]+")
|
636
|
+
INLINE_MARKUP = Regexp.new("(`|\\{\\{|\\}\\}|\\[[#{MACRO_OPS}]+|[#{MACRO_OPS}]+\\]|\\\\|\\|)")
|
637
|
+
|
638
|
+
# Expands markup for a specific line.
|
639
|
+
def fmt_line(expr)
|
640
|
+
buff = []
|
641
|
+
bt = false
|
642
|
+
cd = false
|
643
|
+
esc = false
|
644
|
+
|
645
|
+
macro = false
|
646
|
+
macro_buff = ''
|
647
|
+
|
648
|
+
inline_op = nil
|
649
|
+
inline_delim = nil
|
650
|
+
inline_args = nil
|
651
|
+
inline_part = nil
|
652
|
+
inline_esc = false
|
653
|
+
|
654
|
+
# split and preserve delimiters: ` {{ }} [[ ]]
|
655
|
+
# - preserve contents of code blocks (only replacing unescaped placeholder values)
|
656
|
+
## - handle anchor and image
|
657
|
+
toks = expr.gsub(/\\[trn]/, HASH_LINE_ESCAPE).split(INLINE_MARKUP)
|
658
|
+
i = 0
|
659
|
+
|
660
|
+
while i < toks.length
|
661
|
+
part = toks[i]
|
662
|
+
i += 1
|
663
|
+
next if part == ''
|
664
|
+
|
665
|
+
if esc
|
666
|
+
buff << '\\' if part == '\\' || part[0..1] == '${'
|
667
|
+
buff << part
|
668
|
+
esc = false
|
669
|
+
elsif part == '\\'
|
670
|
+
esc = true
|
671
|
+
elsif part == '`' && !cd
|
672
|
+
if !bt
|
673
|
+
bt = true
|
674
|
+
buff << "<code class='tick'>"
|
675
|
+
else
|
676
|
+
bt = false
|
677
|
+
buff << '</code>'
|
678
|
+
end
|
679
|
+
elsif part == '{{' && !bt
|
680
|
+
cd = true
|
681
|
+
buff << "<code class='norm'>"
|
682
|
+
elsif part == '}}' && !bt
|
683
|
+
buff << '</code>'
|
684
|
+
cd = false
|
685
|
+
elsif bt || cd
|
686
|
+
buff << html_escape(expand_expr(part))
|
687
|
+
elsif (part[0] == '[' && part.match(INLINE_MARKUP_REGEX_OP))
|
688
|
+
# parse OP + {term or '|'}* + DELIM
|
689
|
+
inline_op = part
|
690
|
+
i = expand_macro_brackets(inline_op, i, toks, buff)
|
691
|
+
inline_op = nil
|
692
|
+
else
|
693
|
+
buff << part
|
694
|
+
end
|
695
|
+
end
|
696
|
+
# if inline_op
|
697
|
+
# inline_args << inline_part if inline_part
|
698
|
+
# @macros[inline_op].process(buff, inline_op, inline_args, nil, nil)
|
699
|
+
# end
|
700
|
+
return expand_expr(buff.join(''))
|
701
|
+
end
|
702
|
+
|
703
|
+
def expand_macro_brackets(inline_op, i, toks, buff)
|
704
|
+
inline_delim = inline_op.reverse.gsub('[', ']')
|
705
|
+
inline_args = []
|
706
|
+
inline_part = nil
|
707
|
+
inline_esc = false
|
708
|
+
|
709
|
+
# @todo check for quotes?
|
710
|
+
# @todo make this a static function
|
711
|
+
while i < toks.length
|
712
|
+
part = toks[i]
|
713
|
+
i += 1
|
714
|
+
|
715
|
+
if inline_esc
|
716
|
+
inline_part += part
|
717
|
+
inline_esc = false if part != ''
|
718
|
+
elsif part == '\\'
|
719
|
+
esc = true
|
720
|
+
elsif part.match(INLINE_MARKUP_REGEX_OP)
|
721
|
+
i = expand_macro_brackets(part, i, toks, buff)
|
722
|
+
inline_part = '' if !inline_part
|
723
|
+
inline_part += buff.pop
|
724
|
+
elsif part.end_with?(inline_delim) || part.end_with?('/]')
|
725
|
+
# in the case where a special char immediately precedes end delimiter, move it on to
|
726
|
+
# the inline body (e.g. `[//emphasis.//]` or `[*bold.*]`)
|
727
|
+
len = part.end_with?(inline_delim) ? inline_delim.length : 2
|
728
|
+
if part.length > len
|
729
|
+
inline_part += part[0...-len]
|
730
|
+
end
|
731
|
+
break
|
732
|
+
elsif part == '|'
|
733
|
+
inline_args << inline_part
|
734
|
+
inline_part = nil
|
735
|
+
else
|
736
|
+
inline_part = '' if !inline_part
|
737
|
+
inline_part += part
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
inline_args << inline_part if inline_part
|
742
|
+
if @macros[inline_op]
|
743
|
+
@macros[inline_op].process(buff, inline_op, inline_args, nil, nil)
|
744
|
+
else
|
745
|
+
buff << "#{inline_op}#{inline_args.join('|')}#{inline_delim}"
|
746
|
+
end
|
747
|
+
|
748
|
+
return i
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
753
|
+
require_relative './eggshell/block.rb'
|
754
|
+
require_relative './eggshell/processor-context.rb'
|
755
|
+
require_relative './eggshell/expression-evaluator.rb'
|
756
|
+
require_relative './eggshell/macro-handler.rb'
|
757
|
+
require_relative './eggshell/block-handler.rb'
|
758
|
+
require_relative './eggshell/bundles.rb'
|